diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn
index 4085b6d50..d1e91f24b 100644
--- a/.clj-kondo/config.edn
+++ b/.clj-kondo/config.edn
@@ -17,6 +17,7 @@
{:exclude-files
["data_readers.clj"
"app/util/perf.cljs"
+ "app/common/logging.cljc"
"app/common/exceptions.cljc"]}
:linters
diff --git a/.gitignore b/.gitignore
index d1cc5a7af..d11830e81 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,4 +38,8 @@ node_modules
/deploy
/web
/_dump
-/vendor/svgclean/bundle*.js
\ No newline at end of file
+/vendor/svgclean/bundle*.js
+
+.calva
+.clj-kondo
+.lsp
diff --git a/CHANGES.md b/CHANGES.md
index 463d3d2ac..28e18de98 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,31 +1,91 @@
# CHANGELOG
+
## :rocket: Next
### :boom: Breaking changes
### :sparkles: New features
### :bug: Bugs fixed
### :arrow_up: Deps updates
-### :boom: Breaking changes
### :heart: Community contributions by (Thank you!)
+
+## 1.9.0-alpha
+
+### :boom: Breaking changes
+
+- Some stroke-caps can change behaviour.
+- Text display bug fix could potentialy make some texts jump a line.
+
+### :sparkles: New features
+
+- Add boolean shapes: intersections, unions, difference and exclusions[Taiga #748](https://tree.taiga.io/project/penpot/us/748).
+- Add advanced prototyping [Taiga #244](https://tree.taiga.io/project/penpot/us/244).
+- Add multiple flows [Taiga #2091](https://tree.taiga.io/project/penpot/us/2091).
+- Change order of the teams menu so it's in the joined time order.
+
+### :bug: Bugs fixed
+
+- Enhance duplicating prototype connections behaviour [Taiga #2093](https://tree.taiga.io/project/penpot/us/2093).
+- Ignore constraints in horizontal or vertical flip [Taiga #2038](https://tree.taiga.io/project/penpot/issue/2038).
+- Fix color and typographies refs lost when duplicated file [Taiga #2165](https://tree.taiga.io/project/penpot/issue/2165).
+- Fix problem with overflow dropdown on stroke-cap [#1216](https://github.com/penpot/penpot/issues/1216).
+- Fix menu context for single element nested in components [#1186](https://github.com/penpot/penpot/issues/1186).
+- Fix error screen when operations over comments fail [#1219](https://github.com/penpot/penpot/issues/1219).
+- Fix undo problem when changing typography/color from library [#1230](https://github.com/penpot/penpot/issues/1230).
+- Fix problem with text margin while rendering [#1231](https://github.com/penpot/penpot/issues/1231).
+- Fix problem with masked texts on exporting [Taiga #2116](https://tree.taiga.io/project/penpot/issue/2116).
+- Fix text editor enter behaviour with centered texts [Taiga #2126](https://tree.taiga.io/project/penpot/issue/2126).
+- Fix residual stroke on imported svg [Taiga #2125](https://tree.taiga.io/project/penpot/issue/2125).
+- Add links for terms of service and privacy policy in register checkbox [Taiga #2020](https://tree.taiga.io/project/penpot/issue/2020).
+- Allow three character hex and web colors in color picker hex input [#1184](https://github.com/penpot/penpot/issues/1184).
+- Allow lowercase search for fonts [#1180](https://github.com/penpot/penpot/issues/1180).
+- Fix group renaming problem [Taiga #1969](https://tree.taiga.io/project/penpot/issue/1969).
+- Fix export group with shadows on children [Taiga #2036](https://tree.taiga.io/project/penpot/issue/2036).
+- Fix zoom context menu in viewer [Taiga #2041](https://tree.taiga.io/project/penpot/issue/2041).
+- Fix stroke caps adjustments in relation with stroke size [Taiga #2123](https://tree.taiga.io/project/penpot/issue/2123).
+- Fix problem duplicating paths [Taiga #2147](https://tree.taiga.io/project/penpot/issue/2147).
+- Fix problem inheriting attributes from SVG root when importing [Taiga #2124](https://tree.taiga.io/project/penpot/issue/2124).
+- Fix problem with lines and inside/outside stroke [Taiga #2146](https://tree.taiga.io/project/penpot/issue/2146).
+- Add stroke width in selection calculation [Taiga #2146](https://tree.taiga.io/project/penpot/issue/2146).
+- Fix shift+wheel to horizontal scrolling in MacOS [#1217](https://github.com/penpot/penpot/issues/1217).
+- Fix path stroke is not working properly with high thickness [Taiga #2154](https://tree.taiga.io/project/penpot/issue/2154).
+- Fix bug with transformation operations [Taiga #2155](https://tree.taiga.io/project/penpot/issue/2155).
+- Fix bug in firefox when a text box is inside a mask [Taiga #2152](https://tree.taiga.io/project/penpot/issue/2152).
+- Fix problem with stroke inside/outside [Taiga #2186](https://tree.taiga.io/project/penpot/issue/2186)
+- Fix masks export area [Taiga #2189](https://tree.taiga.io/project/penpot/issue/2189)
+- Fix paste in place in arboards [Taiga #2188](https://tree.taiga.io/project/penpot/issue/2188)
+- Fix font size input stuck on selection change [Taiga #2184](https://tree.taiga.io/project/penpot/issue/2184)
+- Fix stroke cut on shapes export [Taiga #2171](https://tree.taiga.io/project/penpot/issue/2171)
+- Fix no color when boolean with an SVG [Taiga #2193](https://tree.taiga.io/project/penpot/issue/2193)
+- Fix unlink color styles at strokes [Taiga #2206](https://tree.taiga.io/project/penpot/issue/2206).
+
+### :arrow_up: Deps updates
+
+### :heart: Community contributions by (Thank you!)
+
+- To the translation community for the hard work on making penpot
+ available on so many languages.
+
+
+
## 1.8.4-alpha
### :bug: Bugs fixed
-- Fix problem importing components [Taiga #2151](https://tree.taiga.io/project/penpot/issue/2151)
+- Fix problem importing components [Taiga #2151](https://tree.taiga.io/project/penpot/issue/2151).
## 1.8.3-alpha
### :sparkles: New features
-- Adds progress report to importing process
+- Adds progress report to importing process.
## 1.8.2-alpha
### :bug: Bugs fixed
-- Fix problem with masking images in viewer [#1238](https://github.com/penpot/penpot/issues/1238)
+- Fix problem with masking images in viewer [#1238](https://github.com/penpot/penpot/issues/1238).
## 1.8.1-alpha
diff --git a/backend/deps.edn b/backend/deps.edn
index 0bee0c0c5..45f6cbb6d 100644
--- a/backend/deps.edn
+++ b/backend/deps.edn
@@ -12,7 +12,7 @@
org.zeromq/jeromq {:mvn/version "0.5.2"}
com.taoensso/nippy {:mvn/version "3.1.1"}
- com.github.luben/zstd-jni {:mvn/version "1.4.9-5"}
+ com.github.luben/zstd-jni {:mvn/version "1.5.0-4"}
;; NOTE: don't upgrade to latest version, breaking change is
;; introduced on 0.10.0 that suffixes counters with _total if they
@@ -24,14 +24,14 @@
org.eclipse.jetty/jetty-servlet]}
io.prometheus/simpleclient_httpserver {:mvn/version "0.9.0"}
- io.lettuce/lettuce-core {:mvn/version "6.1.2.RELEASE"}
- java-http-clj/java-http-clj {:mvn/version "0.4.2"}
+ io.lettuce/lettuce-core {:mvn/version "6.1.5.RELEASE"}
+ java-http-clj/java-http-clj {:mvn/version "0.4.3"}
- info.sunng/ring-jetty9-adapter {:mvn/version "0.15.1"}
- com.github.seancorfield/next.jdbc {:mvn/version "1.2.659"}
- metosin/reitit-ring {:mvn/version "0.5.13"}
- org.postgresql/postgresql {:mvn/version "42.2.20"}
- com.zaxxer/HikariCP {:mvn/version "4.0.3"}
+ info.sunng/ring-jetty9-adapter {:mvn/version "0.15.2"}
+ com.github.seancorfield/next.jdbc {:mvn/version "1.2.709"}
+ metosin/reitit-ring {:mvn/version "0.5.15"}
+ org.postgresql/postgresql {:mvn/version "42.2.23"}
+ com.zaxxer/HikariCP {:mvn/version "5.0.0"}
funcool/datoteka {:mvn/version "2.0.0"}
@@ -39,14 +39,20 @@
buddy/buddy-hashers {:mvn/version "1.8.1"}
buddy/buddy-sign {:mvn/version "3.4.1"}
- org.jsoup/jsoup {:mvn/version "1.13.1"}
+ org.jsoup/jsoup {:mvn/version "1.14.2"}
org.im4java/im4java {:mvn/version "1.4.0"}
- org.lz4/lz4-java {:mvn/version "1.7.1"}
+ org.lz4/lz4-java {:mvn/version "1.8.0"}
org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"}
integrant/integrant {:mvn/version "0.8.0"}
- software.amazon.awssdk/s3 {:mvn/version "2.16.62"}}
+ io.sentry/sentry {:mvn/version "5.1.2"}
+
+ ;; Pretty Print specs
+ fipp/fipp {:mvn/version "0.6.24"}
+ pretty-spec/pretty-spec {:mvn/version "0.1.4"}
+
+ software.amazon.awssdk/s3 {:mvn/version "2.17.40"}}
:paths ["src" "resources"]
:aliases
@@ -55,7 +61,8 @@
{com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
org.clojure/test.check {:mvn/version "RELEASE"}
- com.clojure-goes-fast/clj-async-profiler {:mvn/version "0.5.0"}
+ org.clojure/data.csv {:mvn/version "1.0.0"}
+ com.clojure-goes-fast/clj-async-profiler {:mvn/version "0.5.1"}
criterium/criterium {:mvn/version "RELEASE"}
mockery/mockery {:mvn/version "RELEASE"}}
@@ -66,13 +73,13 @@
:args {}}
:kaocha
- {:extra-deps {lambdaisland/kaocha {:mvn/version "1.0.829"}}
+ {:extra-deps {lambdaisland/kaocha {:mvn/version "1.0.887"}}
:main-opts ["-m" "kaocha.runner"]}
:test
{:extra-deps {io.github.cognitect-labs/test-runner
{:git/url "https://github.com/cognitect-labs/test-runner.git"
- :sha "705ad25bbf0228b1c38d0244a36001c2987d7337"}}
+ :git/sha "dd6da11611eeb87f08780a30ac8ea6012d4c05ce"}}
:exec-fn cognitect.test-runner.api/test}
:outdated
diff --git a/backend/resources/api-doc.css b/backend/resources/api-doc.css
new file mode 100644
index 000000000..b9b14a889
--- /dev/null
+++ b/backend/resources/api-doc.css
@@ -0,0 +1,101 @@
+* {
+ font-family: "JetBrains Mono", monospace;
+ font-size: 12px;
+}
+
+pre {
+ margin: 0px;
+}
+
+body {
+ margin: 0px;
+ padding: 0px;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ display: flex;
+ justify-content: center;
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-width: 900px;
+ width: 900px;
+}
+
+header {
+ border-bottom: 1px solid #c0c0c0;
+ display: flex;
+ justify-content: center;
+ width: 100%;
+}
+
+.rpc-doc-content {
+ margin-top: 20px;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ /* border: 1px solid red; */
+ padding: 5px;
+}
+
+.rpc-doc-content > h2:not(:first-child) {
+ margin-top: 30px;
+}
+
+
+.rpc-items {
+ list-style: none;
+ padding: 0px;
+ margin: 0px;
+}
+
+.rpc-item {
+ /* border: 1px solid red; */
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+}
+
+.rpc-item:not(:last-child) {
+ margin-bottom: 3px;
+}
+
+.rpc-row-info {
+ cursor: pointer;
+ display: flex;
+ background-color: #eeeeee;
+ padding: 5px 10px;
+}
+
+.rpc-row-info > *:not(:last-child) {
+ margin-right: 10px;
+}
+
+.rpc-row-info > * {
+ /* border: 1px solid green; */
+}
+
+.rpc-row-info > .type {
+ font-weight: bold;
+ width: 70px;
+}
+
+.rpc-row-info > .name {
+ width: 280px;
+ /* font-weight: bold; */
+}
+
+.rpc-row-info > .tags > .tag > span:first-child {
+ font-weight: bold;
+}
+
+.hidden {
+ display: none;
+}
+
+.rpc-row-detail {
+ padding: 5px 10px;
+ padding-bottom: 20px;
+}
diff --git a/backend/resources/api-doc.js b/backend/resources/api-doc.js
new file mode 100644
index 000000000..90a0e4f92
--- /dev/null
+++ b/backend/resources/api-doc.js
@@ -0,0 +1,27 @@
+(function() {
+ document.addEventListener("DOMContentLoaded", function(event) {
+ const rows = document.querySelectorAll(".rpc-row-info");
+
+ const onRowClick = (event) => {
+ const target = event.currentTarget;
+ for (let node of rows) {
+ if (node !== target) {
+ node.nextElementSibling.classList.add("hidden");
+ } else {
+ const sibling = target.nextElementSibling;
+
+ if (sibling.classList.contains("hidden")) {
+ sibling.classList.remove("hidden");
+ } else {
+ sibling.classList.add("hidden");
+ }
+ }
+ }
+ };
+
+ for (let node of rows) {
+ node.addEventListener("click", onRowClick);
+ }
+
+ });
+})();
diff --git a/backend/resources/api-doc.tmpl b/backend/resources/api-doc.tmpl
new file mode 100644
index 000000000..f319a4692
--- /dev/null
+++ b/backend/resources/api-doc.tmpl
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+ Builtin API Documentation - Penpot
+
+
+
+
+
+
+
+ Penpot API Documentation
+
+
+
+ RPC QUERY METHODS:
+
+ {% for item in query-methods %}
+ -
+
+ {#
{{item.type}}
#}
+
{{item.name}}
+
+
+ Auth:
+ {% if item.auth %}YES{% else %}NO{% endif %}
+
+
+
+
+ {% if item.docs %}
+
DOCSTRING:
+
{{item.docs}}
+ {% endif %}
+
+
SPEC EXPLAIN:
+
{{item.spec}}
+
+
+ {% endfor %}
+
+
+ RPC MUTATION METHODS:
+
+ {% for item in mutation-methods %}
+ -
+
+ {#
{{item.type}}
#}
+
{{item.name}}
+
+
+ Auth:
+ {% if item.auth %}YES{% else %}NO{% endif %}
+
+
+
+
+ {% if item.docs %}
+
DOCSTRING:
+
{{item.docs}}
+ {% endif %}
+
+
SPEC EXPLAIN:
+
{{item.spec}}
+
+
+ {% endfor %}
+
+
+
+
+
+
diff --git a/backend/resources/error-report.tmpl b/backend/resources/error-report.tmpl
index a04f0df6b..452036997 100644
--- a/backend/resources/error-report.tmpl
+++ b/backend/resources/error-report.tmpl
@@ -2,6 +2,7 @@
+
penpot - error report {{id}}
diff --git a/backend/scripts/repl b/backend/scripts/repl
index cee9d8c5b..f2321e346 100755
--- a/backend/scripts/repl
+++ b/backend/scripts/repl
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
-export PENPOT_ASSERTS_ENABLED=true
+export PENPOT_FLAGS="enable-asserts enable-audit-log $PENPOT_FLAGS"
export OPTIONS="-A:jmx-remote:dev -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -J-Dlog4j2.configurationFile=log4j2-devenv.xml -J-Djdk.attach.allowAttachSelf -J-XX:+UseZGC -J-XX:ConcGCThreads=1 -J-XX:-OmitStackTraceInFastThrow -J-Xms50m -J-Xmx512m";
# export OPTIONS="$OPTIONS -J-XX:+UnlockDiagnosticVMOptions";
diff --git a/backend/scripts/start-dev b/backend/scripts/start-dev
index a25db5350..88cc5c0c0 100755
--- a/backend/scripts/start-dev
+++ b/backend/scripts/start-dev
@@ -10,6 +10,23 @@ if [ ! -e ~/.fixtures-loaded ]; then
touch ~/.fixtures-loaded
fi
-clojure -A:dev -M -m app.main
+if [ "$1" = "--watch" ]; then
+ echo "Start Watch..."
+
+ clojure -A:dev -M -m app.main &
+ PID=$!
+
+ npx nodemon \
+ --watch src \
+ --watch ../common \
+ --ext "clj" \
+ --signal SIGKILL \
+ --exec 'echo "(user/restart)" | nc -N localhost 6062'
+
+ kill -9 $PID
+else
+ clojure -A:dev -M -m app.main
+fi
+
diff --git a/backend/src/app/cli/fixtures.clj b/backend/src/app/cli/fixtures.clj
index cd09769cb..4128c9954 100644
--- a/backend/src/app/cli/fixtures.clj
+++ b/backend/src/app/cli/fixtures.clj
@@ -7,13 +7,13 @@
(ns app.cli.fixtures
"A initial fixtures."
(:require
+ [app.common.logging :as l]
[app.common.pages :as cp]
[app.common.uuid :as uuid]
[app.db :as db]
[app.main :as main]
[app.rpc.mutations.profile :as profile]
[app.util.blob :as blob]
- [app.util.logging :as l]
[buddy.hashers :as hashers]
[integrant.core :as ig]))
diff --git a/backend/src/app/cli/manage.clj b/backend/src/app/cli/manage.clj
index 29975d0a9..d20758196 100644
--- a/backend/src/app/cli/manage.clj
+++ b/backend/src/app/cli/manage.clj
@@ -7,11 +7,11 @@
(ns app.cli.manage
"A manage cli api."
(:require
+ [app.common.logging :as l]
[app.db :as db]
[app.main :as main]
[app.rpc.mutations.profile :as profile]
[app.rpc.queries.profile :refer [retrieve-profile-data-by-email]]
- [app.util.logging :as l]
[clojure.string :as str]
[clojure.tools.cli :refer [parse-opts]]
[integrant.core :as ig])
diff --git a/backend/src/app/cli/migrate_media.clj b/backend/src/app/cli/migrate_media.clj
index 0d627afdc..b940b1a33 100644
--- a/backend/src/app/cli/migrate_media.clj
+++ b/backend/src/app/cli/migrate_media.clj
@@ -6,12 +6,12 @@
(ns app.cli.migrate-media
(:require
+ [app.common.logging :as l]
[app.common.media :as cm]
[app.config :as cf]
[app.db :as db]
[app.main :as main]
[app.storage :as sto]
- [app.util.logging :as l]
[cuerdas.core :as str]
[datoteka.core :as fs]
[integrant.core :as ig]))
diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj
index 27df0e778..2c1d77aba 100644
--- a/backend/src/app/config.clj
+++ b/backend/src/app/config.clj
@@ -10,6 +10,7 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
+ [app.common.flags :as flags]
[app.common.spec :as us]
[app.common.version :as v]
[app.util.time :as dt]
@@ -50,8 +51,6 @@
:default-blob-version 3
:loggers-zmq-uri "tcp://localhost:45556"
- :asserts-enabled false
-
:public-uri "http://localhost:3449"
:redis-uri "redis://redis/0"
@@ -61,15 +60,11 @@
:assets-storage-backend :assets-fs
:storage-assets-fs-directory "assets"
- :feedback-destination "info@example.com"
- :feedback-enabled false
-
:assets-path "/internal/assets/"
:rlimits-password 10
:rlimits-image 2
- :smtp-enabled false
:smtp-default-reply-to "Penpot "
:smtp-default-from "Penpot "
@@ -79,10 +74,6 @@
:profile-bounce-max-age (dt/duration {:days 7})
:profile-bounce-threshold 10
- :allow-demo-users true
- :registration-enabled true
-
- :telemetry-enabled false
:telemetry-uri "https://telemetry.penpot.app/"
:ldap-user-query "(|(uid=:username)(mail=:username))"
@@ -92,27 +83,29 @@
:ldap-attrs-photo "jpegPhoto"
;; a server prop key where initial project is stored.
- :initial-project-skey "initial-project"
- })
+ :initial-project-skey "initial-project"})
-(s/def ::audit-enabled ::us/boolean)
-(s/def ::audit-archive-enabled ::us/boolean)
-(s/def ::audit-archive-uri ::us/string)
-(s/def ::audit-archive-gc-enabled ::us/boolean)
-(s/def ::audit-archive-gc-max-age ::dt/duration)
+(s/def ::flags ::us/words)
+
+;; DEPRECATED PROPERTIES: should be removed in 1.10
+(s/def ::registration-enabled ::us/boolean)
+(s/def ::smtp-enabled ::us/boolean)
+(s/def ::telemetry-enabled ::us/boolean)
+(s/def ::asserts-enabled ::us/boolean)
+;; END DEPRECATED
+
+(s/def ::audit-log-archive-uri ::us/string)
+(s/def ::audit-log-gc-max-age ::dt/duration)
(s/def ::secret-key ::us/string)
(s/def ::allow-demo-users ::us/boolean)
-(s/def ::asserts-enabled ::us/boolean)
(s/def ::assets-path ::us/string)
(s/def ::database-password (s/nilable ::us/string))
(s/def ::database-uri ::us/string)
(s/def ::database-username (s/nilable ::us/string))
(s/def ::default-blob-version ::us/integer)
(s/def ::error-report-webhook ::us/string)
-(s/def ::feedback-destination ::us/string)
-(s/def ::feedback-enabled ::us/boolean)
-(s/def ::feedback-token ::us/string)
+(s/def ::user-feedback-destination ::us/string)
(s/def ::github-client-id ::us/string)
(s/def ::github-client-secret ::us/string)
(s/def ::gitlab-base-uri ::us/string)
@@ -158,12 +151,10 @@
(s/def ::public-uri ::us/string)
(s/def ::redis-uri ::us/string)
(s/def ::registration-domain-whitelist ::us/set-of-str)
-(s/def ::registration-enabled ::us/boolean)
(s/def ::rlimits-image ::us/integer)
(s/def ::rlimits-password ::us/integer)
(s/def ::smtp-default-from ::us/string)
(s/def ::smtp-default-reply-to ::us/string)
-(s/def ::smtp-enabled ::us/boolean)
(s/def ::smtp-host ::us/string)
(s/def ::smtp-password (s/nilable ::us/string))
(s/def ::smtp-port ::us/integer)
@@ -180,28 +171,27 @@
(s/def ::storage-fdata-s3-bucket ::us/string)
(s/def ::storage-fdata-s3-region ::us/keyword)
(s/def ::storage-fdata-s3-prefix ::us/string)
-(s/def ::telemetry-enabled ::us/boolean)
(s/def ::telemetry-uri ::us/string)
(s/def ::telemetry-with-taiga ::us/boolean)
(s/def ::tenant ::us/string)
+(s/def ::sentry-trace-sample-rate ::us/number)
+(s/def ::sentry-attach-stack-trace ::us/boolean)
+(s/def ::sentry-debug ::us/boolean)
+(s/def ::sentry-dsn ::us/string)
+
(s/def ::config
(s/keys :opt-un [::secret-key
+ ::flags
::allow-demo-users
- ::audit-enabled
- ::audit-archive-enabled
- ::audit-archive-uri
- ::audit-archive-gc-enabled
- ::audit-archive-gc-max-age
- ::asserts-enabled
+ ::audit-log-archive-uri
+ ::audit-log-gc-max-age
::database-password
::database-uri
::database-username
::default-blob-version
::error-report-webhook
- ::feedback-destination
- ::feedback-enabled
- ::feedback-token
+ ::user-feedback-destination
::github-client-id
::github-client-secret
::gitlab-base-uri
@@ -249,6 +239,10 @@
::registration-enabled
::rlimits-image
::rlimits-password
+ ::sentry-dsn
+ ::sentry-debug
+ ::sentry-attach-stack-trace
+ ::sentry-trace-sample-rate
::smtp-default-from
::smtp-default-reply-to
::smtp-enabled
@@ -258,26 +252,27 @@
::smtp-ssl
::smtp-tls
::smtp-username
-
::srepl-host
::srepl-port
-
::assets-storage-backend
::storage-assets-fs-directory
::storage-assets-s3-bucket
::storage-assets-s3-region
-
::fdata-storage-backend
::storage-fdata-s3-bucket
::storage-fdata-s3-region
::storage-fdata-s3-prefix
-
::telemetry-enabled
::telemetry-uri
::telemetry-referer
::telemetry-with-taiga
::tenant]))
+(defn- parse-flags
+ [config]
+ (-> (:flags config)
+ (flags/parse flags/default)))
+
(defn read-env
[prefix]
(let [prefix (str prefix "-")
@@ -304,11 +299,14 @@
(println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;")))
(throw e))))
-(def version (v/parse (or (some-> (io/resource "version.txt")
- (slurp)
- (str/trim))
- "%version%")))
-(def config (atom (read-config)))
+(def version
+ (v/parse (or (some-> (io/resource "version.txt")
+ (slurp)
+ (str/trim))
+ "%version%")))
+
+(def ^:dynamic config (read-config))
+(def ^:dynamic flags (parse-flags config))
(def deletion-delay
(dt/duration {:days 7}))
@@ -316,9 +314,9 @@
(defn get
"A configuration getter. Helps code be more testable."
([key]
- (c/get @config key))
+ (c/get config key))
([key default]
- (c/get @config key default)))
+ (c/get config key default)))
;; Set value for all new threads bindings.
-(alter-var-root #'*assert* (constantly (get :asserts-enabled)))
+(alter-var-root #'*assert* (constantly (contains? flags :backend-asserts)))
diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj
index 6f1b940af..8925c12d0 100644
--- a/backend/src/app/db.clj
+++ b/backend/src/app/db.clj
@@ -9,13 +9,13 @@
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.point :as gpt]
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.common.transit :as t]
[app.common.uuid :as uuid]
[app.db.sql :as sql]
[app.metrics :as mtx]
[app.util.json :as json]
- [app.util.logging :as l]
[app.util.migrations :as mg]
[app.util.time :as dt]
[clojure.java.io :as io]
@@ -46,28 +46,26 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare instrument-jdbc!)
+(declare apply-migrations!)
(s/def ::name keyword?)
(s/def ::uri ::us/not-empty-string)
(s/def ::min-pool-size ::us/integer)
(s/def ::max-pool-size ::us/integer)
(s/def ::migrations map?)
+(s/def ::read-only ::us/boolean)
(defmethod ig/pre-init-spec ::pool [_]
- (s/keys :req-un [::uri ::name ::min-pool-size ::max-pool-size ::migrations ::mtx/metrics]))
+ (s/keys :req-un [::uri ::name ::min-pool-size ::max-pool-size]
+ :opt-un [::migrations ::mtx/metrics ::read-only]))
(defmethod ig/init-key ::pool
- [_ {:keys [migrations metrics] :as cfg}]
- (l/info :action "initialize connection pool"
- :name (d/name (:name cfg))
- :uri (:uri cfg))
- (instrument-jdbc! (:registry metrics))
+ [_ {:keys [migrations metrics name] :as cfg}]
+ (l/info :action "initialize connection pool" :name (d/name name) :uri (:uri cfg))
+ (some-> metrics :registry instrument-jdbc!)
+
(let [pool (create-pool cfg)]
- (when (seq migrations)
- (with-open [conn ^AutoCloseable (open pool)]
- (mg/setup! conn)
- (doseq [[name steps] migrations]
- (mg/migrate! conn {:name (d/name name) :steps steps}))))
+ (some->> (seq migrations) (apply-migrations! pool))
pool))
(defmethod ig/halt-key! ::pool
@@ -84,37 +82,50 @@
:name "database_query_total"
:help "An absolute counter of database queries."}))
+(defn- apply-migrations!
+ [pool migrations]
+ (with-open [conn ^AutoCloseable (open pool)]
+ (mg/setup! conn)
+ (doseq [[name steps] migrations]
+ (mg/migrate! conn {:name (d/name name) :steps steps}))))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API & Impl
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def initsql
- (str "SET statement_timeout = 120000;\n"
- "SET idle_in_transaction_session_timeout = 120000;"))
+ (str "SET statement_timeout = 200000;\n"
+ "SET idle_in_transaction_session_timeout = 200000;"))
(defn- create-datasource-config
- [{:keys [metrics] :as cfg}]
+ [{:keys [metrics read-only] :or {read-only false} :as cfg}]
(let [dburi (:uri cfg)
username (:username cfg)
password (:password cfg)
- config (HikariConfig.)
- mtf (PrometheusMetricsTrackerFactory. (:registry metrics))]
+ config (HikariConfig.)]
(doto config
(.setJdbcUrl (str "jdbc:" dburi))
(.setPoolName (d/name (:name cfg)))
(.setAutoCommit true)
- (.setReadOnly false)
- (.setConnectionTimeout 8000) ;; 8seg
- (.setValidationTimeout 8000) ;; 8seg
- (.setIdleTimeout 120000) ;; 2min
- (.setMaxLifetime 1800000) ;; 30min
+ (.setReadOnly read-only)
+ (.setConnectionTimeout 10000) ;; 10seg
+ (.setValidationTimeout 10000) ;; 10seg
+ (.setIdleTimeout 120000) ;; 2min
+ (.setMaxLifetime 1800000) ;; 30min
(.setMinimumIdle (:min-pool-size cfg 0))
(.setMaximumPoolSize (:max-pool-size cfg 30))
- (.setMetricsTrackerFactory mtf)
(.setConnectionInitSql initsql)
(.setInitializationFailTimeout -1))
+
+ ;; When metrics namespace is provided
+ (when metrics
+ (->> (:registry metrics)
+ (PrometheusMetricsTrackerFactory.)
+ (.setMetricsTrackerFactory config)))
+
(when username (.setUsername config username))
(when password (.setPassword config password))
+
config))
(defn pool?
@@ -127,7 +138,7 @@
[pool]
(.isClosed ^HikariDataSource pool))
-(defn- create-pool
+(defn create-pool
[cfg]
(let [dsc (create-datasource-config cfg)]
(jdbc-dt/read-as-instant)
diff --git a/backend/src/app/emails.clj b/backend/src/app/emails.clj
index ac55b688b..43b648274 100644
--- a/backend/src/app/emails.clj
+++ b/backend/src/app/emails.clj
@@ -7,12 +7,12 @@
(ns app.emails
"Main api for send emails."
(:require
+ [app.common.logging :as l]
[app.common.spec :as us]
- [app.config :as cfg]
+ [app.config :as cf]
[app.db :as db]
[app.db.sql :as sql]
[app.util.emails :as emails]
- [app.util.logging :as l]
[app.worker :as wrk]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
@@ -54,10 +54,10 @@
(defn allow-send-emails?
[conn profile]
(when-not (:is-muted profile false)
- (let [complaint-threshold (cfg/get :profile-complaint-threshold)
- complaint-max-age (cfg/get :profile-complaint-max-age)
- bounce-threshold (cfg/get :profile-bounce-threshold)
- bounce-max-age (cfg/get :profile-bounce-max-age)
+ (let [complaint-threshold (cf/get :profile-complaint-threshold)
+ complaint-max-age (cf/get :profile-complaint-max-age)
+ bounce-threshold (cf/get :profile-bounce-threshold)
+ bounce-max-age (cf/get :profile-bounce-max-age)
{:keys [complaints bounces] :as result}
(db/exec-one! conn [sql:profile-complaint-report
@@ -140,19 +140,17 @@
(declare send-console!)
-(s/def ::username ::cfg/smtp-username)
-(s/def ::password ::cfg/smtp-password)
-(s/def ::tls ::cfg/smtp-tls)
-(s/def ::ssl ::cfg/smtp-ssl)
-(s/def ::host ::cfg/smtp-host)
-(s/def ::port ::cfg/smtp-port)
-(s/def ::default-reply-to ::cfg/smtp-default-reply-to)
-(s/def ::default-from ::cfg/smtp-default-from)
-(s/def ::enabled ::cfg/smtp-enabled)
+(s/def ::username ::cf/smtp-username)
+(s/def ::password ::cf/smtp-password)
+(s/def ::tls ::cf/smtp-tls)
+(s/def ::ssl ::cf/smtp-ssl)
+(s/def ::host ::cf/smtp-host)
+(s/def ::port ::cf/smtp-port)
+(s/def ::default-reply-to ::cf/smtp-default-reply-to)
+(s/def ::default-from ::cf/smtp-default-from)
(defmethod ig/pre-init-spec ::sendmail-handler [_]
- (s/keys :req-un [::enabled]
- :opt-un [::username
+ (s/keys :opt-un [::username
::password
::tls
::ssl
@@ -164,9 +162,12 @@
(defmethod ig/init-key ::sendmail-handler
[_ cfg]
(fn [{:keys [props] :as task}]
- (if (:enabled cfg)
- (emails/send! cfg props)
- (send-console! cfg props))))
+ (let [enabled? (or (contains? cf/flags :smtp)
+ (cf/get :smtp-enabled)
+ (:enabled task))]
+ (if enabled?
+ (emails/send! cfg props)
+ (send-console! cfg props)))))
(defn- send-console!
[cfg email]
diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj
index 3681118ad..0dc98852b 100644
--- a/backend/src/app/http.clj
+++ b/backend/src/app/http.clj
@@ -8,11 +8,12 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.common.spec :as us]
+ [app.http.doc :as doc]
[app.http.errors :as errors]
[app.http.middleware :as middleware]
[app.metrics :as mtx]
- [app.util.logging :as l]
[clojure.spec.alpha :as s]
[integrant.core :as ig]
[reitit.ring :as rr]
@@ -141,7 +142,8 @@
["/webhooks"
["/sns" {:post (:sns-webhook cfg)}]]
- ["/api" {:middleware [[middleware/etag]
+ ["/api" {:middleware [[middleware/cors]
+ [middleware/etag]
[middleware/format-response-body]
[middleware/params]
[middleware/multipart-params]
@@ -150,6 +152,8 @@
[middleware/errors errors/handle]
[middleware/cookies]]}
+ ["/_doc" {:get (doc/handler rpc)}]
+
["/feedback" {:middleware [(:middleware session)]
:post feedback}]
["/auth/oauth/:provider" {:post (:handler oauth)}]
diff --git a/backend/src/app/http/awsns.clj b/backend/src/app/http/awsns.clj
index d5c29a979..7117c6466 100644
--- a/backend/src/app/http/awsns.clj
+++ b/backend/src/app/http/awsns.clj
@@ -8,10 +8,10 @@
"AWS SNS webhook handler for bounces."
(:require
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.db :as db]
[app.db.sql :as sql]
[app.util.http :as http]
- [app.util.logging :as l]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]
diff --git a/backend/src/app/http/doc.clj b/backend/src/app/http/doc.clj
new file mode 100644
index 000000000..13a6075cc
--- /dev/null
+++ b/backend/src/app/http/doc.clj
@@ -0,0 +1,53 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.http.doc
+ "API autogenerated documentation."
+ (:require
+ [app.common.data :as d]
+ [app.config :as cf]
+ [app.util.services :as sv]
+ [app.util.template :as tmpl]
+ [clojure.java.io :as io]
+ [clojure.spec.alpha :as s]
+ [pretty-spec.core :as ps]))
+
+(defn get-spec-str
+ [k]
+ (with-out-str
+ (ps/pprint (s/form k)
+ {:ns-aliases {"clojure.spec.alpha" "s"
+ "clojure.core.specs.alpha" "score"
+ "clojure.core" nil}})))
+
+(defn prepare-context
+ [rpc]
+ (letfn [(gen-doc [type [name f]]
+ (let [mdata (meta f)]
+ ;; (prn name mdata)
+ {:type (d/name type)
+ :name (d/name name)
+ :auth (:auth mdata true)
+ :docs (::sv/docs mdata)
+ :spec (get-spec-str (::sv/spec mdata))}))]
+ {:query-methods
+ (into []
+ (map (partial gen-doc :query))
+ (->> rpc :methods :query (sort-by first)))
+ :mutation-methods
+ (into []
+ (map (partial gen-doc :mutation))
+ (->> rpc :methods :mutation (sort-by first)))}))
+
+(defn handler
+ [rpc]
+ (let [context (prepare-context rpc)]
+ (if (contains? cf/flags :api-doc)
+ (fn [_]
+ {:status 200
+ :body (-> (io/resource "api-doc.tmpl")
+ (tmpl/render context))})
+ (constantly {:status 404 :body ""}))))
diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj
index 753c218d8..ab8f7e1d2 100644
--- a/backend/src/app/http/errors.clj
+++ b/backend/src/app/http/errors.clj
@@ -7,31 +7,49 @@
(ns app.http.errors
"A errors handling for the http server."
(:require
+ [app.common.data :as d]
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.common.uuid :as uuid]
- [app.util.logging :as l]
- [cuerdas.core :as str]
- [expound.alpha :as expound]))
+ [clojure.pprint]
+ [cuerdas.core :as str]))
-(defn- explain-error
- [error]
- (with-out-str
- (expound/printer (:data error))))
+(defn- parse-client-ip
+ [{:keys [headers] :as request}]
+ (or (some-> (get headers "x-forwarded-for") (str/split ",") first)
+ (get headers "x-real-ip")
+ (get request :remote-addr)))
+
+(defn- stringify-data
+ [data]
+ (binding [clojure.pprint/*print-right-margin* 200]
+ (let [result (with-out-str (clojure.pprint/pprint data))]
+ (str/prune result (* 1024 1024) "[...]"))))
(defn get-error-context
[request error]
- (let [edata (ex-data error)]
- (merge
- {:id (uuid/next)
- :path (:uri request)
- :method (:request-method request)
- :params (:params request)
- :data edata}
+ (let [data (ex-data error)]
+ (d/without-nils
+ (merge
+ {:id (str (uuid/next))
+ :path (str (:uri request))
+ :method (name (:request-method request))
+ :hint (or (:hint data) (ex-message error))
+ :params (stringify-data (:params request))
+ :data (stringify-data (dissoc data :explain))
+ :ip-addr (parse-client-ip request)
+ :explain (str/prune (:explain data) (* 1024 1024) "[...]")}
+
+ (when-let [id (:profile-id request)]
+ {:profile-id id})
+
(let [headers (:headers request)]
{:user-agent (get headers "user-agent")
:frontend-version (get headers "x-frontend-version" "unknown")})
- (when (and (map? edata) (:data edata))
- {:explain (explain-error edata)}))))
+
+ (when (map? data)
+ {:error-type (:type data)
+ :error-code (:code data)})))))
(defmulti handle-exception
(fn [err & _rest]
@@ -43,7 +61,6 @@
[err _]
{:status 401 :body (ex-data err)})
-
(defmethod handle-exception :restriction
[err _]
{:status 400 :body (ex-data err)})
@@ -57,13 +74,10 @@
{:status 400
:headers {"content-type" "text/html"}
:body (str ""
- (explain-error edata)
+ (:explain edata)
"
\n")}
{:status 400
- :body (cond-> edata
- (map? (:data edata))
- (-> (assoc :explain (explain-error edata))
- (dissoc :data)))})))
+ :body (dissoc edata :data)})))
(defmethod handle-exception :assertion
[error request]
@@ -77,9 +91,7 @@
{:status 500
:body {:type :server-error
:code :assertion
- :data (-> edata
- (assoc :explain (explain-error edata))
- (dissoc :data))}}))
+ :data (dissoc edata :data)}}))
(defmethod handle-exception :not-found
[err _]
diff --git a/backend/src/app/http/feedback.clj b/backend/src/app/http/feedback.clj
index dd78cfd57..e82a93a4e 100644
--- a/backend/src/app/http/feedback.clj
+++ b/backend/src/app/http/feedback.clj
@@ -10,7 +10,7 @@
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.spec :as us]
- [app.config :as cfg]
+ [app.config :as cf]
[app.db :as db]
[app.emails :as eml]
[app.rpc.queries.profile :as profile]
@@ -24,8 +24,8 @@
(defmethod ig/init-key ::handler
[_ {:keys [pool] :as scfg}]
- (let [ftoken (cfg/get :feedback-token ::no-token)
- enabled (cfg/get :feedback-enabled)]
+ (let [ftoken (cf/get :feedback-token ::no-token)
+ enabled (contains? cf/flags :user-feedback)]
(fn [{:keys [profile-id] :as request}]
(let [token (get-in request [:headers "x-feedback-token"])
params (d/merge (:params request)
@@ -58,7 +58,7 @@
(defn send-feedback
[pool profile params]
(let [params (us/conform ::feedback params)
- destination (cfg/get :feedback-destination)]
+ destination (cf/get :feedback-destination)]
(eml/send! {::eml/conn pool
::eml/factory eml/feedback
:to destination
diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj
index c6ee09f2e..8c8bf1151 100644
--- a/backend/src/app/http/middleware.clj
+++ b/backend/src/app/http/middleware.clj
@@ -6,10 +6,11 @@
(ns app.http.middleware
(:require
+ [app.common.logging :as l]
[app.common.transit :as t]
+ [app.config :as cf]
[app.metrics :as mtx]
[app.util.json :as json]
- [app.util.logging :as l]
[buddy.core.codecs :as bc]
[buddy.core.hash :as bh]
[clojure.java.io :as io]
@@ -176,3 +177,29 @@
:uri (str (:uri request) (when qstring (str "?" qstring)))
:method (name (:request-method request)))
(handler request)))))
+
+(defn- wrap-cors
+ [handler]
+ (if-not (contains? cf/flags :cors)
+ handler
+ (letfn [(add-cors-headers [response request]
+ (-> response
+ (update
+ :headers
+ (fn [headers]
+ (-> headers
+ (assoc "access-control-allow-origin" (get-in request [:headers "origin"]))
+ (assoc "access-control-allow-methods" "GET,POST,DELETE,OPTIONS,PUT,HEAD,PATCH")
+ (assoc "access-control-allow-credentials" "true")
+ (assoc "access-control-expose-headers" "x-requested-with, content-type, cookie")
+ (assoc "access-control-allow-headers" "x-frontend-version, content-type, accept, x-requested-width"))))))]
+ (fn [request]
+ (if (= (:request-method request) :options)
+ (-> {:status 200 :body ""}
+ (add-cors-headers request))
+ (let [response (handler request)]
+ (add-cors-headers response request)))))))
+
+(def cors
+ {:name ::cors
+ :compile (constantly wrap-cors)})
diff --git a/backend/src/app/http/oauth.clj b/backend/src/app/http/oauth.clj
index 528d90427..64b1c7036 100644
--- a/backend/src/app/http/oauth.clj
+++ b/backend/src/app/http/oauth.clj
@@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.common.uri :as u]
[app.config :as cf]
@@ -15,7 +16,6 @@
[app.loggers.audit :as audit]
[app.rpc.queries.profile :as profile]
[app.util.http :as http]
- [app.util.logging :as l]
[app.util.time :as dt]
[clojure.data.json :as json]
[clojure.set :as set]
@@ -62,6 +62,13 @@
:cause e)
nil)))
+(defn- qualify-props
+ [provider props]
+ (reduce-kv (fn [result k v]
+ (assoc result (keyword (:name provider) (name k)) v))
+ {}
+ props))
+
(defn- retrieve-user-info
[{:keys [provider] :as cfg} tdata]
(try
@@ -76,8 +83,8 @@
{:backend (:name provider)
:email (:email info)
:fullname (:name info)
- :props (dissoc info :name :email)})))
-
+ :props (->> (dissoc info :name :email)
+ (qualify-props provider))})))
(catch Exception e
(l/error :hint "unexpected exception on retrieve-user-info"
:cause e)
@@ -138,15 +145,14 @@
;; --- HTTP HANDLERS
-(defn extract-props
+(defn extract-utm-props
+ "Extracts additional data from user params."
[params]
(reduce-kv (fn [params k v]
(let [sk (name k)]
(cond-> params
- (or (str/starts-with? sk "pm_")
- (str/starts-with? sk "pm-")
- (str/starts-with? sk "utm_"))
- (assoc (-> sk str/kebab keyword) v))))
+ (str/starts-with? sk "utm_")
+ (assoc (->> sk str/kebab (keyword "penpot")) v))))
{}
params))
@@ -210,7 +216,7 @@
(defn- auth-handler
[{:keys [tokens] :as cfg} {:keys [params] :as request}]
(let [invitation (:invitation-token params)
- props (extract-props params)
+ props (extract-utm-props params)
state (tokens :generate
{:iss :oauth
:invitation-token invitation
diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj
index acff16136..6b9a566fd 100644
--- a/backend/src/app/http/session.clj
+++ b/backend/src/app/http/session.clj
@@ -8,11 +8,11 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.config :as cfg]
[app.db :as db]
[app.metrics :as mtx]
[app.util.async :as aa]
- [app.util.logging :as l]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.core.async :as a]
@@ -53,7 +53,12 @@
(defn- add-cookies
[response {:keys [id] :as session}]
- (assoc response :cookies {cookie-name {:path "/" :http-only true :value id}}))
+ (let [cors? (contains? cfg/flags :cors)]
+ (assoc response :cookies {cookie-name {:path "/"
+ :http-only true
+ :value id
+ :same-site (if cors? :none :strict)
+ :secure true}})))
(defn- clear-cookies
[response]
diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj
index 5f19403de..e312ebba2 100644
--- a/backend/src/app/loggers/audit.clj
+++ b/backend/src/app/loggers/audit.clj
@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.common.transit :as t]
[app.common.uuid :as uuid]
@@ -16,7 +17,6 @@
[app.db :as db]
[app.util.async :as aa]
[app.util.http :as http]
- [app.util.logging :as l]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.core.async :as a]
@@ -36,6 +36,7 @@
[profile]
(-> profile
(select-keys [:is-active :is-muted :auth-backend :email :default-team-id :default-project-id :fullname :lang])
+ (merge (:props profile))
(d/without-nils)))
(defn clean-props
@@ -88,9 +89,9 @@
(s/def ::events (s/every ::event))
(defmethod ig/init-key ::http-handler
- [_ {:keys [executor enabled] :as cfg}]
- (fn [{:keys [params _headers _cookies profile-id] :as request}]
- (when enabled
+ [_ {:keys [executor] :as cfg}]
+ (fn [{:keys [params profile-id] :as request}]
+ (when (contains? cf/flags :audit-log)
(let [events (->> (:events params)
(remove #(not= profile-id (:profile-id %)))
(us/conform ::events))
@@ -137,10 +138,9 @@
;; an external storage and data cleared.
(declare persist-events)
-(s/def ::enabled ::us/boolean)
(defmethod ig/pre-init-spec ::collector [_]
- (s/keys :req-un [::db/pool ::wrk/executor ::enabled]))
+ (s/keys :req-un [::db/pool ::wrk/executor]))
(def event-xform
(comp
@@ -148,9 +148,9 @@
(map clean-props)))
(defmethod ig/init-key ::collector
- [_ {:keys [enabled] :as cfg}]
- (when enabled
- (l/info :msg "intializing audit collector")
+ [_ cfg]
+ (when (contains? cf/flags :audit-log)
+ (l/info :msg "intializing audit log collector")
(let [input (a/chan 512 event-xform)
buffer (aa/batch input {:max-batch-size 100
:max-batch-age (* 10 1000) ; 10s
@@ -204,15 +204,16 @@
(s/def ::tokens fn?)
(defmethod ig/pre-init-spec ::archive-task [_]
- (s/keys :req-un [::db/pool ::tokens ::enabled]
+ (s/keys :req-un [::db/pool ::tokens]
:opt-un [::uri]))
(defmethod ig/init-key ::archive-task
- [_ {:keys [uri enabled] :as cfg}]
+ [_ {:keys [uri] :as cfg}]
(fn [props]
;; NOTE: this let allows overwrite default configured values from
;; the repl, when manually invoking the task.
- (let [enabled (or enabled (:enabled props false))
+ (let [enabled (or (contains? cf/flags :audit-log-archive)
+ (:enabled props false))
uri (or uri (:uri props))
cfg (assoc cfg :uri uri)]
(when (and enabled (not uri))
@@ -271,11 +272,12 @@
:headers headers
:body body}
resp (http/send! params)]
- (when (not= (:status resp) 204)
- (ex/raise :type :internal
- :code :unable-to-send-events
- :hint "unable to send events"
- :context resp))))
+ (if (= (:status resp) 204)
+ true
+ (do
+ (l/warn :hint "unable to archive events"
+ :resp-status (:status resp))
+ false))))
(mark-as-archived [conn rows]
(db/exec-one! conn ["update audit_log set archived_at=now() where id = ANY(?)"
@@ -290,26 +292,14 @@
events (into [] xform rows)]
(when-not (empty? events)
(l/debug :action "archive-events" :uri uri :events (count events))
- (send events)
- (mark-as-archived conn rows)
- :continue)))))
+ (when (send events)
+ (mark-as-archived conn rows)
+ :continue))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GC Task
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(declare clean-archived)
-
-(s/def ::max-age ::cf/audit-archive-gc-max-age)
-
-(defmethod ig/pre-init-spec ::archive-gc-task [_]
- (s/keys :req-un [::db/pool ::enabled ::max-age]))
-
-(defmethod ig/init-key ::archive-gc-task
- [_ cfg]
- (fn [_]
- (clean-archived cfg)))
-
(def sql:clean-archived
"delete from audit_log
where archived_at is not null
@@ -322,3 +312,13 @@
result (:next.jdbc/update-count result)]
(l/debug :action "clean archived audit log" :removed result)
result))
+
+(s/def ::max-age ::cf/audit-log-gc-max-age)
+
+(defmethod ig/pre-init-spec ::gc-task [_]
+ (s/keys :req-un [::db/pool ::max-age]))
+
+(defmethod ig/init-key ::gc-task
+ [_ cfg]
+ (fn [_]
+ (clean-archived cfg)))
diff --git a/backend/src/app/loggers/database.clj b/backend/src/app/loggers/database.clj
new file mode 100644
index 000000000..2685661d8
--- /dev/null
+++ b/backend/src/app/loggers/database.clj
@@ -0,0 +1,126 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.loggers.database
+ "A specific logger impl that persists errors on the database."
+ (:require
+ [app.common.exceptions :as ex]
+ [app.common.logging :as l]
+ [app.common.spec :as us]
+ [app.common.uuid :as uuid]
+ [app.config :as cf]
+ [app.db :as db]
+ [app.util.async :as aa]
+ [app.util.template :as tmpl]
+ [app.worker :as wrk]
+ [clojure.core.async :as a]
+ [clojure.java.io :as io]
+ [clojure.spec.alpha :as s]
+ [cuerdas.core :as str]
+ [integrant.core :as ig]))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Error Listener
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(declare handle-event)
+
+(defonce enabled (atom true))
+
+(defn- persist-on-database!
+ [{:keys [pool] :as cfg} {:keys [id] :as event}]
+ (db/with-atomic [conn pool]
+ (db/insert! conn :server-error-report
+ {:id id :content (db/tjson event)})))
+
+(defn- parse-context
+ [event]
+ (reduce-kv
+ (fn [acc k v]
+ (cond
+ (= k :id) (assoc acc k (uuid/uuid v))
+ (= k :profile-id) (assoc acc k (uuid/uuid v))
+ (str/blank? v) acc
+ :else (assoc acc k v)))
+ {}
+ (:context event)))
+
+(defn parse-event
+ [event]
+ (-> (parse-context event)
+ (merge (dissoc event :context))
+ (assoc :tenant (cf/get :tenant))
+ (assoc :host (cf/get :host))
+ (assoc :public-uri (cf/get :public-uri))
+ (assoc :version (:full cf/version))))
+
+(defn handle-event
+ [{:keys [executor] :as cfg} event]
+ (aa/with-thread executor
+ (try
+ (let [event (parse-event event)]
+ (persist-on-database! cfg event))
+ (catch Exception e
+ (l/warn :hint "unexpected exception on database error logger"
+ :cause e)))))
+
+(defmethod ig/pre-init-spec ::reporter [_]
+ (s/keys :req-un [::wrk/executor ::db/pool ::receiver]))
+
+(defmethod ig/init-key ::reporter
+ [_ {:keys [receiver] :as cfg}]
+ (l/info :msg "initializing database error persistence")
+ (let [output (a/chan (a/sliding-buffer 128)
+ (filter #(= (:level %) "error")))]
+ (receiver :sub output)
+ (a/go-loop []
+ (let [msg (a/ (io/resource "error-report.tmpl")
+ (tmpl/render content)))]
+
+
+ (fn [request]
+ (let [result (some-> (parse-id request)
+ (retrieve-report)
+ (render-template))]
+ (if result
+ {:status 200
+ :headers {"content-type" "text/html; charset=utf-8"
+ "x-robots-tag" "noindex"}
+ :body result}
+ {:status 404
+ :body "not found"})))))
diff --git a/backend/src/app/loggers/loki.clj b/backend/src/app/loggers/loki.clj
index 7344bad1f..5f6980d32 100644
--- a/backend/src/app/loggers/loki.clj
+++ b/backend/src/app/loggers/loki.clj
@@ -7,12 +7,12 @@
(ns app.loggers.loki
"A Loki integration."
(:require
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.config :as cfg]
[app.util.async :as aa]
[app.util.http :as http]
[app.util.json :as json]
- [app.util.logging :as l]
[app.worker :as wrk]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
diff --git a/backend/src/app/loggers/mattermost.clj b/backend/src/app/loggers/mattermost.clj
index f4a60e5c1..ca23b9229 100644
--- a/backend/src/app/loggers/mattermost.clj
+++ b/backend/src/app/loggers/mattermost.clj
@@ -7,32 +7,51 @@
(ns app.loggers.mattermost
"A mattermost integration for error reporting."
(:require
- [app.common.exceptions :as ex]
- [app.common.spec :as us]
- [app.common.uuid :as uuid]
- [app.config :as cfg]
+ [app.common.logging :as l]
+ [app.config :as cf]
[app.db :as db]
+ [app.loggers.database :as ldb]
[app.util.async :as aa]
[app.util.http :as http]
[app.util.json :as json]
- [app.util.logging :as l]
- [app.util.template :as tmpl]
[app.worker :as wrk]
[clojure.core.async :as a]
- [clojure.java.io :as io]
[clojure.spec.alpha :as s]
- [cuerdas.core :as str]
[integrant.core :as ig]))
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Error Listener
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defonce enabled (atom true))
-(declare handle-event)
+(defn- send-mattermost-notification!
+ [cfg {:keys [host id public-uri] :as event}]
+ (try
+ (let [uri (:uri cfg)
+ text (str "Exception on (host: " host ", url: " public-uri "/dbg/error-by-id/" id ")\n"
+ (when-let [pid (:profile-id event)]
+ (str "- profile-id: #uuid-" pid "\n")))
+ rsp (http/send! {:uri uri
+ :method :post
+ :headers {"content-type" "application/json"}
+ :body (json/encode-str {:text text})})]
+ (when (not= (:status rsp) 200)
+ (l/error :hint "error on sending data to mattermost"
+ :response (pr-str rsp))))
-(defonce enabled-mattermost (atom true))
+ (catch Exception e
+ (l/error :hint "unexpected exception on error reporter"
+ :cause e))))
-(s/def ::uri ::us/string)
+(defn handle-event
+ [{:keys [executor] :as cfg} event]
+ (aa/with-thread executor
+ (try
+ (let [event (ldb/parse-event event)]
+ (when @enabled
+ (send-mattermost-notification! cfg event)))
+ (catch Exception e
+ (l/warn :hint "unexpected exception on error reporter" :cause e)))))
+
+
+(s/def ::uri ::cf/error-report-webhook)
(defmethod ig/pre-init-spec ::reporter [_]
(s/keys :req-un [::wrk/executor ::db/pool ::receiver]
@@ -58,95 +77,3 @@
[_ output]
(when output
(a/close! output)))
-
-(defn- send-mattermost-notification!
- [cfg {:keys [host id] :as cdata}]
- (try
- (let [uri (:uri cfg)
- text (str "Unhandled exception (host: " host ", url: " (cfg/get :public-uri) "/dbg/error-by-id/" id "\n"
- "- profile-id: #" (:profile-id cdata) "\n")
- rsp (http/send! {:uri uri
- :method :post
- :headers {"content-type" "application/json"}
- :body (json/encode-str {:text text})})]
- (when (not= (:status rsp) 200)
- (l/error :hint "error on sending data to mattermost"
- :response (pr-str rsp))))
-
- (catch Exception e
- (l/error :hint "unexpected exception on error reporter"
- :cause e))))
-
-(defn- persist-on-database!
- [{:keys [pool] :as cfg} {:keys [id] :as cdata}]
- (db/with-atomic [conn pool]
- (db/insert! conn :server-error-report
- {:id id :content (db/tjson cdata)})))
-
-(defn- parse-context
- [event]
- (reduce-kv
- (fn [acc k v]
- (cond
- (= k :id) (assoc acc k (uuid/uuid v))
- (= k :profile-id) (assoc acc k (uuid/uuid v))
- (str/blank? v) acc
- :else (assoc acc k v)))
- {:id (uuid/next)}
- (:context event)))
-
-(defn- parse-event
- [event]
- (-> (parse-context event)
- (merge (dissoc event :context))
- (assoc :tenant (cfg/get :tenant))
- (assoc :host (cfg/get :host))
- (assoc :public-uri (cfg/get :public-uri))
- (assoc :version (:full cfg/version))))
-
-(defn handle-event
- [{:keys [executor] :as cfg} event]
- (aa/with-thread executor
- (try
- (let [cdata (parse-event event)]
- (when @enabled-mattermost
- (send-mattermost-notification! cfg cdata))
- (persist-on-database! cfg cdata))
- (catch Exception e
- (l/error :hint "unexpected exception on error reporter"
- :cause e)))))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Http Handler
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(defmethod ig/pre-init-spec ::handler [_]
- (s/keys :req-un [::db/pool]))
-
-(defmethod ig/init-key ::handler
- [_ {:keys [pool] :as cfg}]
- (letfn [(parse-id [request]
- (let [id (get-in request [:path-params :id])
- id (us/uuid-conformer id)]
- (when (uuid? id)
- id)))
- (retrieve-report [id]
- (ex/ignoring
- (when-let [{:keys [content] :as row} (db/get-by-id pool :server-error-report id)]
- (assoc row :content (db/decode-transit-pgobject content)))))
-
- (render-template [{:keys [content] :as report}]
- (some-> (io/resource "error-report.tmpl")
- (tmpl/render content)))]
-
-
- (fn [request]
- (let [result (some-> (parse-id request)
- (retrieve-report)
- (render-template))]
- (if result
- {:status 200
- :headers {"content-type" "text/html; charset=utf-8"}
- :body result}
- {:status 404
- :body "not found"})))))
diff --git a/backend/src/app/loggers/sentry.clj b/backend/src/app/loggers/sentry.clj
new file mode 100644
index 000000000..78ac1ee39
--- /dev/null
+++ b/backend/src/app/loggers/sentry.clj
@@ -0,0 +1,172 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.loggers.sentry
+ "A mattermost integration for error reporting."
+ (:require
+ [app.common.logging :as l]
+ [app.common.uuid :as uuid]
+ [app.config :as cf]
+ [app.db :as db]
+ [app.util.async :as aa]
+ [app.worker :as wrk]
+ [clojure.core.async :as a]
+ [clojure.spec.alpha :as s]
+ [cuerdas.core :as str]
+ [integrant.core :as ig])
+ (:import
+ io.sentry.Scope
+ io.sentry.IHub
+ io.sentry.Hub
+ io.sentry.NoOpHub
+ io.sentry.protocol.User
+ io.sentry.SentryOptions
+ io.sentry.SentryLevel
+ io.sentry.ScopeCallback))
+
+(defonce enabled (atom true))
+
+(defn- parse-context
+ [event]
+ (reduce-kv
+ (fn [acc k v]
+ (cond
+ (= k :id) (assoc acc k (uuid/uuid v))
+ (= k :profile-id) (assoc acc k (uuid/uuid v))
+ (str/blank? v) acc
+ :else (assoc acc k v)))
+ {}
+ (:context event)))
+
+(defn- parse-event
+ [event]
+ (assoc event :context (parse-context event)))
+
+(defn- build-sentry-options
+ [cfg]
+ (let [version (:base cf/version)]
+ (doto (SentryOptions.)
+ (.setDebug (:debug cfg false))
+ (.setTracesSampleRate (:traces-sample-rate cfg 1.0))
+ (.setDsn (:dsn cfg))
+ (.setServerName (cf/get :host))
+ (.setEnvironment (cf/get :tenant))
+ (.setAttachServerName true)
+ (.setAttachStacktrace (:attach-stack-trace cfg false))
+ (.setRelease (str "backend@" (if (= version "0.0.0") "develop" version))))))
+
+(defn handle-event
+ [^IHub shub event]
+ (letfn [(set-user! [^Scope scope {:keys [context] :as event}]
+ (let [user (User.)]
+ (.setIpAddress ^User user ^String (:ip-addr context))
+ (when-let [pid (:profile-id context)]
+ (.setId ^User user ^String (str pid)))
+ (.setUser scope ^User user)))
+
+ (set-level! [^Scope scope]
+ (.setLevel scope SentryLevel/ERROR))
+
+ (set-context! [^Scope scope {:keys [context] :as event}]
+ (let [uri (str (cf/get :public-uri) "/dbg/error-by-id/" (:id context))]
+ (.setContexts scope "detailed_error_uri" ^String uri))
+ (when-let [vers (:frontend-version event)]
+ (.setContexts scope "frontend_version" ^String vers))
+ (when-let [puri (:public-uri event)]
+ (.setContexts scope "public_uri" ^String (str puri)))
+ (when-let [uagent (:user-agent context)]
+ (.setContexts scope "user_agent" ^String uagent))
+ (when-let [tenant (:tenant event)]
+ (.setTag scope "tenant" ^String tenant))
+ (when-let [type (:error-type context)]
+ (.setTag scope "error_type" ^String (str type)))
+ (when-let [code (:error-code context)]
+ (.setTag scope "error_code" ^String (str code)))
+ )
+
+ (capture [^Scope scope {:keys [context error] :as event}]
+ (let [msg (str (:message error) "\n\n"
+
+ "======================================================\n"
+ "=================== Params ===========================\n"
+ "======================================================\n"
+
+ (:params context) "\n"
+
+ (when (:explain context)
+ (str "======================================================\n"
+ "=================== Explain ==========================\n"
+ "======================================================\n"
+ (:explain context) "\n"))
+
+ (when (:data context)
+ (str "======================================================\n"
+ "=================== Error Data =======================\n"
+ "======================================================\n"
+ (:data context) "\n"))
+
+ (str "======================================================\n"
+ "=================== Stack Trace ======================\n"
+ "======================================================\n"
+ (:trace error))
+
+ "\n")]
+ (set-user! scope event)
+ (set-level! scope)
+ (set-context! scope event)
+ (.captureMessage ^IHub shub msg)
+ ))
+ ]
+ ;; (clojure.pprint/pprint event)
+
+ (when @enabled
+ (.withScope ^IHub shub (reify ScopeCallback
+ (run [_ scope]
+ (->> event
+ (parse-event)
+ (capture scope))))))
+
+ ))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Error Listener
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(s/def ::receiver any?)
+(s/def ::dsn ::cf/sentry-dsn)
+(s/def ::trace-sample-rate ::cf/sentry-trace-sample-rate)
+(s/def ::attach-stack-trace ::cf/sentry-attach-stack-trace)
+(s/def ::debug ::cf/sentry-debug)
+
+(defmethod ig/pre-init-spec ::reporter [_]
+ (s/keys :req-un [::wrk/executor ::db/pool ::receiver]
+ :opt-un [::dsn ::trace-sample-rate ::attach-stack-trace]))
+
+(defmethod ig/init-key ::reporter
+ [_ {:keys [receiver dsn executor] :as cfg}]
+ (l/info :msg "initializing sentry reporter" :dsn dsn)
+ (let [opts (build-sentry-options cfg)
+ shub (if dsn
+ (Hub. ^SentryOptions opts)
+ (NoOpHub/getInstance))
+ output (a/chan (a/sliding-buffer 128)
+ (filter #(= (:level %) "error")))]
+ (receiver :sub output)
+ (a/go-loop []
+ (let [event (a/ path slurp svg/parse get-basic-info-from-svg)]
+ (let [info (some-> path slurp svg/pre-process svg/parse get-basic-info-from-svg)]
(when-not info
(ex/raise :type :validation
:code :invalid-svg-file
diff --git a/backend/src/app/metrics.clj b/backend/src/app/metrics.clj
index 148e9b3a2..b1d0033e6 100644
--- a/backend/src/app/metrics.clj
+++ b/backend/src/app/metrics.clj
@@ -7,7 +7,7 @@
(ns app.metrics
(:require
[app.common.exceptions :as ex]
- [app.util.logging :as l]
+ [app.common.logging :as l]
[clojure.spec.alpha :as s]
[integrant.core :as ig])
(:import
diff --git a/backend/src/app/msgbus.clj b/backend/src/app/msgbus.clj
index f7a013f72..146da0c83 100644
--- a/backend/src/app/msgbus.clj
+++ b/backend/src/app/msgbus.clj
@@ -8,10 +8,10 @@
"The msgbus abstraction implemented using redis as underlying backend."
(:require
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.config :as cfg]
[app.util.blob :as blob]
- [app.util.logging :as l]
[app.util.time :as dt]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
diff --git a/backend/src/app/notifications.clj b/backend/src/app/notifications.clj
index f7efa6f3f..62490b212 100644
--- a/backend/src/app/notifications.clj
+++ b/backend/src/app/notifications.clj
@@ -7,12 +7,12 @@
(ns app.notifications
"A websocket based notifications mechanism."
(:require
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.common.transit :as t]
[app.db :as db]
[app.metrics :as mtx]
[app.util.async :as aa]
- [app.util.logging :as l]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.core.async :as a]
@@ -69,6 +69,7 @@
:mtx-messages mtx-messages
:mtx-sessions mtx-sessions
)]
+
(-> #(handler cfg %)
(wrap-session)
(wrap-keyword-params)
diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj
index d77f83e3e..724b527aa 100644
--- a/backend/src/app/rpc.clj
+++ b/backend/src/app/rpc.clj
@@ -8,12 +8,12 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.db :as db]
[app.loggers.audit :as audit]
[app.metrics :as mtx]
[app.rlimits :as rlm]
- [app.util.logging :as l]
[app.util.services :as sv]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
@@ -97,36 +97,39 @@
auth? (:auth mdata true)]
(l/trace :action "register" :name (::sv/name mdata))
- (fn [params]
+ (with-meta
+ (fn [params]
- ;; Raise authentication error when rpc method requires auth but
- ;; no profile-id is found in the request.
- (when (and auth? (not (uuid? (:profile-id params))))
- (ex/raise :type :authentication
- :code :authentication-required
- :hint "authentication required for this endpoint"))
+ ;; Raise authentication error when rpc method requires auth but
+ ;; no profile-id is found in the request.
+ (when (and auth? (not (uuid? (:profile-id params))))
+ (ex/raise :type :authentication
+ :code :authentication-required
+ :hint "authentication required for this endpoint"))
- (let [params' (dissoc params ::request)
- params' (us/conform spec params')
- result (f cfg params')]
+ (let [params' (dissoc params ::request)
+ params' (us/conform spec params')
+ result (f cfg params')]
- ;; When audit log is enabled (default false).
- (when (fn? audit)
- (let [resultm (meta result)
- request (::request params)
- profile-id (or (:profile-id params')
- (:profile-id result)
- (::audit/profile-id resultm))
- props (d/merge params' (::audit/props resultm))]
- (audit :cmd :submit
- :type (::type cfg)
- :name (or (::audit/name resultm)
- (::sv/name mdata))
- :profile-id profile-id
- :ip-addr (audit/parse-client-ip request)
- :props props)))
+ ;; When audit log is enabled (default false).
+ (when (fn? audit)
+ (let [resultm (meta result)
+ request (::request params)
+ profile-id (or (:profile-id params')
+ (:profile-id result)
+ (::audit/profile-id resultm))
+ props (d/merge params' (::audit/props resultm))]
+ (audit :cmd :submit
+ :type (or (::audit/type resultm)
+ (::type cfg))
+ :name (or (::audit/name resultm)
+ (::sv/name mdata))
+ :profile-id profile-id
+ :ip-addr (audit/parse-client-ip request)
+ :props props)))
- result))))
+ result))
+ mdata)))
(defn- process-method
[cfg vfn]
@@ -148,10 +151,8 @@
'app.rpc.queries.teams
'app.rpc.queries.comments
'app.rpc.queries.profile
- 'app.rpc.queries.recent-files
'app.rpc.queries.viewer
- 'app.rpc.queries.fonts
- 'app.rpc.queries.svg)
+ 'app.rpc.queries.fonts)
(map (partial process-method cfg))
(into {}))))
@@ -170,7 +171,6 @@
'app.rpc.mutations.files
'app.rpc.mutations.comments
'app.rpc.mutations.projects
- 'app.rpc.mutations.viewer
'app.rpc.mutations.teams
'app.rpc.mutations.management
'app.rpc.mutations.ldap
diff --git a/backend/src/app/rpc/mutations/demo.clj b/backend/src/app/rpc/mutations/demo.clj
index fc7f184bf..cca26dbb4 100644
--- a/backend/src/app/rpc/mutations/demo.clj
+++ b/backend/src/app/rpc/mutations/demo.clj
@@ -9,7 +9,7 @@
(:require
[app.common.exceptions :as ex]
[app.common.uuid :as uuid]
- [app.config :as cfg]
+ [app.config :as cf]
[app.db :as db]
[app.loggers.audit :as audit]
[app.rpc.mutations.profile :as profile]
@@ -35,11 +35,11 @@
:email email
:fullname fullname
:is-demo true
- :deleted-at (dt/in-future cfg/deletion-delay)
+ :deleted-at (dt/in-future cf/deletion-delay)
:password password
:props {:onboarding-viewed true}}]
- (when-not (cfg/get :allow-demo-users)
+ (when-not (contains? cf/flags :demo-users)
(ex/raise :type :validation
:code :demo-users-not-allowed
:hint "Demo users are disabled by config."))
diff --git a/backend/src/app/rpc/mutations/fonts.clj b/backend/src/app/rpc/mutations/fonts.clj
index 85c604b15..b9682dad2 100644
--- a/backend/src/app/rpc/mutations/fonts.clj
+++ b/backend/src/app/rpc/mutations/fonts.clj
@@ -9,14 +9,12 @@
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.uuid :as uuid]
- [app.config :as cf]
[app.db :as db]
[app.media :as media]
[app.rpc.queries.teams :as teams]
[app.storage :as sto]
[app.util.services :as sv]
[app.util.time :as dt]
- [app.worker :as wrk]
[clojure.spec.alpha :as s]))
(declare create-font-variant)
@@ -129,13 +127,6 @@
(db/with-atomic [conn pool]
(teams/check-edition-permissions! conn profile-id team-id)
- ;; Schedule object deletion
- (wrk/submit! {::wrk/task :delete-object
- ::wrk/delay cf/deletion-delay
- ::wrk/conn conn
- :id id
- :type :team-font-variant})
-
(db/update! conn :team-font-variant
{:deleted-at (dt/now)}
{:id id :team-id team-id})
diff --git a/backend/src/app/rpc/mutations/ldap.clj b/backend/src/app/rpc/mutations/ldap.clj
index d982a30b0..0f6675f24 100644
--- a/backend/src/app/rpc/mutations/ldap.clj
+++ b/backend/src/app/rpc/mutations/ldap.clj
@@ -7,13 +7,13 @@
(ns app.rpc.mutations.ldap
(:require
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.config :as cfg]
[app.db :as db]
[app.loggers.audit :as audit]
[app.rpc.mutations.profile :as profile-m]
[app.rpc.queries.profile :as profile-q]
- [app.util.logging :as l]
[app.util.services :as sv]
[clj-ldap.client :as ldap]
[clojure.spec.alpha :as s]
diff --git a/backend/src/app/rpc/mutations/management.clj b/backend/src/app/rpc/mutations/management.clj
index 1cf111729..76d997fee 100644
--- a/backend/src/app/rpc/mutations/management.clj
+++ b/backend/src/app/rpc/mutations/management.clj
@@ -39,11 +39,23 @@
[file index]
(letfn [(process-form [form]
(cond-> form
- ;; Relink Components
+ ;; Relink library items
(and (map? form)
(uuid? (:component-file form)))
(update :component-file #(get index % %))
+ (and (map? form)
+ (uuid? (:fill-color-ref-file form)))
+ (update :fill-color-ref-file #(get index % %))
+
+ (and (map? form)
+ (uuid? (:stroke-color-ref-file form)))
+ (update :stroke-color-ref-file #(get index % %))
+
+ (and (map? form)
+ (uuid? (:typography-ref-file form)))
+ (update :typography-ref-file #(get index % %))
+
;; Relink Image Shapes
(and (map? form)
(map? (:metadata form))
diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj
index 099092ba5..906ec83ad 100644
--- a/backend/src/app/rpc/mutations/profile.clj
+++ b/backend/src/app/rpc/mutations/profile.clj
@@ -12,7 +12,7 @@
[app.config :as cf]
[app.db :as db]
[app.emails :as eml]
- [app.http.oauth :refer [extract-props]]
+ [app.http.oauth :refer [extract-utm-props]]
[app.loggers.audit :as audit]
[app.media :as media]
[app.metrics :as mtx]
@@ -100,10 +100,9 @@
(sv/defmethod ::prepare-register-profile {:auth false}
[{:keys [pool tokens] :as cfg} params]
- (when-not (cf/get :registration-enabled)
+ (when-not (contains? cf/flags :registration)
(ex/raise :type :restriction
:code :registration-disabled))
-
(when-let [domains (cf/get :registration-domain-whitelist)]
(when-not (email-domain-in-whitelist? domains (:email params))
(ex/raise :type :validation
@@ -128,23 +127,16 @@
;; --- MUTATION: Register Profile
(s/def ::accept-terms-and-privacy ::us/boolean)
-(s/def ::accept-newsletter-subscription ::us/boolean)
(s/def ::token ::us/not-empty-string)
(s/def ::register-profile
- (s/keys :req-un [::token ::fullname
- ::accept-terms-and-privacy]
- :opt-un [::accept-newsletter-subscription]))
+ (s/keys :req-un [::token ::fullname]))
(sv/defmethod ::register-profile {:auth false :rlimit :password}
[{:keys [pool] :as cfg} params]
- (when-not (:accept-terms-and-privacy params)
- (ex/raise :type :validation
- :code :invalid-terms-and-privacy))
-
(db/with-atomic [conn pool]
- (let [cfg (assoc cfg :conn conn)]
- (register-profile cfg params))))
+ (-> (assoc cfg :conn conn)
+ (register-profile params))))
(defn- annotate-profile-register
"A helper for properly increase the profile-register metric once the
@@ -163,6 +155,7 @@
(create-profile conn)
(create-profile-relations conn)
(decode-profile-row))]
+
(sid/load-initial-project! conn profile)
(cond
@@ -204,7 +197,6 @@
ptoken (tokens :generate-predefined
{:iss :profile-identity
:profile-id (:id profile)})]
-
(eml/send! {::eml/conn conn
::eml/factory eml/register
:public-uri (:public-uri cfg)
@@ -224,18 +216,17 @@
[conn params]
(let [id (or (:id params) (uuid/next))
- props (-> (extract-props params)
+ props (-> (extract-utm-props params)
(merge (:props params))
- (assoc :accept-terms-and-privacy (:accept-terms-and-privacy params true))
- (assoc :accept-newsletter-subscription (:accept-newsletter-subscription params false))
(db/tjson))
password (if-let [password (:password params)]
(derive-password password)
"!")
- locale (as-> (:locale params) locale
- (and (string? locale) (not (str/blank? locale)) locale))
+ locale (:locale params)
+ locale (when (and (string? locale) (not (str/blank? locale)))
+ locale)
backend (:backend params "penpot")
is-demo (:is-demo params false)
@@ -359,11 +350,14 @@
(defn- update-profile
[conn {:keys [id fullname lang theme] :as params}]
- (db/update! conn :profile
- {:fullname fullname
- :lang lang
- :theme theme}
- {:id id}))
+ (let [profile (db/update! conn :profile
+ {:fullname fullname
+ :lang lang
+ :theme theme}
+ {:id id})]
+ (-> profile
+ (profile/decode-profile-row)
+ (profile/strip-private-attrs))))
(s/def ::update-profile
(s/keys :req-un [::id ::fullname]
@@ -372,8 +366,9 @@
(sv/defmethod ::update-profile
[{:keys [pool] :as cfg} params]
(db/with-atomic [conn pool]
- (update-profile conn params)
- nil))
+ (let [profile (update-profile conn params)]
+ (with-meta profile
+ {::audit/props (audit/profile->props profile)}))))
;; --- MUTATION: Update Password
@@ -458,7 +453,8 @@
params (assoc params
:profile profile
:email (str/lower email))]
- (if (cf/get :smtp-enabled)
+ (if (or (cf/get :smtp-enabled)
+ (contains? cf/flags :smtp))
(request-email-change cfg params)
(change-email-inmediatelly cfg params)))))
@@ -591,11 +587,15 @@
(db/with-atomic [conn pool]
(let [profile (profile/retrieve-profile-data conn profile-id)
props (reduce-kv (fn [props k v]
- (if (nil? v)
- (dissoc props k)
- (assoc props k v)))
+ ;; We don't accept namespaced keys
+ (if (simple-ident? k)
+ (if (nil? v)
+ (dissoc props k)
+ (assoc props k v))
+ props))
(:props profile)
props)]
+
(db/update! conn :profile
{:props (db/tjson props)}
{:id profile-id})
diff --git a/backend/src/app/rpc/mutations/share_link.clj b/backend/src/app/rpc/mutations/share_link.clj
index 0e366957f..6079ecf7d 100644
--- a/backend/src/app/rpc/mutations/share_link.clj
+++ b/backend/src/app/rpc/mutations/share_link.clj
@@ -31,6 +31,11 @@
:opt-un [::pages]))
(sv/defmethod ::create-share-link
+ "Creates a share-link object.
+
+ Share links are resources that allows external users access to
+ specific files with specific permissions (flags)."
+
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(files/check-edition-permissions! conn profile-id file-id)
diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj
index 0d385fa2e..625a13af6 100644
--- a/backend/src/app/rpc/mutations/teams.clj
+++ b/backend/src/app/rpc/mutations/teams.clj
@@ -132,8 +132,8 @@
(sv/defmethod ::delete-team
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
(db/with-atomic [conn pool]
- (let [perms (teams/check-edition-permissions! conn profile-id id)]
- (when-not (some :is-owner perms)
+ (let [perms (teams/get-permissions conn profile-id id)]
+ (when-not (:is-owner perms)
(ex/raise :type :validation
:code :only-owner-can-delete-team))
@@ -300,7 +300,7 @@
(sv/defmethod ::invite-team-member
[{:keys [pool tokens] :as cfg} {:keys [profile-id team-id email role] :as params}]
(db/with-atomic [conn pool]
- (let [perms (teams/check-edition-permissions! conn profile-id team-id)
+ (let [perms (teams/get-permissions conn profile-id team-id)
profile (db/get-by-id conn :profile profile-id)
member (profile/retrieve-profile-data-by-email conn email)
team (db/get-by-id conn :team team-id)
@@ -316,7 +316,7 @@
{:iss :profile-identity
:profile-id (:id profile)})]
- (when-not (some :is-admin perms)
+ (when-not (:is-admin perms)
(ex/raise :type :validation
:code :insufficient-permissions))
diff --git a/backend/src/app/rpc/mutations/verify_token.clj b/backend/src/app/rpc/mutations/verify_token.clj
index 61b5f9abb..1fa32b81d 100644
--- a/backend/src/app/rpc/mutations/verify_token.clj
+++ b/backend/src/app/rpc/mutations/verify_token.clj
@@ -9,6 +9,7 @@
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.db :as db]
+ [app.loggers.audit :as audit]
[app.metrics :as mtx]
[app.rpc.mutations.teams :as teams]
[app.rpc.queries.profile :as profile]
@@ -63,7 +64,10 @@
(with-meta claims
{:transform-response ((:create session) profile-id)
- :before-complete (annotate-profile-activation metrics)})))
+ :before-complete (annotate-profile-activation metrics)
+ ::audit/name "verify-profile-email"
+ ::audit/props (audit/profile->props profile)
+ ::audit/profile-id (:id profile)})))
(defmethod process-token :auth
[{:keys [conn] :as cfg} _params {:keys [profile-id] :as claims}]
@@ -116,8 +120,7 @@
;; user is already logged in with some account.
(and (uuid? profile-id)
(uuid? member-id))
- (do
- (accept-invitation cfg claims)
+ (let [profile (accept-invitation cfg claims)]
(if (= member-id profile-id)
;; If the current session is already matches the invited
;; member, then just return the token and leave the frontend
@@ -131,27 +134,44 @@
;; account.
(with-meta
(assoc claims :state :created)
- {:transform-response ((:create session) member-id)})))
+ {:transform-response ((:create session) member-id)
+ ::audit/name "accept-team-invitation"
+ ::audit/props (merge
+ (audit/profile->props profile)
+ {:team-id (:team-id claims)
+ :role (:role claims)})
+ ::audit/profile-id profile-id})))
;; This happens when member-id is not filled in the invitation but
;; the user already has an account (probably with other mail) and
;; is already logged-in.
(and (uuid? profile-id)
(nil? member-id))
- (do
- (accept-invitation cfg (assoc claims :member-id profile-id))
- (assoc claims :state :created))
+ (let [profile (accept-invitation cfg (assoc claims :member-id profile-id))]
+ (with-meta
+ (assoc claims :state :created)
+ {::audit/name "accept-team-invitation"
+ ::audit/props (merge
+ (audit/profile->props profile)
+ {:team-id (:team-id claims)
+ :role (:role claims)})
+ ::audit/profile-id profile-id}))
;; This happens when member-id is filled but the accessing user is
;; not logged-in. In this case we proceed to accept invitation and
;; leave the user logged-in.
(and (nil? profile-id)
(uuid? member-id))
- (do
- (accept-invitation cfg claims)
+ (let [profile (accept-invitation cfg claims)]
(with-meta
(assoc claims :state :created)
- {:transform-response ((:create session) member-id)}))
+ {:transform-response ((:create session) member-id)
+ ::audit/name "accept-team-invitation"
+ ::audit/props (merge
+ (audit/profile->props profile)
+ {:team-id (:team-id claims)
+ :role (:role claims)})
+ ::audit/profile-id member-id}))
;; In this case, we wait until frontend app redirect user to
;; registeration page, the user is correctly registered and the
diff --git a/backend/src/app/rpc/mutations/viewer.clj b/backend/src/app/rpc/mutations/viewer.clj
deleted file mode 100644
index 85beafa96..000000000
--- a/backend/src/app/rpc/mutations/viewer.clj
+++ /dev/null
@@ -1,49 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-
-(ns app.rpc.mutations.viewer
- (:require
- [app.common.spec :as us]
- [app.db :as db]
- [app.rpc.queries.files :as files]
- [app.util.services :as sv]
- [buddy.core.codecs :as bc]
- [buddy.core.nonce :as bn]
- [clojure.spec.alpha :as s]))
-
-(s/def ::profile-id ::us/uuid)
-(s/def ::file-id ::us/uuid)
-(s/def ::page-id ::us/uuid)
-
-(s/def ::create-file-share-token
- (s/keys :req-un [::profile-id ::file-id ::page-id]))
-
-(sv/defmethod ::create-file-share-token
- [{:keys [pool] :as cfg} {:keys [profile-id file-id page-id] :as params}]
- (db/with-atomic [conn pool]
- (files/check-edition-permissions! conn profile-id file-id)
- (let [token (-> (bn/random-bytes 16)
- (bc/bytes->b64u)
- (bc/bytes->str))]
- (db/insert! conn :file-share-token
- {:file-id file-id
- :page-id page-id
- :token token})
- {:token token})))
-
-
-(s/def ::token ::us/not-empty-string)
-(s/def ::delete-file-share-token
- (s/keys :req-un [::profile-id ::file-id ::token]))
-
-(sv/defmethod ::delete-file-share-token
- [{:keys [pool] :as cfg} {:keys [profile-id file-id token]}]
- (db/with-atomic [conn pool]
- (files/check-edition-permissions! conn profile-id file-id)
- (db/delete! conn :file-share-token
- {:file-id file-id
- :token token})
- nil))
diff --git a/backend/src/app/rpc/permissions.clj b/backend/src/app/rpc/permissions.clj
index 9481bcd57..363f967e6 100644
--- a/backend/src/app/rpc/permissions.clj
+++ b/backend/src/app/rpc/permissions.clj
@@ -41,59 +41,24 @@
"A simple factory for edition permission predicate functions."
[qfn]
(us/assert fn? qfn)
- (fn [& args]
- (let [rows (apply qfn args)]
- (when-not (or (empty? rows)
- (not (or (some :can-edit rows)
- (some :is-admin rows)
- (some :is-owner rows))))
- rows))))
+ (fn check
+ ([perms] (:can-edit perms))
+ ([conn & args] (check (apply qfn conn args)))))
(defn make-read-predicate-fn
"A simple factory for read permission predicate functions."
[qfn]
(us/assert fn? qfn)
- (fn [& args]
- (let [rows (apply qfn args)]
- (when (seq rows)
- rows))))
+ (fn check
+ ([perms] (:can-read perms))
+ ([conn & args] (check (apply qfn conn args)))))
(defn make-check-fn
"Helper that converts a predicate permission function to a check
function (function that raises an exception)."
[pred]
(fn [& args]
- (when-not (seq (apply pred args))
+ (when-not (apply pred args)
(ex/raise :type :not-found
:code :object-not-found
:hint "not found"))))
-
-
-;; TODO: the following functions are deprecated and replaced with the
-;; new ones. Should not be used.
-
-(defn make-edition-check-fn
- "A simple factory for edition permission check functions."
- [qfn]
- (us/assert fn? qfn)
- (fn [& args]
- (let [rows (apply qfn args)]
- (if (or (empty? rows)
- (not (or (some :can-edit rows)
- (some :is-admin rows)
- (some :is-owner rows))))
- (ex/raise :type :not-found
- :code :object-not-found
- :hint "not found")
- rows))))
-
-(defn make-read-check-fn
- "A simple factory for read permission check functions."
- [qfn]
- (us/assert fn? qfn)
- (fn [& args]
- (let [rows (apply qfn args)]
- (if-not (seq rows)
- (ex/raise :type :not-found
- :code :object-not-found)
- rows))))
diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj
index 032032216..7caff964b 100644
--- a/backend/src/app/rpc/queries/files.clj
+++ b/backend/src/app/rpc/queries/files.clj
@@ -12,6 +12,7 @@
[app.db :as db]
[app.rpc.permissions :as perms]
[app.rpc.queries.projects :as projects]
+ [app.rpc.queries.share-link :refer [retrieve-share-link]]
[app.rpc.queries.teams :as teams]
[app.storage.impl :as simpl]
[app.util.blob :as blob]
@@ -59,7 +60,7 @@
where f.id = ?
and ppr.profile_id = ?")
-(defn- retrieve-file-permissions
+(defn retrieve-file-permissions
[conn profile-id file-id]
(when (and profile-id file-id)
(db/exec! conn [sql:file-permissions
@@ -67,11 +68,37 @@
file-id profile-id
file-id profile-id])))
+(defn get-permissions
+ ([conn profile-id file-id]
+ (let [rows (retrieve-file-permissions conn profile-id file-id)
+ is-owner (boolean (some :is-owner rows))
+ is-admin (boolean (some :is-admin rows))
+ can-edit (boolean (some :can-edit rows))]
+ (when (seq rows)
+ {:type :membership
+ :is-owner is-owner
+ :is-admin (or is-owner is-admin)
+ :can-edit (or is-owner is-admin can-edit)
+ :can-read true})))
+ ([conn profile-id file-id share-id]
+ (let [perms (get-permissions conn profile-id file-id)
+ ldata (retrieve-share-link conn file-id share-id)]
+
+ ;; NOTE: in a future when share-link becomes more powerfull and
+ ;; will allow us specify which parts of the app is availabel, we
+ ;; will probably need to tweak this function in order to expose
+ ;; this flags to the frontend.
+ (cond
+ (some? perms) perms
+ (some? ldata) {:type :share-link
+ :can-read true
+ :flags (:flags ldata)}))))
+
(def has-edit-permissions?
- (perms/make-edition-predicate-fn retrieve-file-permissions))
+ (perms/make-edition-predicate-fn get-permissions))
(def has-read-permissions?
- (perms/make-read-predicate-fn retrieve-file-permissions))
+ (perms/make-read-predicate-fn get-permissions))
(def check-edition-permissions!
(perms/make-check-fn has-edit-permissions?))
@@ -79,7 +106,6 @@
(def check-read-permissions!
(perms/make-check-fn has-read-permissions?))
-
;; --- Query: Files search
;; TODO: this query need to a good refactor
@@ -131,29 +157,6 @@
profile-id team-id
search-term])))
-
-;; --- Query: Files
-
-;; DEPRECATED: should be removed probably on 1.6.x
-
-(def ^:private sql:files
- "select f.*
- from file as f
- where f.project_id = ?
- and f.deleted_at is null
- order by f.modified_at desc")
-
-(s/def ::project-id ::us/uuid)
-(s/def ::files
- (s/keys :req-un [::profile-id ::project-id]))
-
-(sv/defmethod ::files
- [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
- (with-open [conn (db/open pool)]
- (projects/check-read-permissions! conn profile-id project-id)
- (into [] decode-row-xf (db/exec! conn [sql:files project-id]))))
-
-
;; --- Query: Project Files
(def ^:private sql:project-files
@@ -201,11 +204,15 @@
(s/keys :req-un [::profile-id ::id]))
(sv/defmethod ::file
+ "Retrieve a file by its ID. Only authenticated users."
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
(db/with-atomic [conn pool]
- (let [cfg (assoc cfg :conn conn)]
- (check-edition-permissions! conn profile-id id)
- (retrieve-file cfg id))))
+ (let [cfg (assoc cfg :conn conn)
+ perms (get-permissions conn profile-id id)]
+
+ (check-read-permissions! perms)
+ (some-> (retrieve-file cfg id)
+ (assoc :permissions perms)))))
(s/def ::page
(s/keys :req-un [::profile-id ::file-id]))
@@ -240,7 +247,8 @@
(sv/defmethod ::page
[{:keys [pool] :as cfg} {:keys [profile-id file-id strip-thumbnails]}]
(db/with-atomic [conn pool]
- (check-edition-permissions! conn profile-id file-id)
+ (check-read-permissions! conn profile-id file-id)
+
(let [cfg (assoc cfg :conn conn)
file (retrieve-file cfg file-id)
page-id (get-in file [:data :pages 0])]
@@ -250,28 +258,6 @@
;; --- Query: Shared Library Files
-;; DEPRECATED: and will be removed on 1.6.x
-
-(def ^:private sql:shared-files
- "select f.*
- from file as f
- inner join project as p on (p.id = f.project_id)
- where f.is_shared = true
- and f.deleted_at is null
- and p.deleted_at is null
- and p.team_id = ?
- order by f.modified_at desc")
-
-(s/def ::shared-files
- (s/keys :req-un [::profile-id ::team-id]))
-
-(sv/defmethod ::shared-files
- [{:keys [pool] :as cfg} {:keys [team-id] :as params}]
- (into [] decode-row-xf (db/exec! pool [sql:shared-files team-id])))
-
-
-;; --- Query: Shared Library Files
-
(def ^:private sql:team-shared-files
"select f.id,
f.project_id,
@@ -336,7 +322,7 @@
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(let [cfg (assoc cfg :conn conn)]
- (check-edition-permissions! conn profile-id file-id)
+ (check-read-permissions! conn profile-id file-id)
(retrieve-file-libraries cfg false file-id))))
;; --- QUERY: team-recent-files
diff --git a/backend/src/app/rpc/queries/profile.clj b/backend/src/app/rpc/queries/profile.clj
index 41f322881..6843a0719 100644
--- a/backend/src/app/rpc/queries/profile.clj
+++ b/backend/src/app/rpc/queries/profile.clj
@@ -70,6 +70,10 @@
[conn profile]
(merge profile (retrieve-additional-data conn (:id profile))))
+(defn- filter-profile-props
+ [props]
+ (into {} (filter (fn [[k _]] (simple-ident? k))) props))
+
(defn decode-profile-row
[{:keys [props] :as row}]
(cond-> row
@@ -90,7 +94,7 @@
(ex/raise :type :not-found
:hint "Object doest not exists."))
- profile))
+ (update profile :props filter-profile-props)))
(def ^:private sql:profile-by-email
"select p.* from profile as p
diff --git a/backend/src/app/rpc/queries/projects.clj b/backend/src/app/rpc/queries/projects.clj
index 29195ceb0..47886784a 100644
--- a/backend/src/app/rpc/queries/projects.clj
+++ b/backend/src/app/rpc/queries/projects.clj
@@ -31,18 +31,31 @@
where ppr.project_id = ?
and ppr.profile_id = ?")
-(defn- retrieve-project-permissions
+(defn- get-permissions
[conn profile-id project-id]
- (db/exec! conn [sql:project-permissions
- project-id profile-id
- project-id profile-id]))
+ (let [rows (db/exec! conn [sql:project-permissions
+ project-id profile-id
+ project-id profile-id])
+ is-owner (boolean (some :is-owner rows))
+ is-admin (boolean (some :is-admin rows))
+ can-edit (boolean (some :can-edit rows))]
+ (when (seq rows)
+ {:is-owner is-owner
+ :is-admin (or is-owner is-admin)
+ :can-edit (or is-owner is-admin can-edit)
+ :can-read true})))
+
+(def has-edit-permissions?
+ (perms/make-edition-predicate-fn get-permissions))
+
+(def has-read-permissions?
+ (perms/make-read-predicate-fn get-permissions))
(def check-edition-permissions!
- (perms/make-edition-check-fn retrieve-project-permissions))
+ (perms/make-check-fn has-edit-permissions?))
(def check-read-permissions!
- (perms/make-read-check-fn retrieve-project-permissions))
-
+ (perms/make-check-fn has-read-permissions?))
;; --- Query: Projects
diff --git a/backend/src/app/rpc/queries/recent_files.clj b/backend/src/app/rpc/queries/recent_files.clj
deleted file mode 100644
index e878d34e6..000000000
--- a/backend/src/app/rpc/queries/recent_files.clj
+++ /dev/null
@@ -1,42 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-
-(ns app.rpc.queries.recent-files
- (:require
- [app.common.spec :as us]
- [app.db :as db]
- [app.rpc.queries.files :refer [decode-row-xf]]
- [app.rpc.queries.teams :as teams]
- [app.util.services :as sv]
- [clojure.spec.alpha :as s]))
-
-;; DEPRECATED: should be removed on 1.6.x
-
-(def sql:recent-files
- "with recent_files as (
- select f.*, row_number() over w as row_num
- from file as f
- join project as p on (p.id = f.project_id)
- where p.team_id = ?
- and p.deleted_at is null
- and f.deleted_at is null
- window w as (partition by f.project_id order by f.modified_at desc)
- order by f.modified_at desc
- )
- select * from recent_files where row_num <= 10;")
-
-(s/def ::team-id ::us/uuid)
-(s/def ::profile-id ::us/uuid)
-
-(s/def ::recent-files
- (s/keys :req-un [::profile-id ::team-id]))
-
-(sv/defmethod ::recent-files
- [{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
- (with-open [conn (db/open pool)]
- (teams/check-read-permissions! conn profile-id team-id)
- (let [files (db/exec! conn [sql:recent-files team-id])]
- (into [] decode-row-xf files))))
diff --git a/backend/src/app/rpc/queries/share_link.clj b/backend/src/app/rpc/queries/share_link.clj
new file mode 100644
index 000000000..0bc567364
--- /dev/null
+++ b/backend/src/app/rpc/queries/share_link.clj
@@ -0,0 +1,23 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.rpc.queries.share-link
+ (:require
+ [app.db :as db]))
+
+(defn decode-share-link-row
+ [row]
+ (-> row
+ (update :flags db/decode-pgarray #{})
+ (update :pages db/decode-pgarray #{})))
+
+(defn retrieve-share-link
+ [conn file-id share-id]
+ (some-> (db/get-by-params conn :share-link
+ {:id share-id :file-id file-id}
+ {:check-not-found false})
+ (decode-share-link-row)))
+
diff --git a/backend/src/app/rpc/queries/teams.clj b/backend/src/app/rpc/queries/teams.clj
index 7bad8ba59..407237237 100644
--- a/backend/src/app/rpc/queries/teams.clj
+++ b/backend/src/app/rpc/queries/teams.clj
@@ -24,16 +24,29 @@
where tpr.profile_id = ?
and tpr.team_id = ?")
-(defn- retrieve-team-permissions
+(defn get-permissions
[conn profile-id team-id]
- (db/exec! conn [sql:team-permissions profile-id team-id]))
+ (let [rows (db/exec! conn [sql:team-permissions profile-id team-id])
+ is-owner (boolean (some :is-owner rows))
+ is-admin (boolean (some :is-admin rows))
+ can-edit (boolean (some :can-edit rows))]
+ (when (seq rows)
+ {:is-owner is-owner
+ :is-admin (or is-owner is-admin)
+ :can-edit (or is-owner is-admin can-edit)
+ :can-read true})))
+
+(def has-edit-permissions?
+ (perms/make-edition-predicate-fn get-permissions))
+
+(def has-read-permissions?
+ (perms/make-read-predicate-fn get-permissions))
(def check-edition-permissions!
- (perms/make-edition-check-fn retrieve-team-permissions))
+ (perms/make-check-fn has-edit-permissions?))
(def check-read-permissions!
- (perms/make-read-check-fn retrieve-team-permissions))
-
+ (perms/make-check-fn has-read-permissions?))
;; --- Query: Teams
@@ -58,12 +71,26 @@
join team as t on (t.id = tp.team_id)
where t.deleted_at is null
and tp.profile_id = ?
- order by t.created_at asc")
+ order by tp.created_at asc")
+
+(defn process-permissions
+ [team]
+ (let [is-owner (:is-owner team)
+ is-admin (:is-admin team)
+ can-edit (:can-edit team)
+ permissions {:type :membership
+ :is-owner is-owner
+ :is-admin (or is-owner is-admin)
+ :can-edit (or is-owner is-admin can-edit)}]
+ (-> team
+ (dissoc :is-owner :is-admin :can-edit)
+ (assoc :permissions permissions))))
(defn retrieve-teams
[conn profile-id]
(let [defaults (profile/retrieve-additional-data conn profile-id)]
- (db/exec! conn [sql:teams (:default-team-id defaults) profile-id])))
+ (->> (db/exec! conn [sql:teams (:default-team-id defaults) profile-id])
+ (mapv process-permissions))))
;; --- Query: Team (by ID)
@@ -86,7 +113,7 @@
(when-not result
(ex/raise :type :not-found
:code :team-does-not-exist))
- result))
+ (process-permissions result)))
;; --- Query: Team Members
diff --git a/backend/src/app/rpc/queries/viewer.clj b/backend/src/app/rpc/queries/viewer.clj
index 2f5334d99..9d35f9e07 100644
--- a/backend/src/app/rpc/queries/viewer.clj
+++ b/backend/src/app/rpc/queries/viewer.clj
@@ -10,29 +10,17 @@
[app.common.spec :as us]
[app.db :as db]
[app.rpc.queries.files :as files]
+ [app.rpc.queries.share-link :as slnk]
[app.rpc.queries.teams :as teams]
[app.util.services :as sv]
[clojure.spec.alpha :as s]))
;; --- Query: View Only Bundle
-(defn- decode-share-link-row
- [row]
- (-> row
- (update :flags db/decode-pgarray #{})
- (update :pages db/decode-pgarray #{})))
-
(defn- retrieve-project
[conn id]
(db/get-by-id conn :project id {:columns [:id :name :team-id]}))
-(defn- retrieve-share-link
- [{:keys [conn]} file-id id]
- (some-> (db/get-by-params conn :share-link
- {:id id :file-id file-id}
- {:check-not-found false})
- (decode-share-link-row)))
-
(defn- retrieve-bundle
[{:keys [conn] :as cfg} file-id]
(let [file (files/retrieve-file cfg file-id)
@@ -41,7 +29,7 @@
users (teams/retrieve-users conn (:team-id project))
links (->> (db/query conn :share-link {:file-id file-id})
- (mapv decode-share-link-row))
+ (mapv slnk/decode-share-link-row))
fonts (db/query conn :team-font-variant
{:team-id (:team-id project)
@@ -64,8 +52,11 @@
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}]
(db/with-atomic [conn pool]
(let [cfg (assoc cfg :conn conn)
- bundle (retrieve-bundle cfg file-id)
- slink (retrieve-share-link cfg file-id share-id)]
+ slink (slnk/retrieve-share-link conn file-id share-id)
+ perms (files/get-permissions conn profile-id file-id share-id)
+
+ bundle (some-> (retrieve-bundle cfg file-id)
+ (assoc :permissions perms))]
;; When we have neither profile nor share, we just return a not
;; found response to the user.
@@ -80,13 +71,6 @@
(files/check-read-permissions! conn profile-id file-id))
(cond-> bundle
- ;; If we have current profile, put
- (some? profile-id)
- (as-> $ (let [edit? (boolean (files/has-edit-permissions? conn profile-id file-id))
- read? (boolean (files/has-read-permissions? conn profile-id file-id))]
- (-> (assoc $ :permissions {:read read? :edit edit?})
- (cond-> (not edit?) (dissoc :share-links)))))
-
(some? slink)
(assoc :share slink)
@@ -97,61 +81,3 @@
(-> data
(update :pages (fn [pages] (filterv #(contains? allowed-pages %) pages)))
(update :pages-index (fn [index] (select-keys index allowed-pages)))))))))))
-
-;; --- Query: Viewer Bundle (by Page ID)
-
-;; DEPRECATED: should be removed in 1.9.x
-
-(declare check-shared-token!)
-(declare retrieve-shared-token)
-
-(s/def ::id ::us/uuid)
-(s/def ::page-id ::us/uuid)
-(s/def ::token ::us/string)
-
-(s/def ::viewer-bundle
- (s/keys :req-un [::file-id ::page-id]
- :opt-un [::profile-id ::token]))
-
-(sv/defmethod ::viewer-bundle {:auth false}
- [{:keys [pool] :as cfg} {:keys [profile-id file-id page-id token] :as params}]
- (db/with-atomic [conn pool]
- (let [cfg (assoc cfg :conn conn)
- file (files/retrieve-file cfg file-id)
- project (retrieve-project conn (:project-id file))
- page (get-in file [:data :pages-index page-id])
- file (merge (dissoc file :data)
- (select-keys (:data file) [:colors :media :typographies]))
- libs (files/retrieve-file-libraries cfg false file-id)
- users (teams/retrieve-users conn (:team-id project))
-
- fonts (db/query conn :team-font-variant
- {:team-id (:team-id project)
- :deleted-at nil})
-
- bundle {:file file
- :page page
- :users users
- :fonts fonts
- :project project
- :libraries libs}]
-
- (if (string? token)
- (do
- (check-shared-token! conn file-id page-id token)
- (assoc bundle :token token))
- (let [stoken (retrieve-shared-token conn file-id page-id)]
- (files/check-read-permissions! conn profile-id file-id)
- (assoc bundle :token (:token stoken)))))))
-
-(defn check-shared-token!
- [conn file-id page-id token]
- (let [sql "select exists(select 1 from file_share_token where file_id=? and page_id=? and token=?) as exists"]
- (when-not (:exists (db/exec-one! conn [sql file-id page-id token]))
- (ex/raise :type :not-found
- :code :object-not-found))))
-
-(defn retrieve-shared-token
- [conn file-id page-id]
- (let [sql "select * from file_share_token where file_id=? and page_id=?"]
- (db/exec-one! conn [sql file-id page-id])))
diff --git a/backend/src/app/srepl.clj b/backend/src/app/srepl.clj
index 9d7c6c825..e71a2ac26 100644
--- a/backend/src/app/srepl.clj
+++ b/backend/src/app/srepl.clj
@@ -7,9 +7,9 @@
(ns app.srepl
"Server Repl."
(:require
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.srepl.main]
- [app.util.logging :as l]
[clojure.core.server :as ccs]
[clojure.main :as cm]
[clojure.spec.alpha :as s]
diff --git a/backend/src/app/storage.clj b/backend/src/app/storage.clj
index e6acb0776..a06c228e5 100644
--- a/backend/src/app/storage.clj
+++ b/backend/src/app/storage.clj
@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.db :as db]
@@ -16,7 +17,6 @@
[app.storage.fs :as sfs]
[app.storage.impl :as impl]
[app.storage.s3 :as ss3]
- [app.util.logging :as l]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.spec.alpha :as s]
diff --git a/backend/src/app/tasks/delete_object.clj b/backend/src/app/tasks/delete_object.clj
deleted file mode 100644
index b1b3e701b..000000000
--- a/backend/src/app/tasks/delete_object.clj
+++ /dev/null
@@ -1,70 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-
-;; TODO: DEPRECATED
-;; Should be removed in the 1.8.x
-
-(ns app.tasks.delete-object
- "Generic task for permanent deletion of objects."
- (:require
- [app.common.data :as d]
- [app.common.spec :as us]
- [app.db :as db]
- [app.storage :as sto]
- [app.util.logging :as l]
- [clojure.spec.alpha :as s]
- [integrant.core :as ig]))
-
-(declare handle-deletion)
-
-(defmethod ig/pre-init-spec ::handler [_]
- (s/keys :req-un [::db/pool ::sto/storage]))
-
-(defmethod ig/init-key ::handler
- [_ {:keys [pool] :as cfg}]
- (fn [{:keys [props] :as task}]
- (us/verify ::props props)
- (db/with-atomic [conn pool]
- (let [cfg (assoc cfg :conn conn)]
- (handle-deletion cfg props)))))
-
-(s/def ::type ::us/keyword)
-(s/def ::id ::us/uuid)
-(s/def ::props (s/keys :req-un [::id ::type]))
-
-(defmulti handle-deletion
- (fn [_ props] (:type props)))
-
-(defmethod handle-deletion :default
- [_cfg {:keys [type]}]
- (l/warn :hint "no handler found"
- :type (d/name type)))
-
-(defmethod handle-deletion :file
- [{:keys [conn]} {:keys [id] :as props}]
- (let [sql "delete from file where id=? and deleted_at is not null"]
- (db/exec-one! conn [sql id])))
-
-(defmethod handle-deletion :project
- [{:keys [conn]} {:keys [id] :as props}]
- (let [sql "delete from project where id=? and deleted_at is not null"]
- (db/exec-one! conn [sql id])))
-
-(defmethod handle-deletion :team
- [{:keys [conn]} {:keys [id] :as props}]
- (let [sql "delete from team where id=? and deleted_at is not null"]
- (db/exec-one! conn [sql id])))
-
-(defmethod handle-deletion :team-font-variant
- [{:keys [conn storage]} {:keys [id] :as props}]
- (let [font (db/get-by-id conn :team-font-variant id {:check-not-found false})
- storage (assoc storage :conn conn)]
- (when (:deleted-at font)
- (db/delete! conn :team-font-variant {:id id})
- (some->> (:woff1-file-id font) (sto/del-object storage))
- (some->> (:woff2-file-id font) (sto/del-object storage))
- (some->> (:otf-file-id font) (sto/del-object storage))
- (some->> (:ttf-file-id font) (sto/del-object storage)))))
diff --git a/backend/src/app/tasks/delete_profile.clj b/backend/src/app/tasks/delete_profile.clj
deleted file mode 100644
index 67a1733df..000000000
--- a/backend/src/app/tasks/delete_profile.clj
+++ /dev/null
@@ -1,79 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-
-(ns app.tasks.delete-profile
- "Task for permanent deletion of profiles."
- (:require
- [app.common.spec :as us]
- [app.db :as db]
- [app.db.sql :as sql]
- [app.util.logging :as l]
- [clojure.spec.alpha :as s]
- [integrant.core :as ig]))
-
-;; TODO: DEPRECATED
-;; Should be removed in the 1.8.x
-
-(declare delete-profile-data)
-
-;; --- INIT
-
-(defmethod ig/pre-init-spec ::handler [_]
- (s/keys :req-un [::db/pool]))
-
-;; This task is responsible to permanently delete a profile with all
-;; the dependent data. As step (1) we delete all owned teams of the
-;; profile (that will cause to delete all underlying projects, files,
-;; file_media and mark to be deleted storage_object's used by team,
-;; profile and files previously deleted. Then, finally as step (2) we
-;; proceed to delete the profile row.
-;;
-;; The storage_objects marked as deleted will be deleted by the
-;; corresponding garbage collector task.
-
-(s/def ::profile-id ::us/uuid)
-(s/def ::props (s/keys :req-un [::profile-id]))
-
-(defmethod ig/init-key ::handler
- [_ {:keys [pool] :as cfg}]
- (fn [{:keys [props] :as task}]
- (us/verify ::props props)
- (db/with-atomic [conn pool]
- (let [id (:profile-id props)
- profile (db/exec-one! conn (sql/select :profile {:id id} {:for-update true}))]
- (if (or (:is-demo profile)
- (:deleted-at profile))
- (delete-profile-data conn id)
- (l/warn :hint "profile does not match constraints for deletion"
- :profile-id id))))))
-
-;; --- IMPL
-
-(def ^:private sql:remove-owned-teams
- "delete from team
- where id in (
- select tpr.team_id
- from team_profile_rel as tpr
- where tpr.is_owner is true
- and tpr.profile_id = ?
- )")
-
-(defn- delete-teams
- [conn profile-id]
- (db/exec-one! conn [sql:remove-owned-teams profile-id]))
-
-(defn delete-profile
- [conn profile-id]
- (db/delete! conn :profile {:id profile-id}))
-
-(defn- delete-profile-data
- [conn profile-id]
- (l/debug :action "delete profile"
- :profile-id profile-id)
- (delete-teams conn profile-id)
- (delete-profile conn profile-id)
- true)
-
diff --git a/backend/src/app/tasks/file_media_gc.clj b/backend/src/app/tasks/file_media_gc.clj
index aedcbc78b..2b8a13384 100644
--- a/backend/src/app/tasks/file_media_gc.clj
+++ b/backend/src/app/tasks/file_media_gc.clj
@@ -9,10 +9,10 @@
objects from files. A file is ellegible to be garbage collected
after some period of inactivity (the default threshold is 72h)."
(:require
+ [app.common.logging :as l]
[app.common.pages.migrations :as pmg]
[app.db :as db]
[app.util.blob :as blob]
- [app.util.logging :as l]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
diff --git a/backend/src/app/tasks/file_offload.clj b/backend/src/app/tasks/file_offload.clj
index ee784d02d..a43afb8a9 100644
--- a/backend/src/app/tasks/file_offload.clj
+++ b/backend/src/app/tasks/file_offload.clj
@@ -7,11 +7,11 @@
(ns app.tasks.file-offload
"A maintenance task that offloads file data to an external storage (S3)."
(:require
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.db :as db]
[app.storage :as sto]
[app.storage.impl :as simpl]
- [app.util.logging :as l]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
diff --git a/backend/src/app/tasks/file_xlog_gc.clj b/backend/src/app/tasks/file_xlog_gc.clj
index 3037a7d5b..b8dce2fa5 100644
--- a/backend/src/app/tasks/file_xlog_gc.clj
+++ b/backend/src/app/tasks/file_xlog_gc.clj
@@ -8,8 +8,8 @@
"A maintenance task that performs a garbage collection of the file
change (transaction) log."
(:require
+ [app.common.logging :as l]
[app.db :as db]
- [app.util.logging :as l]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
diff --git a/backend/src/app/tasks/objects_gc.clj b/backend/src/app/tasks/objects_gc.clj
index 112524560..a3d06dcdd 100644
--- a/backend/src/app/tasks/objects_gc.clj
+++ b/backend/src/app/tasks/objects_gc.clj
@@ -8,11 +8,11 @@
"A maintenance task that performs a general purpose garbage collection
of deleted objects."
(:require
+ [app.common.logging :as l]
[app.config :as cf]
[app.db :as db]
[app.storage :as sto]
[app.storage.impl :as simpl]
- [app.util.logging :as l]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
diff --git a/backend/src/app/tasks/tasks_gc.clj b/backend/src/app/tasks/tasks_gc.clj
index a3560f0e1..788e29269 100644
--- a/backend/src/app/tasks/tasks_gc.clj
+++ b/backend/src/app/tasks/tasks_gc.clj
@@ -8,8 +8,8 @@
"A maintenance task that performs a cleanup of already executed tasks
from the database table."
(:require
+ [app.common.logging :as l]
[app.db :as db]
- [app.util.logging :as l]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
diff --git a/backend/src/app/util/logging.clj b/backend/src/app/util/logging.clj
deleted file mode 100644
index 9a08e66b3..000000000
--- a/backend/src/app/util/logging.clj
+++ /dev/null
@@ -1,110 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-
-(ns app.util.logging
- (:require
- [clojure.pprint :refer [pprint]])
- (:import
- org.apache.logging.log4j.Level
- org.apache.logging.log4j.LogManager
- org.apache.logging.log4j.Logger
- org.apache.logging.log4j.ThreadContext
- org.apache.logging.log4j.message.MapMessage
- org.apache.logging.log4j.spi.LoggerContext))
-
-(defn build-map-message
- [m]
- (let [message (MapMessage. (count m))]
- (reduce-kv #(.with ^MapMessage %1 (name %2) %3) message m)))
-
-(defprotocol ILogger
- (-enabled? [logger level])
- (-write! [logger level throwable message]))
-
-(def logger-context
- (LogManager/getContext false))
-
-(def logging-agent
- (agent nil :error-mode :continue))
-
-(defn get-logger
- [lname]
- (.getLogger ^LoggerContext logger-context ^String lname))
-
-(defn get-level
- [level]
- (case level
- :trace Level/TRACE
- :debug Level/DEBUG
- :info Level/INFO
- :warn Level/WARN
- :error Level/ERROR
- :fatal Level/FATAL))
-
-(defn enabled?
- [logger level]
- (.isEnabled ^Logger logger ^Level level))
-
-(defn write-log!
- [logger level e msg]
- (if e
- (.log ^Logger logger
- ^Level level
- ^Object msg
- ^Throwable e)
- (.log ^Logger logger
- ^Level level
- ^Object msg)))
-
-(defmacro log
- [& {:keys [level cause ::logger ::async ::raw] :as props}]
- (let [props (dissoc props :level :cause ::logger ::async ::raw)
- logger (or logger (str *ns*))
- logger-sym (gensym "log")
- level-sym (gensym "log")]
- `(let [~logger-sym (get-logger ~logger)
- ~level-sym (get-level ~level)]
- (if (enabled? ~logger-sym ~level-sym)
- ~(if async
- `(send-off logging-agent
- (fn [_#]
- (let [message# (or ~raw (build-map-message ~props))]
- (write-log! ~logger-sym ~level-sym ~cause message#))))
- `(let [message# (or ~raw (build-map-message ~props))]
- (write-log! ~logger-sym ~level-sym ~cause message#)))))))
-
-(defmacro info
- [& params]
- `(log :level :info ~@params))
-
-(defmacro error
- [& params]
- `(log :level :error ~@params))
-
-(defmacro warn
- [& params]
- `(log :level :warn ~@params))
-
-(defmacro debug
- [& params]
- `(log :level :debug ~@params))
-
-(defmacro trace
- [& params]
- `(log :level :trace ~@params))
-
-(defn update-thread-context!
- [data]
- (run! (fn [[key val]]
- (ThreadContext/put
- (name key)
- (cond
- (coll? val)
- (binding [clojure.pprint/*print-right-margin* 120]
- (with-out-str (pprint val)))
- (instance? clojure.lang.Named val) (name val)
- :else (str val))))
- data))
diff --git a/backend/src/app/util/migrations.clj b/backend/src/app/util/migrations.clj
index f79c50b2b..7b7fb8a44 100644
--- a/backend/src/app/util/migrations.clj
+++ b/backend/src/app/util/migrations.clj
@@ -6,7 +6,7 @@
(ns app.util.migrations
(:require
- [app.util.logging :as l]
+ [app.common.logging :as l]
[clojure.java.io :as io]
[clojure.spec.alpha :as s]
[next.jdbc :as jdbc]))
diff --git a/backend/src/app/util/services.clj b/backend/src/app/util/services.clj
index 1621ad760..9faa8adcb 100644
--- a/backend/src/app/util/services.clj
+++ b/backend/src/app/util/services.clj
@@ -7,21 +7,34 @@
(ns app.util.services
"A helpers and macros for define rpc like registry based services."
(:refer-clojure :exclude [defmethod])
- (:require [app.common.data :as d]))
+ (:require
+ [app.common.data :as d]
+ [cuerdas.core :as str]))
(defmacro defmethod
[sname & body]
- (let [[mdata args body] (if (map? (first body))
- [(first body) (first (rest body)) (drop 2 body)]
- [nil (first body) (rest body)])
- mdata (assoc mdata
- ::spec sname
- ::name (name sname))
+ (let [[docs body] (if (string? (first body))
+ [(first body) (rest body)]
+ [nil body])
+ [mdata body] (if (map? (first body))
+ [(first body) (rest body)]
+ [nil body])
- sym (symbol (str "sm$" (name sname)))]
- `(do
- (def ~sym (fn ~args ~@body))
- (reset-meta! (var ~sym) ~mdata))))
+ [args body] (if (vector? (first body))
+ [(first body) (rest body)]
+ [nil body])]
+ (when-not args
+ (throw (IllegalArgumentException. "Missing arguments on `defmethod` macro.")))
+
+ (let [mdata (assoc mdata
+ ::docs (some-> docs str/<<-)
+ ::spec sname
+ ::name (name sname))
+
+ sym (symbol (str "sm$" (name sname)))]
+ `(do
+ (def ~sym (fn ~args ~@body))
+ (reset-meta! (var ~sym) ~mdata)))))
(def nsym-xf
(comp
diff --git a/backend/src/app/rpc/queries/svg.clj b/backend/src/app/util/svg.clj
similarity index 79%
rename from backend/src/app/rpc/queries/svg.clj
rename to backend/src/app/util/svg.clj
index 63c0b8aeb..9c3aab795 100644
--- a/backend/src/app/rpc/queries/svg.clj
+++ b/backend/src/app/util/svg.clj
@@ -4,13 +4,10 @@
;;
;; Copyright (c) UXBOX Labs SL
-(ns app.rpc.queries.svg
+(ns app.util.svg
(:require
[app.common.exceptions :as ex]
- [app.common.spec :as us]
- [app.util.logging :as l]
- [app.util.services :as sv]
- [clojure.spec.alpha :as s]
+ [app.common.logging :as l]
[clojure.xml :as xml]
[cuerdas.core :as str])
(:import
@@ -39,14 +36,6 @@
:hint "invalid svg file"
:cause e))))
-(declare pre-process)
-
-(s/def ::data ::us/string)
-(s/def ::parsed-svg (s/keys :req-un [::data]))
-
-(sv/defmethod ::parsed-svg
- [_ {:keys [data] :as params}]
- (->> data pre-process parse))
;; --- PROCESSORS
diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj
index ed9d1982f..49370e164 100644
--- a/backend/src/app/worker.clj
+++ b/backend/src/app/worker.clj
@@ -9,12 +9,12 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.db :as db]
[app.metrics :as mtx]
[app.util.async :as aa]
- [app.util.logging :as l]
[app.util.time :as dt]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
diff --git a/backend/test/app/services_files_test.clj b/backend/test/app/services_files_test.clj
index e22d8afc8..0fb5acc58 100644
--- a/backend/test/app/services_files_test.clj
+++ b/backend/test/app/services_files_test.clj
@@ -53,21 +53,6 @@
(t/is (= (:id data) (:id result)))
(t/is (= (:name data) (:name result))))))
- (t/testing "query files (deprecated)"
- (let [data {::th/type :files
- :project-id proj-id
- :profile-id (:id prof)}
- out (th/query! data)]
-
- ;; (th/print-result! out)
- (t/is (nil? (:error out)))
-
- (let [result (:result out)]
- (t/is (= 1 (count result)))
- (t/is (= file-id (get-in result [0 :id])))
- (t/is (= "new name" (get-in result [0 :name])))
- (t/is (= 1 (count (get-in result [0 :data :pages])))))))
-
(t/testing "query files"
(let [data {::th/type :project-files
:project-id proj-id
@@ -120,7 +105,7 @@
(t/is (= (:type error-data) :not-found)))))
(t/testing "query list files after delete"
- (let [data {::th/type :files
+ (let [data {::th/type :project-files
:project-id proj-id
:profile-id (:id prof)}
out (th/query! data)]
diff --git a/backend/test/app/services_profile_test.clj b/backend/test/app/services_profile_test.clj
index d5e6dac43..b51bcd8c0 100644
--- a/backend/test/app/services_profile_test.clj
+++ b/backend/test/app/services_profile_test.clj
@@ -89,7 +89,7 @@
;; (th/print-result! out)
(t/is (nil? (:error out)))
- (t/is (nil? (:result out)))))
+ (t/is (map? (:result out)))))
(t/testing "query profile after update"
(let [data {::th/type :profile
@@ -136,7 +136,7 @@
(t/is (nil? (:error out))))
;; query files after profile soft deletion
- (let [params {::th/type :files
+ (let [params {::th/type :project-files
:project-id (:default-project-id prof)
:profile-id (:id prof)}
out (th/query! params)]
@@ -177,17 +177,6 @@
(t/is (string? token))
- ;; try register without accepting terms
- (let [data {::th/type :register-profile
- :token token
- :fullname "foobar"
- :accept-terms-and-privacy false}
- out (th/mutation! data)]
- (let [error (:error out)]
- (t/is (th/ex-info? error))
- (t/is (th/ex-of-type? error :validation))
- (t/is (th/ex-of-code? error :invalid-terms-and-privacy))))
-
;; try register without token
(let [data {::th/type :register-profile
:fullname "foobar"
@@ -205,16 +194,11 @@
:accept-terms-and-privacy true
:accept-newsletter-subscription true}]
(let [{:keys [result error]} (th/mutation! data)]
- (t/is (nil? error))
- (t/is (true? (get-in result [:props :accept-newsletter-subscription])))
- (t/is (true? (get-in result [:props :accept-terms-and-privacy])))))
+ (t/is (nil? error))))
))
(t/deftest prepare-register-with-registration-disabled
- (with-mocks [mock {:target 'app.config/get
- :return (th/mock-config-get-with
- {:registration-enabled false})}]
-
+ (th/with-mocks {#'app.config/flags nil}
(let [data {::th/type :prepare-register-profile
:email "user@example.com"
:password "foobar"}]
diff --git a/backend/test/app/services_teams_test.clj b/backend/test/app/services_teams_test.clj
index 2831ea4ee..6dd7470da 100644
--- a/backend/test/app/services_teams_test.clj
+++ b/backend/test/app/services_teams_test.clj
@@ -33,11 +33,13 @@
:role :editor
:profile-id (:id profile1)}]
- ;; (th/print-result! out)
;; invite external user without complaints
(let [data (assoc data :email "foo@bar.com")
out (th/mutation! data)]
+
+ ;; (th/print-result! out)
+
(t/is (nil? (:result out)))
(t/is (= 1 (:call-count (deref mock)))))
@@ -111,6 +113,7 @@
:id (:id team)
:profile-id (:id profile1)}
out (th/mutation! params)]
+ ;; (th/print-result! out)
(t/is (nil? (:error out))))
;; query the list of teams after soft deletion
@@ -133,7 +136,6 @@
:profile-id (:id profile1)}
out (th/query! data)]
;; (th/print-result! out)
-
(t/is (nil? (:error out)))
(let [result (:result out)]
(t/is (= 0 (count result)))))
diff --git a/backend/test/app/test_helpers.clj b/backend/test/app/test_helpers.clj
index ba8999123..43334b883 100644
--- a/backend/test/app/test_helpers.clj
+++ b/backend/test/app/test_helpers.clj
@@ -7,6 +7,7 @@
(ns app.test-helpers
(:require
[app.common.data :as d]
+ [app.common.flags :as flags]
[app.common.pages :as cp]
[app.common.spec :as us]
[app.common.uuid :as uuid]
@@ -336,9 +337,15 @@
[data]
(fn
([key]
- (get data key (get @cf/config key)))
+ (get data key (get cf/config key)))
([key default]
- (get data key (get @cf/config key default)))))
+ (get data key (get cf/config key default)))))
+
+
+(defmacro with-mocks
+ [rebinds & body]
+ `(with-redefs-fn ~rebinds
+ (fn [] ~@body)))
(defn reset-mock!
[m]
diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc
index 3d227cb38..77dcae010 100644
--- a/common/src/app/common/data.cljc
+++ b/common/src/app/common/data.cljc
@@ -69,6 +69,11 @@
(next colls))
(persistent! result))))
+(defn preconj
+ [coll elem]
+ (assert (vector? coll))
+ (concat [elem] coll))
+
(defn enumerate
([items] (enumerate items 0))
([items start]
@@ -154,6 +159,11 @@
([mfn coll]
(into {} (mapm mfn) coll)))
+(defn removev
+ "Returns a vector of the items in coll for which (fn item) returns logical false"
+ [fn coll]
+ (filterv (comp not fn) coll))
+
(defn filterm
"Filter values of a map that satisfy a predicate"
[pred coll]
diff --git a/common/src/app/common/exceptions.cljc b/common/src/app/common/exceptions.cljc
index 5818578a9..3371dbbd9 100644
--- a/common/src/app/common/exceptions.cljc
+++ b/common/src/app/common/exceptions.cljc
@@ -27,7 +27,10 @@
[& {:keys [message hint cause] :as params}]
(s/assert ::error-params params)
(let [message (or message hint "")
- payload (dissoc params :cause)]
+ payload (-> params
+ (dissoc :cause)
+ (dissoc :message)
+ (assoc :hint message))]
(ex-info message payload cause)))
(defmacro raise
diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc
index 4a497cda5..0ae1127df 100644
--- a/common/src/app/common/file_builder.cljc
+++ b/common/src/app/common/file_builder.cljc
@@ -278,6 +278,48 @@
(-> file
(update :parent-stack pop))))
+(defn add-bool [file data]
+ (let [frame-id (:current-frame-id file)
+ name (:name data)
+ obj (-> {:id (uuid/next)
+ :type :bool
+ :name name
+ :shapes []
+ :frame-id frame-id}
+ (merge data)
+ (check-name file :bool)
+ (d/without-nils))]
+ (-> file
+ (commit-shape obj)
+ (assoc :last-id (:id obj))
+ (add-name (:name obj))
+ (update :parent-stack conjv (:id obj)))))
+
+(defn close-bool [file]
+ (let [bool-id (-> file :parent-stack peek)
+ bool (lookup-shape file bool-id)
+ children (->> bool :shapes (mapv #(lookup-shape file %)))
+
+ file
+ (let [objects (lookup-objects file)
+ bool' (gsh/update-bool-selrect bool children objects)]
+ (commit-change
+ file
+ {:type :mod-obj
+ :id bool-id
+ :operations
+ [{:type :set :attr :selrect :val (:selrect bool')}
+ {:type :set :attr :points :val (:points bool')}
+ {:type :set :attr :x :val (-> bool' :selrect :x)}
+ {:type :set :attr :y :val (-> bool' :selrect :y)}
+ {:type :set :attr :width :val (-> bool' :selrect :width)}
+ {:type :set :attr :height :val (-> bool' :selrect :height)}]}
+
+ {:add-container? true}))]
+
+ (-> file
+ (update :parent-stack pop))))
+
(defn create-shape [file type data]
(let [frame-id (:current-frame-id file)
frame (when-not (= frame-id root-frame)
@@ -332,21 +374,76 @@
(-> file
(update :parent-stack pop)))
+(defn- read-classifier
+ [interaction-src]
+ (select-keys interaction-src [:event-type :action-type]))
+
+(defmulti read-event-opts :event-type)
+
+(defmethod read-event-opts :after-delay
+ [interaction-src]
+ (select-keys interaction-src [:delay]))
+
+(defmethod read-event-opts :default
+ [_]
+ {})
+
+(defmulti read-action-opts :action-type)
+
+(defmethod read-action-opts :navigate
+ [interaction-src]
+ (select-keys interaction-src [:destination]))
+
+(defmethod read-action-opts :open-overlay
+ [interaction-src]
+ (select-keys interaction-src [:destination
+ :overlay-position
+ :overlay-pos-type
+ :close-click-outside
+ :background-overlay]))
+
+(defmethod read-action-opts :toggle-overlay
+ [interaction-src]
+ (select-keys interaction-src [:destination
+ :overlay-position
+ :overlay-pos-type
+ :close-click-outside
+ :background-overlay]))
+
+(defmethod read-action-opts :close-overlay
+ [interaction-src]
+ (select-keys interaction-src [:destination]))
+
+(defmethod read-action-opts :prev-screen
+ [_]
+ {})
+
+(defmethod read-action-opts :open-url
+ [interaction-src]
+ (select-keys interaction-src [:url]))
+
(defn add-interaction
- [file from-id {:keys [action-type event-type destination]}]
+ [file from-id interaction-src]
(assert (some? (lookup-shape file from-id)) (str "Cannot locate shape with id " from-id))
- (assert (some? (lookup-shape file destination)) (str "Cannot locate shape with id " destination))
- (let [interactions (->> (lookup-shape file from-id)
- :interactions
- (filterv #(or (not= (:action-type %) action-type)
- (not= (:event-type %) event-type))))
- interactions (-> interactions
+ (let [{:keys [event-type action-type]} (read-classifier interaction-src)
+ {:keys [delay]} (read-event-opts interaction-src)
+ {:keys [destination overlay-pos-type overlay-position url
+ close-click-outside background-overlay]} (read-action-opts interaction-src)
+
+ interactions (-> (lookup-shape file from-id)
+ :interactions
(conjv
- {:action-type action-type
- :event-type event-type
- :destination destination}))]
+ (d/without-nils {:event-type event-type
+ :action-type action-type
+ :delay delay
+ :destination destination
+ :overlay-pos-type overlay-pos-type
+ :overlay-position overlay-position
+ :url url
+ :close-click-outside close-click-outside
+ :background-overlay background-overlay})))]
(commit-change
file
{:type :mod-obj
diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc
index a5a051f7c..acc1d2c5c 100644
--- a/common/src/app/common/flags.cljc
+++ b/common/src/app/common/flags.cljc
@@ -9,24 +9,31 @@
(:require
[cuerdas.core :as str]))
+(def default
+ #{:backend-asserts
+ :api-doc
+ :registration
+ :demo-users})
+
(defn parse
- [default flags]
- (loop [flags (seq flags)
- result default]
- (let [item (first flags)]
- (if (nil? item)
- result
- (let [sname (name item)]
- (cond
- (str/starts-with? sname "enable-")
- (recur (rest flags)
- (conj result (keyword (subs sname 7))))
+ ([flags] (parse flags #{}))
+ ([flags default]
+ (loop [flags (seq flags)
+ result default]
+ (let [item (first flags)]
+ (if (nil? item)
+ result
+ (let [sname (name item)]
+ (cond
+ (str/starts-with? sname "enable-")
+ (recur (rest flags)
+ (conj result (keyword (subs sname 7))))
- (str/starts-with? sname "disable-")
- (recur (rest flags)
- (disj result (keyword (subs sname 8))))
+ (str/starts-with? sname "disable-")
+ (recur (rest flags)
+ (disj result (keyword (subs sname 8))))
- :else
- (recur (rest flags) result)))))))
+ :else
+ (recur (rest flags) result))))))))
diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc
index 0d3feeb06..b73a050e6 100644
--- a/common/src/app/common/geom/point.cljc
+++ b/common/src/app/common/geom/point.cljc
@@ -22,7 +22,8 @@
(defn ^boolean point?
"Return true if `v` is Point instance."
[v]
- (instance? Point v))
+ (or (instance? Point v)
+ (and (map? v) (contains? v :x) (contains? v :y))))
(defn ^boolean point-like?
[{:keys [x y] :as v}]
@@ -257,15 +258,12 @@
(and (mth/almost-zero? x)
(mth/almost-zero? y)))
-(defn line-val
- "Given a line with two points p1-p2 and a 'percent'. Returns the point in the vector
- generated by these two points. For example: for p1=(0,0) p2=(1,1) and v=0.25 will return
- the point (0.25, 0.25)"
- [p1 p2 v]
- (let [v (-> (to-vec p1 p2)
- (scale v))]
- (add p1 v)))
-
+(defn lerp
+ "Calculates a linear interpolation between two points given a tvalue"
+ [p1 p2 t]
+ (let [x (mth/lerp (:x p1) (:x p2) t)
+ y (mth/lerp (:y p1) (:y p2) t)]
+ (point x y)))
(defn rotate
"Rotates the point around center with an angle"
diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc
index 8771ff2e3..91a7043ba 100644
--- a/common/src/app/common/geom/shapes.cljc
+++ b/common/src/app/common/geom/shapes.cljc
@@ -8,11 +8,13 @@
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
+ [app.common.geom.shapes.bool :as gsb]
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.intersect :as gin]
[app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.rect :as gpr]
- [app.common.geom.shapes.transforms :as gtr]))
+ [app.common.geom.shapes.transforms :as gtr]
+ [app.common.math :as mth]))
;; --- Setup (Initialize)
;; FIXME: Is this the correct place for these functions?
@@ -126,6 +128,13 @@
(assoc :selrect selrect
:points points))))
+(defn shape-stroke-margin
+ [shape stroke-width]
+ (if (= (:type shape) :path)
+ ;; TODO: Calculate with the stroke offset (not implemented yet
+ (mth/sqrt (* 2 stroke-width stroke-width))
+ (- (mth/sqrt (* 2 stroke-width stroke-width)) stroke-width)))
+
;; EXPORTS
(d/export gco/center-shape)
@@ -133,19 +142,20 @@
(d/export gco/center-rect)
(d/export gco/center-points)
(d/export gco/make-centered-rect)
+(d/export gco/transform-points)
(d/export gpr/rect->selrect)
(d/export gpr/rect->points)
(d/export gpr/points->selrect)
(d/export gpr/points->rect)
(d/export gpr/center->rect)
+(d/export gpr/join-rects)
(d/export gtr/move)
(d/export gtr/absolute-move)
(d/export gtr/transform-matrix)
(d/export gtr/inverse-transform-matrix)
(d/export gtr/transform-point-center)
-(d/export gtr/transform-points)
(d/export gtr/transform-rect)
(d/export gtr/calculate-adjust-matrix)
(d/export gtr/update-group-selrect)
@@ -156,12 +166,15 @@
(d/export gtr/calc-child-modifiers)
;; PATHS
-(d/export gsp/content->points)
(d/export gsp/content->selrect)
(d/export gsp/transform-content)
+(d/export gsp/open-path?)
;; Intersection
(d/export gin/overlaps?)
(d/export gin/has-point?)
(d/export gin/has-point-rect?)
(d/export gin/rect-contains-shape?)
+
+;; Bool
+(d/export gsb/update-bool-selrect)
diff --git a/common/src/app/common/geom/shapes/bool.cljc b/common/src/app/common/geom/shapes/bool.cljc
new file mode 100644
index 000000000..93b7ccc72
--- /dev/null
+++ b/common/src/app/common/geom/shapes/bool.cljc
@@ -0,0 +1,32 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.common.geom.shapes.bool
+ (:require
+ [app.common.geom.shapes.path :as gsp]
+ [app.common.geom.shapes.rect :as gpr]
+ [app.common.geom.shapes.transforms :as gtr]
+ [app.common.path.bool :as pb]
+ [app.common.path.shapes-to-path :as stp]))
+
+(defn update-bool-selrect
+ "Calculates the selrect+points for the boolean shape"
+ [shape children objects]
+
+ (let [content (->> children
+ (map #(stp/convert-to-path % objects))
+ (mapv :content)
+ (pb/content-bool (:bool-type shape)))
+
+ [points selrect]
+ (if (empty? content)
+ (let [selrect (gtr/selection-rect children)
+ points (gpr/rect->points selrect)]
+ [points selrect])
+ (gsp/content->points+selrect shape content))]
+ (-> shape
+ (assoc :selrect selrect)
+ (assoc :points points))))
diff --git a/common/src/app/common/geom/shapes/common.cljc b/common/src/app/common/geom/shapes/common.cljc
index 9b5c6d1b3..00eec4386 100644
--- a/common/src/app/common/geom/shapes/common.cljc
+++ b/common/src/app/common/geom/shapes/common.cljc
@@ -6,6 +6,7 @@
(ns app.common.geom.shapes.common
(:require
+ [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.math :as mth]))
@@ -48,3 +49,14 @@
:y (- (:y center) (/ height 2.0))
:width width
:height height})
+
+(defn transform-points
+ ([points matrix]
+ (transform-points points nil matrix))
+ ([points center matrix]
+ (let [prev (if center (gmt/translate-matrix center) (gmt/matrix))
+ post (if center (gmt/translate-matrix (gpt/negate center)) (gmt/matrix))
+
+ tr-point (fn [point]
+ (gpt/transform point (gmt/multiply prev matrix post)))]
+ (mapv tr-point points))))
diff --git a/common/src/app/common/geom/shapes/intersect.cljc b/common/src/app/common/geom/shapes/intersect.cljc
index 4b0593dc1..3cc358965 100644
--- a/common/src/app/common/geom/shapes/intersect.cljc
+++ b/common/src/app/common/geom/shapes/intersect.cljc
@@ -284,12 +284,19 @@
(defn overlaps?
"General case to check for overlaping between shapes and a rectangle"
[shape rect]
- (or (not shape)
- (let [path? (= :path (:type shape))
- circle? (= :circle (:type shape))]
- (and (overlaps-rect-points? rect (:points shape))
- (or (not path?) (overlaps-path? shape rect))
- (or (not circle?) (overlaps-ellipse? shape rect))))))
+ (let [stroke-width (/ (or (:stroke-width shape) 0) 2)
+ rect (-> rect
+ (update :x - stroke-width)
+ (update :y - stroke-width)
+ (update :width + (* 2 stroke-width))
+ (update :height + (* 2 stroke-width))
+ )]
+ (or (not shape)
+ (let [path? (= :path (:type shape))
+ circle? (= :circle (:type shape))]
+ (and (overlaps-rect-points? rect (:points shape))
+ (or (not path?) (overlaps-path? shape rect))
+ (or (not circle?) (overlaps-ellipse? shape rect)))))))
(defn has-point-rect?
[rect point]
@@ -308,3 +315,4 @@
(->> shape
:points
(every? (partial has-point-rect? rect))))
+
diff --git a/common/src/app/common/geom/shapes/path.cljc b/common/src/app/common/geom/shapes/path.cljc
index ff39fa7db..d8bd67cfc 100644
--- a/common/src/app/common/geom/shapes/path.cljc
+++ b/common/src/app/common/geom/shapes/path.cljc
@@ -7,97 +7,276 @@
(ns app.common.geom.shapes.path
(:require
[app.common.data :as d]
+ [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
+ [app.common.geom.shapes.common :as gsc]
[app.common.geom.shapes.rect :as gpr]
- [app.common.math :as mth]))
+ [app.common.math :as mth]
+ [app.common.path.commands :as upc]
+ [app.common.path.subpaths :as sp]))
-(defn content->points [content]
+(def ^:const curve-curve-precision 0.1)
+(def ^:const curve-range-precision 2)
+
+(defn s= [a b]
+ (mth/almost-zero? (- a b)))
+
+(defn calculate-opposite-handler
+ "Given a point and its handler, gives the symetric handler"
+ [point handler]
+ (let [handler-vector (gpt/to-vec point handler)]
+ (gpt/add point (gpt/negate handler-vector))))
+
+(defn opposite-handler
+ "Calculates the coordinates of the opposite handler"
+ [point handler]
+ (let [phv (gpt/to-vec point handler)]
+ (gpt/add point (gpt/negate phv))))
+
+(defn opposite-handler-keep-distance
+ "Calculates the coordinates of the opposite handler but keeping the old distance"
+ [point handler old-opposite]
+ (let [old-distance (gpt/distance point old-opposite)
+ phv (gpt/to-vec point handler)
+ phv2 (gpt/multiply
+ (gpt/unit (gpt/negate phv))
+ (gpt/point old-distance))]
+ (gpt/add point phv2)))
+
+(defn content->points
+ "Returns the points in the given content"
+ [content]
(->> content
- (map #(when (-> % :params :x) (gpt/point (-> % :params :x) (-> % :params :y))))
+ (map #(when (-> % :params :x)
+ (gpt/point (-> % :params :x) (-> % :params :y))))
(remove nil?)
(into [])))
+(defn line-values
+ [[from-p to-p] t]
+ (let [move-v (-> (gpt/to-vec from-p to-p)
+ (gpt/scale t))]
+ (gpt/add from-p move-v)))
+
+(defn line-windup
+ [[from-p to-p :as l] t]
+ (let [p (line-values l t)
+ cy (:y p)
+ ay (:y to-p)
+ by (:y from-p)]
+ (cond
+ (and (> (- cy ay) 0) (not (s= cy ay))) 1
+ (and (< (- cy ay) 0) (not (s= cy ay))) -1
+ (< (- cy by) 0) 1
+ (> (- cy by) 0) -1
+ :else 0)))
+
;; https://medium.com/@Acegikmo/the-ever-so-lovely-b%C3%A9zier-curve-eb27514da3bf
;; https://en.wikipedia.org/wiki/Bernstein_polynomial
(defn curve-values
"Parametric equation for cubic beziers. Given a start and end and
two intermediate points returns points for values of t.
If you draw t on a plane you got the bezier cube"
- [start end h1 h2 t]
+ ([[start end h1 h2] t]
+ (curve-values start end h1 h2 t))
- (let [t2 (* t t) ;; t square
- t3 (* t2 t) ;; t cube
+ ([start end h1 h2 t]
+ (let [t2 (* t t) ;; t square
+ t3 (* t2 t) ;; t cube
- start-v (+ (- t3) (* 3 t2) (* -3 t) 1)
- h1-v (+ (* 3 t3) (* -6 t2) (* 3 t))
- h2-v (+ (* -3 t3) (* 3 t2))
- end-v t3
+ start-v (+ (- t3) (* 3 t2) (* -3 t) 1)
+ h1-v (+ (* 3 t3) (* -6 t2) (* 3 t))
+ h2-v (+ (* -3 t3) (* 3 t2))
+ end-v t3
- coord-v (fn [coord]
- (+ (* (coord start) start-v)
- (* (coord h1) h1-v)
- (* (coord h2) h2-v)
- (* (coord end) end-v)))]
+ coord-v (fn [coord]
+ (+ (* (coord start) start-v)
+ (* (coord h1) h1-v)
+ (* (coord h2) h2-v)
+ (* (coord end) end-v)))]
- (gpt/point (coord-v :x) (coord-v :y))))
+ (gpt/point (coord-v :x) (coord-v :y)))))
+
+(defn curve-tangent
+ "Retrieve the tangent vector to the curve in the point `t`"
+ [[start end h1 h2] t]
+
+ (let [coords [[(:x start) (:x h1) (:x h2) (:x end)]
+ [(:y start) (:y h1) (:y h2) (:y end)]]
+
+ solve-derivative
+ (fn [[c0 c1 c2 c3]]
+ ;; Solve B'(t) given t to retrieve the value for the
+ ;; first derivative
+ (let [t2 (* t t)]
+ (+ (* c0 (+ (* -3 t2) (* 6 t) -3))
+ (* c1 (+ (* 9 t2) (* -12 t) 3))
+ (* c2 (+ (* -9 t2) (* 6 t)))
+ (* c3 (* 3 t2)))))
+
+ [x y] (->> coords (mapv solve-derivative))
+
+ ;; normalize value
+ d (mth/sqrt (+ (* x x) (* y y)))]
+
+ (gpt/point (/ x d) (/ y d))))
+
+(defn curve-windup
+ [curve t]
+
+ (let [tangent (curve-tangent curve t)]
+ (cond
+ (> (:y tangent) 0) -1
+ (< (:y tangent) 0) 1
+ :else 0)))
(defn curve-split
"Splits a curve into two at the given parametric value `t`.
Calculates the Casteljau's algorithm intermediate points"
- [start end h1 h2 t]
+ ([[start end h1 h2] t]
+ (curve-split start end h1 h2 t))
- (let [p1 (gpt/line-val start h1 t)
- p2 (gpt/line-val h1 h2 t)
- p3 (gpt/line-val h2 end t)
- p4 (gpt/line-val p1 p2 t)
- p5 (gpt/line-val p2 p3 t)
- sp (gpt/line-val p4 p5 t)]
- [[start sp p1 p4]
- [sp end p5 p3]]))
+ ([start end h1 h2 t]
+ (let [p1 (gpt/lerp start h1 t)
+ p2 (gpt/lerp h1 h2 t)
+ p3 (gpt/lerp h2 end t)
+ p4 (gpt/lerp p1 p2 t)
+ p5 (gpt/lerp p2 p3 t)
+ sp (gpt/lerp p4 p5 t)]
+ [[start sp p1 p4]
+ [sp end p5 p3]])))
+
+(defn subcurve-range
+ "Given a curve returns a new curve between the values t1-t2"
+ ([[start end h1 h2] [t1 t2]]
+ (subcurve-range start end h1 h2 t1 t2))
+
+ ([[start end h1 h2] t1 t2]
+ (subcurve-range start end h1 h2 t1 t2))
+
+ ([start end h1 h2 t1 t2]
+ ;; Make sure that t2 is greater than t1
+ (let [[t1 t2] (if (< t1 t2) [t1 t2] [t2 t1])
+ t2' (/ (- t2 t1) (- 1 t1))
+ [_ curve'] (curve-split start end h1 h2 t1)]
+ (first (curve-split curve' t2')))))
+
+
+;; https://trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm
+(defn- solve-roots
+ "Solvers a quadratic or cubic equation given by the parameters a b c d"
+ ([a b c]
+ (solve-roots a b c 0))
+
+ ([a b c d]
+ (let [sqrt-b2-4ac (mth/sqrt (- (* b b) (* 4 a c)))]
+ (cond
+ ;; No solutions
+ (and (mth/almost-zero? d) (mth/almost-zero? a) (mth/almost-zero? b))
+ []
+
+ ;; Linear solution
+ (and (mth/almost-zero? d) (mth/almost-zero? a))
+ [(/ (- c) b)]
+
+ ;; Cuadratic
+ (mth/almost-zero? d)
+ [(/ (+ (- b) sqrt-b2-4ac)
+ (* 2 a))
+ (/ (- (- b) sqrt-b2-4ac)
+ (* 2 a))]
+
+ ;; Cubic
+ :else
+ (let [a (/ a d)
+ b (/ b d)
+ c (/ c d)
+
+ p (/ (- (* 3 b) (* a a)) 3)
+ q (/ (+ (* 2 a a a) (* -9 a b) (* 27 c)) 27)
+
+ p3 (/ p 3)
+ q2 (/ q 2)
+ discriminant (+ (* q2 q2) (* p3 p3 p3))]
+
+ (cond
+ (< discriminant 0)
+ (let [mp3 (/ (- p) 3)
+ mp33 (* mp3 mp3 mp3)
+ r (mth/sqrt mp33)
+ t (/ (- q) (* 2 r))
+ cosphi (cond (< t -1) -1
+ (> t 1) 1
+ :else t)
+ phi (mth/acos cosphi)
+ crtr (mth/cubicroot r)
+ t1 (* 2 crtr)
+ root1 (- (* t1 (mth/cos (/ phi 3))) (/ a 3))
+ root2 (- (* t1 (mth/cos (/ (+ phi (* 2 mth/PI)) 3))) (/ a 3))
+ root3 (- (* t1 (mth/cos (/ (+ phi (* 4 mth/PI)) 3))) (/ a 3))]
+
+ [root1 root2 root3])
+
+ (mth/almost-zero? discriminant)
+ (let [u1 (if (< q2 0) (mth/cubicroot (- q2)) (- (mth/cubicroot q2)))
+ root1 (- (* 2 u1) (/ a 3))
+ root2 (- (- u1) (/ a 3))]
+ [root1 root2])
+
+ :else
+ (let [sd (mth/sqrt discriminant)
+ u1 (mth/cubicroot (- sd q2))
+ v1 (mth/cubicroot (+ sd q2))
+ root (- u1 v1 (/ a 3))]
+ [root])))))))
;; https://pomax.github.io/bezierinfo/#extremities
(defn curve-extremities
- "Given a cubic bezier cube finds its roots in t. This are the extremities
- if we calculate its values for x, y we can find a bounding box for the curve."
- [start end h1 h2]
+ "Calculates the extremities by solving the first derivative for a cubic
+ bezier and then solving the quadratic formula"
+ ([[start end h1 h2]]
+ (curve-extremities start end h1 h2))
- (let [coords [[(:x start) (:x h1) (:x h2) (:x end)]
- [(:y start) (:y h1) (:y h2) (:y end)]]
+ ([start end h1 h2]
- coord->tvalue
- (fn [[c0 c1 c2 c3]]
+ (let [coords [[(:x start) (:x h1) (:x h2) (:x end)]
+ [(:y start) (:y h1) (:y h2) (:y end)]]
- (let [a (+ (* -3 c0) (* 9 c1) (* -9 c2) (* 3 c3))
- b (+ (* 6 c0) (* -12 c1) (* 6 c2))
- c (+ (* 3 c1) (* -3 c0))
+ coord->tvalue
+ (fn [[c0 c1 c2 c3]]
+ (let [a (+ (* -3 c0) (* 9 c1) (* -9 c2) (* 3 c3))
+ b (+ (* 6 c0) (* -12 c1) (* 6 c2))
+ c (+ (* 3 c1) (* -3 c0))]
- sqrt-b2-4ac (mth/sqrt (- (* b b) (* 4 a c)))]
+ (solve-roots a b c)))]
+ (->> coords
+ (mapcat coord->tvalue)
- (cond
- (and (mth/almost-zero? a)
- (not (mth/almost-zero? b)))
- ;; When the term a is close to zero we have a linear equation
- [(/ (- c) b)]
+ ;; Only values in the range [0, 1] are valid
+ (filterv #(and (> % 0.01) (< % 0.99)))))))
- ;; If a is not close to zero return the two roots for a cuadratic
- (not (mth/almost-zero? a))
- [(/ (+ (- b) sqrt-b2-4ac)
- (* 2 a))
- (/ (- (- b) sqrt-b2-4ac)
- (* 2 a))]
+(defn curve-roots
+ "Uses cardano algorithm to find the roots for a cubic bezier"
+ ([[start end h1 h2] coord]
+ (curve-roots start end h1 h2 coord))
- ;; If a and b close to zero we can't find a root for a constant term
- :else
- [])))]
- (->> coords
- (mapcat coord->tvalue)
+ ([start end h1 h2 coord]
- ;; Only values in the range [0, 1] are valid
- (filter #(and (>= % 0) (<= % 1)))
+ (let [coords [[(get start coord) (get h1 coord) (get h2 coord) (get end coord)]]
- ;; Pass t-values to actual points
- (map #(curve-values start end h1 h2 %)))
- ))
+ coord->tvalue
+ (fn [[pa pb pc pd]]
+
+ (let [a (+ (* 3 pa) (* -6 pb) (* 3 pc))
+ b (+ (* -3 pa) (* 3 pb))
+ c pa
+ d (+ (- pa) (* 3 pb) (* -3 pc) pd)]
+
+ (solve-roots a b c d)))]
+ (->> coords
+ (mapcat coord->tvalue)
+ ;; Only values in the range [0, 1] are valid
+ (filterv #(and (>= % 0) (<= % 1)))))))
(defn command->point
([command] (command->point command nil))
@@ -107,7 +286,50 @@
ykey (keyword (str prefix "y"))
x (get params xkey)
y (get params ykey)]
- (gpt/point x y))))
+ (when (and (some? x) (some? y))
+ (gpt/point x y)))))
+
+(defn command->line
+ ([cmd]
+ (command->line cmd (:prev cmd)))
+ ([cmd prev]
+ [prev (command->point cmd)]))
+
+(defn command->bezier
+ ([cmd]
+ (command->bezier cmd (:prev cmd)))
+ ([cmd prev]
+ [prev
+ (command->point cmd)
+ (gpt/point (-> cmd :params :c1x) (-> cmd :params :c1y))
+ (gpt/point (-> cmd :params :c2x) (-> cmd :params :c2y))]))
+
+(defn command->selrect
+ ([command]
+ (command->selrect command (:prev command)))
+
+ ([command prev-point]
+ (let [points (case (:command command)
+ :move-to [(command->point command)]
+
+ ;; If it's a line we add the beginning point and endpoint
+ :line-to [prev-point (command->point command)]
+
+ ;; We return the bezier extremities
+ :curve-to (d/concat
+ [prev-point
+ (command->point command)]
+ (let [curve [prev-point
+ (command->point command)
+ (command->point command :c1)
+ (command->point command :c2)]]
+ (->> (curve-extremities curve)
+ (mapv #(curve-values curve %)))))
+ [])
+ selrect (gpr/points->selrect points)]
+ (-> selrect
+ (update :width #(if (mth/almost-zero? %) 1 %))
+ (update :height #(if (mth/almost-zero? %) 1 %))))))
(defn content->selrect [content]
(let [calc-extremities
@@ -123,10 +345,12 @@
:curve-to (d/concat
[(command->point prev)
(command->point command)]
- (curve-extremities (command->point prev)
- (command->point command)
- (command->point command :c1)
- (command->point command :c2)))
+ (let [curve [(command->point prev)
+ (command->point command)
+ (command->point command :c1)
+ (command->point command :c2)]]
+ (->> (curve-extremities curve)
+ (mapv #(curve-values curve %)))))
[]))
extremities (mapcat calc-extremities
@@ -154,7 +378,11 @@
(not (nil? c1x)) (set-tr :c1x :c1y)
(not (nil? c2x)) (set-tr :c2x :c2y)))]
- (mapv #(update % :params transform-params) content)))
+ (->> content
+ (mapv (fn [cmd]
+ (cond-> cmd
+ (map? cmd)
+ (update :params transform-params)))))))
(defn transform-content
[content transform]
@@ -302,24 +530,25 @@
"Given a path and a position"
[shape position]
- (let [point+distance (fn [[cur-cmd prev-cmd]]
- (let [from-p (command->point prev-cmd)
- to-p (command->point cur-cmd)
- h1 (gpt/point (get-in cur-cmd [:params :c1x])
- (get-in cur-cmd [:params :c1y]))
- h2 (gpt/point (get-in cur-cmd [:params :c2x])
- (get-in cur-cmd [:params :c2y]))
- point
- (case (:command cur-cmd)
- :line-to
- (line-closest-point position from-p to-p)
+ (let [point+distance
+ (fn [[cur-cmd prev-cmd]]
+ (let [from-p (command->point prev-cmd)
+ to-p (command->point cur-cmd)
+ h1 (gpt/point (get-in cur-cmd [:params :c1x])
+ (get-in cur-cmd [:params :c1y]))
+ h2 (gpt/point (get-in cur-cmd [:params :c2x])
+ (get-in cur-cmd [:params :c2y]))
+ point
+ (case (:command cur-cmd)
+ :line-to
+ (line-closest-point position from-p to-p)
- :curve-to
- (curve-closest-point position from-p to-p h1 h2)
+ :curve-to
+ (curve-closest-point position from-p to-p h1 h2)
- nil)]
- (when point
- [point (gpt/distance point position)])))
+ nil)]
+ (when point
+ [point (gpt/distance point position)])))
find-min-point (fn [[min-p min-dist :as acc] [cur-p cur-dist :as cur]]
(if (and (some? acc) (or (not cur) (<= min-dist cur-dist)))
@@ -331,3 +560,399 @@
(map point+distance)
(reduce find-min-point)
(first))))
+
+(defn- get-line-tval
+ [[{x1 :x y1 :y} {x2 :x y2 :y}] {:keys [x y]}]
+ (cond
+ (and (s= x1 x2) (s= y1 y2))
+ ##Inf
+
+ (s= x1 x2)
+ (/ (- y y1) (- y2 y1))
+
+ :else
+ (/ (- x x1) (- x2 x1))))
+
+(defn- curve-range->rect
+ [curve from-t to-t]
+
+ (let [[from-p to-p :as curve] (subcurve-range curve from-t to-t)
+ extremes (->> (curve-extremities curve)
+ (mapv #(curve-values curve %)))]
+ (gpr/points->rect (into [from-p to-p] extremes))))
+
+(defn line-has-point?
+ "Using the line equation we put the x value and check if matches with
+ the given Y. If it does the point is inside the line"
+ [point [from-p to-p]]
+ (let [{x1 :x y1 :y} from-p
+ {x2 :x y2 :y} to-p
+ {px :x py :y} point
+
+ m (when-not (s= x1 x2) (/ (- y2 y1) (- x2 x1)))
+ vy (when (some? m) (+ (* m px) (* (- m) x1) y1))]
+
+ ;; If x1 = x2 there is no slope, to see if the point is in the line
+ ;; only needs to check the x is the same
+ (or (and (s= x1 x2) (s= px x1))
+ (and (some? vy) (s= py vy)))))
+
+(defn segment-has-point?
+ "Using the line equation we put the x value and check if matches with
+ the given Y. If it does the point is inside the line"
+ [point line]
+
+ (and (line-has-point? point line)
+ (let [t (get-line-tval line point)]
+ (and (or (> t 0) (s= t 0))
+ (or (< t 1) (s= t 1))))))
+
+(defn curve-has-point?
+ [point curve]
+ (letfn [(check-range [from-t to-t]
+ (let [r (curve-range->rect curve from-t to-t)]
+ (when (gpr/contains-point? r point)
+ (if (s= from-t to-t)
+ (< (gpt/distance (curve-values curve from-t) point) 0.1)
+
+ (let [half-t (+ from-t (/ (- to-t from-t) 2.0))]
+ (or (check-range from-t half-t)
+ (check-range half-t to-t)))))))]
+
+ (check-range 0 1)))
+
+(defn line-line-crossing
+ [[from-p1 to-p1 :as l1] [from-p2 to-p2 :as l2]]
+
+ (let [{x1 :x y1 :y} from-p1
+ {x2 :x y2 :y} to-p1
+
+ {x3 :x y3 :y} from-p2
+ {x4 :x y4 :y} to-p2
+
+ nx (- (* (- x3 x4) (- (* x1 y2) (* y1 x2)))
+ (* (- x1 x2) (- (* x3 y4) (* y3 x4))))
+
+ ny (- (* (- y3 y4) (- (* x1 y2) (* y1 x2)))
+ (* (- y1 y2) (- (* x3 y4) (* y3 x4))))
+
+ d (- (* (- x1 x2) (- y3 y4))
+ (* (- y1 y2) (- x3 x4)))]
+
+ (cond
+ (not (mth/almost-zero? d))
+ ;; Coordinates in the line. We calculate the tvalue that will
+ ;; return 0-1 as a percentage in the segment
+ (let [cross-p (gpt/point (/ nx d) (/ ny d))
+ t1 (get-line-tval l1 cross-p)
+ t2 (get-line-tval l2 cross-p)]
+ [t1 t2])
+
+ ;; If they are parallels they could define the same line
+ (line-has-point? from-p2 l1) [(get-line-tval l1 from-p2) 0]
+ (line-has-point? to-p2 l1) [(get-line-tval l1 to-p2) 1]
+ (line-has-point? to-p1 l2) [1 (get-line-tval l2 to-p1)]
+ (line-has-point? from-p1 l2) [0 (get-line-tval l2 from-p1)]
+
+ :else
+ nil)))
+
+(defn line-curve-crossing
+ [[from-p1 to-p1]
+ [from-p2 to-p2 h1-p2 h2-p2]]
+
+ (let [theta (-> (mth/atan2 (- (:y to-p1) (:y from-p1))
+ (- (:x to-p1) (:x from-p1)))
+ (mth/degrees))
+
+ transform (-> (gmt/matrix)
+ (gmt/rotate (- theta))
+ (gmt/translate (gpt/negate from-p1)))
+
+ c2' [(gpt/transform from-p2 transform)
+ (gpt/transform to-p2 transform)
+ (gpt/transform h1-p2 transform)
+ (gpt/transform h2-p2 transform)]]
+
+ (curve-roots c2' :y)))
+
+
+
+(defn ray-line-intersect
+ [point [a b :as line]]
+
+ ;; If the ray is paralell to the line there will be no crossings
+ (let [ray-line [point (gpt/point (inc (:x point)) (:y point))]
+ ;; Rays fail when fall just in a vertex so we move a bit upward
+ ;; because only want to use this for insideness
+ a (if (and (some? a) (s= (:y a) (:y point))) (update a :y + 10) a)
+ b (if (and (some? b) (s= (:y b) (:y point))) (update b :y + 10) b)
+ [ray-t line-t] (line-line-crossing ray-line [a b])]
+
+ (when (and (some? line-t) (some? ray-t)
+ (> ray-t 0)
+ (or (> line-t 0) (s= line-t 0))
+ (or (< line-t 1) (s= line-t 1)))
+ [[(line-values line line-t)
+ (line-windup line line-t)]])))
+
+(defn line-line-intersect
+ [l1 l2]
+
+ (let [[l1-t l2-t] (line-line-crossing l1 l2)]
+ (when (and (some? l1-t) (some? l2-t)
+ (or (> l1-t 0) (s= l1-t 0))
+ (or (< l1-t 1) (s= l1-t 1))
+ (or (> l2-t 0) (s= l2-t 0))
+ (or (< l2-t 1) (s= l2-t 1)))
+ [[l1-t] [l2-t]])))
+
+(defn ray-curve-intersect
+ [ray-line c2]
+
+ (let [;; ray-line [point (gpt/point (inc (:x point)) (:y point))]
+ curve-ts (->> (line-curve-crossing ray-line c2)
+ (filterv #(let [curve-v (curve-values c2 %)
+ curve-tg (curve-tangent c2 %)
+ curve-tg-angle (gpt/angle curve-tg)
+ ray-t (get-line-tval ray-line curve-v)]
+ (and (> ray-t 0)
+ (> (mth/abs (- curve-tg-angle 180)) 0.01)
+ (> (mth/abs (- curve-tg-angle 0)) 0.01)) )))]
+ (->> curve-ts
+ (mapv #(vector (curve-values c2 %)
+ (curve-windup c2 %))))))
+
+(defn line-curve-intersect
+ [l1 c2]
+
+ (let [curve-ts (->> (line-curve-crossing l1 c2)
+ (filterv
+ (fn [curve-t]
+ (let [curve-t (if (mth/almost-zero? curve-t) 0 curve-t)
+ curve-v (curve-values c2 curve-t)
+ line-t (get-line-tval l1 curve-v)]
+ (and (>= curve-t 0) (<= curve-t 1)
+ (>= line-t 0) (<= line-t 1))))))
+
+ ;; Intersection line-curve points
+ intersect-ps (->> curve-ts
+ (mapv #(curve-values c2 %)))
+
+ line-ts (->> intersect-ps
+ (mapv #(get-line-tval l1 %)))]
+
+ [line-ts curve-ts]))
+
+(defn curve-curve-intersect
+ [c1 c2]
+
+ (letfn [(check-range [c1-from c1-to c2-from c2-to]
+ (let [r1 (curve-range->rect c1 c1-from c1-to)
+ r2 (curve-range->rect c2 c2-from c2-to)]
+
+ (when (gpr/overlaps-rects? r1 r2)
+ (let [p1 (curve-values c1 c1-from)
+ p2 (curve-values c2 c2-from)]
+
+ (if (< (gpt/distance p1 p2) curve-curve-precision)
+ [{:p1 p1
+ :p2 p2
+ :d (gpt/distance p1 p2)
+ :t1 (mth/precision c1-from 4)
+ :t2 (mth/precision c2-from 4)}]
+
+ (let [c1-half (+ c1-from (/ (- c1-to c1-from) 2))
+ c2-half (+ c2-from (/ (- c2-to c2-from) 2))
+
+ ts-1 (check-range c1-from c1-half c2-from c2-half)
+ ts-2 (check-range c1-from c1-half c2-half c2-to)
+ ts-3 (check-range c1-half c1-to c2-from c2-half)
+ ts-4 (check-range c1-half c1-to c2-half c2-to)]
+
+ (d/concat [] ts-1 ts-2 ts-3 ts-4)))))))
+
+ (remove-close-ts [{cp1 :p1 cp2 :p2}]
+ (fn [{:keys [p1 p2]}]
+ (and (>= (gpt/distance p1 cp1) curve-range-precision)
+ (>= (gpt/distance p2 cp2) curve-range-precision))))
+
+ (process-ts [ts]
+ (loop [current (first ts)
+ pending (rest ts)
+ c1-ts []
+ c2-ts []]
+
+ (if (nil? current)
+ [c1-ts c2-ts]
+
+ (let [pending (->> pending (filter (remove-close-ts current)))
+ c1-ts (conj c1-ts (:t1 current))
+ c2-ts (conj c2-ts (:t2 current))]
+ (recur (first pending)
+ (rest pending)
+ c1-ts
+ c2-ts)))))]
+
+ (->> (check-range 0 1 0 1)
+ (sort-by :d)
+ (process-ts))))
+
+(defn curve->rect
+ [[from-p to-p :as curve]]
+ (let [extremes (->> (curve-extremities curve)
+ (mapv #(curve-values curve %)))]
+ (gpr/points->rect (into [from-p to-p] extremes))))
+
+
+(defn is-point-in-border?
+ [point content]
+
+ (letfn [(inside-border? [cmd]
+ (case (:command cmd)
+ :line-to (segment-has-point? point (command->line cmd))
+ :curve-to (curve-has-point? point (command->bezier cmd))
+ #_:else false))]
+
+ (->> content
+ (some inside-border?))))
+
+(defn is-point-in-content?
+ [point content]
+ (let [selrect (content->selrect content)
+ ray-line [point (gpt/point (inc (:x point)) (:y point))]
+
+ closed-content
+ (into []
+ (comp (filter sp/is-closed?)
+ (mapcat :data))
+ (->> content
+ (sp/close-subpaths)
+ (sp/get-subpaths)))
+
+ cast-ray
+ (fn [cmd]
+ (case (:command cmd)
+ :line-to (ray-line-intersect point (command->line cmd))
+ :curve-to (ray-curve-intersect ray-line (command->bezier cmd))
+ #_:else []))]
+
+ (and (gpr/contains-point? selrect point)
+ (->> closed-content
+ (mapcat cast-ray)
+ (map second)
+ (reduce +)
+ (not= 0)))))
+
+(defn split-line-to
+ "Given a point and a line-to command will create a two new line-to commands
+ that will split the original line into two given a value between 0-1"
+ [from-p cmd t-val]
+ (let [to-p (upc/command->point cmd)
+ sp (gpt/lerp from-p to-p t-val)]
+ [(upc/make-line-to sp) cmd]))
+
+(defn split-curve-to
+ "Given the point and a curve-to command will split the curve into two new
+ curve-to commands given a value between 0-1"
+ [from-p cmd t-val]
+ (let [params (:params cmd)
+ end (gpt/point (:x params) (:y params))
+ h1 (gpt/point (:c1x params) (:c1y params))
+ h2 (gpt/point (:c2x params) (:c2y params))
+ [[_ to1 h11 h21]
+ [_ to2 h12 h22]] (curve-split from-p end h1 h2 t-val)]
+ [(upc/make-curve-to to1 h11 h21)
+ (upc/make-curve-to to2 h12 h22)]))
+
+(defn split-line-to-ranges
+ "Splits a line into several lines given the points in `values`
+ for example (split-line-to-ranges p c [0 0.25 0.5 0.75 1] will split
+ the line into 4 lines"
+ [from-p cmd values]
+ (let [values (->> values (filter #(and (> % 0) (< % 1))))]
+ (if (empty? values)
+ [cmd]
+ (let [to-p (upc/command->point cmd)
+ values-set (->> (conj values 1) (into (sorted-set)))]
+ (->> values-set
+ (mapv (fn [val]
+ (-> (gpt/lerp from-p to-p val)
+ #_(gpt/round 2)
+ (upc/make-line-to)))))))))
+
+(defn split-curve-to-ranges
+ "Splits a curve into several curves given the points in `values`
+ for example (split-curve-to-ranges p c [0 0.25 0.5 0.75 1] will split
+ the curve into 4 curves that draw the same curve"
+ [from-p cmd values]
+
+ (let [values (->> values (filter #(and (> % 0) (< % 1))))]
+ (if (empty? values)
+ [cmd]
+ (let [to-p (upc/command->point cmd)
+ params (:params cmd)
+ h1 (gpt/point (:c1x params) (:c1y params))
+ h2 (gpt/point (:c2x params) (:c2y params))
+
+ values-set (->> (conj values 0 1) (into (sorted-set)))]
+
+ (->> (d/with-prev values-set)
+ (rest)
+ (mapv
+ (fn [[t1 t0]]
+ (let [[_ to-p h1' h2'] (subcurve-range from-p to-p h1 h2 t0 t1)]
+ (upc/make-curve-to (-> to-p #_(gpt/round 2)) h1' h2')))))))))
+
+(defn content-center
+ [content]
+ (-> content
+ content->selrect
+ gsc/center-selrect))
+
+(defn content->points+selrect
+ "Given the content of a shape, calculate its points and selrect"
+ [shape content]
+ (let [{:keys [flip-x flip-y]} shape
+ transform
+ (cond-> (:transform shape (gmt/matrix))
+ flip-x (gmt/scale (gpt/point -1 1))
+ flip-y (gmt/scale (gpt/point 1 -1)))
+
+ transform-inverse
+ (cond-> (gmt/matrix)
+ flip-x (gmt/scale (gpt/point -1 1))
+ flip-y (gmt/scale (gpt/point 1 -1))
+ :always (gmt/multiply (:transform-inverse shape (gmt/matrix))))
+
+ center (or (gsc/center-shape shape)
+ (content-center content))
+
+ base-content (transform-content
+ content
+ (gmt/transform-in center transform-inverse))
+
+ ;; Calculates the new selrect with points given the old center
+ points (-> (content->selrect base-content)
+ (gpr/rect->points)
+ (gsc/transform-points center transform))
+
+ points-center (gsc/center-points points)
+
+ ;; Points is now the selrect but the center is different so we can create the selrect
+ ;; through points
+ selrect (-> points
+ (gsc/transform-points points-center transform-inverse)
+ (gpr/points->selrect))]
+ [points selrect]))
+
+
+(defn open-path?
+ [shape]
+
+ (and (= :path (:type shape))
+ (not (->> shape
+ :content
+ (sp/close-subpaths)
+ (sp/get-subpaths)
+ (every? sp/is-closed?)))))
diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc
index 91e7d18a9..047781a70 100644
--- a/common/src/app/common/geom/shapes/rect.cljc
+++ b/common/src/app/common/geom/shapes/rect.cljc
@@ -7,7 +7,8 @@
(ns app.common.geom.shapes.rect
(:require
[app.common.geom.point :as gpt]
- [app.common.geom.shapes.common :as gco]))
+ [app.common.geom.shapes.common :as gco]
+ [app.common.math :as mth]))
(defn rect->points [{:keys [x y width height]}]
;; (assert (number? x))
@@ -47,6 +48,16 @@
(defn rect->selrect [rect]
(-> rect rect->points points->selrect))
+(defn join-rects [rects]
+ (let [minx (transduce (comp (map :x) (remove nil?)) min ##Inf rects)
+ miny (transduce (comp (map :y) (remove nil?)) min ##Inf rects)
+ maxx (transduce (comp (map #(+ (:x %) (:width %))) (remove nil?)) max ##-Inf rects)
+ maxy (transduce (comp (map #(+ (:y %) (:height %))) (remove nil?)) max ##-Inf rects)]
+ {:x minx
+ :y miny
+ :width (- maxx minx)
+ :height (- maxy miny)}))
+
(defn join-selrects [selrects]
(let [minx (transduce (comp (map :x1) (remove nil?)) min ##Inf selrects)
miny (transduce (comp (map :y1) (remove nil?)) min ##Inf selrects)
@@ -70,3 +81,43 @@
:y (- (:y center) (/ height 2))
:width width
:height height})
+
+(defn s=
+ [a b]
+ (mth/almost-zero? (- a b)))
+
+(defn overlaps-rects?
+ "Check for two rects to overlap. Rects won't overlap only if
+ one of them is fully to the left or the top"
+ [rect-a rect-b]
+
+ (let [x1a (:x rect-a)
+ y1a (:y rect-a)
+ x2a (+ (:x rect-a) (:width rect-a))
+ y2a (+ (:y rect-a) (:height rect-a))
+
+ x1b (:x rect-b)
+ y1b (:y rect-b)
+ x2b (+ (:x rect-b) (:width rect-b))
+ y2b (+ (:y rect-b) (:height rect-b))]
+
+ (and (or (> x2a x1b) (s= x2a x1b))
+ (or (>= x2b x1a) (s= x2b x1a))
+ (or (<= y1b y2a) (s= y1b y2a))
+ (or (<= y1a y2b) (s= y1a y2b)))))
+
+(defn contains-point?
+ [rect point]
+ (assert (gpt/point? point))
+ (let [x1 (:x rect)
+ y1 (:y rect)
+ x2 (+ (:x rect) (:width rect))
+ y2 (+ (:y rect) (:height rect))
+
+ px (:x point)
+ py (:y point)]
+
+ (and (or (> px x1) (s= px x1))
+ (or (< px x2) (s= px x2))
+ (or (> py y1) (s= py y1))
+ (or (< py y2) (s= py y2)))))
diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc
index 97250b361..bfb6a751f 100644
--- a/common/src/app/common/geom/shapes/transforms.cljc
+++ b/common/src/app/common/geom/shapes/transforms.cljc
@@ -18,7 +18,6 @@
[app.common.spec :as us]
[app.common.text :as txt]))
-
;; --- Relative Movement
(defn- move-selrect [selrect {dx :x dy :y}]
@@ -161,23 +160,12 @@
matrix
(gmt/translate-matrix (gpt/negate center)))))
-(defn transform-points
- ([points matrix]
- (transform-points points nil matrix))
- ([points center matrix]
- (let [prev (if center (gmt/translate-matrix center) (gmt/matrix))
- post (if center (gmt/translate-matrix (gpt/negate center)) (gmt/matrix))
-
- tr-point (fn [point]
- (gpt/transform point (gmt/multiply prev matrix post)))]
- (mapv tr-point points))))
-
(defn transform-rect
"Transform a rectangles and changes its attributes"
[rect matrix]
(let [points (-> (gpr/rect->points rect)
- (transform-points matrix))]
+ (gco/transform-points matrix))]
(gpr/points->rect points)))
(defn calculate-adjust-matrix
@@ -201,12 +189,12 @@
stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0))
h1 (max 1 (calculate-height points-temp))
- h2 (max 1 (calculate-height (transform-points points-rec center stretch-matrix)))
+ h2 (max 1 (calculate-height (gco/transform-points points-rec center stretch-matrix)))
h3 (if-not (mth/almost-zero? h2) (/ h1 h2) 1)
h3 (if (mth/nan? h3) 1 h3)
w1 (max 1 (calculate-width points-temp))
- w2 (max 1 (calculate-width (transform-points points-rec center stretch-matrix)))
+ w2 (max 1 (calculate-width (gco/transform-points points-rec center stretch-matrix)))
w3 (if-not (mth/almost-zero? w2) (/ w1 w2) 1)
w3 (if (mth/nan? w3) 1 w3)
@@ -214,7 +202,7 @@
rotation-angle (calculate-rotation
center
- (transform-points points-rec (gco/center-points points-rec) stretch-matrix)
+ (gco/transform-points points-rec (gco/center-points points-rec) stretch-matrix)
points-temp
flip-x
flip-y)
@@ -232,14 +220,13 @@
"Given a new set of points transformed, set up the rectangle so it keeps
its properties. We adjust de x,y,width,height and create a custom transform"
[shape transform round-coords?]
- ;;
- (let [points (-> shape :points (transform-points transform))
+ (let [points (-> shape :points (gco/transform-points transform))
center (gco/center-points points)
;; Reverse the current transformation stack to get the base rectangle
tr-inverse (:transform-inverse shape (gmt/matrix))
- points-temp (transform-points points center tr-inverse)
+ points-temp (gco/transform-points points center tr-inverse)
points-temp-dim (calculate-dimensions points-temp)
;; This rectangle is the new data for the current rectangle. We want to change our rectangle
@@ -305,12 +292,12 @@
points (->> children (mapcat :points))
;; Invert to get the points minus the transforms applied to the group
- base-points (transform-points points shape-center (:transform-inverse group (gmt/matrix)))
+ base-points (gco/transform-points points shape-center (:transform-inverse group (gmt/matrix)))
;; Defines the new selection rect with its transformations
new-points (-> (gpr/points->selrect base-points)
(gpr/rect->points)
- (transform-points shape-center (:transform group (gmt/matrix))))
+ (gco/transform-points shape-center (:transform group (gmt/matrix))))
;; Calculte the new selrect
new-selrect (gpr/points->selrect base-points)]
@@ -457,8 +444,10 @@
transform))
(defn- set-flip [shape modifiers]
- (let [rx (get-in modifiers [:resize-vector :x])
- ry (get-in modifiers [:resize-vector :y])]
+ (let [rx (or (get-in modifiers [:resize-vector :x])
+ (get-in modifiers [:resize-vector-2 :x]))
+ ry (or (get-in modifiers [:resize-vector :y])
+ (get-in modifiers [:resize-vector-2 :y]))]
(cond-> shape
(and rx (< rx 0)) (-> (update :flip-x not)
(update :rotation -))
@@ -517,7 +506,7 @@
(defn calc-child-modifiers
"Given the modifiers to apply to the parent, calculate the corresponding
modifiers for the child, depending on the child constraints."
- [parent child parent-modifiers]
+ [parent child parent-modifiers ignore-constraints]
(let [parent-rect (:selrect parent)
child-rect (:selrect child)
@@ -544,15 +533,19 @@
transformed-parent-rect (-> parent-rect
(gpr/rect->points)
- (transform-points parent-displacement)
- (transform-points parent-origin (gmt/scale-matrix parent-vector))
- (transform-points parent-origin-2 (gmt/scale-matrix parent-vector-2))
+ (gco/transform-points parent-displacement)
+ (gco/transform-points parent-origin (gmt/scale-matrix parent-vector))
+ (gco/transform-points parent-origin-2 (gmt/scale-matrix parent-vector-2))
(gpr/points->selrect))
;; Calculate the modifiers in the horizontal and vertical directions
;; depending on the child constraints.
- constraints-h (get child :constraints-h (spec/default-constraints-h child))
- constraints-v (get child :constraints-v (spec/default-constraints-v child))
+ constraints-h (if-not ignore-constraints
+ (get child :constraints-h (spec/default-constraints-h child))
+ :scale)
+ constraints-v (if-not ignore-constraints
+ (get child :constraints-v (spec/default-constraints-v child))
+ :scale)
modifiers-h (case constraints-h
:left
@@ -692,3 +685,12 @@
(assoc :resize-transform (:resize-transform parent-modifiers)
:resize-transform-inverse (:resize-transform-inverse parent-modifiers)))))
+
+(defn selection-rect
+ "Returns a rect that contains all the shapes and is aware of the
+ rotation of each shape. Mainly used for multiple selection."
+ [shapes]
+ (->> shapes
+ (transform-shape)
+ (map (comp gpr/points->selrect :points))
+ (gpr/join-selrects)))
diff --git a/common/src/app/common/logging.cljc b/common/src/app/common/logging.cljc
new file mode 100644
index 000000000..a861bcc27
--- /dev/null
+++ b/common/src/app/common/logging.cljc
@@ -0,0 +1,297 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.common.logging
+ (:require
+ [app.common.exceptions :as ex]
+ [clojure.pprint :refer [pprint]]
+ [cuerdas.core :as str]
+ #?(:cljs [goog.log :as glog]))
+ #?(:cljs (:require-macros [app.common.logging]))
+ #?(:clj
+ (:import
+ org.apache.logging.log4j.Level
+ org.apache.logging.log4j.LogManager
+ org.apache.logging.log4j.Logger
+ org.apache.logging.log4j.ThreadContext
+ org.apache.logging.log4j.message.MapMessage
+ org.apache.logging.log4j.spi.LoggerContext)))
+
+#?(:clj
+ (defn build-map-message
+ [m]
+ (let [message (MapMessage. (count m))]
+ (reduce-kv #(.with ^MapMessage %1 (name %2) %3) message m))))
+
+#?(:clj
+ (def logger-context
+ (LogManager/getContext false)))
+
+#?(:clj
+ (def logging-agent
+ (agent nil :error-mode :continue)))
+
+(defn get-logger
+ [lname]
+ #?(:clj (.getLogger ^LoggerContext logger-context ^String lname)
+ :cljs
+ (glog/getLogger
+ (cond
+ (string? lname) lname
+ (= lname :root) ""
+ (simple-ident? lname) (name lname)
+ (qualified-ident? lname) (str (namespace lname) "." (name lname))
+ :else (str lname)))))
+
+(defn get-level
+ [level]
+ #?(:clj
+ (case level
+ :trace Level/TRACE
+ :debug Level/DEBUG
+ :info Level/INFO
+ :warn Level/WARN
+ :error Level/ERROR
+ :fatal Level/FATAL)
+ :cljs
+ (case level
+ :off (.-OFF ^js glog/Level)
+ :shout (.-SHOUT ^js glog/Level)
+ :error (.-SEVERE ^js glog/Level)
+ :severe (.-SEVERE ^js glog/Level)
+ :warning (.-WARNING ^js glog/Level)
+ :warn (.-WARNING ^js glog/Level)
+ :info (.-INFO ^js glog/Level)
+ :config (.-CONFIG ^js glog/Level)
+ :debug (.-FINE ^js glog/Level)
+ :fine (.-FINE ^js glog/Level)
+ :finer (.-FINER ^js glog/Level)
+ :trace (.-FINER ^js glog/Level)
+ :finest (.-FINEST ^js glog/Level)
+ :all (.-ALL ^js glog/Level))))
+
+(defn write-log!
+ [logger level exception message]
+ #?(:clj
+ (if exception
+ (.log ^Logger logger
+ ^Level level
+ ^Object message
+ ^Throwable exception)
+ (.log ^Logger logger
+ ^Level level
+ ^Object message))
+ :cljs
+ (when glog/ENABLED
+ (when-let [l (get-logger logger)]
+ (let [level (get-level level)
+ record (glog/LogRecord. level message (.getName ^js l))]
+ (when exception (.setException record exception))
+ (glog/publishLogRecord l record))))))
+
+#?(:clj
+ (defn enabled?
+ [logger level]
+ (.isEnabled ^Logger logger ^Level level)))
+
+(defmacro log
+ [& {:keys [level cause ::logger ::async ::raw] :as props}]
+ (if (:ns &env) ; CLJS
+ `(write-log! ~(or logger (str *ns*))
+ ~level
+ ~cause
+ ~(dissoc props :level :cause ::logger ::raw))
+ (let [props (dissoc props :level :cause ::logger ::async ::raw)
+ logger (or logger (str *ns*))
+ logger-sym (gensym "log")
+ level-sym (gensym "log")]
+ `(let [~logger-sym (get-logger ~logger)
+ ~level-sym (get-level ~level)]
+ (if (enabled? ~logger-sym ~level-sym)
+ ~(if async
+ `(send-off logging-agent
+ (fn [_#]
+ (let [message# (or ~raw (build-map-message ~props))]
+ (write-log! ~logger-sym ~level-sym ~cause message#))))
+ `(let [message# (or ~raw (build-map-message ~props))]
+ (write-log! ~logger-sym ~level-sym ~cause message#))))))))
+
+(defmacro info
+ [& params]
+ `(log :level :info ~@params))
+
+(defmacro error
+ [& params]
+ `(log :level :error ~@params))
+
+(defmacro warn
+ [& params]
+ `(log :level :warn ~@params))
+
+(defmacro debug
+ [& params]
+ `(log :level :debug ~@params))
+
+(defmacro trace
+ [& params]
+ `(log :level :trace ~@params))
+
+(defmacro set-level!
+ ([level]
+ (when (:ns &env)
+ `(set-level* ~(str *ns*) ~level)))
+ ([n level]
+ (when (:ns &env)
+ `(set-level* ~n ~level))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; CLJ Specific
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#?(:clj
+ (defn update-thread-context!
+ [data]
+ (run! (fn [[key val]]
+ (ThreadContext/put
+ (name key)
+ (cond
+ (coll? val)
+ (binding [clojure.pprint/*print-right-margin* 120]
+ (with-out-str (pprint val)))
+ (instance? clojure.lang.Named val) (name val)
+ :else (str val))))
+ data)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; CLJS Specific
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#?(:cljs
+ (def ^:private colors
+ {:gray3 "#8e908c"
+ :gray4 "#969896"
+ :gray5 "#4d4d4c"
+ :gray6 "#282a2e"
+ :black "#1d1f21"
+ :red "#c82829"
+ :blue "#4271ae"
+ :orange "#f5871f"}))
+
+#?(:cljs
+ (defn- level->color
+ [level]
+ (letfn [(get-level-value [l] (.-value ^js (get-level l)))]
+ (condp <= (get-level-value level)
+ (get-level-value :error) (get colors :red)
+ (get-level-value :warn) (get colors :orange)
+ (get-level-value :info) (get colors :blue)
+ (get-level-value :debug) (get colors :gray4)
+ (get-level-value :trace) (get colors :gray3)
+ (get colors :gray2)))))
+
+#?(:cljs
+ (defn- level->short-name
+ [l]
+ (case l
+ :fine "DBG"
+ :debug "DBG"
+ :finer "TRC"
+ :trace "TRC"
+ :info "INF"
+ :warn "WRN"
+ :warning "WRN"
+ :error "ERR"
+ (subs (.-name ^js (get-level l)) 0 3))))
+
+#?(:cljs
+ (defn set-level*
+ "Set the level (a keyword) of the given logger, identified by name."
+ [name lvl]
+ (some-> (get-logger name)
+ (glog/setLevel (get-level lvl)))))
+
+
+#?(:cljs
+ (defn set-levels!
+ [lvls]
+ (doseq [[logger level] lvls
+ :let [level (if (string? level) (keyword level) level)]]
+ (set-level* logger level))))
+
+#?(:cljs
+ (defn- prepare-message
+ [message]
+ (loop [kvpairs (seq message)
+ message (array-map)
+ specials []]
+ (if (nil? kvpairs)
+ [message specials]
+ (let [[k v] (first kvpairs)]
+ (cond
+ (= k :err)
+ (recur (next kvpairs)
+ message
+ (conj specials [:error nil v]))
+
+ (and (qualified-ident? k)
+ (= "js" (namespace k)))
+ (recur (next kvpairs)
+ message
+ (conj specials [:js (name k) (if (object? v) v (clj->js v))]))
+
+ :else
+ (recur (next kvpairs)
+ (assoc message k v)
+ specials)))))))
+
+#?(:cljs
+ (defn default-handler
+ [{:keys [message level logger-name]}]
+ (let [header-styles (str "font-weight: 600; color: " (level->color level))
+ normal-styles (str "font-weight: 300; color: " (get colors :gray6))
+ level-name (level->short-name level)
+ header (str "%c" level-name " [" logger-name "] ")]
+
+ (if (string? message)
+ (let [message (str header "%c" message)]
+ (js/console.log message header-styles normal-styles))
+ (let [[message specials] (prepare-message message)]
+ (if (seq specials)
+ (let [message (str header "%c" (pr-str message))]
+ (js/console.group message header-styles normal-styles)
+ (doseq [[type n v] specials]
+ (case type
+ :js (js/console.log n v)
+ :error (if (ex/ex-info? v)
+ (js/console.error (pr-str v))
+ (js/console.error v))))
+ (js/console.groupEnd message))
+ (let [message (str header "%c" (pr-str message))]
+ (js/console.log message header-styles normal-styles))))))))
+
+#?(:cljs
+ (defn record->map
+ [^js record]
+ {:seqn (.-sequenceNumber_ record)
+ :time (.-time_ record)
+ :level (keyword (str/lower (.-name (.-level_ record))))
+ :message (.-msg_ record)
+ :logger-name (.-loggerName_ record)
+ :exception (.-exception_ record)}))
+
+#?(:cljs
+ (defonce default-console-handler
+ (comp default-handler record->map)))
+
+#?(:cljs
+ (defn initialize!
+ []
+ (let [l (get-logger :root)]
+ (glog/removeHandler l default-console-handler)
+ (glog/addHandler l default-console-handler)
+ nil)))
+
+
diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc
index 22ebd97af..67a327da8 100644
--- a/common/src/app/common/math.cljc
+++ b/common/src/app/common/math.cljc
@@ -72,17 +72,24 @@
[v]
(* v v))
+(defn pow
+ "Returns the base to the exponent power."
+ [b e]
+ #?(:cljs (js/Math.pow b e)
+ :clj (Math/pow b e)))
+
(defn sqrt
"Returns the square root of a number."
[v]
#?(:cljs (js/Math.sqrt v)
:clj (Math/sqrt v)))
-(defn pow
- "Returns the base to the exponent power."
- [b e]
- #?(:cljs (js/Math.pow b e)
- :clj (Math/pow b e)))
+(defn cubicroot
+ "Returns the cubic root of a number"
+ [v]
+ (if (pos? v)
+ (pow v (/ 1 3))
+ (- (pow (- v) (/ 1 3)))))
(defn floor
"Returns the largest integer less than or
@@ -143,7 +150,7 @@
(if (> num to) to num)))
(defn almost-zero? [num]
- (< (abs num) 1e-8))
+ (< (abs (double num)) 1e-5))
(defonce float-equal-precision 0.001)
@@ -151,3 +158,9 @@
"Equality for float numbers. Check if the difference is within a range"
[num1 num2]
(<= (abs (- num1 num2)) float-equal-precision))
+
+(defn lerp
+ "Calculates a the linear interpolation between two values and a given percent"
+ [v0 v1 t]
+ (+ (* (- 1 t) v0)
+ (* t v1)))
diff --git a/common/src/app/common/pages.cljc b/common/src/app/common/pages.cljc
index fdf02cfa3..00725454a 100644
--- a/common/src/app/common/pages.cljc
+++ b/common/src/app/common/pages.cljc
@@ -40,9 +40,11 @@
(d/export helpers/get-children)
(d/export helpers/get-children-objects)
(d/export helpers/get-object-with-children)
+(d/export helpers/select-children)
(d/export helpers/is-shape-grouped)
(d/export helpers/get-parent)
(d/export helpers/get-parents)
+(d/export helpers/get-frame)
(d/export helpers/clean-loops)
(d/export helpers/calculate-invalid-targets)
(d/export helpers/valid-frame-target)
@@ -66,13 +68,14 @@
(d/export helpers/merge-path-item)
(d/export helpers/compact-path)
(d/export helpers/compact-name)
+(d/export helpers/unframed-shape?)
;; Indices
(d/export indices/calculate-z-index)
(d/export indices/update-z-index)
(d/export indices/generate-child-all-parents-index)
(d/export indices/generate-child-parent-index)
-(d/export indices/create-mask-index)
+(d/export indices/create-clip-index)
;; Process changes
(d/export changes/process-changes)
diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc
index 997edc170..a2211b238 100644
--- a/common/src/app/common/pages/changes.cljc
+++ b/common/src/app/common/pages/changes.cljc
@@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.shapes :as gsh]
+ [app.common.geom.shapes.bool :as gshb]
[app.common.pages.common :refer [component-sync-attrs]]
[app.common.pages.helpers :as cph]
[app.common.pages.init :as init]
@@ -156,7 +157,7 @@
(sequence (comp
(mapcat #(cons % (cph/get-parents % objects)))
(map #(get objects %))
- (filter #(= (:type %) :group))
+ (filter #(contains? #{:group :bool} (:type %)))
(map :id)
(distinct))
shapes)))
@@ -177,6 +178,9 @@
(empty? children)
group
+ (= :bool (:type group))
+ (gshb/update-bool-selrect group children objects)
+
(:masked-group? group)
(set-mask-selrect group children)
diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc
new file mode 100644
index 000000000..45fcebd23
--- /dev/null
+++ b/common/src/app/common/pages/changes_builder.cljc
@@ -0,0 +1,167 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.common.pages.changes-builder
+ (:require
+ [app.common.data :as d]
+ [app.common.pages :as cp]
+ [app.common.pages.helpers :as h]))
+
+;; Auxiliary functions to help create a set of changes (undo + redo)
+
+(defn empty-changes [origin page-id]
+ (let [changes {:redo-changes []
+ :undo-changes []
+ :origin origin}]
+ (with-meta changes
+ {::page-id page-id})))
+
+(defn with-objects [changes objects]
+ (vary-meta changes assoc ::objects objects))
+
+(defn add-obj
+ ([changes obj index]
+ (add-obj changes (assoc obj ::index index)))
+
+ ([changes obj]
+ (let [add-change
+ {:type :add-obj
+ :id (:id obj)
+ :page-id (::page-id (meta changes))
+ :parent-id (:parent-id obj)
+ :frame-id (:frame-id obj)
+ :index (::index obj)
+ :obj (dissoc obj ::index :parent-id)}
+
+ del-change
+ {:type :del-obj
+ :id (:id obj)
+ :page-id (::page-id (meta changes))}]
+
+ (-> changes
+ (update :redo-changes conj add-change)
+ (update :undo-changes d/preconj del-change)))))
+
+(defn change-parent
+ [changes parent-id shapes]
+ (assert (contains? (meta changes) ::objects) "Call (with-objects) first to use this function")
+
+ (let [objects (::objects (meta changes))
+ set-parent-change
+ {:type :mov-objects
+ :parent-id parent-id
+ :page-id (::page-id (meta changes))
+ :shapes (->> shapes (mapv :id))}
+
+ mk-undo-change
+ (fn [change-set shape]
+ (d/preconj
+ change-set
+ {:type :mov-objects
+ :page-id (::page-id (meta changes))
+ :parent-id (:parent-id shape)
+ :shapes [(:id shape)]
+ :index (cp/position-on-parent (:id shape) objects)}))]
+
+ (-> changes
+ (update :redo-changes conj set-parent-change)
+ (update :undo-changes #(reduce mk-undo-change % shapes)))))
+
+(defn- generate-operation
+ "Given an object old and new versions and an attribute will append into changes
+ the set and undo operations"
+ [changes attr old new ignore-geometry?]
+ (let [old-val (get old attr)
+ new-val (get new attr)]
+ (if (= old-val new-val)
+ changes
+ (-> changes
+ (update :rops conj {:type :set :attr attr :val new-val :ignore-geometry ignore-geometry?})
+ (update :uops conj {:type :set :attr attr :val old-val :ignore-touched true})))))
+
+(defn update-shapes
+ "Calculate the changes and undos to be done when a function is applied to a
+ single object"
+ ([changes ids update-fn]
+ (update-shapes changes ids update-fn nil))
+
+ ([changes ids update-fn {:keys [attrs ignore-geometry?] :or {attrs nil ignore-geometry? false}}]
+ (assert (contains? (meta changes) ::objects) "Call (with-objects) first to use this function")
+ (let [objects (::objects (meta changes))
+
+ update-shape
+ (fn [changes id]
+ (let [old-obj (get objects id)
+ new-obj (update-fn old-obj)
+
+ attrs (or attrs (d/concat #{} (keys old-obj) (keys new-obj)))
+
+ {rops :rops uops :uops}
+ (reduce #(generate-operation %1 %2 old-obj new-obj ignore-geometry?)
+ {:rops [] :uops []}
+ attrs)
+
+ uops (cond-> uops
+ (seq uops)
+ (conj {:type :set-touched :touched (:touched old-obj)}))
+
+ change {:type :mod-obj
+ :page-id (::page-id (meta changes))
+ :id id}]
+
+ (cond-> changes
+ (seq rops)
+ (update :redo-changes conj (assoc change :operations rops))
+
+ (seq uops)
+ (update :undo-changes d/preconj (assoc change :operations uops)))))]
+
+ (reduce update-shape changes ids))))
+
+(defn remove-objects
+ [changes ids]
+ (assert (contains? (meta changes) ::objects) "Call (with-objects) first to use this function")
+ (let [page-id (::page-id (meta changes))
+ objects (::objects (meta changes))
+
+ add-redo-change
+ (fn [change-set id]
+ (conj change-set
+ {:type :del-obj
+ :page-id page-id
+ :id id}))
+
+ add-undo-change-shape
+ (fn [change-set id]
+ (let [shape (get objects id)]
+ (d/preconj
+ change-set
+ {:type :add-obj
+ :page-id page-id
+ :parent-id (:frame-id shape)
+ :frame-id (:frame-id shape)
+ :id id
+ :obj (cond-> shape
+ (contains? shape :shapes)
+ (assoc :shapes []))})))
+
+ add-undo-change-parent
+ (fn [change-set id]
+ (let [shape (get objects id)]
+ (d/preconj
+ change-set
+ {:type :mov-objects
+ :page-id page-id
+ :parent-id (:parent-id shape)
+ :shapes [id]
+ :index (h/position-on-parent id objects)
+ :ignore-touched true})))]
+
+ (-> changes
+ (update :redo-changes #(reduce add-redo-change % ids))
+ (update :undo-changes #(as-> % $
+ (reduce add-undo-change-parent $ ids)
+ (reduce add-undo-change-shape $ ids))))))
diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc
index 6b30e3a9e..909908820 100644
--- a/common/src/app/common/pages/helpers.cljc
+++ b/common/src/app/common/pages/helpers.cljc
@@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.spec :as us]
+ [app.common.types.interactions :as cti]
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
@@ -138,6 +139,10 @@
[id objects]
(mapv #(get objects %) (cons id (get-children id objects))))
+(defn select-children [id objects]
+ (->> (get-children id objects)
+ (select-keys objects)))
+
(defn is-shape-grouped
"Checks if a shape is inside a group"
[shape-id objects]
@@ -161,6 +166,12 @@
(when parent-id
(lazy-seq (cons parent-id (get-parents parent-id objects))))))
+(defn get-frame
+ "Get the frame that contains the shape. If the shape is already a frame, get itself."
+ [shape objects]
+ (if (= (:type shape) :frame)
+ shape
+ (get objects (:frame-id shape))))
(defn clean-loops
"Clean a list of ids from circular references."
@@ -466,3 +477,17 @@
(let [path-split (split-path path)]
(merge-path-item (first path-split) name)))
+(defn connected-frame?
+ "Check if some frame is origin or destination of any navigate interaction
+ in the page"
+ [frame-id objects]
+ (let [children (get-object-with-children frame-id objects)]
+ (or (some cti/flow-origin? (map :interactions children))
+ (some #(cti/flow-to? % frame-id) (map :interactions (vals objects))))))
+
+(defn unframed-shape?
+ "Checks if it's a non-frame shape in the top level."
+ [shape]
+ (and (not= (:type shape) :frame)
+ (= (:frame-id shape) uuid/zero)))
+
diff --git a/common/src/app/common/pages/indices.cljc b/common/src/app/common/pages/indices.cljc
index c1681fef1..5a5f40595 100644
--- a/common/src/app/common/pages/indices.cljc
+++ b/common/src/app/common/pages/indices.cljc
@@ -95,16 +95,24 @@
(map #(vector (:id %) (shape->parents %)))
(into {})))))
-(defn create-mask-index
+(defn create-clip-index
"Retrieves the mask information for an object"
[objects parents-index]
- (let [retrieve-masks
+ (let [retrieve-clips
(fn [_ parents]
- ;; TODO: use transducers?
- (->> parents
- (map #(get objects %))
- (filter #(:masked-group? %))
- ;; Retrieve the masking element
- (mapv #(get objects (->> % :shapes first)))))]
+ (let [lookup-object (fn [id] (get objects id))
+ get-clip-parents
+ (fn [shape]
+ (cond-> []
+ (:masked-group? shape)
+ (conj (get objects (->> shape :shapes first)))
+
+ (= :bool (:type shape))
+ (conj shape)))]
+
+ (into []
+ (comp (map lookup-object)
+ (mapcat get-clip-parents))
+ parents)))]
(->> parents-index
- (d/mapm retrieve-masks))))
+ (d/mapm retrieve-clips))))
diff --git a/common/src/app/common/pages/spec.cljc b/common/src/app/common/pages/spec.cljc
index aec112016..286148439 100644
--- a/common/src/app/common/pages/spec.cljc
+++ b/common/src/app/common/pages/spec.cljc
@@ -9,6 +9,8 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.spec :as us]
+ [app.common.types.interactions :as cti]
+ [app.common.types.page-options :as cto]
[app.common.uuid :as uuid]
[clojure.set :as set]
[clojure.spec.alpha :as s]))
@@ -30,9 +32,6 @@
(s/def ::component-root? boolean?)
(s/def ::shape-ref uuid?)
-(s/def ::safe-integer ::us/safe-integer)
-(s/def ::safe-number ::us/safe-number)
-
(s/def :internal.matrix/a ::us/safe-number)
(s/def :internal.matrix/b ::us/safe-number)
(s/def :internal.matrix/c ::us/safe-number)
@@ -61,15 +60,15 @@
;; GRADIENTS
(s/def :internal.gradient.stop/color ::string)
-(s/def :internal.gradient.stop/opacity ::safe-number)
-(s/def :internal.gradient.stop/offset ::safe-number)
+(s/def :internal.gradient.stop/opacity ::us/safe-number)
+(s/def :internal.gradient.stop/offset ::us/safe-number)
(s/def :internal.gradient/type #{:linear :radial})
-(s/def :internal.gradient/start-x ::safe-number)
-(s/def :internal.gradient/start-y ::safe-number)
-(s/def :internal.gradient/end-x ::safe-number)
-(s/def :internal.gradient/end-y ::safe-number)
-(s/def :internal.gradient/width ::safe-number)
+(s/def :internal.gradient/start-x ::us/safe-number)
+(s/def :internal.gradient/start-y ::us/safe-number)
+(s/def :internal.gradient/end-x ::us/safe-number)
+(s/def :internal.gradient/end-y ::us/safe-number)
+(s/def :internal.gradient/width ::us/safe-number)
(s/def :internal.gradient/stop
(s/keys :req-un [:internal.gradient.stop/color
@@ -95,7 +94,7 @@
(s/def :internal.color/path (s/nilable ::string))
(s/def :internal.color/value (s/nilable ::string))
(s/def :internal.color/color (s/nilable ::string))
-(s/def :internal.color/opacity (s/nilable ::safe-number))
+(s/def :internal.color/opacity (s/nilable ::us/safe-number))
(s/def :internal.color/gradient (s/nilable ::gradient))
(s/def ::color
@@ -113,10 +112,10 @@
(s/def :internal.shadow/id uuid?)
(s/def :internal.shadow/style #{:drop-shadow :inner-shadow})
(s/def :internal.shadow/color ::color)
-(s/def :internal.shadow/offset-x ::safe-number)
-(s/def :internal.shadow/offset-y ::safe-number)
-(s/def :internal.shadow/blur ::safe-number)
-(s/def :internal.shadow/spread ::safe-number)
+(s/def :internal.shadow/offset-x ::us/safe-number)
+(s/def :internal.shadow/offset-y ::us/safe-number)
+(s/def :internal.shadow/blur ::us/safe-number)
+(s/def :internal.shadow/spread ::us/safe-number)
(s/def :internal.shadow/hidden boolean?)
(s/def :internal.shadow/shadow
@@ -137,7 +136,7 @@
(s/def :internal.blur/id uuid?)
(s/def :internal.blur/type #{:layer-blur})
-(s/def :internal.blur/value ::safe-number)
+(s/def :internal.blur/value ::us/safe-number)
(s/def :internal.blur/hidden boolean?)
(s/def ::blur
@@ -146,57 +145,6 @@
:internal.blur/value
:internal.blur/hidden]))
-;; Page Options
-(s/def :internal.page.grid.color/value string?)
-(s/def :internal.page.grid.color/opacity ::safe-number)
-
-(s/def :internal.page.grid/size ::safe-integer)
-(s/def :internal.page.grid/color
- (s/keys :req-un [:internal.page.grid.color/value
- :internal.page.grid.color/opacity]))
-
-(s/def :internal.page.grid/type #{:stretch :left :center :right})
-(s/def :internal.page.grid/item-length (s/nilable ::safe-integer))
-(s/def :internal.page.grid/gutter (s/nilable ::safe-integer))
-(s/def :internal.page.grid/margin (s/nilable ::safe-integer))
-
-(s/def :internal.page.grid/square
- (s/keys :req-un [:internal.page.grid/size
- :internal.page.grid/color]))
-
-(s/def :internal.page.grid/column
- (s/keys :req-un [:internal.page.grid/size
- :internal.page.grid/color
- :internal.page.grid/type
- :internal.page.grid/item-length
- :internal.page.grid/gutter
- :internal.page.grid/margin]))
-
-(s/def :internal.page.grid/row :internal.page.grid/column)
-
-(s/def :internal.page.options/background string?)
-(s/def :internal.page.options/saved-grids
- (s/keys :req-un [:internal.page.grid/square
- :internal.page.grid/row
- :internal.page.grid/column]))
-
-(s/def :internal.page/options
- (s/keys :opt-un [:internal.page.options/background]))
-
-;; Interactions
-
-(s/def :internal.shape.interaction/event-type #{:click}) ; In the future we will have more options
-(s/def :internal.shape.interaction/action-type #{:navigate})
-(s/def :internal.shape.interaction/destination ::uuid)
-
-(s/def :internal.shape/interaction
- (s/keys :req-un [:internal.shape.interaction/event-type
- :internal.shape.interaction/action-type
- :internal.shape.interaction/destination]))
-
-(s/def :internal.shape/interactions
- (s/coll-of :internal.shape/interaction :kind vector?))
-
;; Size constraints
(s/def :internal.shape/constraints-h #{:left :right :leftright :center :scale})
@@ -227,33 +175,33 @@
(s/def :internal.shape/content any?)
(s/def :internal.shape/fill-color string?)
-(s/def :internal.shape/fill-opacity ::safe-number)
+(s/def :internal.shape/fill-opacity ::us/safe-number)
(s/def :internal.shape/fill-color-gradient (s/nilable ::gradient))
(s/def :internal.shape/fill-color-ref-file (s/nilable uuid?))
(s/def :internal.shape/fill-color-ref-id (s/nilable uuid?))
(s/def :internal.shape/font-family string?)
-(s/def :internal.shape/font-size ::safe-integer)
+(s/def :internal.shape/font-size ::us/safe-integer)
(s/def :internal.shape/font-style string?)
(s/def :internal.shape/font-weight string?)
(s/def :internal.shape/hidden boolean?)
-(s/def :internal.shape/letter-spacing ::safe-number)
-(s/def :internal.shape/line-height ::safe-number)
+(s/def :internal.shape/letter-spacing ::us/safe-number)
+(s/def :internal.shape/line-height ::us/safe-number)
(s/def :internal.shape/locked boolean?)
(s/def :internal.shape/page-id uuid?)
-(s/def :internal.shape/proportion ::safe-number)
+(s/def :internal.shape/proportion ::us/safe-number)
(s/def :internal.shape/proportion-lock boolean?)
-(s/def :internal.shape/rx ::safe-number)
-(s/def :internal.shape/ry ::safe-number)
-(s/def :internal.shape/r1 ::safe-number)
-(s/def :internal.shape/r2 ::safe-number)
-(s/def :internal.shape/r3 ::safe-number)
-(s/def :internal.shape/r4 ::safe-number)
+(s/def :internal.shape/rx ::us/safe-number)
+(s/def :internal.shape/ry ::us/safe-number)
+(s/def :internal.shape/r1 ::us/safe-number)
+(s/def :internal.shape/r2 ::us/safe-number)
+(s/def :internal.shape/r3 ::us/safe-number)
+(s/def :internal.shape/r4 ::us/safe-number)
(s/def :internal.shape/stroke-color string?)
(s/def :internal.shape/stroke-color-gradient (s/nilable ::gradient))
(s/def :internal.shape/stroke-color-ref-file (s/nilable uuid?))
(s/def :internal.shape/stroke-color-ref-id (s/nilable uuid?))
-(s/def :internal.shape/stroke-opacity ::safe-number)
+(s/def :internal.shape/stroke-opacity ::us/safe-number)
(s/def :internal.shape/stroke-style #{:solid :dotted :dashed :mixed :none :svg})
(def stroke-caps-line #{:round :square})
@@ -266,26 +214,26 @@
[shape]
(= (:type shape) :path))
-(s/def :internal.shape/stroke-width ::safe-number)
+(s/def :internal.shape/stroke-width ::us/safe-number)
(s/def :internal.shape/stroke-alignment #{:center :inner :outer})
(s/def :internal.shape/text-align #{"left" "right" "center" "justify"})
-(s/def :internal.shape/x ::safe-number)
-(s/def :internal.shape/y ::safe-number)
-(s/def :internal.shape/cx ::safe-number)
-(s/def :internal.shape/cy ::safe-number)
-(s/def :internal.shape/width ::safe-number)
-(s/def :internal.shape/height ::safe-number)
+(s/def :internal.shape/x ::us/safe-number)
+(s/def :internal.shape/y ::us/safe-number)
+(s/def :internal.shape/cx ::us/safe-number)
+(s/def :internal.shape/cy ::us/safe-number)
+(s/def :internal.shape/width ::us/safe-number)
+(s/def :internal.shape/height ::us/safe-number)
(s/def :internal.shape/index integer?)
(s/def :internal.shape/shadow ::shadow)
(s/def :internal.shape/blur ::blur)
-(s/def :internal.shape/x1 ::safe-number)
-(s/def :internal.shape/y1 ::safe-number)
-(s/def :internal.shape/x2 ::safe-number)
-(s/def :internal.shape/y2 ::safe-number)
+(s/def :internal.shape/x1 ::us/safe-number)
+(s/def :internal.shape/y1 ::us/safe-number)
+(s/def :internal.shape/x2 ::us/safe-number)
+(s/def :internal.shape/y2 ::us/safe-number)
(s/def :internal.shape.export/suffix string?)
-(s/def :internal.shape.export/scale ::safe-number)
+(s/def :internal.shape.export/scale ::us/safe-number)
(s/def :internal.shape/export
(s/keys :req-un [::type
:internal.shape.export/suffix
@@ -361,7 +309,7 @@
:internal.shape/transform-inverse
:internal.shape/width
:internal.shape/height
- :internal.shape/interactions
+ ::cti/interactions
:internal.shape/masked-group?
:internal.shape/shadow
:internal.shape/blur]))
@@ -386,7 +334,7 @@
(s/def ::page
(s/keys :req-un [::id
::name
- :internal.page/options
+ ::cto/options
:internal.page/objects]))
@@ -397,8 +345,8 @@
:internal.color/gradient]))
(s/def :internal.media-object/name ::string)
-(s/def :internal.media-object/width ::safe-integer)
-(s/def :internal.media-object/height ::safe-integer)
+(s/def :internal.media-object/width ::us/safe-integer)
+(s/def :internal.media-object/height ::us/safe-integer)
(s/def :internal.media-object/mtype ::string)
(s/def ::media-object
diff --git a/common/src/app/common/path/bool.cljc b/common/src/app/common/path/bool.cljc
new file mode 100644
index 000000000..ddb388091
--- /dev/null
+++ b/common/src/app/common/path/bool.cljc
@@ -0,0 +1,309 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.common.path.bool
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.point :as gpt]
+ [app.common.geom.shapes.path :as gsp]
+ [app.common.geom.shapes.rect :as gpr]
+ [app.common.path.commands :as upc]
+ [app.common.path.subpaths :as ups]))
+
+(defn add-previous
+ ([content]
+ (add-previous content nil))
+ ([content first]
+ (->> (d/with-prev content)
+ (mapv (fn [[cmd prev]]
+ (cond-> cmd
+ (and (nil? prev) (some? first))
+ (assoc :prev first)
+
+ (some? prev)
+ (assoc :prev (gsp/command->point prev))))))))
+
+(defn close-paths
+ "Removes the :close-path commands and replace them for line-to so we can calculate
+ the intersections"
+ [content]
+
+ (loop [head (first content)
+ content (rest content)
+ result []
+ last-move nil
+ last-p nil]
+
+ (if (nil? head)
+ result
+ (let [head-p (gsp/command->point head)
+ head (cond
+ (and (= :close-path (:command head))
+ (< (gpt/distance last-p last-move) 0.01))
+ nil
+
+ (= :close-path (:command head))
+ (upc/make-line-to last-move)
+
+ :else
+ head)]
+
+ (recur (first content)
+ (rest content)
+ (cond-> result (some? head) (conj head))
+ (if (= :move-to (:command head))
+ head-p
+ last-move)
+ head-p)))))
+
+(defn- split-command
+ [cmd values]
+ (case (:command cmd)
+ :line-to (gsp/split-line-to-ranges (:prev cmd) cmd values)
+ :curve-to (gsp/split-curve-to-ranges (:prev cmd) cmd values)
+ [cmd]))
+
+(defn split-ts [seg-1 seg-2]
+ (cond
+ (and (= :line-to (:command seg-1))
+ (= :line-to (:command seg-2)))
+ (gsp/line-line-intersect (gsp/command->line seg-1) (gsp/command->line seg-2))
+
+ (and (= :line-to (:command seg-1))
+ (= :curve-to (:command seg-2)))
+ (gsp/line-curve-intersect (gsp/command->line seg-1) (gsp/command->bezier seg-2))
+
+ (and (= :curve-to (:command seg-1))
+ (= :line-to (:command seg-2)))
+ (let [[seg-2' seg-1']
+ (gsp/line-curve-intersect (gsp/command->line seg-2) (gsp/command->bezier seg-1))]
+ ;; Need to reverse because we send the arguments reversed
+ [seg-1' seg-2'])
+
+ (and (= :curve-to (:command seg-1))
+ (= :curve-to (:command seg-2)))
+ (gsp/curve-curve-intersect (gsp/command->bezier seg-1) (gsp/command->bezier seg-2))
+
+ :else
+ [[] []]))
+
+(defn split
+ [seg-1 seg-2]
+ (let [r1 (gsp/command->selrect seg-1)
+ r2 (gsp/command->selrect seg-2)]
+ (if (not (gpr/overlaps-rects? r1 r2))
+ [[seg-1] [seg-2]]
+ (let [[ts-seg-1 ts-seg-2] (split-ts seg-1 seg-2)]
+ [(-> (split-command seg-1 ts-seg-1) (add-previous (:prev seg-1)))
+ (-> (split-command seg-2 ts-seg-2) (add-previous (:prev seg-2)))]))))
+
+(defn content-intersect-split
+ [content-a content-b]
+
+ (let [cache (atom {})]
+ (letfn [(split-cache [seg-1 seg-2]
+ (cond
+ (contains? @cache [seg-1 seg-2])
+ (first (get @cache [seg-1 seg-2]))
+
+ (contains? @cache [seg-2 seg-1])
+ (second (get @cache [seg-2 seg-1]))
+
+ :else
+ (let [value (split seg-1 seg-2)]
+ (swap! cache assoc [seg-1 seg-2] value)
+ (first value))))
+
+ (split-segment-on-content
+ [segment content]
+
+ (loop [current (first content)
+ content (rest content)
+ result [segment]]
+
+ (if (nil? current)
+ result
+ (let [result (->> result (into [] (mapcat #(split-cache % current))))]
+ (recur (first content)
+ (rest content)
+ result)))))
+
+ (split-content
+ [content-a content-b]
+ (into []
+ (mapcat #(split-segment-on-content % content-b))
+ content-a))]
+
+ [(split-content content-a content-b)
+ (split-content content-b content-a)])))
+
+(defn is-segment?
+ [cmd]
+ (and (contains? cmd :prev)
+ (contains? #{:line-to :curve-to} (:command cmd))))
+
+(defn contains-segment?
+ [segment content]
+
+ (let [point (case (:command segment)
+ :line-to (-> (gsp/command->line segment)
+ (gsp/line-values 0.5))
+
+ :curve-to (-> (gsp/command->bezier segment)
+ (gsp/curve-values 0.5)))]
+
+ (or (gsp/is-point-in-content? point content)
+ (gsp/is-point-in-border? point content))))
+
+(defn inside-segment?
+ [segment content]
+ (let [point (case (:command segment)
+ :line-to (-> (gsp/command->line segment)
+ (gsp/line-values 0.5))
+
+ :curve-to (-> (gsp/command->bezier segment)
+ (gsp/curve-values 0.5)))]
+
+ (gsp/is-point-in-content? point content)))
+
+(defn overlap-segment?
+ "Finds if the current segment is overlapping against other
+ segment meaning they have the same coordinates"
+ [segment content]
+
+ (let [overlap-single?
+ (fn [other]
+ (when (and (= (:command segment) (:command other))
+ (contains? #{:line-to :curve-to} (:command segment)))
+
+ (case (:command segment)
+ :line-to (let [[p1 q1] (gsp/command->line segment)
+ [p2 q2] (gsp/command->line other)]
+
+ (when (or (and (< (gpt/distance p1 p2) 0.1)
+ (< (gpt/distance q1 q2) 0.1))
+ (and (< (gpt/distance p1 q2) 0.1)
+ (< (gpt/distance q1 p2) 0.1)))
+ [segment other]))
+
+ :curve-to (let [[p1 q1 h11 h21] (gsp/command->bezier segment)
+ [p2 q2 h12 h22] (gsp/command->bezier other)]
+
+ (when (or (and (< (gpt/distance p1 p2) 0.1)
+ (< (gpt/distance q1 q2) 0.1)
+ (< (gpt/distance h11 h12) 0.1)
+ (< (gpt/distance h21 h22) 0.1))
+
+ (and (< (gpt/distance p1 q2) 0.1)
+ (< (gpt/distance q1 p2) 0.1)
+ (< (gpt/distance h11 h22) 0.1)
+ (< (gpt/distance h21 h12) 0.1)))
+
+ [segment other])))))]
+
+ (->> content
+ (d/seek overlap-single?)
+ (some?))))
+
+(defn create-union [content-a content-a-split content-b content-b-split]
+ ;; Pick all segments in content-a that are not inside content-b
+ ;; Pick all segments in content-b that are not inside content-a
+ (let [content
+ (d/concat
+ []
+ (->> content-a-split (filter #(not (contains-segment? % content-b))))
+ (->> content-b-split (filter #(not (contains-segment? % content-a)))))
+
+ ;; Overlapping segments should be added when they are part of the border
+ border-content
+ (->> content-b-split
+ (filterv #(and (contains-segment? % content-a)
+ (overlap-segment? % content-a-split)
+ (not (inside-segment? % content)))))]
+
+ (d/concat content border-content)))
+
+(defn create-difference [content-a content-a-split content-b content-b-split]
+ ;; Pick all segments in content-a that are not inside content-b
+ ;; Pick all segments in content b that are inside content-a
+ ;; removing overlapping
+ (d/concat
+ []
+ (->> content-a-split (filter #(not (contains-segment? % content-b))))
+
+ ;; Reverse second content so we can have holes inside other shapes
+ (->> content-b-split
+ (filter #(and (contains-segment? % content-a)
+ (not (overlap-segment? % content-a-split)))))))
+
+(defn create-intersection [content-a content-a-split content-b content-b-split]
+ ;; Pick all segments in content-a that are inside content-b
+ ;; Pick all segments in content-b that are inside content-a
+ (d/concat
+ []
+ (->> content-a-split (filter #(contains-segment? % content-b)))
+ (->> content-b-split (filter #(contains-segment? % content-a)))))
+
+
+(defn create-exclusion [content-a content-b]
+ ;; Pick all segments
+ (d/concat [] content-a content-b))
+
+
+(defn fix-move-to
+ [content]
+ ;; Remove the field `:prev` and makes the necesaries `move-to`
+ ;; then clean the subpaths
+
+ (loop [current (first content)
+ content (rest content)
+ prev nil
+ result []]
+
+ (if (nil? current)
+ result
+
+ (let [result (if (not= (:prev current) prev)
+ (conj result (upc/make-move-to (:prev current)))
+ result)]
+ (recur (first content)
+ (rest content)
+ (gsp/command->point current)
+ (conj result (dissoc current :prev)))))))
+
+(defn content-bool-pair
+ [bool-type content-a content-b]
+
+ (let [content-a (-> content-a (close-paths) (add-previous))
+
+ content-b (-> content-b
+ (close-paths)
+ (cond-> (ups/clockwise? content-b)
+ (ups/reverse-content))
+ (add-previous))
+
+ ;; Split content in new segments in the intersection with the other path
+ [content-a-split content-b-split] (content-intersect-split content-a content-b)
+ content-a-split (->> content-a-split add-previous (filter is-segment?))
+ content-b-split (->> content-b-split add-previous (filter is-segment?))
+
+ bool-content
+ (case bool-type
+ :union (create-union content-a content-a-split content-b content-b-split)
+ :difference (create-difference content-a content-a-split content-b content-b-split)
+ :intersection (create-intersection content-a content-a-split content-b content-b-split)
+ :exclude (create-exclusion content-a-split content-b-split))]
+
+ (->> (fix-move-to bool-content)
+ (ups/close-subpaths))))
+
+(defn content-bool
+ [bool-type contents]
+ ;; We apply the boolean operation in to each pair and the result to the next
+ ;; element
+ (->> contents
+ (reduce (partial content-bool-pair bool-type))
+ (into [])))
diff --git a/frontend/src/app/util/path/commands.cljs b/common/src/app/common/path/commands.cljc
similarity index 99%
rename from frontend/src/app/util/path/commands.cljs
rename to common/src/app/common/path/commands.cljc
index 84a7725ef..80737db8c 100644
--- a/frontend/src/app/util/path/commands.cljs
+++ b/common/src/app/common/path/commands.cljc
@@ -4,7 +4,7 @@
;;
;; Copyright (c) UXBOX Labs SL
-(ns app.util.path.commands
+(ns app.common.path.commands
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]))
@@ -199,3 +199,4 @@
(if (= prefix :c1)
(command->point (get content (dec index)))
(command->point (get content index))))
+
diff --git a/common/src/app/common/path/shapes_to_path.cljc b/common/src/app/common/path/shapes_to_path.cljc
new file mode 100644
index 000000000..d2f905914
--- /dev/null
+++ b/common/src/app/common/path/shapes_to_path.cljc
@@ -0,0 +1,227 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.common.path.shapes-to-path
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.matrix :as gmt]
+ [app.common.geom.point :as gpt]
+ [app.common.geom.shapes.common :as gsc]
+ [app.common.geom.shapes.path :as gsp]
+ [app.common.path.bool :as pb]
+ [app.common.path.commands :as pc]))
+
+(def ^:const bezier-circle-c 0.551915024494)
+
+(def dissoc-attrs
+ [:x :y :width :height
+ :rx :ry :r1 :r2 :r3 :r4
+ :metadata :shapes])
+
+(def allowed-transform-types
+ #{:rect
+ :circle
+ :image})
+
+(def style-group-properties
+ [:shadow
+ :blur])
+
+(def style-properties
+ (d/concat
+ style-group-properties
+ [:fill-color
+ :fill-opacity
+ :fill-color-gradient
+ :fill-color-ref-file
+ :fill-color-ref-id
+ :fill-image
+ :stroke-color
+ :stroke-color-ref-file
+ :stroke-color-ref-id
+ :stroke-opacity
+ :stroke-style
+ :stroke-width
+ :stroke-alignment
+ :stroke-cap-start
+ :stroke-cap-end]))
+
+(defn make-corner-arc
+ "Creates a curvle corner for border radius"
+ [from to corner radius]
+ (let [x (case corner
+ :top-left (:x from)
+ :top-right (- (:x from) radius)
+ :bottom-right (- (:x to) radius)
+ :bottom-left (:x to))
+
+ y (case corner
+ :top-left (- (:y from) radius)
+ :top-right (:y from)
+ :bottom-right (- (:y to) (* 2 radius))
+ :bottom-left (- (:y to) radius))
+
+ width (* radius 2)
+ height (* radius 2)
+
+ c bezier-circle-c
+ c1x (+ x (* (/ width 2) (- 1 c)))
+ c2x (+ x (* (/ width 2) (+ 1 c)))
+ c1y (+ y (* (/ height 2) (- 1 c)))
+ c2y (+ y (* (/ height 2) (+ 1 c)))
+
+ h1 (case corner
+ :top-left (assoc from :y c1y)
+ :top-right (assoc from :x c2x)
+ :bottom-right (assoc from :y c2y)
+ :bottom-left (assoc from :x c1x))
+
+ h2 (case corner
+ :top-left (assoc to :x c1x)
+ :top-right (assoc to :y c1y)
+ :bottom-right (assoc to :x c2x)
+ :bottom-left (assoc to :y c2y))]
+
+ (pc/make-curve-to to h1 h2)))
+
+(defn circle->path
+ "Creates the bezier curves to approximate a circle shape"
+ [x y width height]
+ (let [mx (+ x (/ width 2))
+ my (+ y (/ height 2))
+ ex (+ x width)
+ ey (+ y height)
+
+ p1 (gpt/point mx y)
+ p2 (gpt/point ex my)
+ p3 (gpt/point mx ey)
+ p4 (gpt/point x my)
+
+ c bezier-circle-c
+ c1x (+ x (* (/ width 2) (- 1 c)))
+ c2x (+ x (* (/ width 2) (+ 1 c)))
+ c1y (+ y (* (/ height 2) (- 1 c)))
+ c2y (+ y (* (/ height 2) (+ 1 c)))]
+
+ [(pc/make-move-to p1)
+ (pc/make-curve-to p2 (assoc p1 :x c2x) (assoc p2 :y c1y))
+ (pc/make-curve-to p3 (assoc p2 :y c2y) (assoc p3 :x c2x))
+ (pc/make-curve-to p4 (assoc p3 :x c1x) (assoc p4 :y c2y))
+ (pc/make-curve-to p1 (assoc p4 :y c1y) (assoc p1 :x c1x))]))
+
+(defn rect->path
+ "Creates a bezier curve that approximates a rounded corner rectangle"
+ [x y width height r1 r2 r3 r4 rx]
+ (let [[r1 r2 r3 r4] (->> [r1 r2 r3 r4] (mapv #(or % rx 0)))
+ p1 (gpt/point x (+ y r1))
+ p2 (gpt/point (+ x r1) y)
+
+ p3 (gpt/point (+ width x (- r2)) y)
+ p4 (gpt/point (+ width x) (+ y r2))
+
+ p5 (gpt/point (+ width x) (+ height y (- r3)))
+ p6 (gpt/point (+ width x (- r3)) (+ height y))
+
+ p7 (gpt/point (+ x r4) (+ height y))
+ p8 (gpt/point x (+ height y (- r4)))]
+ (-> []
+ (conj (pc/make-move-to p1))
+ (cond-> (not= p1 p2)
+ (conj (make-corner-arc p1 p2 :top-left r1)))
+ (conj (pc/make-line-to p3))
+ (cond-> (not= p3 p4)
+ (conj (make-corner-arc p3 p4 :top-right r2)))
+ (conj (pc/make-line-to p5))
+ (cond-> (not= p5 p6)
+ (conj (make-corner-arc p5 p6 :bottom-right r3)))
+ (conj (pc/make-line-to p7))
+ (cond-> (not= p7 p8)
+ (conj (make-corner-arc p7 p8 :bottom-left r4)))
+ (conj (pc/make-line-to p1)))))
+
+(declare convert-to-path)
+
+(defn fix-first-relative
+ "Fix an issue with the simplify commands not changing the first relative"
+ [content]
+ (let [head (first content)]
+ (cond-> content
+ (and head (:relative head))
+ (update 0 assoc :relative false))))
+
+(defn group-to-path
+ [group objects]
+ (let [xform (comp (map #(get objects %))
+ (map #(-> (convert-to-path % objects))))
+
+ child-as-paths (into [] xform (:shapes group))
+ head (last child-as-paths)
+ head-data (select-keys head style-properties)
+ content (into []
+ (comp (filter #(= :path (:type %)))
+ (mapcat #(fix-first-relative (:content %))))
+ child-as-paths)]
+ (-> group
+ (assoc :type :path)
+ (assoc :content content)
+ (merge head-data)
+ (d/without-keys dissoc-attrs))))
+
+(defn bool-to-path
+ [shape objects]
+
+ (let [children (->> (:shapes shape)
+ (map #(get objects %))
+ (map #(convert-to-path % objects)))
+ bool-type (:bool-type shape)
+ head (if (= bool-type :difference) (first children) (last children))
+ head (cond-> head
+ (and (contains? head :svg-attrs) (nil? (:fill-color head)))
+ (assoc :fill-color "#000000"))
+
+ head-data (select-keys head style-properties)
+ content (pb/content-bool (:bool-type shape) (mapv :content children))]
+
+ (-> shape
+ (assoc :type :path)
+ (assoc :content content)
+ (merge head-data)
+ (d/without-keys dissoc-attrs))))
+
+(defn convert-to-path
+ "Transforms the given shape to a path"
+ ([shape]
+ (convert-to-path shape {}))
+ ([{:keys [type x y width height r1 r2 r3 r4 rx metadata] :as shape} objects]
+ (assert (map? objects))
+ (case (:type shape)
+ :group
+ (group-to-path shape objects)
+
+ :bool
+ (bool-to-path shape objects)
+
+ (:rect :circle :image :text)
+ (let [new-content
+ (case type
+ :circle (circle->path x y width height)
+ #_:else (rect->path x y width height r1 r2 r3 r4 rx))
+
+ ;; Apply the transforms that had the shape
+ transform (:transform shape)
+ new-content (cond-> new-content
+ (some? transform)
+ (gsp/transform-content (gmt/transform-in (gsc/center-shape shape) transform)))]
+
+ (-> shape
+ (assoc :type :path)
+ (assoc :content new-content)
+ (cond-> (= :image type)
+ (assoc :fill-image metadata))
+ (d/without-keys dissoc-attrs)))
+
+ ;; For the rest return the plain shape
+ shape)))
diff --git a/frontend/src/app/util/path/subpaths.cljs b/common/src/app/common/path/subpaths.cljc
similarity index 66%
rename from frontend/src/app/util/path/subpaths.cljs
rename to common/src/app/common/path/subpaths.cljc
index 010f1343a..202e273b1 100644
--- a/frontend/src/app/util/path/subpaths.cljs
+++ b/common/src/app/common/path/subpaths.cljc
@@ -4,10 +4,16 @@
;;
;; Copyright (c) UXBOX Labs SL
-(ns app.util.path.subpaths
+(ns app.common.path.subpaths
(:require
[app.common.data :as d]
- [app.util.path.commands :as upc]))
+ [app.common.geom.point :as gpt]
+ [app.common.path.commands :as upc]))
+
+(defn pt=
+ "Check if two points are close"
+ [p1 p2]
+ (< (gpt/distance p1 p2) 0.1))
(defn make-subpath
"Creates a subpath either from a single command or with all the data"
@@ -67,16 +73,22 @@
(fn [subpaths current]
(let [is-move? (= :move-to (:command current))
last-idx (dec (count subpaths))]
- (if is-move?
+ (cond
+ is-move?
(conj subpaths (make-subpath current))
- (update subpaths last-idx add-subpath-command current))))]
+
+ (>= last-idx 0)
+ (update subpaths last-idx add-subpath-command current)
+
+ :else
+ subpaths)))]
(->> content
(reduce reduce-subpath []))))
(defn subpaths-join
"Join two subpaths together when the first finish where the second starts"
[subpath other]
- (assert (= (:to subpath) (:from other)))
+ (assert (pt= (:to subpath) (:from other)))
(-> subpath
(update :data d/concat (rest (:data other)))
(assoc :to (:to other))))
@@ -88,21 +100,31 @@
(let [merge-with-candidate
(fn [[candidate result] current]
(cond
- (= (:to current) (:from current))
+ (pt= (:to current) (:from current))
+ ;; Subpath is already a closed path
[candidate (conj result current)]
- (= (:to candidate) (:from current))
+ (pt= (:to candidate) (:from current))
[(subpaths-join candidate current) result]
- (= (:to candidate) (:to current))
+ (pt= (:from candidate) (:to current))
+ [(subpaths-join current candidate) result]
+
+ (pt= (:to candidate) (:to current))
[(subpaths-join candidate (reverse-subpath current)) result]
+ (pt= (:from candidate) (:from current))
+ [(subpaths-join (reverse-subpath current) candidate) result]
+
:else
[candidate (conj result current)]))]
(->> subpaths
(reduce merge-with-candidate [candidate []]))))
+(defn is-closed? [subpath]
+ (pt= (:from subpath) (:to subpath)))
+
(defn close-subpaths
"Searches a path for posible supaths that can create closed loops and merge them"
[content]
@@ -114,7 +136,7 @@
(if (some? current)
(let [[new-current new-subpaths]
- (if (= (:from current) (:to current))
+ (if (is-closed? current)
[current subpaths]
(merge-paths current subpaths))]
@@ -134,3 +156,38 @@
(->> closed-subpaths
(mapcat :data)
(into []))))
+
+(defn reverse-content
+ "Given a content reverse the order of the commands"
+ [content]
+
+ (->> content
+ (get-subpaths)
+ (mapv reverse-subpath)
+ (reverse)
+ (mapcat :data)
+ (into [])))
+
+;; https://mathworld.wolfram.com/PolygonArea.html
+(defn clockwise?
+ "Check whether the first subpath is clockwise or counter-clock wise"
+ [content]
+ (let [subpath (->> content get-subpaths first :data)]
+ (loop [current (first subpath)
+ subpath (rest subpath)
+ first-point nil
+ signed-area 0]
+
+ (if (nil? current)
+ (> signed-area 0)
+
+ (let [{x1 :x y1 :y :as p} (upc/command->point current)
+ last? (nil? (first subpath))
+ first-point (if (nil? first-point) p first-point)
+ {x2 :x y2 :y} (if last? first-point (upc/command->point (first subpath)))
+ signed-area (+ signed-area (- (* x1 y2) (* x2 y1)))]
+
+ (recur (first subpath)
+ (rest subpath)
+ first-point
+ signed-area))))))
diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc
index ca369fb51..88f8eab9a 100644
--- a/common/src/app/common/spec.cljc
+++ b/common/src/app/common/spec.cljc
@@ -111,6 +111,16 @@
(s/def ::point gpt/point?)
(s/def ::id ::uuid)
+(s/def ::words
+ (s/conformer
+ (fn [s]
+ (cond
+ (set? s) s
+ (string? s) (into #{} (map keyword) (str/words s))
+ :else ::s/invalid))
+ (fn [s]
+ (str/join " " (map name s)))))
+
(defn bytes?
"Test if a first parameter is a byte
array or not."
@@ -196,7 +206,7 @@
:name (pr-str spec)
:line (:line &env)
:file (:file (:meta nsdata))})
- message (str "Spec Assertion: '" (pr-str spec) "'")]
+ message (str "spec assert: '" (pr-str spec) "'")]
`(spec-assert* ~spec ~x ~message ~context))))
(defmacro verify
@@ -208,7 +218,7 @@
:name (pr-str spec)
:line (:line &env)
:file (:file (:meta nsdata))})
- message (str "Spec Assertion: '" (pr-str spec) "'")]
+ message (str "spec verify: '" (pr-str spec) "'")]
`(spec-assert* ~spec ~x ~message ~context)))
;; --- Public Api
diff --git a/common/src/app/common/types/interactions.cljc b/common/src/app/common/types/interactions.cljc
new file mode 100644
index 000000000..ebbc81e70
--- /dev/null
+++ b/common/src/app/common/types/interactions.cljc
@@ -0,0 +1,376 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.common.types.interactions
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.point :as gpt]
+ [app.common.spec :as us]
+ [clojure.spec.alpha :as s]))
+
+;; WARNING: options are not deleted when changing event or action type, so it can be
+;; restored if the user changes it back later.
+;;
+;; But that means that an interaction may have for example a delay or
+;; destination, even if its type does not require it (but a previous type did).
+;;
+;; So make sure to use has-delay/has-destination... functions, or similar,
+;; before reading them.
+
+;; -- Options depending on event type
+
+(s/def ::event-type #{:click
+ :mouse-press
+ :mouse-over
+ :mouse-enter
+ :mouse-leave
+ :after-delay})
+
+(s/def ::delay ::us/safe-integer)
+
+(defmulti event-opts-spec :event-type)
+
+(defmethod event-opts-spec :after-delay [_]
+ (s/keys :req-un [::delay]))
+
+(defmethod event-opts-spec :default [_]
+ (s/keys :req-un []))
+
+(s/def ::event-opts
+ (s/multi-spec event-opts-spec ::event-type))
+
+;; -- Options depending on action type
+
+(s/def ::action-type #{:navigate
+ :open-overlay
+ :toggle-overlay
+ :close-overlay
+ :prev-screen
+ :open-url})
+
+(s/def ::destination (s/nilable ::us/uuid))
+(s/def ::overlay-pos-type #{:manual
+ :center
+ :top-left
+ :top-right
+ :top-center
+ :bottom-left
+ :bottom-right
+ :bottom-center})
+(s/def ::overlay-position ::us/point)
+(s/def ::url ::us/string)
+(s/def ::close-click-outside ::us/boolean)
+(s/def ::background-overlay ::us/boolean)
+
+(defmulti action-opts-spec :action-type)
+
+(defmethod action-opts-spec :navigate [_]
+ (s/keys :req-un [::destination]))
+
+(defmethod action-opts-spec :open-overlay [_]
+ (s/keys :req-un [::destination
+ ::overlay-position
+ ::overlay-pos-type]
+ :opt-un [::close-click-outside
+ ::background-overlay]))
+
+(defmethod action-opts-spec :toggle-overlay [_]
+ (s/keys :req-un [::destination
+ ::overlay-position
+ ::overlay-pos-type]
+ :opt-un [::close-click-outside
+ ::background-overlay]))
+
+(defmethod action-opts-spec :close-overlay [_]
+ (s/keys :req-un [::destination]))
+
+(defmethod action-opts-spec :prev-screen [_]
+ (s/keys :req-un []))
+
+(defmethod action-opts-spec :open-url [_]
+ (s/keys :req-un [::url]))
+
+(s/def ::action-opts
+ (s/multi-spec action-opts-spec ::action-type))
+
+;; -- Interaction
+
+(s/def ::classifier
+ (s/keys :req-un [::event-type
+ ::action-type]))
+
+(s/def ::interaction
+ (s/merge ::classifier
+ ::event-opts
+ ::action-opts))
+
+(s/def ::interactions
+ (s/coll-of ::interaction :kind vector?))
+
+(def default-interaction
+ {:event-type :click
+ :action-type :navigate
+ :destination nil})
+
+(def default-delay 600)
+
+;; -- Helpers for interaction
+
+(declare calc-overlay-pos-initial)
+
+(defn set-event-type
+ [interaction event-type shape]
+ (us/verify ::interaction interaction)
+ (us/verify ::event-type event-type)
+ (assert (or (not= event-type :after-delay)
+ (= (:type shape) :frame)))
+ (if (= (:event-type interaction) event-type)
+ interaction
+ (case event-type
+
+ :after-delay
+ (assoc interaction
+ :event-type event-type
+ :delay (get interaction :delay default-delay))
+
+ (assoc interaction
+ :event-type event-type))))
+
+
+(defn set-action-type
+ [interaction action-type]
+ (us/verify ::interaction interaction)
+ (us/verify ::action-type action-type)
+ (if (= (:action-type interaction) action-type)
+ interaction
+ (case action-type
+
+ :navigate
+ (assoc interaction
+ :action-type action-type
+ :destination (get interaction :destination))
+
+ (:open-overlay :toggle-overlay)
+ (let [overlay-pos-type (get interaction :overlay-pos-type :center)
+ overlay-position (get interaction :overlay-position (gpt/point 0 0))]
+ (assoc interaction
+ :action-type action-type
+ :overlay-pos-type overlay-pos-type
+ :overlay-position overlay-position))
+
+ :close-overlay
+ (assoc interaction
+ :action-type action-type
+ :destination (get interaction :destination))
+
+ :prev-screen
+ (assoc interaction
+ :action-type action-type)
+
+ :open-url
+ (assoc interaction
+ :action-type action-type
+ :url (get interaction :url "")))))
+
+(defn has-delay
+ [interaction]
+ (= (:event-type interaction) :after-delay))
+
+(defn set-delay
+ [interaction delay]
+ (us/verify ::interaction interaction)
+ (us/verify ::delay delay)
+ (assert (has-delay interaction))
+ (assoc interaction :delay delay))
+
+(defn has-destination
+ [interaction]
+ (#{:navigate :open-overlay :toggle-overlay :close-overlay}
+ (:action-type interaction)))
+
+(defn destination?
+ [interaction]
+ (and (has-destination interaction)
+ (some? (:destination interaction))))
+
+(defn set-destination
+ [interaction destination]
+ (us/verify ::interaction interaction)
+ (us/verify ::destination destination)
+ (assert (has-destination interaction))
+ (cond-> interaction
+ :always
+ (assoc :destination destination)
+
+ (or (= (:action-type interaction) :open-overlay)
+ (= (:action-type interaction) :toggle-overlay))
+ (assoc :overlay-pos-type :center
+ :overlay-position (gpt/point 0 0))))
+
+(defn has-url
+ [interaction]
+ (= (:action-type interaction) :open-url))
+
+(defn set-url
+ [interaction url]
+ (us/verify ::interaction interaction)
+ (us/verify ::url url)
+ (assert (has-url interaction))
+ (assoc interaction :url url))
+
+(defn has-overlay-opts
+ [interaction]
+ (#{:open-overlay :toggle-overlay} (:action-type interaction)))
+
+(defn set-overlay-pos-type
+ [interaction overlay-pos-type shape objects]
+ (us/verify ::interaction interaction)
+ (us/verify ::overlay-pos-type overlay-pos-type)
+ (assert (has-overlay-opts interaction))
+ (assoc interaction
+ :overlay-pos-type overlay-pos-type
+ :overlay-position (calc-overlay-pos-initial (:destination interaction)
+ shape
+ objects
+ overlay-pos-type)))
+(defn toggle-overlay-pos-type
+ [interaction overlay-pos-type shape objects]
+ (us/verify ::interaction interaction)
+ (us/verify ::overlay-pos-type overlay-pos-type)
+ (assert (has-overlay-opts interaction))
+ (let [new-pos-type (if (= (:overlay-pos-type interaction) overlay-pos-type)
+ :manual
+ overlay-pos-type)]
+ (assoc interaction
+ :overlay-pos-type new-pos-type
+ :overlay-position (calc-overlay-pos-initial (:destination interaction)
+ shape
+ objects
+ new-pos-type))))
+(defn set-overlay-position
+ [interaction overlay-position]
+ (us/verify ::interaction interaction)
+ (us/verify ::overlay-position overlay-position)
+ (assert (has-overlay-opts interaction))
+ (assoc interaction
+ :overlay-pos-type :manual
+ :overlay-position overlay-position))
+
+(defn set-close-click-outside
+ [interaction close-click-outside]
+ (us/verify ::interaction interaction)
+ (us/verify ::us/boolean close-click-outside)
+ (assert (has-overlay-opts interaction))
+ (assoc interaction :close-click-outside close-click-outside))
+
+(defn set-background-overlay
+ [interaction background-overlay]
+ (us/verify ::interaction interaction)
+ (us/verify ::us/boolean background-overlay)
+ (assert (has-overlay-opts interaction))
+ (assoc interaction :background-overlay background-overlay))
+
+(defn- calc-overlay-pos-initial
+ [destination shape objects overlay-pos-type]
+ (if (= overlay-pos-type :manual)
+ (let [dest-frame (get objects destination)
+ overlay-size (:selrect dest-frame)
+ orig-frame (if (= (:type shape) :frame)
+ shape
+ (get objects (:frame-id shape)))
+ frame-size (:selrect orig-frame)]
+ (gpt/point (/ (- (:width frame-size) (:width overlay-size)) 2)
+ (/ (- (:height frame-size) (:height overlay-size)) 2)))
+ (gpt/point 0 0)))
+
+(defn calc-overlay-position
+ [interaction base-frame dest-frame frame-offset]
+ (us/verify ::interaction interaction)
+ (assert (has-overlay-opts interaction))
+ (if (nil? dest-frame)
+ (gpt/point 0 0)
+ (let [overlay-size (:selrect dest-frame)
+ base-frame-size (:selrect base-frame)]
+ (case (:overlay-pos-type interaction)
+ :center
+ (gpt/point (/ (- (:width base-frame-size) (:width overlay-size)) 2)
+ (/ (- (:height base-frame-size) (:height overlay-size)) 2))
+
+ :top-left
+ (gpt/point 0 0)
+
+ :top-right
+ (gpt/point (- (:width base-frame-size) (:width overlay-size))
+ 0)
+
+ :top-center
+ (gpt/point (/ (- (:width base-frame-size) (:width overlay-size)) 2)
+ 0)
+
+ :bottom-left
+ (gpt/point 0
+ (- (:height base-frame-size) (:height overlay-size)))
+
+ :bottom-right
+ (gpt/point (- (:width base-frame-size) (:width overlay-size))
+ (- (:height base-frame-size) (:height overlay-size)))
+
+ :bottom-center
+ (gpt/point (/ (- (:width base-frame-size) (:width overlay-size)) 2)
+ (- (:height base-frame-size) (:height overlay-size)))
+
+ :manual
+ (gpt/add (:overlay-position interaction) frame-offset)))))
+
+;; -- Helpers for interactions
+
+(defn add-interaction
+ [interactions interaction]
+ (conj (or interactions []) interaction))
+
+(defn remove-interaction
+ [interactions index]
+ (let [interactions (or interactions [])]
+ (into (subvec interactions 0 index)
+ (subvec interactions (inc index)))))
+
+(defn update-interaction
+ [interactions index update-fn]
+ (update interactions index update-fn))
+
+(defn remap-interactions
+ "Update all interactions whose destination points to a shape in the
+ map to the new id. And remove the ones whose destination does not exist
+ in the map nor in the objects tree."
+ [interactions ids-map objects]
+ (when (some? interactions)
+ (let [xform (comp (filter (fn [interaction]
+ (let [destination (:destination interaction)]
+ (or (nil? destination)
+ (contains? ids-map destination)
+ (contains? objects destination)))))
+ (map (fn [interaction]
+ (d/update-when interaction :destination #(get ids-map % %)))))]
+ (into [] xform interactions))))
+
+(defn actionable?
+ "Check if there is any interaction that is clickable by the user"
+ [interactions]
+ (some #(= (:event-type %) :click) interactions))
+
+(defn flow-origin?
+ "Check if there is any interaction that is the start or the continuation of a flow"
+ [interactions]
+ (some #(and (#{:navigate :open-overlay :toggle-overlay :close-overlay} (:action-type %))
+ (some? (:destination %)))
+ interactions))
+
+(defn flow-to?
+ "Check if there is any interaction that flows into the given frame"
+ [interactions frame-id]
+ (some #(and (#{:navigate :open-overlay :toggle-overlay :close-overlay} (:action-type %))
+ (= (:destination %) frame-id))
+ interactions))
diff --git a/common/src/app/common/types/page_options.cljc b/common/src/app/common/types/page_options.cljc
new file mode 100644
index 000000000..c15258eda
--- /dev/null
+++ b/common/src/app/common/types/page_options.cljc
@@ -0,0 +1,94 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.common.types.page-options
+ (:require
+ [app.common.data :as d]
+ [app.common.spec :as us]
+ [clojure.spec.alpha :as s]))
+
+;; --- Grid options
+
+(s/def :artboard-grid.color/color ::us/string)
+(s/def :artboard-grid.color/opacity ::us/safe-number)
+
+(s/def :artboard-grid/size ::us/safe-integer)
+(s/def :artboard-grid/color (s/keys :req-un [:artboard-grid.color/color
+ :artboard-grid.color/opacity]))
+(s/def :artboard-grid/type #{:stretch :left :center :right})
+(s/def :artboard-grid/item-length (s/nilable ::us/safe-integer))
+(s/def :artboard-grid/gutter (s/nilable ::us/safe-integer))
+(s/def :artboard-grid/margin (s/nilable ::us/safe-integer))
+
+(s/def :artboard-grid/square
+ (s/keys :req-un [:artboard-grid/size
+ :artboard-grid/color]))
+
+(s/def :artboard-grid/column
+ (s/keys :req-un [:artboard-grid/size
+ :artboard-grid/color
+ :artboard-grid/margin
+ :artboard-grid/gutter]
+ :opt-un [:artboard-grid/type
+ :artboard-grid/item-length]))
+
+(s/def :artboard-grid/row :artboard-grid/column)
+
+(s/def ::saved-grids
+ (s/keys :opt-un [:artboard-grid/square
+ :artboard-grid/row
+ :artboard-grid/column]))
+
+;; --- Background options
+
+(s/def ::background string?)
+
+;; --- Flow options
+
+(s/def :interactions-flow/id ::us/uuid)
+(s/def :interactions-flow/name ::us/string)
+(s/def :interactions-flow/starting-frame ::us/uuid)
+
+(s/def ::flow
+ (s/keys :req-un [:interactions-flow/id
+ :interactions-flow/name
+ :interactions-flow/starting-frame]))
+
+(s/def ::flows
+ (s/coll-of ::flow :kind vector?))
+
+;; --- Options
+
+(s/def ::options
+ (s/keys :opt-un [::background
+ ::saved-grids
+ ::flows]))
+
+;; --- Helpers for flow
+
+(defn rename-flow
+ [flow name]
+ (assoc flow :name name))
+
+;; --- Helpers for flows
+
+(defn add-flow
+ [flows flow]
+ (conj (or flows []) flow))
+
+(defn remove-flow
+ [flows flow-id]
+ (d/removev #(= (:id %) flow-id) flows))
+
+(defn update-flow
+ [flows flow-id update-fn]
+ (let [index (d/index-of-pred flows #(= (:id %) flow-id))]
+ (update flows index update-fn)))
+
+(defn get-frame-flow
+ [flows frame-id]
+ (d/seek #(= (:starting-frame %) frame-id) flows))
+
diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile
index bdfbd3341..6b8290e3a 100644
--- a/docker/devenv/Dockerfile
+++ b/docker/devenv/Dockerfile
@@ -3,10 +3,10 @@ LABEL maintainer="Andrey Antukh "
ARG DEBIAN_FRONTEND=noninteractive
-ENV NODE_VERSION=v14.17.5 \
- CLOJURE_VERSION=1.10.3.933 \
- CLJKONDO_VERSION=2021.07.28 \
- BABASHKA_VERSION=0.5.1 \
+ENV NODE_VERSION=v14.17.6 \
+ CLOJURE_VERSION=1.10.3.967 \
+ CLJKONDO_VERSION=2021.09.15 \
+ BABASHKA_VERSION=0.6.1 \
LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8
@@ -28,6 +28,7 @@ RUN set -ex; \
rlwrap \
unzip \
fakeroot \
+ netcat \
; \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
locale-gen; \
@@ -172,9 +173,10 @@ COPY files/vimrc /root/.vimrc
COPY files/tmux.conf /root/.tmux.conf
COPY files/sudoers /etc/sudoers
-COPY files/start-tmux.sh /home/start-tmux.sh
-COPY files/entrypoint.sh /home/entrypoint.sh
-COPY files/init.sh /home/init.sh
+COPY files/start-tmux.sh /home/start-tmux.sh
+COPY files/start-tmux-back.sh /home/start-tmux-back.sh
+COPY files/entrypoint.sh /home/entrypoint.sh
+COPY files/init.sh /home/init.sh
ENTRYPOINT ["/home/entrypoint.sh"]
CMD ["/home/init.sh"]
diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml
index c15bd7d00..764b638db 100644
--- a/docker/devenv/docker-compose.yaml
+++ b/docker/devenv/docker-compose.yaml
@@ -5,7 +5,7 @@ networks:
driver: bridge
ipam:
config:
- - subnet: 172.177.09.0/24
+ - subnet: 172.177.9.0/24
volumes:
postgres_data:
@@ -13,6 +13,7 @@ volumes:
services:
main:
+ profiles: ["full"]
privileged: true
image: "penpotapp/devenv:latest"
build:
@@ -49,6 +50,57 @@ services:
- PENPOT_SMTP_PASSWORD=
- PENPOT_SMTP_SSL=false
- PENPOT_SMTP_TLS=false
+ - PENPOT_FLAGS="enable-cors"
+
+ # LDAP setup
+ - PENPOT_LDAP_HOST=ldap
+ - PENPOT_LDAP_PORT=10389
+ - PENPOT_LDAP_SSL=false
+ - PENPOT_LDAP_STARTTLS=false
+ - PENPOT_LDAP_BASE_DN=ou=people,dc=planetexpress,dc=com
+ - PENPOT_LDAP_BIND_DN=cn=admin,dc=planetexpress,dc=com
+ - PENPOT_LDAP_BIND_PASSWORD=GoodNewsEveryone
+ - PENPOT_LDAP_ATTRS_USERNAME=uid
+ - PENPOT_LDAP_ATTRS_EMAIL=mail
+ - PENPOT_LDAP_ATTRS_FULLNAME=cn
+ - PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto
+
+ backend:
+ profiles: ["backend"]
+ privileged: true
+ image: "penpotapp/devenv:latest"
+ build:
+ context: "."
+ container_name: "penpot-backend"
+ stop_signal: SIGINT
+
+ depends_on:
+ - postgres
+ - redis
+
+ volumes:
+ - "user_data:/home/penpot/"
+ - "${PWD}:/home/penpot/penpot"
+
+ ports:
+ - 6060:6060
+ - 6061:6061
+ - 9090:9090
+
+ environment:
+ - EXTERNAL_UID=${CURRENT_USER_ID}
+ - PENPOT_SECRET_KEY=super-secret-devenv-key
+ # STMP setup
+ - PENPOT_SMTP_ENABLED=true
+ - PENPOT_SMTP_DEFAULT_FROM=no-reply@example.com
+ - PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@example.com
+ - PENPOT_SMTP_HOST=mailer
+ - PENPOT_SMTP_PORT=1025
+ - PENPOT_SMTP_USERNAME=
+ - PENPOT_SMTP_PASSWORD=
+ - PENPOT_SMTP_SSL=false
+ - PENPOT_SMTP_TLS=false
+ - PENPOT_FLAGS="enable-cors"
# LDAP setup
- PENPOT_LDAP_HOST=ldap
diff --git a/docker/devenv/files/start-tmux-back.sh b/docker/devenv/files/start-tmux-back.sh
new file mode 100755
index 000000000..7e427080f
--- /dev/null
+++ b/docker/devenv/files/start-tmux-back.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+sudo chown penpot:users /home/penpot
+
+cd ~;
+
+source ~/.bashrc
+
+set -e;
+
+echo "[start-tmux.sh] Installing node dependencies"
+pushd ~/penpot/exporter/
+yarn install
+popd
+
+tmux -2 new-session -d -s penpot
+
+tmux rename-window -t penpot:0 'exporter'
+tmux select-window -t penpot:0
+tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
+tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l
+tmux send-keys -t penpot 'clojure -M:dev:shadow-cljs watch main' enter
+
+tmux split-window -v
+tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
+tmux send-keys -t penpot './scripts/wait-and-start.sh' enter
+
+tmux new-window -t penpot:1 -n 'backend'
+tmux select-window -t penpot:1
+tmux send-keys -t penpot 'cd penpot/backend' enter C-l
+tmux send-keys -t penpot './scripts/start-dev' enter
+
+tmux -2 attach-session -t penpot
diff --git a/docker/devenv/files/start-tmux.sh b/docker/devenv/files/start-tmux.sh
index 6676425a8..2891f18bc 100755
--- a/docker/devenv/files/start-tmux.sh
+++ b/docker/devenv/files/start-tmux.sh
@@ -18,6 +18,11 @@ popd
tmux -2 new-session -d -s penpot
+tmux rename-window -t penpot:0 'gulp'
+tmux select-window -t penpot:0
+tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
+tmux send-keys -t penpot 'npx gulp watch' enter
+
tmux new-window -t penpot:1 -n 'shadow watch'
tmux select-window -t penpot:1
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
@@ -38,9 +43,4 @@ tmux select-window -t penpot:3
tmux send-keys -t penpot 'cd penpot/backend' enter C-l
tmux send-keys -t penpot './scripts/start-dev' enter
-tmux rename-window -t penpot:0 'gulp'
-tmux select-window -t penpot:0
-tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
-tmux send-keys -t penpot 'npx gulp watch' enter
-
tmux -2 attach-session -t penpot
diff --git a/exporter/deps.edn b/exporter/deps.edn
index 5c55ace1e..328023c7c 100644
--- a/exporter/deps.edn
+++ b/exporter/deps.edn
@@ -14,7 +14,7 @@
:dev
{:extra-deps
- {thheller/shadow-cljs {:mvn/version "2.15.2"}}}
+ {thheller/shadow-cljs {:mvn/version "2.15.10"}}}
:shadow-cljs
{:main-opts ["-m" "shadow.cljs.devtools.cli"]}
diff --git a/exporter/package.json b/exporter/package.json
index 52cfa2501..5f70a41b7 100644
--- a/exporter/package.json
+++ b/exporter/package.json
@@ -9,10 +9,12 @@
"author": "UXBOX LABS SL",
"license": "SEE LICENSE IN ",
"dependencies": {
+ "@sentry/node": "^6.13.1",
+ "@sentry/tracing": "^6.13.1",
+ "cookies": "^0.8.0",
"generic-pool": "^3.8.2",
"inflation": "^2.0.0",
"jszip": "^3.7.0",
- "koa": "^2.13.0",
"luxon": "^2.0.1",
"puppeteer-core": "^10.1.0",
"raw-body": "^2.4.1",
@@ -20,7 +22,7 @@
"xregexp": "^5.0.2"
},
"devDependencies": {
- "shadow-cljs": "^2.15.2",
- "source-map-support": "^0.5.19"
+ "shadow-cljs": "^2.15.10",
+ "source-map-support": "^0.5.20"
}
}
diff --git a/exporter/src/app/browser.cljs b/exporter/src/app/browser.cljs
index 35f1b9a0b..a3775a048 100644
--- a/exporter/src/app/browser.cljs
+++ b/exporter/src/app/browser.cljs
@@ -108,7 +108,6 @@
(p/then (fn [browser]
(let [id (deref pool-browser-id)]
(log/info :origin "factory" :action "create" :browser-id id)
- (unchecked-set browser "__num_use" 0)
(unchecked-set browser "__id" id)
(swap! pool-browser-id inc)
browser))))))
@@ -118,16 +117,9 @@
(.close ^js obj)))
(validate [obj]
- (let [max-use (cf/get :browser-max-usage 10)
- num-use (unchecked-get obj "__num_use")
- id (unchecked-get obj "__id")]
-
- (log/info :origin "factory" :action "validate" :browser-id id :max-use max-use :num-use num-use :obj obj)
- (if (> num-use max-use)
- (p/resolved false)
- (do
- (unchecked-set obj "__num_use" (inc num-use))
- (p/resolved (.isConnected ^js obj))))))]
+ (let [id (unchecked-get obj "__id")]
+ (log/info :origin "factory" :action "validate" :browser-id id :obj obj)
+ (p/resolved (.isConnected ^js obj))))]
#js {:create create
:destroy destroy
@@ -139,10 +131,10 @@
(let [opts #js {:max (cf/get :browser-pool-max 3)
:min (cf/get :browser-pool-min 0)
:testOnBorrow true
- :evictionRunIntervalMillis 30000
+ :evictionRunIntervalMillis 5000
:numTestsPerEvictionRun 5
:acquireTimeoutMillis 120000 ; 2min
- :idleTimeoutMillis 30000}]
+ :idleTimeoutMillis 10000}]
(reset! pool (gp/createPool browser-pool-factory opts))
(p/resolved nil)))
@@ -155,19 +147,29 @@
(p/then (fn [] (.clear ^js pool))))))
(defn exec!
- [f]
- (letfn [(on-acquire [pool browser]
- (p/let [ctx (.createIncognitoBrowserContext ^js browser)
- page (.newPage ^js ctx)]
- (-> (p/do! (f page))
- (p/handle
- (fn [result error]
- (-> (p/do! (.close ^js ctx)
- (.release ^js pool browser))
- (p/handle (fn [_ _]
- (if result
- (p/resolved result)
- (p/rejected error))))))))))]
+ [callback]
+ (letfn [(on-release [pool browser ctx result error]
+ (-> (p/do! (.close ^js ctx))
+ (p/handle
+ (fn [_ _]
+ (.release ^js pool browser)))
+ (p/handle
+ (fn [_ _]
+ (let [id (unchecked-get browser "__id")]
+ (log/info :origin "exec" :action "release" :browser-id id))
+ (if result
+ (p/resolved result)
+ (p/rejected error))))))
+
+ (on-context [pool browser ctx]
+ (-> (p/do! (.newPage ^js ctx))
+ (p/then callback)
+ (p/handle #(on-release pool browser ctx %1 %2))))
+
+ (on-acquire [pool browser]
+ (-> (.createIncognitoBrowserContext ^js browser)
+ (p/then #(on-context pool browser %))))]
+
(when-let [pool (deref pool)]
- (-> (.acquire ^js pool)
+ (-> (p/do! (.acquire ^js pool))
(p/then (partial on-acquire pool))))))
diff --git a/exporter/src/app/config.cljs b/exporter/src/app/config.cljs
index 7c79a533c..307019171 100644
--- a/exporter/src/app/config.cljs
+++ b/exporter/src/app/config.cljs
@@ -7,9 +7,12 @@
(ns app.config
(:refer-clojure :exclude [get])
(:require
+ ["fs" :as fs]
["process" :as process]
+ [app.common.exceptions :as ex]
[app.common.data :as d]
[app.common.spec :as us]
+ [app.common.version :as v]
[cljs.core :as c]
[cljs.pprint]
[cljs.spec.alpha :as s]
@@ -18,18 +21,26 @@
(def defaults
{:public-uri "http://localhost:3449"
+ :tenant "dev"
+ :host "devenv"
:http-server-port 6061
:browser-concurrency 5
:browser-strategy :incognito})
-(s/def ::browser-executable-path ::us/string)
-(s/def ::public-uri ::us/string)
-(s/def ::http-server-port ::us/integer)
(s/def ::browser-concurrency ::us/integer)
+(s/def ::browser-executable-path ::us/string)
(s/def ::browser-strategy ::us/keyword)
+(s/def ::http-server-port ::us/integer)
+(s/def ::public-uri ::us/string)
+(s/def ::sentry-dsn ::us/string)
+(s/def ::tenant ::us/string)
+(s/def ::host ::us/string)
(s/def ::config
(s/keys :opt-un [::public-uri
+ ::sentry-dsn
+ ::host
+ ::tenant
::http-server-port
::browser-concurrency
::browser-strategy
@@ -59,6 +70,10 @@
(def config
(atom (prepare-config)))
+(def version
+ (atom (v/parse (or (some-> (ex/ignoring (fs/readFileSync "version.txt"))
+ (str/trim))
+ "%version%"))))
(defn get
"A configuration getter."
diff --git a/exporter/src/app/core.cljs b/exporter/src/app/core.cljs
index 7fafd8393..29eb8865d 100644
--- a/exporter/src/app/core.cljs
+++ b/exporter/src/app/core.cljs
@@ -10,11 +10,13 @@
[lambdaisland.glogi.console :as glogi-console]
[promesa.core :as p]
[app.http :as http]
+ [app.sentry :as sentry]
[app.config]
[app.browser :as bwr]))
(glogi-console/install!)
(enable-console-print!)
+(sentry/init!)
(defonce state (atom nil))
diff --git a/exporter/src/app/http.cljs b/exporter/src/app/http.cljs
index c8d3d4168..a36e030d9 100644
--- a/exporter/src/app/http.cljs
+++ b/exporter/src/app/http.cljs
@@ -10,6 +10,9 @@
[app.http.export :refer [export-handler]]
[app.http.export-frames :refer [export-frames-handler]]
[app.http.impl :as impl]
+ [app.util.transit :as t]
+ [app.sentry :as sentry]
+ [cuerdas.core :as str]
[lambdaisland.glogi :as log]
[promesa.core :as p]
[reitit.core :as r]))
@@ -20,13 +23,48 @@
(def instance (atom nil))
+(defn- on-error
+ [error request]
+ (let [{:keys [type message code] :as data} (ex-data error)]
+ (sentry/capture-exception error {::sentry/request request
+ :ex-data data})
+
+ (cond
+ (= :validation type)
+ (let [header (get-in request [:headers "accept"])]
+ (if (and (str/starts-with? header "text/html")
+ (= :spec-validation (:code data)))
+ {:status 400
+ :headers {"content-type" "text/html"}
+ :body (str "" (:explain data) "
\n")}
+ {:status 400
+ :headers {"content-type" "text/html"}
+ :body (str "" (:explain data) "
\n")}))
+
+ (and (= :internal type)
+ (= :browser-not-ready code))
+ {:status 503
+ :headers {"x-error" (t/encode data)}
+ :body ""}
+
+ :else
+ (do
+ (log/error :msg "Unexpected error" :error error)
+ (js/console.error error)
+ {:status 500
+ :headers {"x-error" (t/encode data)}
+ :body ""}))))
+
(defn init
[]
(let [router (r/router routes)
- handler (impl/handler router)
- server (impl/server handler)
+ handler (impl/router-handler router)
+ server (impl/server handler on-error)
port (cf/get :http-server-port 6061)]
(.listen server port)
+ (log/info :msg "welcome to penpot"
+ :module "exporter"
+ :version (:full @cf/version))
(log/info :msg "starting http server" :port port)
(reset! instance server)))
diff --git a/exporter/src/app/http/impl.cljs b/exporter/src/app/http/impl.cljs
index 3401194d4..67d54f6d8 100644
--- a/exporter/src/app/http/impl.cljs
+++ b/exporter/src/app/http/impl.cljs
@@ -7,8 +7,8 @@
(ns app.http.impl
(:require
["http" :as http]
+ ["cookies" :as Cookies]
["inflation" :as inflate]
- ["koa" :as koa]
["raw-body" :as raw-body]
[app.util.transit :as t]
[cuerdas.core :as str]
@@ -17,94 +17,66 @@
[promesa.core :as p]
[reitit.core :as r]))
+(def methods-with-body
+ #{"POST" "PUT" "DELETE"})
+
(defn- match
- [router ctx]
- (let [uri (u/uri (unchecked-get ctx "originalUrl"))]
- (when-let [match (r/match-by-path router (:path uri))]
- (assoc match :query-params (u/query-string->map (:query uri))))))
-
-(defn- handle-error
- [error request]
- (let [{:keys [type message code] :as data} (ex-data error)]
- (cond
- (= :validation type)
- (let [header (get-in request [:headers "accept"])]
- (if (and (str/starts-with? header "text/html")
- (= :spec-validation (:code data)))
- {:status 400
- :headers {"content-type" "text/html"}
- :body (str "" (:explain data) "
\n")}
- {:status 400
- :headers {"content-type" "text/html"}
- :body (str "" (:explain data) "
\n")}))
-
- (and (= :internal type)
- (= :browser-not-ready code))
- {:status 503
- :headers {"x-error" (t/encode data)}
- :body ""}
-
- :else
- (do
- (log/error :msg "Unexpected error"
- :error error)
- (js/console.error error)
- {:status 500
- :headers {"x-error" (t/encode data)}
- :body ""}))))
+ [router {:keys [path query] :as request}]
+ (when-let [match (r/match-by-path router path)]
+ (assoc match :query-params (u/query-string->map query))))
(defn- handle-response
- [ctx {:keys [body headers status] :or {headers {} status 200}}]
- (run! (fn [[k v]] (.set ^js ctx k v)) headers)
- (set! (.-body ^js ctx) body)
- (set! (.-status ^js ctx) status)
- nil)
+ [req res]
+ (fn [{:keys [body headers status] :or {headers {} status 200}}]
+ (.writeHead ^js res status (clj->js headers))
+ (.end ^js res body)))
(defn- parse-headers
- [ctx]
- (let [orig (unchecked-get ctx "headers")]
+ [req]
+ (let [orig (unchecked-get req "headers")]
(persistent!
(reduce #(assoc! %1 %2 (unchecked-get orig %2))
(transient {})
(js/Object.keys orig)))))
-(def parse-body? #{"POST" "PUT" "DELETE"})
-
(defn- parse-body
- [ctx]
- (let [headers (unchecked-get ctx "headers")
- ctype (unchecked-get headers "content-type")]
- (when (parse-body? (.-method ^js ctx))
- (-> (inflate (.-req ^js ctx))
- (raw-body #js {:limit "5mb" :encoding "utf8"})
+ [req]
+ (let [headers (unchecked-get req "headers")
+ method (unchecked-get req "method")
+ ctype (unchecked-get headers "content-type")
+ opts #js {:limit "5mb" :encoding "utf8"}]
+ (when (contains? methods-with-body method)
+ (-> (raw-body (inflate req) opts)
(p/then (fn [data]
- (cond-> data
- (= ctype "application/transit+json")
- (t/decode))))))))
+ (cond-> data
+ (= ctype "application/transit+json")
+ (t/decode))))))))
-(defn- wrap-handler
- [f]
- (fn [ctx]
- (p/let [cookies (unchecked-get ctx "cookies")
- headers (parse-headers ctx)
- body (parse-body ctx)
- request {:method (str/lower (unchecked-get ctx "method"))
- :body body
- :ctx ctx
- :headers headers
- :cookies cookies}]
- (-> (p/do! (f request))
- (p/then (fn [rsp]
- (when (map? rsp)
- (handle-response ctx rsp))))
- (p/catch (fn [err]
- (->> (handle-error err request)
- (handle-response ctx))))))))
+(defn- handler-adapter
+ [handler on-error]
+ (fn [req res]
+ (let [cookies (new Cookies req res)
+ headers (parse-headers req)
+ uri (u/uri (unchecked-get req "url"))
+ request {:method (str/lower (unchecked-get req "method"))
+ :path (:path uri)
+ :query (:query uri)
+ :url uri
+ :headers headers
+ :cookies cookies
+ :internal-request req
+ :internal-response res}]
+ (-> (parse-body req)
+ (p/then (fn [body]
+ (let [request (assoc request :body body)]
+ (handler request))))
+ (p/catch (fn [error] (on-error error request)))
+ (p/then (handle-response req res))))))
-(defn- router-handler
+(defn router-handler
[router]
- (fn [{:keys [ctx body] :as request}]
- (let [route (match router ctx)
+ (fn [{:keys [body] :as request}]
+ (let [route (match router request)
params (merge {}
(:query-params route)
(:path-params route)
@@ -120,16 +92,5 @@
:body "Not found"}))))
(defn server
- [handler]
- (.createServer http @handler))
-
-(defn handler
- [router]
- (let [instance (doto (new koa)
- (.use (-> (router-handler router)
- (wrap-handler))))]
- (specify! instance
- cljs.core/IDeref
- (-deref [_]
- (.callback instance)))))
-
+ [handler on-error]
+ (.createServer ^js http (handler-adapter handler on-error)))
diff --git a/exporter/src/app/renderer/svg.cljs b/exporter/src/app/renderer/svg.cljs
index 93e0649f4..54544aa24 100644
--- a/exporter/src/app/renderer/svg.cljs
+++ b/exporter/src/app/renderer/svg.cljs
@@ -210,11 +210,10 @@
:height (.. attrs -height -value)
:colors (.split colors ",")}))
- (extract-single-node [node]
+ (extract-single-node [[shot node]]
(log/trace :fn :extract-single-node)
- (p/let [attrs (bw/eval! node extract-element-attrs)
- shot (bw/screenshot node {:omit-background? true :type "png"})]
+ (p/let [attrs (bw/eval! node extract-element-attrs)]
{:id (unchecked-get attrs "id")
:x (unchecked-get attrs "x")
:y (unchecked-get attrs "y")
@@ -223,13 +222,21 @@
:colors (vec (unchecked-get attrs "colors"))
:data shot}))
+ (resolve-text-node [page node]
+ (p/let [attrs (bw/eval! node extract-element-attrs)
+ id (unchecked-get attrs "id")
+ text-node (bw/select page (str "#screenshot-text-" id " foreignObject"))
+ shot (bw/screenshot text-node {:omit-background? true :type "png"})]
+ [shot node]))
+
(clean-temp-data [{:keys [tempdir] :as node}]
(p/do!
(sh/rmdir! tempdir)
(dissoc node :tempdir)))
- (process-text-node [item]
+ (process-text-node [page item]
(-> (p/resolved item)
+ (p/then (partial resolve-text-node page))
(p/then extract-single-node)
(p/then trace-node)
(p/then clean-temp-data)))
@@ -237,7 +244,7 @@
(process-text-nodes [page]
(log/trace :fn :process-text-nodes)
(-> (bw/select-all page "#screenshot foreignObject")
- (p/then (fn [nodes] (p/all (map process-text-node nodes))))))
+ (p/then (fn [nodes] (p/all (map (partial process-text-node page) nodes))))))
(extract-svg [page]
(p/let [dom (bw/select page "#screenshot")
@@ -271,7 +278,7 @@
(p/let [page (render-in-page page rctx)]
(extract-svg page)))]
- (let [path (str "/render-object/" file-id "/" page-id "/" object-id)
+ (let [path (str "/render-object/" file-id "/" page-id "/" object-id "?render-texts=true")
uri (-> (u/uri (cf/get :public-uri))
(assoc :path "/")
(assoc :fragment path))
diff --git a/exporter/src/app/sentry.cljs b/exporter/src/app/sentry.cljs
new file mode 100644
index 000000000..4f7c9be5c
--- /dev/null
+++ b/exporter/src/app/sentry.cljs
@@ -0,0 +1,44 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.sentry
+ (:require
+ ["@sentry/node" :as sentry]
+ ["@sentry/tracing" :as sentry-t]
+ [app.common.data :as d]
+ [app.config :as cf]))
+
+(defn init!
+ []
+ (when-let [dsn (cf/get :sentry-dsn)]
+ (sentry/init
+ #js {:dsn dsn
+ :release (str "frontend@" (:base @cf/version))
+ :serverName (cf/get :host)
+ :environment (cf/get :tenant)
+ :autoSessionTracking false
+ :attachStacktrace false
+ :maxBreadcrumbs 20
+ :tracesSampleRate 1.0})))
+
+(def parse-request (unchecked-get sentry/Handlers "parseRequest"))
+
+(defn capture-exception
+ [error {:keys [::request ::tags] :as context}]
+ (let [context (-> (dissoc context ::request ::tags)
+ (d/without-nils))]
+ (sentry/withScope
+ (fn [scope]
+ (.addEventProcessor ^js scope (fn [event]
+ (let [node-request (:internal-request request)]
+ (parse-request event node-request))))
+ (doseq [[k v] tags]
+ (.setTag ^js scope (if (keyword? k) (name k) (str k)) (str v)))
+
+ (doseq [[k v] context]
+ (.setContext ^js scope (if (keyword? k) (name k) (str k)) (clj->js v)))
+
+ (sentry/captureException error)))))
diff --git a/exporter/yarn.lock b/exporter/yarn.lock
index 62e1b751b..a671fedf5 100644
--- a/exporter/yarn.lock
+++ b/exporter/yarn.lock
@@ -10,6 +10,74 @@
core-js-pure "^3.16.0"
regenerator-runtime "^0.13.4"
+"@sentry/core@6.13.1":
+ version "6.13.1"
+ resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.13.1.tgz#158cb7391c1aed4083fe12f40a3aecc951e3d354"
+ integrity sha512-IGitAHYtsDPhPPcSTDqL/NMOwTbrqPezQOo4rlU1p1lpyZKSKrgzn/8LCrb+eRV2ffaio6+3kwqjCN2SvU8S7A==
+ dependencies:
+ "@sentry/hub" "6.13.1"
+ "@sentry/minimal" "6.13.1"
+ "@sentry/types" "6.13.1"
+ "@sentry/utils" "6.13.1"
+ tslib "^1.9.3"
+
+"@sentry/hub@6.13.1":
+ version "6.13.1"
+ resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.13.1.tgz#587227fa502e3ed699878ac903f2a5100849cb00"
+ integrity sha512-O7bR5suyVNTNyr6tm0IjhZ7NvxSHIbHoy5KYbVv05HQ/DmvAbYWq4dtOMvYQuuTD9krGkZdwPg4Gm6KnFaKqoQ==
+ dependencies:
+ "@sentry/types" "6.13.1"
+ "@sentry/utils" "6.13.1"
+ tslib "^1.9.3"
+
+"@sentry/minimal@6.13.1":
+ version "6.13.1"
+ resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.13.1.tgz#86865cc0d6090d9c8be2c44ec7f14a9c66910e7d"
+ integrity sha512-waRLPRFT1G95LsklH25LvfJy4vSe54PPRSeAGNPa4xVLCP56CnbNXGEXGDyPUewtqESwVpRG6GPL1QRV67IixA==
+ dependencies:
+ "@sentry/hub" "6.13.1"
+ "@sentry/types" "6.13.1"
+ tslib "^1.9.3"
+
+"@sentry/node@^6.13.1":
+ version "6.13.1"
+ resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.13.1.tgz#9b76d5746a6e851a11bcdc25097177cbb28fcc82"
+ integrity sha512-uSAsRPGeTNb6pEzHQ4/RkaylO95f6SmS1yzMizlMkgZq5qPDkmKgN+sZ04g58XlZ6nDKTxYoLSZsfxdaIZNTcw==
+ dependencies:
+ "@sentry/core" "6.13.1"
+ "@sentry/hub" "6.13.1"
+ "@sentry/tracing" "6.13.1"
+ "@sentry/types" "6.13.1"
+ "@sentry/utils" "6.13.1"
+ cookie "^0.4.1"
+ https-proxy-agent "^5.0.0"
+ lru_map "^0.3.3"
+ tslib "^1.9.3"
+
+"@sentry/tracing@6.13.1", "@sentry/tracing@^6.13.1":
+ version "6.13.1"
+ resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.13.1.tgz#eec84740a2566132c14c151e855f2a8a2dac3716"
+ integrity sha512-UF0yHtWXi5SfDa5oKCSw463P3AyAf635w6zFMiLV6kt8DDjnOwJRcT7dekRIU8F4Du+2nWJFPoXsmt1sablycw==
+ dependencies:
+ "@sentry/hub" "6.13.1"
+ "@sentry/minimal" "6.13.1"
+ "@sentry/types" "6.13.1"
+ "@sentry/utils" "6.13.1"
+ tslib "^1.9.3"
+
+"@sentry/types@6.13.1":
+ version "6.13.1"
+ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.13.1.tgz#650e5e5fe4aa415987e14cfa2ca03e5d5eb3e347"
+ integrity sha512-dYm8qv/O6QhOCmWi5rlJBX9rjEqvnjnZH1zqhQCWhMmF9aYS151Y41P1C0TS0o17Z0ClonLiYMG1J+zE/Pmtqg==
+
+"@sentry/utils@6.13.1":
+ version "6.13.1"
+ resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.13.1.tgz#5c974c4235615ac50caed2b25c30b33a6a234425"
+ integrity sha512-qFDup/nBj2u2rAcQADFG3njVYUCo4XOQkCT7XdA5Flg1a++r6tIBdjiRyyjb8nqwHZ/OQsKr/V/EQaJiW29ETA==
+ dependencies:
+ "@sentry/types" "6.13.1"
+ tslib "^1.9.3"
+
"@types/node@*":
version "16.6.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50"
@@ -22,14 +90,6 @@
dependencies:
"@types/node" "*"
-accepts@^1.3.5:
- version "1.3.7"
- resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
- integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
- dependencies:
- mime-types "~2.1.24"
- negotiator "0.6.2"
-
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@@ -37,11 +97,6 @@ agent-base@6:
dependencies:
debug "4"
-any-promise@^1.1.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
- integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
-
asn1.js@^5.2.0:
version "5.4.1"
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
@@ -205,14 +260,6 @@ bytes@3.1.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
-cache-content-type@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c"
- integrity sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==
- dependencies:
- mime-types "^2.1.18"
- ylru "^1.2.0"
-
chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
@@ -226,11 +273,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
inherits "^2.0.1"
safe-buffer "^5.0.1"
-co@^4.6.0:
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
- integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
-
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -246,19 +288,12 @@ constants-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
-content-disposition@~0.5.2:
- version "0.5.3"
- resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
- integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
- dependencies:
- safe-buffer "5.1.2"
+cookie@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
+ integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
-content-type@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
- integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
-
-cookies@~0.8.0:
+cookies@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90"
integrity sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==
@@ -338,33 +373,16 @@ debug@4.3.1:
dependencies:
ms "2.1.2"
-debug@~3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
- integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
- dependencies:
- ms "2.0.0"
-
-deep-equal@~1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
- integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
-
-delegates@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
- integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
-
-depd@^2.0.0, depd@~2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
- integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
-
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
+depd@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
des.js@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
@@ -373,11 +391,6 @@ des.js@^1.0.0:
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
-destroy@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
- integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
-
devtools-protocol@0.0.901419:
version "0.0.901419"
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.901419.tgz#79b5459c48fe7e1c5563c02bd72f8fec3e0cebcd"
@@ -397,11 +410,6 @@ domain-browser@^1.1.1:
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
-ee-first@1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
- integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
-
elliptic@^6.5.3:
version "6.5.4"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
@@ -415,11 +423,6 @@ elliptic@^6.5.3:
minimalistic-assert "^1.0.1"
minimalistic-crypto-utils "^1.0.1"
-encodeurl@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
- integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
-
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@@ -427,11 +430,6 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
dependencies:
once "^1.4.0"
-escape-html@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
- integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
-
events@^3.0.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
@@ -471,11 +469,6 @@ find-up@^4.0.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
-fresh@~0.5.2:
- version "0.5.2"
- resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
- integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
-
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
@@ -510,18 +503,6 @@ glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
-has-symbols@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
- integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
-
-has-tostringtag@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
- integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
- dependencies:
- has-symbols "^1.0.2"
-
hash-base@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
@@ -548,15 +529,7 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
-http-assert@^1.3.0:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.4.1.tgz#c5f725d677aa7e873ef736199b89686cceb37878"
- integrity sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==
- dependencies:
- deep-equal "~1.0.1"
- http-errors "~1.7.2"
-
-http-errors@1.7.3, http-errors@~1.7.2:
+http-errors@1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
@@ -567,23 +540,12 @@ http-errors@1.7.3, http-errors@~1.7.2:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
-http-errors@^1.6.3:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507"
- integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==
- dependencies:
- depd "~1.1.2"
- inherits "2.0.4"
- setprototypeof "1.2.0"
- statuses ">= 1.5.0 < 2"
- toidentifier "1.0.0"
-
https-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
-https-proxy-agent@5.0.0:
+https-proxy-agent@5.0.0, https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
@@ -636,13 +598,6 @@ inherits@2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-is-generator-function@^1.0.7:
- version "1.0.10"
- resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
- integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==
- dependencies:
- has-tostringtag "^1.0.0"
-
isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -670,55 +625,6 @@ keygrip@~1.1.0:
dependencies:
tsscmp "1.0.6"
-koa-compose@^3.0.0:
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-3.2.1.tgz#a85ccb40b7d986d8e5a345b3a1ace8eabcf54de7"
- integrity sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=
- dependencies:
- any-promise "^1.1.0"
-
-koa-compose@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877"
- integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==
-
-koa-convert@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-1.2.0.tgz#da40875df49de0539098d1700b50820cebcd21d0"
- integrity sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=
- dependencies:
- co "^4.6.0"
- koa-compose "^3.0.0"
-
-koa@^2.13.0:
- version "2.13.1"
- resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.1.tgz#6275172875b27bcfe1d454356a5b6b9f5a9b1051"
- integrity sha512-Lb2Dloc72auj5vK4X4qqL7B5jyDPQaZucc9sR/71byg7ryoD1NCaCm63CShk9ID9quQvDEi1bGR/iGjCG7As3w==
- dependencies:
- accepts "^1.3.5"
- cache-content-type "^1.0.0"
- content-disposition "~0.5.2"
- content-type "^1.0.4"
- cookies "~0.8.0"
- debug "~3.1.0"
- delegates "^1.0.0"
- depd "^2.0.0"
- destroy "^1.0.4"
- encodeurl "^1.0.2"
- escape-html "^1.0.3"
- fresh "~0.5.2"
- http-assert "^1.3.0"
- http-errors "^1.6.3"
- is-generator-function "^1.0.7"
- koa-compose "^4.1.0"
- koa-convert "^1.2.0"
- on-finished "^2.3.0"
- only "~0.0.2"
- parseurl "^1.3.2"
- statuses "^1.5.0"
- type-is "^1.6.16"
- vary "^1.1.2"
-
lie@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
@@ -733,6 +639,11 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
+lru_map@^0.3.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
+ integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=
+
luxon@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-2.0.2.tgz#11f2cd4a11655fdf92e076b5782d7ede5bcdd133"
@@ -747,11 +658,6 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
-media-typer@0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
- integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
-
miller-rabin@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@@ -760,18 +666,6 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
-mime-db@1.49.0:
- version "1.49.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed"
- integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==
-
-mime-types@^2.1.18, mime-types@~2.1.24:
- version "2.1.32"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5"
- integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==
- dependencies:
- mime-db "1.49.0"
-
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
@@ -801,21 +695,11 @@ mkdirp@^0.5.1:
dependencies:
minimist "^1.2.5"
-ms@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
- integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
-
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-negotiator@0.6.2:
- version "0.6.2"
- resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
- integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
-
node-fetch@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
@@ -855,13 +739,6 @@ object-assign@^4.1.1:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
-on-finished@^2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
- integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
- dependencies:
- ee-first "1.1.1"
-
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -869,11 +746,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
dependencies:
wrappy "1"
-only@~0.0.2:
- version "0.0.2"
- resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
- integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=
-
os-browserify@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
@@ -914,11 +786,6 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5:
pbkdf2 "^3.0.3"
safe-buffer "^5.1.1"
-parseurl@^1.3.2:
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
- integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
-
path-browserify@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
@@ -1107,16 +974,16 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
-safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
- integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@@ -1142,11 +1009,6 @@ setprototypeof@1.1.1:
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
-setprototypeof@1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
- integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
-
sha.js@^2.4.0, sha.js@^2.4.8:
version "2.4.11"
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
@@ -1160,10 +1022,10 @@ shadow-cljs-jar@1.3.2:
resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b"
integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg==
-shadow-cljs@^2.15.2:
- version "2.15.4"
- resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.15.4.tgz#0d657fc8ab9a02d8980db5c49cb1622e8fc6fa52"
- integrity sha512-xn8UsiVpOf2LTsQZLsCa910CcMCYdMRT6STAsgveOEIncC9cunGdqE7cTq69vTmIijVQmzf0A1nALidyzO3Hcw==
+shadow-cljs@^2.15.10:
+ version "2.15.10"
+ resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.15.10.tgz#b1df65a51707a7c34dad2c788a12edeb41e0321c"
+ integrity sha512-r02f7pvZefiaATt3k7oAb8PNQKaA67iFoowS88Jaf8IClRQL3plBazkDAUWX5yQd9QfT4WhGr6kP0GTzTidQSg==
dependencies:
node-libs-browser "^2.2.1"
readline-sync "^1.4.7"
@@ -1179,10 +1041,10 @@ source-map-support@^0.4.15:
dependencies:
source-map "^0.5.6"
-source-map-support@^0.5.19:
- version "0.5.19"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
- integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
+source-map-support@^0.5.20:
+ version "0.5.20"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"
+ integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
@@ -1197,7 +1059,7 @@ source-map@^0.6.0:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
-"statuses@>= 1.5.0 < 2", statuses@^1.5.0:
+"statuses@>= 1.5.0 < 2":
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
@@ -1278,6 +1140,11 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+tslib@^1.9.3:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
+ integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+
tsscmp@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
@@ -1288,14 +1155,6 @@ tty-browserify@0.0.0:
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=
-type-is@^1.6.16:
- version "1.6.18"
- resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
- integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
- dependencies:
- media-typer "0.3.0"
- mime-types "~2.1.24"
-
unbzip2-stream@1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz#d156d205e670d8d8c393e1c02ebd506422873f6a"
@@ -1336,11 +1195,6 @@ util@^0.11.0:
dependencies:
inherits "2.0.3"
-vary@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
- integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
-
vm-browserify@^1.0.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
@@ -1394,8 +1248,3 @@ yauzl@^2.10.0:
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"
-
-ylru@^1.2.0:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f"
- integrity sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==
diff --git a/frontend/deps.edn b/frontend/deps.edn
index 3ba3255a8..b878e722f 100644
--- a/frontend/deps.edn
+++ b/frontend/deps.edn
@@ -4,7 +4,7 @@
{:local/root "../common"}
binaryage/devtools {:mvn/version "RELEASE"}
- metosin/reitit-core {:mvn/version "0.5.13"}
+ metosin/reitit-core {:mvn/version "0.5.15"}
funcool/beicon {:mvn/version "2021.07.05-1"}
funcool/okulary {:mvn/version "2020.04.14-0"}
@@ -23,7 +23,8 @@
:dev
{:extra-deps
- {thheller/shadow-cljs {:mvn/version "2.15.2"}}}
+ {thheller/shadow-cljs {:mvn/version "2.15.9"}
+ cider/cider-nrepl {:mvn/version "0.26.0"}}}
:shadow-cljs
{:main-opts ["-m" "shadow.cljs.devtools.cli"]}
diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js
index 03c4f1c27..5a64cd8a3 100644
--- a/frontend/gulpfile.js
+++ b/frontend/gulpfile.js
@@ -33,7 +33,7 @@ paths.dist = "./target/dist/";
// Templates
function readLocales() {
- const langs = ["ca", "de", "el", "en", "es", "fr", "tr", "ru", "zh_CN", "pt_BR", "ro"];
+ const langs = ["ar", "he", "ca", "de", "el", "en", "es", "fr", "tr", "ru", "zh_CN", "pt_BR", "ro"];
const result = {};
for (let lang of langs) {
diff --git a/frontend/package.json b/frontend/package.json
index 13261d45a..74b63207e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,7 +12,15 @@
"defaults"
],
"scripts": {
- "validate-translations": "node ./scripts/validate-translations.js"
+ "validate-translations": "node ./scripts/validate-translations.js",
+ "watch-main": "shadow-cljs watch main",
+ "watch-gulp": "gulp watch",
+ "test-watch-compile": "shadow-cljs watch test",
+ "test-run": "node target/tests.js",
+ "test-watch-run": "nodemon --signal SIGKILL --watch target --exec npm run test-run",
+ "test-watch": "npm-run-all --parallel test-watch-compile test-watch-run",
+ "test": "npm run test-watch",
+ "start": "npm-run-all --parallel watch-gulp watch-main"
},
"devDependencies": {
"autoprefixer": "^10.2.4",
@@ -27,21 +35,25 @@
"gulp-sourcemaps": "^3.0.0",
"gulp-svg-sprite": "^1.5.0",
"map-stream": "0.0.7",
- "marked": "^2.1.1",
+ "marked": "^3.0.4",
"mkdirp": "^1.0.4",
+ "nodemon": "^2.0.13",
+ "npm-run-all": "^4.1.5",
"postcss": "^8.3.5",
"postcss-clean": "^1.2.2",
"rimraf": "^3.0.0",
"sass": "^1.35.1",
- "shadow-cljs": "2.15.2"
+ "shadow-cljs": "2.15.9"
},
"dependencies": {
+ "@sentry/browser": "^6.12.0",
+ "@sentry/tracing": "^6.12.0",
"date-fns": "^2.22.1",
"draft-js": "^0.11.7",
"highlight.js": "^11.0.1",
"js-beautify": "^1.14.0",
"jszip": "^3.6.0",
- "luxon": "^1.26.0",
+ "luxon": "^2.0.2",
"mousetrap": "^1.6.5",
"opentype.js": "^1.3.3",
"randomcolor": "^0.6.2",
diff --git a/frontend/resources/images/features/advanced-proto.gif b/frontend/resources/images/features/advanced-proto.gif
new file mode 100644
index 000000000..e8db03218
Binary files /dev/null and b/frontend/resources/images/features/advanced-proto.gif differ
diff --git a/frontend/resources/images/features/booleans.gif b/frontend/resources/images/features/booleans.gif
new file mode 100644
index 000000000..7b8f488be
Binary files /dev/null and b/frontend/resources/images/features/booleans.gif differ
diff --git a/frontend/resources/images/features/flows-proto.gif b/frontend/resources/images/features/flows-proto.gif
new file mode 100644
index 000000000..eb3dfa6dc
Binary files /dev/null and b/frontend/resources/images/features/flows-proto.gif differ
diff --git a/frontend/resources/images/features/libraries-feature.gif b/frontend/resources/images/features/libraries-feature.gif
new file mode 100644
index 000000000..3e996a6c2
Binary files /dev/null and b/frontend/resources/images/features/libraries-feature.gif differ
diff --git a/frontend/resources/images/icons/boolean-difference.svg b/frontend/resources/images/icons/boolean-difference.svg
new file mode 100644
index 000000000..4d5c7f6a8
--- /dev/null
+++ b/frontend/resources/images/icons/boolean-difference.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/boolean-exclude.svg b/frontend/resources/images/icons/boolean-exclude.svg
new file mode 100644
index 000000000..6a3865703
--- /dev/null
+++ b/frontend/resources/images/icons/boolean-exclude.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/boolean-flatten.svg b/frontend/resources/images/icons/boolean-flatten.svg
new file mode 100644
index 000000000..f7816f8b5
--- /dev/null
+++ b/frontend/resources/images/icons/boolean-flatten.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/boolean-intersection.svg b/frontend/resources/images/icons/boolean-intersection.svg
new file mode 100644
index 000000000..3480e6366
--- /dev/null
+++ b/frontend/resources/images/icons/boolean-intersection.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/boolean-union.svg b/frontend/resources/images/icons/boolean-union.svg
new file mode 100644
index 000000000..fdeb117b7
--- /dev/null
+++ b/frontend/resources/images/icons/boolean-union.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/uxbox-logo-icon-loader.svg b/frontend/resources/images/icons/penpot-logo-icon-loader.svg
similarity index 100%
rename from frontend/resources/images/icons/uxbox-logo-icon-loader.svg
rename to frontend/resources/images/icons/penpot-logo-icon-loader.svg
diff --git a/frontend/resources/images/icons/uxbox-logo-icon.svg b/frontend/resources/images/icons/penpot-logo-icon.svg
similarity index 100%
rename from frontend/resources/images/icons/uxbox-logo-icon.svg
rename to frontend/resources/images/icons/penpot-logo-icon.svg
diff --git a/frontend/resources/images/icons/uxbox-logo.svg b/frontend/resources/images/icons/penpot-logo.svg
similarity index 100%
rename from frontend/resources/images/icons/uxbox-logo.svg
rename to frontend/resources/images/icons/penpot-logo.svg
diff --git a/frontend/resources/images/icons/position-bottom-center.svg b/frontend/resources/images/icons/position-bottom-center.svg
new file mode 100644
index 000000000..002466ead
--- /dev/null
+++ b/frontend/resources/images/icons/position-bottom-center.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/images/icons/position-bottom-left.svg b/frontend/resources/images/icons/position-bottom-left.svg
new file mode 100644
index 000000000..4811b74a9
--- /dev/null
+++ b/frontend/resources/images/icons/position-bottom-left.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/images/icons/position-bottom-right.svg b/frontend/resources/images/icons/position-bottom-right.svg
new file mode 100644
index 000000000..ebf861dcf
--- /dev/null
+++ b/frontend/resources/images/icons/position-bottom-right.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/images/icons/position-center.svg b/frontend/resources/images/icons/position-center.svg
new file mode 100644
index 000000000..ce6695ba7
--- /dev/null
+++ b/frontend/resources/images/icons/position-center.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/images/icons/position-top-center.svg b/frontend/resources/images/icons/position-top-center.svg
new file mode 100644
index 000000000..5a971d427
--- /dev/null
+++ b/frontend/resources/images/icons/position-top-center.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/images/icons/position-top-left.svg b/frontend/resources/images/icons/position-top-left.svg
new file mode 100644
index 000000000..0285e444e
--- /dev/null
+++ b/frontend/resources/images/icons/position-top-left.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/frontend/resources/images/icons/position-top-right.svg b/frontend/resources/images/icons/position-top-right.svg
new file mode 100644
index 000000000..838f63602
--- /dev/null
+++ b/frontend/resources/images/icons/position-top-right.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/styles/common/base.scss b/frontend/resources/styles/common/base.scss
index ddd623792..54032cad6 100644
--- a/frontend/resources/styles/common/base.scss
+++ b/frontend/resources/styles/common/base.scss
@@ -63,7 +63,7 @@ a {
}
p {
- font-size: $fs13;
+ font-size: $fs12;
margin-bottom: 1rem;
line-height: $base-lh-sm;
diff --git a/frontend/resources/styles/common/dependencies/fonts.scss b/frontend/resources/styles/common/dependencies/fonts.scss
index 19e9b2f9c..75eb65c1f 100644
--- a/frontend/resources/styles/common/dependencies/fonts.scss
+++ b/frontend/resources/styles/common/dependencies/fonts.scss
@@ -21,7 +21,7 @@ $fs21: 1.315rem;
$fs22: 1.375rem;
$fs24: 1.5rem;
$fs26: 1.625rem;
-$fs28: 1.75rem;
+$fs24: 1.75rem;
$fs30: 1.875rem;
$fs32: 2rem;
$fs34: 2.125rem;
diff --git a/frontend/resources/styles/common/dependencies/helpers.scss b/frontend/resources/styles/common/dependencies/helpers.scss
index ed0f805c1..3562e9370 100644
--- a/frontend/resources/styles/common/dependencies/helpers.scss
+++ b/frontend/resources/styles/common/dependencies/helpers.scss
@@ -6,16 +6,9 @@
// Copyright (c) 2015-2016 Juan de la Cruz
// Padding & Margin sizes
-$x-small: 4px;
-$small: 8px;
-$medium: 16px;
-$big: 20px;
-$x-big: 32px;
-
-// New sizes
$size-1: 0.25rem;
$size-2: 0.5rem;
-$size-3: 0.8rem;
+$size-3: 0.75rem;
$size-4: 1rem;
$size-5: 1.5rem;
$size-6: 2rem;
@@ -38,7 +31,7 @@ $br-huge: 12px;
.row-flex {
align-items: center;
display: flex;
- margin-bottom: $x-small;
+ margin-bottom: $size-1;
&.column {
flex-direction: column;
@@ -59,7 +52,7 @@ $br-huge: 12px;
}
.column-half {
- margin-right: $small;
+ margin-right: $size-2;
}
// Display
diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss
index 3f542ceef..baa6526b6 100644
--- a/frontend/resources/styles/common/framework.scss
+++ b/frontend/resources/styles/common/framework.scss
@@ -14,7 +14,7 @@
border-radius: 3px;
cursor: pointer;
display: flex;
- font-size: $fs13;
+ font-size: $fs12;
height: 30px;
justify-content: center;
min-width: 25px;
@@ -84,7 +84,7 @@
@extend %btn;
background: $color-gray-60;
color: $color-gray-20;
- padding: $x-small;
+ padding: $size-1;
svg {
fill: $color-gray-20;
}
@@ -102,7 +102,7 @@
background: $color-white;
border: 1px solid $color-gray-20;
color: $color-black;
- padding: $x-small;
+ padding: $size-1;
svg {
fill: $color-black;
}
@@ -119,7 +119,7 @@
@extend %btn;
background: transparent;
color: $color-gray-20;
- padding: $x-small;
+ padding: $size-1;
svg {
fill: $color-gray-20;
}
@@ -280,7 +280,7 @@ ul.slider-dots {
li {
align-items: center;
display: flex;
- padding: $small 0;
+ padding: $size-2 0;
&::before {
background-color: $color-complete;
@@ -317,7 +317,7 @@ ul.slider-dots {
cursor: pointer;
font-size: $fs14;
font-weight: bold;
- margin: 0 $small $small 0;
+ margin: 0 $size-2 $size-2 0;
padding: 4px 8px;
text-transform: uppercase;
@@ -392,7 +392,7 @@ ul.slider-dots {
font-size: $fs12;
height: 20px;
position: absolute;
- right: $small;
+ right: $size-2;
text-align: right;
top: 26%;
width: 18px;
@@ -480,7 +480,7 @@ select {
color: $color-gray-60;
font-family: "worksans", sans-serif;
font-size: $fs14;
- margin-bottom: $medium;
+ margin-bottom: $size-4;
-webkit-appearance: none;
-moz-appearance: none;
}
@@ -515,7 +515,7 @@ input[type="checkbox"]:focus {
background-color: $color-white;
box-shadow: none;
outline: none;
- padding: $small $big $small $small;
+ padding: $size-2 $size-5 $size-2 $size-2;
position: relative;
@include placeholder {
@@ -555,7 +555,7 @@ input.element-name {
border: 1px solid $color-gray-40;
border-radius: $br-small;
color: $color-gray-60;
- font-size: $fs13;
+ font-size: $fs12;
margin: 0px;
padding: 3px;
width: 100%;
@@ -572,7 +572,7 @@ input.element-name {
cursor: pointer;
&.small {
- padding: $x-small $big $x-small $x-small;
+ padding: $size-1 $size-5 $size-1 $size-1;
}
}
@@ -713,7 +713,7 @@ input[type=radio]:checked + label:before{
position: absolute;
left: 3.2px;
top: 0;
- font-size: $fs11;
+ font-size: $fs12;
transition: border 0.2s linear 0s, color 0.2s linear 0s;
}
@@ -969,9 +969,9 @@ input[type=range]:focus::-ms-fill-upper {
border-radius: $br-small;
color: $color-gray-60;
content: attr(alt);
- font-size: $fs11;
+ font-size: $fs12;
font-weight: bold;
- padding: $x-small;
+ padding: $size-1;
position: absolute;
left: 130%;
text-align: center;
@@ -1098,7 +1098,7 @@ input[type=range]:focus::-ms-fill-upper {
flex-direction: column;
.actions {
- margin-top: $medium;
+ margin-top: $size-4;
display: flex;
justify-content: flex-start;
}
@@ -1114,7 +1114,7 @@ input[type=range]:focus::-ms-fill-upper {
justify-content: flex-start;
.btn-secondary {
- margin-left: $medium;
+ margin-left: $size-4;
}
}
}
@@ -1167,7 +1167,7 @@ input[type=range]:focus::-ms-fill-upper {
padding-right: 64px;
.icon {
- margin-right: $medium;
+ margin-right: $size-4;
}
.content {
@@ -1175,7 +1175,7 @@ input[type=range]:focus::-ms-fill-upper {
display: flex;
align-items: center;
justify-content: center;
- font-size: $fs15;
+ font-size: $fs14;
}
}
}
@@ -1187,7 +1187,7 @@ input[type=range]:focus::-ms-fill-upper {
display: flex;
.icon {
- padding: $small;
+ padding: $size-2;
width: 48px;
height: 48px;
justify-content: center;
@@ -1198,7 +1198,7 @@ input[type=range]:focus::-ms-fill-upper {
color: $color-black;
display: flex;
font-size: $fs14;
- padding: $small;
+ padding: $size-2;
width: 100%;
align-items: center;
}
diff --git a/frontend/resources/styles/main/layouts/login.scss b/frontend/resources/styles/main/layouts/login.scss
index 49aee2b0f..627753cf0 100644
--- a/frontend/resources/styles/main/layouts/login.scss
+++ b/frontend/resources/styles/main/layouts/login.scss
@@ -64,7 +64,7 @@
width: 100%;
*:not(:last-child) {
- margin-bottom: $medium;
+ margin-bottom: $size-4;
}
}
@@ -72,7 +72,7 @@
width: 412px;
.auth-buttons {
- margin-top: $x-big;
+ margin-top: $size-6;
}
form {
@@ -81,12 +81,12 @@
}
.btn-google-auth {
- margin-bottom: $medium;
+ margin-bottom: $size-4;
text-decoration: none;
}
.btn-gitlab-auth {
- margin-bottom: $medium;
+ margin-bottom: $size-4;
text-decoration: none;
.logo {
@@ -97,7 +97,7 @@
}
.btn-github-auth {
- margin-bottom: $medium;
+ margin-bottom: $size-4;
text-decoration: none;
.logo {
@@ -119,13 +119,13 @@
font-size: $fs14;
flex-direction: column;
justify-content: space-between;
- margin-top: $medium;
- margin-bottom: $medium;
+ margin-top: $size-4;
+ margin-bottom: $size-4;
&.demo {
justify-content: center;
- margin-top: $big;
+ margin-top: $size-5;
}
.link-entry {
@@ -141,11 +141,11 @@
}
.terms-login {
- bottom: $big;
+ bottom: $size-5;
font-size: $fs14;
position: absolute;
span {
- margin: 0 $small;
+ margin: 0 $size-2;
}
}
diff --git a/frontend/resources/styles/main/layouts/viewer.scss b/frontend/resources/styles/main/layouts/viewer.scss
index 7e05a861b..296322332 100644
--- a/frontend/resources/styles/main/layouts/viewer.scss
+++ b/frontend/resources/styles/main/layouts/viewer.scss
@@ -43,3 +43,19 @@
grid-row: 1 / span 2;
}
}
+
+.viewer-overlay {
+ position: absolute;
+}
+
+.viewer-overlay-background {
+ position: absolute;
+ top: 0;
+ left: 0;
+
+ &.visible {
+ background-color: rgb(0, 0, 0, 0.2);
+ }
+}
+
+
diff --git a/frontend/resources/styles/main/partials/activity-bar.scss b/frontend/resources/styles/main/partials/activity-bar.scss
index b1fb0dc94..7e116fae1 100644
--- a/frontend/resources/styles/main/partials/activity-bar.scss
+++ b/frontend/resources/styles/main/partials/activity-bar.scss
@@ -24,7 +24,7 @@
color: $color-gray-40;
font-size: $fs16;
font-weight: bold;
- margin-bottom: $x-small;
+ margin-bottom: $size-1;
}
.date-ribbon {
@@ -40,15 +40,15 @@
.activity-input {
border-bottom: 1px solid $color-gray-10;
display: flex;
- font-size: $fs13;
- padding: $small;
+ font-size: $fs12;
+ padding: $size-2;
width: 100%;
img.activity-author {
border-radius: 50%;
flex-shrink: 0;
height: 30px;
- margin-right: $medium;
+ margin-right: $size-4;
width: 30px;
}
@@ -70,7 +70,7 @@
.activity-time {
color: $color-gray-20;
- font-size: $fs11;
+ font-size: $fs12;
font-style: italic;
}
}
diff --git a/frontend/resources/styles/main/partials/color-bullet.scss b/frontend/resources/styles/main/partials/color-bullet.scss
index eb4882a51..952261776 100644
--- a/frontend/resources/styles/main/partials/color-bullet.scss
+++ b/frontend/resources/styles/main/partials/color-bullet.scss
@@ -78,7 +78,7 @@ ul.palette-menu .color-bullet {
border: 1px solid $color-gray-20;
border-radius: 10px;
height: 20px;
- margin-right: $x-small;
+ margin-right: $size-1;
width: 20px;
}
diff --git a/frontend/resources/styles/main/partials/colorpicker.scss b/frontend/resources/styles/main/partials/colorpicker.scss
index e0b0ed178..f5e8afb3f 100644
--- a/frontend/resources/styles/main/partials/colorpicker.scss
+++ b/frontend/resources/styles/main/partials/colorpicker.scss
@@ -13,7 +13,7 @@
.colorpicker-content {
display: flex;
flex-direction: column;
- padding: $small;
+ padding: $size-2;
& > * {
width: 200px;
@@ -21,7 +21,7 @@
.top-actions {
display: flex;
- margin-bottom: $x-small;
+ margin-bottom: $size-1;
justify-content: space-between;
.picker-btn {
@@ -50,7 +50,7 @@
margin: 0;
border: 1px solid $color-gray-20;
border-radius: 2px;
- margin-left: $x-small;
+ margin-left: $size-1;
}
.active {
@@ -69,8 +69,8 @@
.gradient-stops {
height: 10px;
display: flex;
- margin-top: $small;
- margin-bottom: $medium;
+ margin-top: $size-2;
+ margin-bottom: $size-4;
.gradient-background-wrapper {
height: 100%;
@@ -280,14 +280,14 @@
margin: 0;
border: 1px solid $color-gray-10;
border-radius: 2px;
- font-size: $fs11;
+ font-size: $fs12;
height: 1.5rem;
- padding: 0 $x-small;
+ padding: 0 $size-1;
color: $color-gray-40;
}
label {
- font-size: $fs11;
+ font-size: $fs12;
}
}
@@ -303,9 +303,9 @@
background-position: 95% 48%;
background-size: 10px;
margin: 0;
- margin-bottom: $small;
+ margin-bottom: $size-2;
width: 100%;
- padding: $x-small 0.25rem;
+ padding: $size-1 0.25rem;
font-size: 0.75rem;
color: $color-gray-40;
cursor: pointer;
@@ -434,7 +434,7 @@
.input-text {
color: $color-gray-60;
- font-size: $fs13;
+ font-size: $fs12;
margin: 5px;
padding: 5px;
width: 100%;
@@ -444,7 +444,7 @@
.colorpicker-tabs {
display: flex;
- margin-bottom: $small;
+ margin-bottom: $size-2;
border-radius: 5px;
border: 1px solid $color-gray-10;
height: 2rem;
@@ -480,11 +480,11 @@
.color-data {
align-items: center;
display: flex;
- margin-bottom: $small;
+ margin-bottom: $size-2;
position: relative;
.color-name {
- font-size: $fs13;
+ font-size: $fs12;
margin: 5px 6px 0px 6px;
}
@@ -498,9 +498,9 @@
color: $color-white;
height: 20px;
margin: 5px 0 0 0;
- padding: 0 $x-small;
+ padding: 0 $size-1;
width: 84px;
- font-size: $fs13;
+ font-size: $fs12;
&:focus {
border-color: $color-primary !important;
@@ -523,7 +523,7 @@
.type {
color: $color-gray-10;
- margin-right: $x-small;
+ margin-right: $size-1;
}
.number {
diff --git a/frontend/resources/styles/main/partials/comments.scss b/frontend/resources/styles/main/partials/comments.scss
index cc6248efa..87c0d1eb2 100644
--- a/frontend/resources/styles/main/partials/comments.scss
+++ b/frontend/resources/styles/main/partials/comments.scss
@@ -12,7 +12,7 @@
box-sizing: border-box;
box-shadow: 0px 4px 4px rgba($color-black, 0.25);
- font-size: $fs13;
+ font-size: $fs12;
width: 30px;
height: 30px;
border-radius: 50%;
@@ -69,11 +69,11 @@
textarea {
font-family: "worksans", sans-serif;
- font-size: $fs13;
+ font-size: $fs12;
min-height: 32px;
outline: none;
overflow: hidden;
- padding: $small;
+ padding: $size-2;
resize: none;
width: 100%;
border-radius: 2px;
@@ -104,7 +104,7 @@
.comment {
display: flex;
flex-direction: column;
- padding: $medium $small;
+ padding: $size-4 $size-2;
.author {
display: flex;
@@ -120,7 +120,7 @@
.fullname {
font-weight: 700;
color: $color-gray-60;
- font-size: $fs13;
+ font-size: $fs12;
@include text-ellipsis;
width: 174px;
@@ -183,11 +183,11 @@
}
.content {
- margin: $medium 0;
+ margin: $size-4 0;
font-size: $fs14;
color: $color-black;
.text {
- margin: 0 $small 0 26px;
+ margin: 0 $size-2 0 26px;
white-space: pre-wrap;
display: inline-block;
}
@@ -295,7 +295,7 @@
.comment {
cursor: pointer;
.author {
- margin-bottom: $medium;
+ margin-bottom: $size-4;
.name {
display: flex;
@@ -317,7 +317,7 @@
color: $color-white;
&.replies {
- margin: 0 $small 0 26px;
+ margin: 0 $size-2 0 26px;
display: flex;
.total-replies {
margin-right: 9px;
@@ -455,13 +455,13 @@
display: flex;
flex-direction: column;
font-size: $fs12;
- padding: $big;
+ padding: $size-5;
text-align: center;
svg {
fill: $color-gray-20;
height: 24px;
- margin-bottom: $big;
+ margin-bottom: $size-5;
width: 24px;
}
}
diff --git a/frontend/resources/styles/main/partials/context-menu.scss b/frontend/resources/styles/main/partials/context-menu.scss
index d052cd349..ce19531bc 100644
--- a/frontend/resources/styles/main/partials/context-menu.scss
+++ b/frontend/resources/styles/main/partials/context-menu.scss
@@ -82,7 +82,7 @@
height: 10px;
width: 10px;
transform: rotate(180deg);
- margin-right: $small;
+ margin-right: $size-2;
}
}
}
diff --git a/frontend/resources/styles/main/partials/dashboard-fonts.scss b/frontend/resources/styles/main/partials/dashboard-fonts.scss
index 2370def96..e1ea817eb 100644
--- a/frontend/resources/styles/main/partials/dashboard-fonts.scss
+++ b/frontend/resources/styles/main/partials/dashboard-fonts.scss
@@ -7,13 +7,13 @@
max-width: 1000px;
width: 100%;
display: flex;
- margin-top: $big;
+ margin-top: $size-5;
flex-direction: column;
h3 {
font-size: $fs14;
color: $color-gray-30;
- margin: $x-small;
+ margin: $size-1;
}
.font-item {
@@ -28,7 +28,7 @@
font-size: $fs12;
background-color: $color-white;
align-items: center;
- padding: 0px $big;
+ padding: 0px $size-5;
> .family {
min-width: 200px;
@@ -49,14 +49,14 @@
border: 1px solid $color-gray-30;
border-radius: $br-small;
width: 130px;
- padding: $x-small;
+ padding: $size-1;
margin: 0px;
}
}
}
.font-item {
- margin-top: $big;
+ margin-top: $size-5;
color: $color-gray-40;
font-size: $fs14;
background-color: $color-white;
@@ -65,7 +65,7 @@
width: 100%;
min-height: 97px;
align-items: center;
- padding: $big;
+ padding: $size-5;
justify-content: space-between;
&:not(:first-child) {
@@ -76,7 +76,7 @@
border: 1px solid $color-gray-30;
border-radius: $br-small;
margin: 0px;
- padding: $small;
+ padding: $size-2;
font-size: $fs12;
}
@@ -135,7 +135,7 @@
justify-content: flex-end;
.icon {
- width: $big;
+ width: $size-5;
cursor: pointer;
display: flex;
margin-left: 10px;
@@ -169,9 +169,9 @@
.dashboard-fonts-hero {
font-size: $fs14;
- padding: $x-big;
+ padding: $size-6;
background-color: $color-white;
- margin-top: $x-big;
+ margin-top: $size-6;
display: flex;
justify-content: space-between;
@@ -193,7 +193,7 @@
.desc {
h2 {
- margin-bottom: $medium;
+ margin-bottom: $size-4;
color: $color-black;
}
width: 80%;
diff --git a/frontend/resources/styles/main/partials/dashboard-grid.scss b/frontend/resources/styles/main/partials/dashboard-grid.scss
index 67aaa4ca5..032bab70c 100644
--- a/frontend/resources/styles/main/partials/dashboard-grid.scss
+++ b/frontend/resources/styles/main/partials/dashboard-grid.scss
@@ -28,7 +28,7 @@
flex-direction: column;
flex-shrink: 0;
height: 200px;
- margin: $medium;
+ margin: $size-4;
max-width: 260px;
min-width: 260px;
position: relative;
@@ -85,7 +85,7 @@
&.small-item {
max-width: 12%;
min-width: 190px;
- padding: $medium;
+ padding: $size-4;
justify-content: center;
}
@@ -97,19 +97,19 @@
.item-info {
display: flex;
flex-direction: column;
- padding: $small;
+ padding: $size-2;
text-align: left;
width: 100%;
h3 {
border: 1px solid transparent;
color: $color-gray-60;
- font-size: $fs15;
+ font-size: $fs14;
font-weight: 400;
overflow: hidden;
padding: 0;
height: 27px;
- padding-right: $small;
+ padding-right: $size-2;
text-overflow: ellipsis;
width: 100%;
white-space: nowrap;
@@ -129,7 +129,7 @@
padding: 0px;
height: 25px;
color: $color-gray-60;
- font-size: $fs15;
+ font-size: $fs14;
font-weight: 400;
}
}
@@ -141,8 +141,8 @@
border: 1px solid $color-gray-20;
border-radius: 4px;
position: absolute;
- top: $x-small;
- right: $x-small;
+ top: $size-1;
+ right: $size-1;
height: 32px;
width: 32px;
display: flex;
@@ -163,7 +163,7 @@
span {
color: $color-gray-60;
- font-size: $fs15;
+ font-size: $fs14;
}
&:hover {
@@ -205,7 +205,7 @@
.project-th-icon {
align-items: center;
display: flex;
- margin-right: $small;
+ margin-right: $size-2;
&.menu {
margin-right: 0;
@@ -296,7 +296,7 @@
// STYLES FOR LIBRARIES
&.library {
- padding: $medium;
+ padding: $size-4;
}
.grid-item-th {
diff --git a/frontend/resources/styles/main/partials/dashboard-header.scss b/frontend/resources/styles/main/partials/dashboard-header.scss
index 3c5c7ab40..b9e7a44f7 100644
--- a/frontend/resources/styles/main/partials/dashboard-header.scss
+++ b/frontend/resources/styles/main/partials/dashboard-header.scss
@@ -9,13 +9,13 @@
background-color: $color-white;
display: flex;
height: 63px;
- padding: $x-small $medium $x-small $small;
+ padding: $size-1 $size-4 $size-1 $size-2;
position: relative;
z-index: 10;
justify-content: space-between;
.element-name {
- margin-right: $small;
+ margin-right: $size-2;
}
.btn-secondary {
@@ -27,7 +27,7 @@
svg {
fill: $color-black;
height: 14px;
- margin-right: $x-small;
+ margin-right: $size-1;
width: 14px;
}
@@ -41,7 +41,7 @@
ul {
display: flex;
align-items: center;
- font-size: $fs15;
+ font-size: $fs14;
justify-content: center;
}
@@ -52,7 +52,7 @@
color: $color-gray-30;
display: flex;
height: 40px;
- padding: $x-small $big;
+ padding: $size-1 $size-5;
flex-basis: 140px;
&:hover {
@@ -90,7 +90,7 @@
display: flex;
align-items: center;
cursor: pointer;
- margin-left: $small;
+ margin-left: $size-2;
z-index: 10;
svg {
@@ -109,7 +109,7 @@
}
.pin-icon {
- margin: 0 $small 0 $big;
+ margin: 0 $size-2 0 $size-5;
svg {
fill: $color-gray-20;
}
diff --git a/frontend/resources/styles/main/partials/dashboard-settings.scss b/frontend/resources/styles/main/partials/dashboard-settings.scss
index 7a81add24..8515c28cc 100644
--- a/frontend/resources/styles/main/partials/dashboard-settings.scss
+++ b/frontend/resources/styles/main/partials/dashboard-settings.scss
@@ -64,7 +64,7 @@
border-radius: 50%;
flex-shrink: 0;
height: 120px;
- margin-right: $medium;
+ margin-right: $size-4;
width: 120px;
}
diff --git a/frontend/resources/styles/main/partials/dashboard-sidebar.scss b/frontend/resources/styles/main/partials/dashboard-sidebar.scss
index 2e9055759..3a68e1ac4 100644
--- a/frontend/resources/styles/main/partials/dashboard-sidebar.scss
+++ b/frontend/resources/styles/main/partials/dashboard-sidebar.scss
@@ -141,7 +141,7 @@
}
}
.text {
- font-size: $fs13;
+ font-size: $fs12;
}
}
@@ -188,7 +188,7 @@
border-radius: $br-small;
content: "";
height: 26px;
- margin-right: $small;
+ margin-right: $size-2;
width: 4px;
}
@@ -284,7 +284,7 @@
display: flex;
height: 22px;
margin-left: auto;
- padding: 0 $small;
+ padding: 0 $size-2;
width: 32px;
svg {
@@ -390,7 +390,7 @@
svg {
height: 10px;
margin-left: auto;
- margin-right: $small;
+ margin-right: $size-2;
width: 10px;
}
}
@@ -409,7 +409,7 @@
svg {
fill: $color-gray-20;
- margin-right: $small;
+ margin-right: $size-2;
height: 12px;
width: 12px;
diff --git a/frontend/resources/styles/main/partials/dashboard-team.scss b/frontend/resources/styles/main/partials/dashboard-team.scss
index 6688ffebe..6efc6f178 100644
--- a/frontend/resources/styles/main/partials/dashboard-team.scss
+++ b/frontend/resources/styles/main/partials/dashboard-team.scss
@@ -139,7 +139,7 @@
.name {
margin-top: 10px;
- font-size: $fs28;
+ font-size: $fs24;
color: $color-black;
@include text-ellipsis;
margin-right: 90px;
diff --git a/frontend/resources/styles/main/partials/dashboard.scss b/frontend/resources/styles/main/partials/dashboard.scss
index 6c90ee4f5..3a8c0deb5 100644
--- a/frontend/resources/styles/main/partials/dashboard.scss
+++ b/frontend/resources/styles/main/partials/dashboard.scss
@@ -9,7 +9,7 @@
border-top-right-radius: $br-huge;
border-top-left-radius: $br-huge;
flex: 1 0 0;
- margin-right: $medium;
+ margin-right: $size-4;
overflow-y: auto;
user-select: none;
@@ -19,7 +19,7 @@
}
.dashboard-project-row {
- margin-bottom: $medium;
+ margin-bottom: $size-4;
.project {
align-items: center;
@@ -27,9 +27,9 @@
border-radius: $br-small;
display: flex;
flex-direction: row;
- margin-left: $medium;
- margin-top: $medium;
- padding: $x-small $x-small $x-small $small;
+ margin-left: $size-4;
+ margin-top: $size-4;
+ padding: $size-1 $size-1 $size-1 $size-2;
width: fit-content;
height: 40px;
@@ -40,19 +40,19 @@
h2 {
cursor: pointer;
- font-size: 15px;
+ font-size: $fs14;
line-height: 1rem;
font-weight: 500;
color: $color-black;
- margin-right: $medium;
+ margin-right: $size-4;
}
.edit-wrapper {
- margin-right: $medium;
+ margin-right: $size-4;
}
.info {
- font-size: 15px;
+ font-size: $fs14;
line-height: 1rem;
font-weight: unset;
color: $color-gray-30;
@@ -78,7 +78,7 @@
}
.recent-files-row-title-info {
- font-size: $fs15;
+ font-size: $fs14;
}
.dashboard-table {
@@ -138,7 +138,7 @@
border: 1px solid $color-gray-10;
border-radius: $br-small;
display: flex;
- padding-right: $big;
+ padding-right: $size-5;
position: relative;
input.element-title {
diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss
index fb6d3c686..27da8245d 100644
--- a/frontend/resources/styles/main/partials/modal.scss
+++ b/frontend/resources/styles/main/partials/modal.scss
@@ -48,7 +48,7 @@
justify-content: space-between;
> button {
- font-size: $fs13;
+ font-size: $fs12;
}
> button:not(:first-child) {
margin-left: 25px;
@@ -562,7 +562,7 @@
.header-item {
cursor: pointer;
color: $color-gray-40;
- font-size: $fs15;
+ font-size: $fs14;
&.active {
color: $color-gray-60;
@@ -597,7 +597,7 @@
.section-title {
color: $color-black;
- font-size: $fs15;
+ font-size: $fs14;
padding: 0 $size-4;
font-weight: 500;
}
@@ -664,7 +664,7 @@
.libraries-search {
border: 1px solid $color-gray-20;
margin: $size-4;
- padding: $x-small $small;
+ padding: $size-1 $size-2;
display: flex;
align-items: center;
@@ -720,7 +720,7 @@
flex-shrink: 0;
justify-content: center;
overflow: hidden;
- padding: $x-big;
+ padding: $size-6;
width: 230px;
&.welcome {
@@ -733,11 +733,11 @@
border-bottom-right-radius: 5px;
display: flex;
flex-direction: column;
- padding: $x-big;
+ padding: $size-6;
.modal-title h2 {
color: $color-black;
- font-size: $fs28;
+ font-size: $fs24;
font-weight: 900;
}
@@ -746,19 +746,19 @@
color: $color-black;
font-size: $fs12;
font-weight: bold;
- margin-top: $small;
- padding: 2px $x-small;
+ margin-top: $size-2;
+ padding: 2px $size-1;
width: max-content;
}
.modal-content {
border: none;
- padding: $big 0;
+ padding: $size-5 0;
p {
color: $color-black;
font-size: 16px;
- margin-top: $small;
+ margin-top: $size-2;
}
}
@@ -770,8 +770,8 @@
.skip {
cursor: pointer;
font-family: "worksans", sans-serif;
- font-size: $fs13;
- margin-left: $big;
+ font-size: $fs12;
+ margin-left: $size-5;
&:hover {
color: $color-black;
@@ -789,9 +789,9 @@
background-color: $color-gray-10;
border-radius: 50%;
cursor: pointer;
- height: $small;
- margin-left: $small;
- width: $small;
+ height: $size-2;
+ margin-left: $size-2;
+ width: $size-2;
&.current {
background-color: $color-primary;
@@ -824,7 +824,7 @@
}
&.final {
- padding: $big 0 0 0;
+ padding: $size-5 0 0 0;
.modal-left,
.modal-right {
@@ -834,12 +834,12 @@
flex: 1;
flex-direction: column;
overflow: visible;
- padding: $x-big 40px;
+ padding: $size-6 40px;
text-align: center;
h2 {
font-weight: 900;
- margin-bottom: $big;
+ margin-bottom: $size-5;
font-size: $fs24;
}
@@ -856,7 +856,7 @@
img {
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.25);
border-radius: $br-medium;
- margin-bottom: $x-big;
+ margin-bottom: $size-6;
margin-top: -90px;
width: 150px;
}
@@ -872,7 +872,7 @@
margin-top: auto;
.custom-input {
- margin-bottom: $medium;
+ margin-bottom: $size-4;
input {
width: 200px;
diff --git a/frontend/resources/styles/main/partials/project-bar.scss b/frontend/resources/styles/main/partials/project-bar.scss
index 709d2b2ea..908dc03f9 100644
--- a/frontend/resources/styles/main/partials/project-bar.scss
+++ b/frontend/resources/styles/main/partials/project-bar.scss
@@ -30,15 +30,15 @@
border-bottom: 1px solid $color-gray-10;
font-size: $fs14;
font-weight: bold;
- padding: 0 $small;
+ padding: 0 $size-2;
width: 100%;
}
.btn-primary,
.btn-warning {
- font-size: $fs13;
+ font-size: $fs12;
margin-bottom: .5rem;
- padding: 8px $small;
+ padding: 8px $size-2;
width: 90%;
}
@@ -53,18 +53,18 @@
align-items: center;
cursor: pointer;
display: flex;
- padding: $x-small $small;
+ padding: $size-1 $size-2;
position: relative;
svg {
fill: $color-gray-20;
height: 12px;
- margin-right: $x-small;
+ margin-right: $size-1;
width: 12px;
}
span {
- font-size: $fs13;
+ font-size: $fs12;
}
&:hover,
@@ -86,7 +86,7 @@
svg {
fill: $color-gray-20;
height: 12px;
- margin-right: $small;
+ margin-right: $size-2;
width: 12px;
&:hover {
diff --git a/frontend/resources/styles/main/partials/share-link.scss b/frontend/resources/styles/main/partials/share-link.scss
index af0dd9a11..bd59474e9 100644
--- a/frontend/resources/styles/main/partials/share-link.scss
+++ b/frontend/resources/styles/main/partials/share-link.scss
@@ -64,7 +64,7 @@
.share-link-section {
margin-top: 12px;
label {
- font-size: $fs11;
+ font-size: $fs12;
color: $color-black;
}
diff --git a/frontend/resources/styles/main/partials/sidebar-align-options.scss b/frontend/resources/styles/main/partials/sidebar-align-options.scss
index 7b9750426..18ced6518 100644
--- a/frontend/resources/styles/main/partials/sidebar-align-options.scss
+++ b/frontend/resources/styles/main/partials/sidebar-align-options.scss
@@ -9,12 +9,12 @@
display: flex;
border-bottom: solid 1px $color-gray-60;
height: 40px;
- padding: 0 $x-small;
.align-group {
+ padding: 0 $size-1;
display: flex;
- justify-content: space-evenly;
- width: 100%;
+ justify-content: flex-start;
+ width: 50%;
&:not(:last-child) {
border-right: solid 1px $color-gray-60;
@@ -25,7 +25,12 @@
align-items: center;
cursor: pointer;
display: flex;
- padding: $small $x-small;
+ height: 30px;
+ justify-content: center;
+ margin: 5px 0;
+ padding: $size-2 $size-1;
+ width: 25%;
+
svg {
height: 16px;
width: 16px;
@@ -46,5 +51,13 @@
fill: $color-gray-40;
}
}
+
+ &.selected svg {
+ fill: $color-primary;
+ }
+
+ &.selected:hover svg {
+ fill: $color-white;
+ }
}
}
diff --git a/frontend/resources/styles/main/partials/sidebar-assets.scss b/frontend/resources/styles/main/partials/sidebar-assets.scss
index 122f5e8aa..7fdacb37b 100644
--- a/frontend/resources/styles/main/partials/sidebar-assets.scss
+++ b/frontend/resources/styles/main/partials/sidebar-assets.scss
@@ -14,7 +14,7 @@
.assets-bar-title {
color: $color-gray-10;
font-size: $fs14;
- margin: $small $small 0 $small;
+ margin: $size-2 $size-2 0 $size-2;
display: flex;
align-items: center;
cursor: pointer;
@@ -43,8 +43,8 @@
.search-block {
border: 1px solid $color-gray-30;
- margin: $small $small 0 $small;
- padding: $x-small $small;
+ margin: $size-2 $size-2 0 $size-2;
+ padding: $size-1 $size-2;
display: flex;
align-items: center;
@@ -92,8 +92,8 @@
color: $color-gray-10;
border: 1px solid transparent;
border-bottom-color: $color-gray-40;
- padding: $x-small;
- margin: $small $small $medium $small;
+ padding: $size-1;
+ margin: $size-2 $size-2 $size-4 $size-2;
&:focus {
color: lighten($color-gray-10, 8%);
@@ -107,7 +107,7 @@
}
.collapse-library {
- margin-right: $small;
+ margin-right: $size-2;
&.open svg {
transform: rotate(90deg);
@@ -122,16 +122,16 @@
background-color: $color-gray-60;
display: flex;
align-items: center;
- padding: $medium $small 0 $small;
+ padding: $size-4 $size-2 0 $size-2;
.selected-count {
color: $color-primary;
- font-size: $fs11;
+ font-size: $fs12;
}
.listing-option-btn {
cursor: pointer;
- margin-left: $small;
+ margin-left: $size-2;
&.first {
margin-left: auto;
@@ -147,7 +147,7 @@
.asset-section {
background-color: $color-gray-60;
- padding: $small;
+ padding: $size-2;
font-size: $fs12;
color: $color-gray-20;
/* TODO: see if this is useful, or is better to leave only
@@ -164,7 +164,7 @@
.asset-title {
display: flex;
cursor: pointer;
- font-size: $fs11;
+ font-size: $fs12;
text-transform: uppercase;
& .num-assets {
@@ -188,8 +188,8 @@
.group-title {
display: flex;
cursor: pointer;
- margin-top: $small;
- margin-bottom: $x-small;
+ margin-top: $size-2;
+ margin-bottom: $size-1;
color: $color-white;
& svg {
@@ -226,7 +226,7 @@
}
.asset-title + .asset-grid {
- margin-top: $small;
+ margin-top: $size-2;
}
.asset-grid {
@@ -241,7 +241,7 @@
grid-auto-rows: 10vh;
.grid-cell {
- padding: $x-small;
+ padding: $size-1;
& svg {
height: 10vh;
@@ -258,7 +258,7 @@
display: flex;
align-items: center;
justify-content: center;
- padding: $small;
+ padding: $size-2;
position: relative;
cursor: pointer;
@@ -291,7 +291,7 @@
.editable-label-input {
border: 1px solid $color-gray-20;
border-radius: 3px;
- font-size: $fs11;
+ font-size: $fs12;
padding: 2px;
margin: 0;
height: unset;
@@ -316,14 +316,14 @@
}
.asset-title + .asset-enum {
- margin-top: $small;
+ margin-top: $size-2;
}
.asset-enum {
.enum-item {
display: flex;
align-items: center;
- margin-bottom: $small;
+ margin-bottom: $size-2;
cursor: pointer;
& > svg,
@@ -333,11 +333,11 @@
border: 2px solid transparent;
height: 24px;
width: 24px;
- margin-right: $small;
+ margin-right: $size-2;
}
.item-name {
- width: calc(100% - 24px - #{$small});
+ width: calc(100% - 24px - #{$size-2});
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -378,14 +378,14 @@
align-items: center;
border: 1px solid transparent;
border-radius: $br-small;
- margin-top: $x-small;
+ margin-top: $size-1;
padding: 2px;
font-size: $fs12;
color: $color-white;
cursor: pointer;
& span {
- margin-left: $x-small;
+ margin-left: $size-1;
color: $color-gray-30;
text-transform: uppercase;
}
diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss
index 0380d9303..b3f555d76 100644
--- a/frontend/resources/styles/main/partials/sidebar-element-options.scss
+++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss
@@ -14,7 +14,7 @@
border: 1px solid $color-gray-60;
border-radius: $br-small;
display: flex;
- margin: $x-small;
+ margin: $size-1;
li {
align-items: center;
@@ -24,7 +24,7 @@
display: flex;
flex: 1;
justify-content: center;
- padding: $small;
+ padding: $size-2;
svg {
fill: $color-gray-20;
@@ -60,13 +60,13 @@
.element-set {
border-bottom: 1px solid $color-gray-60;
color: $color-gray-20;
- padding: $small $x-small;
+ padding: $size-2 $size-1;
.element-set-title {
color: $color-gray-20;
display: flex;
- font-size: $fs13;
- padding: $x-small;
+ font-size: $fs14;
+ padding: $size-1;
width: 100%;
align-items: center;
}
@@ -79,14 +79,14 @@
}
.element-list {
- margin-bottom: $small;
+ margin-bottom: $size-2;
li {
align-items: center;
border-bottom: 1px solid $color-gray-60;
display: flex;
flex-direction: row;
- padding: $small;
+ padding: $size-2;
width: 100%;
.list-icon {
@@ -94,7 +94,7 @@
svg {
fill: $color-gray-30;
height: 15px;
- margin-right: $x-small;
+ margin-right: $size-1;
width: 15px;
}
@@ -120,7 +120,7 @@
svg {
fill: $color-gray-60;
height: 15px;
- margin-left: $x-small;
+ margin-left: $size-1;
width: 15px;
&:hover {
@@ -184,7 +184,8 @@
.element-set-content {
display: flex;
flex-direction: column;
- padding: $x-small;
+ padding: $size-1;
+ width: 100%;
.input-text {
background-color: $color-gray-50;
@@ -192,9 +193,9 @@
border-bottom-color: $color-gray-40;
color: $color-white;
font-size: $fs12;
- margin: $x-small;
+ margin: $size-1;
min-width: 0;
- padding: $x-small;
+ padding: $size-1;
width: 100%;
&:focus {
@@ -219,13 +220,46 @@
color: $color-gray-60;
background: $color-white;
font-size: $fs12;
+
+ &:disabled {
+ color: $color-gray-20;
+ }
+ }
+ }
+
+ .input-checkbox {
+ label {
+ color: $color-gray-20;
+ }
+
+ label::before {
+ background-color: transparent;
+ width: 16px;
+ height: 16px;
+ }
+
+ label::after {
+ width: 16px;
+ height: 16px;
+ }
+
+ input:checked + label::before {
+ border-width: 1px;
+ }
+
+ input:checked + label::after {
+ font-size: 0.8rem;
}
}
.element-set-subtitle {
color: $color-gray-20;
- font-size: $fs11;
+ font-size: $fs12;
width: 64px;
+
+ &.wide {
+ min-width: 75px;
+ }
}
.lock-size {
@@ -271,12 +305,12 @@
border: 1px solid $color-gray-40;
border-radius: $br-small;
cursor: pointer;
- padding: $x-small $big $x-small $x-small;
+ padding: $size-1 $size-5 $size-1 $size-1;
position: relative;
.dropdown-button {
position: absolute;
- right: $x-small;
+ right: $size-1;
top: 7px;
svg {
@@ -322,7 +356,7 @@
cursor: pointer;
font-size: $fs14;
display: flex;
- padding: $small;
+ padding: $size-2;
span {
color: $color-gray-20;
@@ -375,7 +409,7 @@
border: 1px solid transparent;
position: relative;
height: 38px;
- margin-right: $small;
+ margin-right: $size-2;
max-height: 30px;
position: relative;
width: 60%;
@@ -412,6 +446,10 @@
color: $color-gray-60;
background: $color-white;
font-size: $fs12;
+
+ &:disabled {
+ color: $color-gray-20;
+ }
}
}
@@ -490,7 +528,7 @@
fill: $color-gray-20;
height: 16px;
width: 16px;
- margin-right: $small;
+ margin-right: $size-2;
}
.row-actions {
@@ -500,7 +538,7 @@
svg {
fill: $color-gray-20;
height: 12px;
- margin-right: $x-small;
+ margin-right: $size-1;
width: 12px;
}
@@ -524,7 +562,7 @@
font-size: $fs12;
span {
- font-size: $fs11;
+ font-size: $fs12;
}
}
}
@@ -539,8 +577,8 @@
display: flex;
flex: 1;
justify-content: flex-end;
- margin: $small 0 $small $small;
- padding: 0 $x-small;
+ margin: $size-2 0 $size-2 $size-2;
+ padding: 0 $size-1;
&:first-child {
justify-content: flex-start;
@@ -552,7 +590,7 @@
display: flex;
height: 20px;
justify-content: center;
- margin-right: $small;
+ margin-right: $size-2;
position: relative;
width: 20px;
@@ -641,7 +679,7 @@
}
.orientation-icon {
- margin-left: $small;
+ margin-left: $size-2;
display: flex;
align-items: center;
@@ -662,7 +700,7 @@
.navigate-icon {
background-color: $color-gray-60;
cursor: pointer;
- margin-left: $small;
+ margin-left: $size-2;
display: flex;
align-items: center;
justify-content: center;
@@ -688,7 +726,7 @@
width: 100%;
&:first-child {
- margin-right: $small;
+ margin-right: $size-2;
}
.icon-before {
@@ -836,7 +874,6 @@
position: relative;
top: 2px;
width: 100%;
- z-index: 20;
}
.btn-options {
@@ -879,12 +916,35 @@
&.selected {
border: 1px solid $color-primary;
}
+
+ &:not(:first-child) {
+ margin-top: 7px;
+ }
+
+ &.open {
+ &:hover {
+ background: unset;
+ }
+ }
+}
+
+.interactions-options {
+ &.element-set {
+ border-bottom: 0;
+ }
+
+ .element-set-options-group {
+ flex-wrap: wrap;
+ }
+
+ &:not(:first-child) {
+ border-top: 1px solid $color-gray-60;
+ }
}
.exports-options,
-.shadow-options{
+.shadow-options {
.element-set-options-group {
- justify-content: space-between;
.delete-icon {
display: flex;
min-width: 40px;
@@ -898,10 +958,6 @@
fill: $color-gray-20;
}
}
-
- &:not(:first-child) {
- margin-top: 7px;
- }
}
.download-button {
@@ -941,11 +997,15 @@
width: 12px;
height: 12px;
fill: $color-gray-20;
+ stroke: $color-gray-20;
}
- &:hover svg {
+ &:hover svg,
+ &.active svg {
fill: $color-primary;
+ stroke: $color-primary;
}
+
&.actions-inside {
position: absolute;
right: 0;
@@ -953,7 +1013,7 @@
}
.element-set-label {
- font-size: $fs11;
+ font-size: $fs12;
padding: 0.5rem;
color: $color-gray-10;
}
@@ -1100,7 +1160,7 @@
.multiple-typography-text,
.multiple-typography-button {
- font-size: $fs13;
+ font-size: $fs12;
display: flex;
align-items: center;
}
@@ -1153,7 +1213,7 @@
position: relative;
.backend-filters {
- padding: $small $medium;
+ padding: $size-2 $size-4;
// width: 220px;
top: 40px;
right: 20px;
@@ -1161,15 +1221,15 @@
.backend-filter {
display: flex;
align-items: center;
- padding: $small 0;
+ padding: $size-2 0;
cursor: pointer;
.checkbox-icon {
display: flex;
justify-content: center;
align-items: center;
- width: $medium;
- height: $medium;
+ width: $size-4;
+ height: $size-4;
border: 1px solid $color-gray-30;
border-radius: $br-small;
@@ -1182,7 +1242,7 @@
}
.backend-name {
- margin-left: $small;
+ margin-left: $size-2;
color: $color-gray-50;
}
@@ -1214,7 +1274,7 @@
align-items: center;
width: 24px;
height: 24px;
- margin-left: $small;
+ margin-left: $size-2;
svg {
width: 16px;
@@ -1246,9 +1306,9 @@
}
.font-item {
- padding-left: $big;
- height: $x-big;
- max-height: $x-big;
+ padding-left: $size-5;
+ height: $size-6;
+ max-height: $size-6;
width: 100%;
display: flex;
align-items: center;
@@ -1272,7 +1332,7 @@
// justify-content: center;
align-items: center;
// border: 1px solid red;
- width: $big
+ width: $size-5
}
.label {
@@ -1400,14 +1460,14 @@
justify-content: flex-start;
.input-select {
- font-size: $fs11;
- margin: 0 $x-small;
+ font-size: $fs12;
+ margin: 0 $size-1;
}
svg {
width: 15px;
height: 15px;
- margin-left: $medium;
+ margin-left: $size-4;
fill: $color-gray-20;
}
@@ -1420,11 +1480,11 @@
}
.fix-when {
- font-size: $fs11;
+ font-size: $fs12;
cursor: pointer;
span {
- margin-left: $small;
+ margin-left: $size-2;
}
&:hover,
@@ -1444,10 +1504,10 @@
border-bottom-color: $color-gray-40;
color: $color-gray-10;
cursor: pointer;
- font-size: $fs11;
- margin: $x-small;
+ font-size: $fs12;
+ margin: $size-1;
overflow: hidden;
- padding: $x-small;
+ padding: $size-1;
padding-right: 20px;
position: relative;
text-overflow: ellipsis;
@@ -1478,7 +1538,8 @@
right: 5px;
top: 30px;
z-index: 12;
- min-width: 200px;
+ width: 200px;
+ height: 320px;
position: fixed;
& li.separator {
@@ -1487,6 +1548,6 @@
& li img {
width: 16px;
- margin-right: $small;
+ margin-right: $size-2;
}
}
diff --git a/frontend/resources/styles/main/partials/sidebar-icons.scss b/frontend/resources/styles/main/partials/sidebar-icons.scss
index 32eab98cc..8af2365f6 100644
--- a/frontend/resources/styles/main/partials/sidebar-icons.scss
+++ b/frontend/resources/styles/main/partials/sidebar-icons.scss
@@ -7,7 +7,7 @@
.figures-catalog {
width: 100%;
- padding: $medium $medium 0 $medium;
+ padding: $size-4 $size-4 0 $size-4;
select {
color: $color-gray-10;
@@ -27,7 +27,7 @@
flex-shrink: 0;
height: 54px;
justify-content: center;
- margin: $medium 0 0 $medium;
+ margin: $size-4 0 0 $size-4;
width: 54px;
svg {
diff --git a/frontend/resources/styles/main/partials/sidebar-interactions.scss b/frontend/resources/styles/main/partials/sidebar-interactions.scss
index bd086d303..568dd76e1 100644
--- a/frontend/resources/styles/main/partials/sidebar-interactions.scss
+++ b/frontend/resources/styles/main/partials/sidebar-interactions.scss
@@ -7,14 +7,20 @@
.interactions-help {
font-size: $fs12;
- margin: 0 $medium;
+ padding: 7px $size-4;
+ margin: 0 -7px;
text-align: center;
+
+ &.separator {
+ padding-bottom: $size-4;
+ border-bottom: 1px solid $color-black;
+ }
}
.interactions-help-icon {
height: 32px;
width: 32px;
- margin: $medium auto;
+ margin: $size-4 auto;
svg {
fill: $color-gray-40;
@@ -22,3 +28,131 @@
width: 32px;
}
}
+
+.interactions-summary {
+ cursor: pointer;
+ flex-basis: 0;
+ flex-grow: 1;
+
+ .trigger-name {
+ font-size: $fs12;
+ color: $color-white;
+ }
+
+ .action-summary {
+ font-size: $fs12;
+ color: $color-gray-20;
+ }
+}
+
+.interactions-element {
+ display: flex;
+ align-items: center;
+ margin: 0 -7px;
+ padding: 0 7px;
+
+ .element-label {
+ color: $color-gray-20;
+ font-size: $fs12;
+ width: 64px;
+ }
+
+ &.separator {
+ border-top: 1px solid $color-black;
+ margin-top: $size-1;
+ }
+}
+
+.interactions-pos-buttons {
+ margin-top: $size-2;
+ padding-top: $size-2;
+ padding-bottom: $size-2;
+ justify-content: space-between;
+
+ .element-set-actions-button {
+ min-width: 18px;
+ min-height: 18px;
+ }
+
+ svg {
+ height: 18px;
+ width: 18px;
+ }
+}
+
+.flow-element {
+ display: flex;
+ align-items: center;
+ padding: $size-1;
+
+ .element-label {
+ font-size: $fs11;
+ }
+
+ .flow-name {
+ cursor: pointer;
+ }
+
+ & input.element-name {
+ background: transparent;
+ border-color: $color-primary;
+ color: $color-white;
+ font-size: $fs11;
+ }
+}
+
+.flow-button {
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-right: $size-2;
+
+ & svg {
+ height: 12px;
+ width: 12px;
+ fill: $color-gray-20;
+ }
+
+ &:hover svg {
+ fill: $color-primary;
+ }
+}
+
+.flow-badge {
+ display: flex;
+
+ & .content {
+ align-items: center;
+ background-color: $color-gray-50;
+ border-radius: 4px;
+ display: flex;
+ height: 24px;
+
+ & svg {
+ height: 12px;
+ margin: 0 $size-2;
+ width: 12px;
+ fill: $color-gray-20;
+ }
+
+ & span {
+ color: $color-gray-20;
+ font-size: $fs12;
+ margin-right: $size-4;
+ }
+ }
+
+ &.selected .content {
+ background-color: $color-primary;
+
+ & svg {
+ fill: $color-gray-60;
+ }
+
+ & span {
+ color: $color-gray-60;
+ }
+ }
+}
+
diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss
index d31e5c7b7..19442c608 100644
--- a/frontend/resources/styles/main/partials/sidebar-layers.scss
+++ b/frontend/resources/styles/main/partials/sidebar-layers.scss
@@ -9,7 +9,7 @@
align-items: center;
display: flex;
height: 32px;
- padding: $x-small $small;
+ padding: $size-1 $size-2;
transition: none;
width: 100%;
diff --git a/frontend/resources/styles/main/partials/sidebar-sitemap.scss b/frontend/resources/styles/main/partials/sidebar-sitemap.scss
index b04d09fe7..57d82086d 100644
--- a/frontend/resources/styles/main/partials/sidebar-sitemap.scss
+++ b/frontend/resources/styles/main/partials/sidebar-sitemap.scss
@@ -21,7 +21,7 @@
svg {
fill: $color-gray-30;
height: 13px;
- margin-right: $x-small;
+ margin-right: $size-1;
width: 13px;
}
@@ -46,7 +46,7 @@
svg {
fill: $color-gray-60;
height: 15px;
- margin-left: $x-small;
+ margin-left: $size-1;
width: 15px;
}
@@ -110,7 +110,7 @@
.element-list-body {
align-items: center;
display: flex;
- padding: $x-small $small;
+ padding: $size-1 $size-2;
transition: none;
width: 100%;
@@ -146,7 +146,7 @@
display: flex;
justify-content: center;
margin-left: auto;
- padding: $x-small;
+ padding: $size-1;
svg {
fill: $color-gray-20;
@@ -175,7 +175,7 @@
}
.collapse-pages {
- margin-left: $small;
+ margin-left: $size-2;
svg {
transform: rotate(90deg);
diff --git a/frontend/resources/styles/main/partials/sidebar-tools.scss b/frontend/resources/styles/main/partials/sidebar-tools.scss
index 7a2b684b7..2254f240b 100644
--- a/frontend/resources/styles/main/partials/sidebar-tools.scss
+++ b/frontend/resources/styles/main/partials/sidebar-tools.scss
@@ -18,7 +18,7 @@
flex-shrink: 0;
height: 54px;
justify-content: center;
- margin: $medium 0 0 $medium;
+ margin: $size-4 0 0 $size-4;
width: 54px;
svg {
diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss
index d2f630bb5..17a3ef6fb 100644
--- a/frontend/resources/styles/main/partials/sidebar.scss
+++ b/frontend/resources/styles/main/partials/sidebar.scss
@@ -82,7 +82,7 @@ $width-settings-bar: 16rem;
align-items: center;
display: flex;
flex-shrink: 0;
- padding: $small;
+ padding: $size-2;
overflow: hidden;
svg {
@@ -93,7 +93,7 @@ $width-settings-bar: 16rem;
span {
color: $color-gray-10;
- font-size: $fs13;
+ font-size: $fs14;
}
span.tool-badge {
@@ -141,7 +141,7 @@ $width-settings-bar: 16rem;
}
.tool-window-icon {
- margin-right: $small;
+ margin-right: $size-2;
display: none;
}
diff --git a/frontend/resources/styles/main/partials/tool-bar.scss b/frontend/resources/styles/main/partials/tool-bar.scss
index 8d66fd882..a95de8606 100644
--- a/frontend/resources/styles/main/partials/tool-bar.scss
+++ b/frontend/resources/styles/main/partials/tool-bar.scss
@@ -24,7 +24,7 @@
li {
cursor: pointer;
- margin-bottom: $big;
+ margin-bottom: $size-5;
svg {
fill: $color-gray-20;
diff --git a/frontend/resources/styles/main/partials/user-settings.scss b/frontend/resources/styles/main/partials/user-settings.scss
index a06c5b150..1927291db 100644
--- a/frontend/resources/styles/main/partials/user-settings.scss
+++ b/frontend/resources/styles/main/partials/user-settings.scss
@@ -165,7 +165,7 @@
border-radius: 50%;
flex-shrink: 0;
height: 120px;
- margin-right: $medium;
+ margin-right: $size-4;
width: 120px;
}
}
@@ -179,7 +179,7 @@
h2 {
font-size: $fs14;
font-weight: normal;
- margin-bottom: $medium;
+ margin-bottom: $size-4;
}
}
}
diff --git a/frontend/resources/styles/main/partials/viewer-header.scss b/frontend/resources/styles/main/partials/viewer-header.scss
index 24b9d802b..ff7808da9 100644
--- a/frontend/resources/styles/main/partials/viewer-header.scss
+++ b/frontend/resources/styles/main/partials/viewer-header.scss
@@ -4,13 +4,13 @@
border-bottom: 1px solid $color-gray-60;
display: flex;
height: 48px;
- padding: 0 $medium 0 55px;
+ padding: 0 $size-4 0 55px;
position: relative;
z-index: 12;
justify-content: space-between;
a {
- font-size: $fs13;
+ font-size: $fs12;
}
.main-icon {
@@ -50,30 +50,23 @@
position: relative;
> * {
- margin-left: $big;
+ margin-left: $size-5;
}
.btn-primary {
flex-shrink: 0;
}
- .zoom-widget {
- .dropdown {
- top: 45px;
- left: 25px;
- }
- }
-
.view-options {
align-items: center;
cursor: pointer;
display: flex;
- width: 90px;
+ position: relative;
> span {
color: $color-gray-10;
- font-size: $fs13;
- margin-right: $x-small;
+ font-size: $fs14;
+ margin-right: $size-1;
}
> .icon {
@@ -96,7 +89,7 @@
}
.dropdown {
- min-width: 260px;
+ min-width: 295px;
top: 45px;
left: -25px;
}
@@ -107,7 +100,7 @@
align-items: center;
cursor: pointer;
display: flex;
- padding: $x-small;
+ padding: $size-1;
position: relative;
.icon {
@@ -118,7 +111,7 @@
svg {
fill: $color-gray-20;
height: 12px;
- margin-right: $small;
+ margin-right: $size-2;
width: 12px;
}
}
@@ -129,7 +122,7 @@
> span {
color: $color-gray-20;
- margin-right: $x-small;
+ margin-right: $size-1;
font-size: $fs14;
overflow-x: hidden;
text-overflow: ellipsis;
@@ -146,7 +139,7 @@
display: flex;
span {
color: $color-white;
- margin-right: $x-small;
+ margin-right: $size-1;
}
.counters {
@@ -192,7 +185,7 @@
margin: 0;
li {
- margin-left: $small;
+ margin-left: $size-2;
position: relative;
img {
diff --git a/frontend/resources/styles/main/partials/viewer-thumbnails.scss b/frontend/resources/styles/main/partials/viewer-thumbnails.scss
index b60a92847..d87f765fa 100644
--- a/frontend/resources/styles/main/partials/viewer-thumbnails.scss
+++ b/frontend/resources/styles/main/partials/viewer-thumbnails.scss
@@ -175,7 +175,7 @@
text-overflow: ellipsis;
span {
- font-size: $fs13;
+ font-size: $fs12;
}
}
}
diff --git a/frontend/resources/styles/main/partials/workspace-header.scss b/frontend/resources/styles/main/partials/workspace-header.scss
index 7c588f358..2b3034974 100644
--- a/frontend/resources/styles/main/partials/workspace-header.scss
+++ b/frontend/resources/styles/main/partials/workspace-header.scss
@@ -10,7 +10,7 @@
border-bottom: 1px solid $color-gray-60;
display: flex;
height: 48px;
- padding: $x-small $medium $x-small 55px;
+ padding: $size-1 $size-4 $size-1 55px;
position: relative;
z-index: 12;
justify-content: space-between;
@@ -56,7 +56,7 @@
}
.shared-badge {
- margin-left: $x-small;
+ margin-left: $size-1;
height: 16px;
width: 16px;
display: flex;
@@ -81,7 +81,7 @@
position: relative;
> * {
- margin-left: $big;
+ margin-left: $size-5;
}
.zoom-dropdown {
@@ -93,13 +93,13 @@
.project-tree {
align-items: center;
display: flex;
- margin-left: $x-small;
- padding: $x-small;
+ margin-left: $size-1;
+ padding: $size-1;
svg {
fill: $color-gray-20;
height: 20px;
- margin-right: $small;
+ margin-right: $size-2;
width: 20px;
}
@@ -112,7 +112,7 @@
&.project-name {
color: $color-gray-20;
- margin-right: $x-small;
+ margin-right: $size-1;
cursor: pointer;
&:hover {
@@ -147,7 +147,7 @@
li {
cursor: pointer;
font-size: $fs14;
- padding: $small;
+ padding: $size-2;
display: flex;
justify-content: space-between;
@@ -159,7 +159,7 @@
span {
color: $color-gray-60;
- margin: 0 $x-small;
+ margin: 0 $size-1;
}
.shortcut {
@@ -183,7 +183,7 @@
margin: 0;
li {
- margin-left: $small;
+ margin-left: $size-2;
position: relative;
img {
diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss
index f167e9814..f6d7816d0 100644
--- a/frontend/resources/styles/main/partials/workspace.scss
+++ b/frontend/resources/styles/main/partials/workspace.scss
@@ -18,12 +18,12 @@
top: 40px;
width: 240px;
z-index: 12;
- padding: $x-small 0;
+ padding: $size-1 0;
li {
align-items: center;
font-size: $fs14;
- padding: $x-small $medium;
+ padding: $size-1 $size-4;
cursor: pointer;
display: flex;
@@ -47,6 +47,17 @@
&:hover {
background-color: $color-primary-lighter;
}
+
+ .submenu-icon {
+ position: absolute;
+ right: 1rem;
+
+ svg {
+ width: 10px;
+ height: 10px;
+ }
+ }
+
}
}
@@ -227,10 +238,6 @@
font-size: $fs12;
}
-.selected .workspace-frame-label {
- fill: $color-primary-dark;
-}
-
.multiuser-cursor {
align-items: center;
display: flex;
@@ -250,8 +257,8 @@
border-radius: $br-small;
color: $color-black;
font-size: $fs12;
- margin-left: $small;
- padding: $x-small;
+ margin-left: $size-2;
+ padding: $size-1;
}
}
diff --git a/frontend/resources/styles/main/partials/zoom-widget.scss b/frontend/resources/styles/main/partials/zoom-widget.scss
index b25192321..7aa2a646a 100644
--- a/frontend/resources/styles/main/partials/zoom-widget.scss
+++ b/frontend/resources/styles/main/partials/zoom-widget.scss
@@ -1,11 +1,12 @@
.zoom-widget {
cursor: pointer;
display: flex;
+ position: relative;
span {
color: $color-gray-10;
font-size: $fs14;
- margin-left: $x-small;
+ margin-left: $size-1;
}
.icon svg {
@@ -16,8 +17,10 @@
.dropdown {
position: absolute;
- z-index: 12;
+ right: 0;
+ top: 25px;
width: 210px;
+ z-index: 12;
background-color: $color-white;
border-radius: $br-small;
@@ -28,7 +31,7 @@
cursor: pointer;
font-size: $fs14;
display: flex;
- padding: $small;
+ padding: $size-2;
span {
color: $color-gray-20;
diff --git a/frontend/scripts/build b/frontend/scripts/build
index 1931f5431..11f608e7f 100755
--- a/frontend/scripts/build
+++ b/frontend/scripts/build
@@ -8,7 +8,7 @@ EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS;
yarn install || exit 1;
npx gulp clean || exit 1;
-clojure -M:dev:shadow-cljs release main --config-merge "{:release-version \"${CURRENT_HASH}\"}" $EXTRA_PARAMS || exit 1
+clojure -J-Xms1G -J-Xmx1G -M:dev:shadow-cljs release main --config-merge "{:release-version \"${CURRENT_HASH}\"}" $EXTRA_PARAMS || exit 1
npx gulp build || exit 1;
npx gulp dist:clean || exit 1;
npx gulp dist:copy || exit 1;
diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn
index 1d9527552..942e2c93d 100644
--- a/frontend/shadow-cljs.edn
+++ b/frontend/shadow-cljs.edn
@@ -1,6 +1,6 @@
{:deps {:aliases [:dev]}
:http {:port 3448}
- :nrepl {:port 3447}
+ :nrepl {:port 3447 :host "0.0.0.0"}
:jvm-opts ["-Xmx700m" "-Xms100m" "-XX:+UseSerialGC" "-XX:-OmitStackTraceInFastThrow"]
:dev-http {8888 "classpath:public"}
@@ -29,7 +29,9 @@
:warnings {:fn-deprecated false}}
:release
- {:compiler-options
+ {:closure-defines {goog.DEBUG false
+ goog.debug.LOGGING_ENABLED true}
+ :compiler-options
{:fn-invoke-direct true
:source-map true
:elide-asserts true
diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs
index 8e43d15aa..9e308a1ca 100644
--- a/frontend/src/app/config.cljs
+++ b/frontend/src/app/config.cljs
@@ -54,22 +54,11 @@
:browser
:webworker))
-(def available-flags
- #{:registration
- :audit-log
- :demo-users
- :user-feedback
- :demo-warning
- :login-with-ldap})
-
-(def default-flags
- #{:registration :demo-users})
-
(defn- parse-flags
[global]
(let [flags (obj/get global "penpotFlags" "")
flags (into #{} (map keyword) (str/words flags))]
- (flags/parse default-flags flags)))
+ (flags/parse flags flags/default)))
(defn- parse-version
[global]
@@ -88,6 +77,7 @@
(def worker-uri (obj/get global "penpotWorkerURI" "/js/worker.js"))
(def translations (obj/get global "penpotTranslations"))
(def themes (obj/get global "penpotThemes"))
+(def sentry-dsn (obj/get global "penpotSentryDsn"))
(def flags (atom (parse-flags global)))
(def version (atom (parse-version global)))
@@ -103,7 +93,8 @@
(when (false? registration)
(swap! flags disj :registration)))
-(def public-uri
+(defn get-public-uri
+ []
(let [uri (u/uri (or (obj/get global "penpotPublicURI")
(.-origin ^js location)))]
;; Ensure that the path always ends with "/"; this ensures that
@@ -112,9 +103,7 @@
(not (str/ends-with? (:path uri) "/"))
(update :path #(str % "/")))))
-(when (= :browser @target)
- (js/console.log
- (str/format "Welcome to penpot! version='%s' base-uri='%s'." (:full @version) (str public-uri))))
+(def public-uri (get-public-uri))
;; --- Helper Functions
diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs
index 9a6842730..04b6a00f4 100644
--- a/frontend/src/app/main.cljs
+++ b/frontend/src/app/main.cljs
@@ -6,25 +6,23 @@
(ns app.main
(:require
- [app.common.spec :as us]
+ [app.common.logging :as log]
[app.common.uuid :as uuid]
- [app.config :as cfg]
+ [app.config :as cf]
[app.main.data.events :as ev]
- [app.main.data.messages :as dm]
[app.main.data.users :as du]
+ [app.main.errors]
+ [app.main.sentry :as sentry]
[app.main.store :as st]
[app.main.ui :as ui]
[app.main.ui.confirm]
[app.main.ui.modal :refer [modal]]
+ [app.main.ui.routes :as rt]
[app.main.worker]
[app.util.dom :as dom]
[app.util.i18n :as i18n]
- [app.util.logging :as log]
- [app.util.router :as rt]
- [app.util.storage :refer [storage]]
[app.util.theme :as theme]
[beicon.core :as rx]
- [cljs.spec.alpha :as s]
[potok.core :as ptk]
[rumext.alpha :as mf]))
@@ -32,79 +30,38 @@
(log/set-level! :root :warn)
(log/set-level! :app :info)
+(when (= :browser @cf/target)
+ (log/info :message "Welcome to penpot" :version (:full @cf/version) :public-uri (str cf/public-uri)))
+
(declare reinit)
-(s/def ::any any?)
-
-(defn match-path
- [router path]
- (when-let [match (rt/match router path)]
- (if-let [conform (get-in match [:data :conform])]
- (let [spath (get conform :path-params ::any)
- squery (get conform :query-params ::any)]
- (try
- (-> (dissoc match :params)
- (assoc :path-params (us/conform spath (get match :path-params))
- :query-params (us/conform squery (get match :query-params))))
- (catch :default _
- nil)))
- match)))
-
-(defn on-navigate
- [router path]
- (let [match (match-path router path)
- profile (:profile @storage)
- nopath? (or (= path "") (= path "/"))
- authed? (and (not (nil? profile))
- (not= (:id profile) uuid/zero))]
-
- (cond
- (and nopath? authed? (nil? match))
- (if (not= uuid/zero profile)
- (st/emit! (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)}))
- (st/emit! (rt/nav :auth-login)))
-
- (and (not authed?) (nil? match))
- (st/emit! (rt/nav :auth-login))
-
- (nil? match)
- (st/emit! (dm/assign-exception {:type :not-found}))
-
- :else
- (st/emit! (rt/navigated match)))))
-
(defn init-ui
[]
(mf/mount (mf/element ui/app) (dom/get-element "app"))
(mf/mount (mf/element modal) (dom/get-element "modal")))
-
(defn initialize
[]
- (letfn [(on-profile [_profile]
- (rx/of (rt/initialize-router ui/routes)
- (rt/initialize-history on-navigate)))]
- (ptk/reify ::initialize
- ptk/UpdateEvent
- (update [_ state]
- (assoc state :session-id (uuid/next)))
+ (ptk/reify ::initialize
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc state :session-id (uuid/next)))
- ptk/WatchEvent
- (watch [_ _ stream]
- (rx/merge
- (rx/of
- (ptk/event ::ev/initialize)
- (du/initialize-profile))
- (->> stream
- (rx/filter (ptk/type? ::du/profile-fetched))
- (rx/take 1)
- (rx/map deref)
- (rx/mapcat on-profile)))))))
+ ptk/WatchEvent
+ (watch [_ _ stream]
+ (rx/merge
+ (rx/of (ptk/event ::ev/initialize)
+ (du/initialize-profile))
+ (->> stream
+ (rx/filter du/profile-fetched?)
+ (rx/take 1)
+ (rx/map #(rt/init-routes)))))))
(defn ^:export init
[]
- (i18n/init! cfg/translations)
- (theme/init! cfg/themes)
+ (sentry/init!)
+ (i18n/init! cf/translations)
+ (theme/init! cf/themes)
(init-ui)
(st/emit! (initialize)))
@@ -114,11 +71,14 @@
(mf/unmount (dom/get-element "modal"))
(init-ui))
-(add-watch i18n/locale "locale" (fn [_ _ o v]
- (when (not= o v)
- (reinit))))
-
(defn ^:dev/after-load after-load
[]
(reinit))
+;; Reload the UI when the language changes
+(add-watch
+ i18n/locale "locale"
+ (fn [_ _ old-value current-value]
+ (when (not= old-value current-value)
+ (reinit))))
+
diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs
index 95387f5ec..e3f26495e 100644
--- a/frontend/src/app/main/data/comments.cljs
+++ b/frontend/src/app/main/data/comments.cljs
@@ -77,7 +77,8 @@
(watch [_ _ _]
(->> (rp/mutation :create-comment-thread params)
(rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %)}))
- (rx/map #(partial created %)))))))
+ (rx/map #(partial created %))
+ (rx/catch #(rx/throw {:type :comment-error})))))))
(defn update-comment-thread-status
[{:keys [id] :as thread}]
@@ -87,7 +88,8 @@
(watch [_ _ _]
(let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0)]
(->> (rp/mutation :update-comment-thread-status {:id id})
- (rx/map (constantly done)))))))
+ (rx/map (constantly done))
+ (rx/catch #(rx/throw {:type :comment-error})))))))
(defn update-comment-thread
@@ -104,6 +106,7 @@
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/mutation :update-comment-thread {:id id :is-resolved is-resolved})
+ (rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore)))))
@@ -118,7 +121,8 @@
(watch [_ _ _]
(rx/concat
(->> (rp/mutation :add-comment {:thread-id (:id thread) :content content})
- (rx/map #(partial created %)))
+ (rx/map #(partial created %))
+ (rx/catch #(rx/throw {:type :comment-error})))
(rx/of (refresh-comment-thread thread)))))))
(defn update-comment
@@ -132,6 +136,7 @@
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/mutation :update-comment {:id id :content content})
+ (rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore)))))
(defn delete-comment-thread
@@ -147,6 +152,7 @@
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/mutation :delete-comment-thread {:id id})
+ (rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore)))))
(defn delete-comment
@@ -160,6 +166,7 @@
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/mutation :delete-comment {:id id})
+ (rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore)))))
(defn refresh-comment-thread
@@ -171,7 +178,8 @@
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/query :comment-thread {:file-id file-id :id id})
- (rx/map #(partial fetched %)))))))
+ (rx/map #(partial fetched %))
+ (rx/catch #(rx/throw {:type :comment-error})))))))
(defn retrieve-comment-threads
[file-id]
@@ -182,7 +190,8 @@
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/query :comment-threads {:file-id file-id})
- (rx/map #(partial fetched %)))))))
+ (rx/map #(partial fetched %))
+ (rx/catch #(rx/throw {:type :comment-error})))))))
(defn retrieve-comments
[thread-id]
@@ -193,7 +202,8 @@
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/query :comments {:thread-id thread-id})
- (rx/map #(partial fetched %)))))))
+ (rx/map #(partial fetched %))
+ (rx/catch #(rx/throw {:type :comment-error})))))))
(defn retrieve-unread-comment-threads
"A event used mainly in dashboard for retrieve all unread threads of a team."
@@ -204,7 +214,8 @@
(watch [_ _ _]
(let [fetched #(assoc %2 :comment-threads (d/index-by :id %1))]
(->> (rp/query :unread-comment-threads {:team-id team-id})
- (rx/map #(partial fetched %)))))))
+ (rx/map #(partial fetched %))
+ (rx/catch #(rx/throw {:type :comment-error})))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs
index 6eb812246..7025dc5ea 100644
--- a/frontend/src/app/main/data/dashboard.cljs
+++ b/frontend/src/app/main/data/dashboard.cljs
@@ -67,6 +67,7 @@
(ptk/reify ::initialize
ptk/UpdateEvent
(update [_ state]
+ (du/set-current-team! id)
(let [prev-team-id (:current-team-id state)]
(cond-> state
(not= prev-team-id id)
@@ -749,7 +750,6 @@
(ptk/reify ::go-to-projects-1
ptk/WatchEvent
(watch [_ _ _]
- (du/set-current-team! team-id)
(rx/of (rt/nav :dashboard-projects {:team-id team-id}))))))
(defn go-to-team-members
diff --git a/frontend/src/app/main/data/fonts.cljs b/frontend/src/app/main/data/fonts.cljs
index 81d00c9e5..96fb4dbba 100644
--- a/frontend/src/app/main/data/fonts.cljs
+++ b/frontend/src/app/main/data/fonts.cljs
@@ -8,12 +8,12 @@
(:require
["opentype.js" :as ot]
[app.common.data :as d]
+ [app.common.logging :as log]
[app.common.media :as cm]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.main.fonts :as fonts]
[app.main.repo :as rp]
- [app.util.logging :as log]
[app.util.webapi :as wa]
[beicon.core :as rx]
[cuerdas.core :as str]
diff --git a/frontend/src/app/main/data/messages.cljs b/frontend/src/app/main/data/messages.cljs
index acd156430..74c1ddebe 100644
--- a/frontend/src/app/main/data/messages.cljs
+++ b/frontend/src/app/main/data/messages.cljs
@@ -128,12 +128,3 @@
:controls controls
:actions actions
:tag tag})))
-
-(defn assign-exception
- [error]
- (ptk/reify ::assign-exception
- ptk/UpdateEvent
- (update [_ state]
- (if (nil? error)
- (dissoc state :exception)
- (assoc state :exception error)))))
diff --git a/frontend/src/app/main/data/shortcuts.cljs b/frontend/src/app/main/data/shortcuts.cljs
index 69c9596bc..0494f9913 100644
--- a/frontend/src/app/main/data/shortcuts.cljs
+++ b/frontend/src/app/main/data/shortcuts.cljs
@@ -8,9 +8,9 @@
(:refer-clojure :exclude [meta reset!])
(:require
["mousetrap" :as mousetrap]
+ [app.common.logging :as log]
[app.common.spec :as us]
- [app.config :as cfg]
- [app.util.logging :as log]
+ [app.config :as cf]
[cljs.spec.alpha :as s]
[potok.core :as ptk]))
@@ -37,7 +37,7 @@
"Adds the control/command modifier to a shortcuts depending on the
operating system for the user"
[shortcut]
- (if (cfg/check-platform? :macos)
+ (if (cf/check-platform? :macos)
(str "command+" shortcut)
(str "ctrl+" shortcut)))
@@ -55,12 +55,12 @@
[key]
;; If the key is "+" we need to surround with quotes
;; otherwise will not be very readable
- (let [key (if (and (not (cfg/check-platform? :macos))
+ (let [key (if (and (not (cf/check-platform? :macos))
(= key "+"))
"\"+\""
key)]
(str
- (if (cfg/check-platform? :macos)
+ (if (cf/check-platform? :macos)
mac-command
"Ctrl+")
key)))
@@ -68,7 +68,7 @@
(defn shift
[key]
(str
- (if (cfg/check-platform? :macos)
+ (if (cf/check-platform? :macos)
mac-shift
"Shift+")
key))
@@ -76,7 +76,7 @@
(defn alt
[key]
(str
- (if (cfg/check-platform? :macos)
+ (if (cf/check-platform? :macos)
mac-option
"Alt+")
key))
@@ -91,19 +91,19 @@
(defn supr
[]
- (if (cfg/check-platform? :macos)
+ (if (cf/check-platform? :macos)
mac-delete
"Supr"))
(defn esc
[]
- (if (cfg/check-platform? :macos)
+ (if (cf/check-platform? :macos)
mac-esc
"Escape"))
(defn enter
[]
- (if (cfg/check-platform? :macos)
+ (if (cf/check-platform? :macos)
mac-enter
"Enter"))
diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs
index 07ad91268..3356c0ae9 100644
--- a/frontend/src/app/main/data/users.cljs
+++ b/frontend/src/app/main/data/users.cljs
@@ -62,14 +62,26 @@
(defn teams-fetched
[teams]
- (let [teams (d/index-by :id teams)]
+ (let [teams (d/index-by :id teams)
+ ids (into #{} (keys teams))]
+
(ptk/reify ::teams-fetched
IDeref
(-deref [_] teams)
ptk/UpdateEvent
(update [_ state]
- (assoc state :teams teams)))))
+ (assoc state :teams teams))
+
+ ptk/EffectEvent
+ (effect [_ _ _]
+ ;; Check if current team-id is part of available teams
+ ;; if not, dissoc it from storage.
+ (when-let [ctid (::current-team-id @storage)]
+ (when-not (contains? ids ctid)
+ (swap! storage dissoc ::current-team-id)))))))
+
+
(defn fetch-teams
[]
@@ -81,6 +93,9 @@
;; --- EVENT: fetch-profile
+(def profile-fetched?
+ (ptk/type? ::profile-fetched))
+
(defn profile-fetched
[{:keys [id] :as profile}]
(us/verify ::profile profile)
diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs
index c30ed5bf2..15f39ba1f 100644
--- a/frontend/src/app/main/data/viewer.cljs
+++ b/frontend/src/app/main/data/viewer.cljs
@@ -31,6 +31,7 @@
:comments-show :unresolved
:selected #{}
:collapsed #{}
+ :overlays []
:hover nil})
(declare fetch-comment-threads)
@@ -110,6 +111,7 @@
(rx/of (df/fonts-fetched fonts)
(bundle-fetched (merge bundle params))))))))))
+(declare go-to-frame-auto)
(defn bundle-fetched
[{:keys [project file share-links libraries users permissions] :as bundle}]
@@ -129,7 +131,15 @@
:permissions permissions
:project project
:pages pages
- :file file}))))))
+ :file file})))
+
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [route (:route state)
+ qparams (:query-params route)
+ index (:index qparams)]
+ (when (nil? index)
+ (rx/of (go-to-frame-auto))))))))
(defn fetch-comment-threads
[{:keys [file-id page-id] :as params}]
@@ -218,6 +228,12 @@
(update [_ state]
(update-in state [:viewer-local :show-thumbnails] not))))
+(def close-thumbnails-panel
+ (ptk/reify ::close-thumbnails-panel
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:viewer-local :show-thumbnails] false))))
+
(def select-prev-frame
(ptk/reify ::select-prev-frame
ptk/WatchEvent
@@ -286,11 +302,15 @@
(update [_ state]
(assoc-in state [:viewer-local :interactions-show?] false))))
-;; --- Navigation
+;; --- Navigation inside page
(defn go-to-frame-by-index
[index]
(ptk/reify ::go-to-frame-by-index
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:viewer-local :overlays] []))
+
ptk/WatchEvent
(watch [_ state _]
(let [route (:route state)
@@ -303,6 +323,10 @@
[frame-id]
(us/verify ::us/uuid frame-id)
(ptk/reify ::go-to-frame
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:viewer-local :overlays] []))
+
ptk/WatchEvent
(watch [_ state _]
(let [route (:route state)
@@ -314,9 +338,27 @@
(when index
(rx/of (go-to-frame-by-index index)))))))
+(defn go-to-frame-auto
+ []
+ (ptk/reify ::go-to-frame-auto
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [route (:route state)
+ qparams (:query-params route)
+ page-id (:page-id qparams)
+ flows (get-in state [:viewer :pages page-id :options :flows])]
+ (if (seq flows)
+ (let [frame-id (:starting-frame (first flows))]
+ (rx/of (go-to-frame frame-id)))
+ (rx/of (go-to-frame-by-index 0)))))))
+
(defn go-to-section
[section]
(ptk/reify ::go-to-section
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:viewer-local :overlays] []))
+
ptk/WatchEvent
(watch [_ state _]
(let [route (:route state)
@@ -324,6 +366,67 @@
qparams (:query-params route)]
(rx/of (rt/nav :viewer pparams (assoc qparams :section section)))))))
+;; --- Overlays
+
+(defn open-overlay
+ [frame-id position close-click-outside background-overlay]
+ (us/verify ::us/uuid frame-id)
+ (us/verify ::us/point position)
+ (us/verify (s/nilable ::us/boolean) close-click-outside)
+ (us/verify (s/nilable ::us/boolean) background-overlay)
+ (ptk/reify ::open-overlay
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [route (:route state)
+ qparams (:query-params route)
+ page-id (:page-id qparams)
+ frames (get-in state [:viewer :pages page-id :frames])
+ frame (d/seek #(= (:id %) frame-id) frames)
+ overlays (get-in state [:viewer-local :overlays])]
+ (if-not (some #(= (:frame %) frame) overlays)
+ (update-in state [:viewer-local :overlays] conj
+ {:frame frame
+ :position position
+ :close-click-outside close-click-outside
+ :background-overlay background-overlay})
+ state)))))
+
+(defn toggle-overlay
+ [frame-id position close-click-outside background-overlay]
+ (us/verify ::us/uuid frame-id)
+ (us/verify ::us/point position)
+ (us/verify (s/nilable ::us/boolean) close-click-outside)
+ (us/verify (s/nilable ::us/boolean) background-overlay)
+ (ptk/reify ::toggle-overlay
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [route (:route state)
+ qparams (:query-params route)
+ page-id (:page-id qparams)
+ frames (get-in state [:viewer :pages page-id :frames])
+ frame (d/seek #(= (:id %) frame-id) frames)
+ overlays (get-in state [:viewer-local :overlays])]
+ (if-not (some #(= (:frame %) frame) overlays)
+ (update-in state [:viewer-local :overlays] conj
+ {:frame frame
+ :position position
+ :close-click-outside close-click-outside
+ :background-overlay background-overlay})
+ (update-in state [:viewer-local :overlays]
+ (fn [overlays]
+ (d/removev #(= (:id (:frame %)) frame-id) overlays))))))))
+
+(defn close-overlay
+ [frame-id]
+ (ptk/reify ::close-overlay
+ ptk/UpdateEvent
+ (update [_ state]
+ (update-in state [:viewer-local :overlays]
+ (fn [overlays]
+ (d/removev #(= (:id (:frame %)) frame-id) overlays))))))
+
+;; --- Objects selection
+
(defn deselect-all []
(ptk/reify ::deselect-all
ptk/UpdateEvent
@@ -397,7 +500,7 @@
(update [_ state]
(assoc-in state [:viewer-local :hover] (when hover? id)))))
-;; --- NAV
+;; --- Navigation outside page
(defn go-to-dashboard
[]
@@ -411,6 +514,10 @@
(defn go-to-page
[page-id]
(ptk/reify ::go-to-page
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:viewer-local :overlays] []))
+
ptk/WatchEvent
(watch [_ state _]
(let [route (:route state)
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index 3411db165..e5e36f6ab 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -22,13 +22,16 @@
[app.config :as cfg]
[app.main.data.events :as ev]
[app.main.data.messages :as dm]
+ [app.main.data.workspace.booleans :as dwb]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.groups :as dwg]
+ [app.main.data.workspace.interactions :as dwi]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.notifications :as dwn]
[app.main.data.workspace.path :as dwdp]
+ [app.main.data.workspace.path.shapes-to-path :as dwps]
[app.main.data.workspace.persistence :as dwp]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh]
@@ -49,8 +52,6 @@
[cuerdas.core :as str]
[potok.core :as ptk]))
-;; (log/set-level! :trace)
-
(s/def ::shape-attrs ::cp/shape-attrs)
(s/def ::set-of-string
(s/every string? :kind set?))
@@ -1096,7 +1097,7 @@
:text
(rx/of (dwc/start-edition-mode id))
- :group
+ (:group :bool)
(rx/of (dwc/select-shapes (into (d/ordered-set) [(last shapes)])))
:svg-raw
@@ -1282,8 +1283,7 @@
(watch [_ state _]
(let [{:keys [current-file-id current-page-id]} state
pparams {:file-id (or file-id current-file-id)}
- qparams {:page-id (or page-id current-page-id)
- :index 0}]
+ qparams {:page-id (or page-id current-page-id)}]
(rx/of ::dwp/force-persist
(rt/nav-new-window* {:rname :viewer
:path-params pparams
@@ -1322,10 +1322,33 @@
(ptk/reify ::show-context-menu
ptk/UpdateEvent
(update [_ state]
- (let [mdata (cond-> params
- (some? shape)
- (assoc :selected
- (wsh/lookup-selected state)))]
+ (let [selected (wsh/lookup-selected state)
+ objects (wsh/lookup-page-objects state)
+
+ selected-with-children
+ (into []
+ (mapcat #(cp/get-object-with-children % objects))
+ selected)
+
+ head (get objects (first selected))
+
+ first-not-group-like?
+ (and (= (count selected) 1)
+ (not (contains? #{:group :bool} (:type head))))
+
+ has-invalid-shapes? (->> selected-with-children
+ (some (comp #{:frame :text} :type)))
+
+ disable-booleans? (or (empty? selected) has-invalid-shapes? first-not-group-like?)
+ disable-flatten? (or (empty? selected) has-invalid-shapes?)
+
+ mdata
+ (-> params
+ (assoc :disable-booleans? disable-booleans?)
+ (assoc :disable-flatten? disable-flatten?)
+ (cond-> (some? shape)
+ (assoc :selected selected)))]
+
(assoc-in state [:workspace-local :context-menu] mdata)))))
(defn show-shape-context-menu
@@ -1534,8 +1557,12 @@
(= :frame (get-in objects [(first selected) :type])))))
(defn- paste-shape
- [{:keys [selected objects images] :as data} in-viewport?] ;; TODO: perhaps rename 'objects' to 'shapes', because it contains only
- (letfn [;; Given a file-id and img (part generated by the ;; the shapes to paste, not the whole page tree of shapes
+ [{selected :selected
+ paste-objects :objects ;; rename this because here comes only the clipboard shapes,
+ images :images ;; not the whole page tree of shapes.
+ :as data}
+ in-viewport?]
+ (letfn [;; Given a file-id and img (part generated by the
;; copy-selected event), uploads the new media.
(upload-media [file-id imgpart]
(->> (http/send! {:uri (:file-data imgpart)
@@ -1567,7 +1594,7 @@
(calculate-paste-position [state mouse-pos in-viewport?]
(let [page-objects (wsh/lookup-page-objects state)
- selected-objs (map #(get objects %) selected)
+ selected-objs (map #(get paste-objects %) selected)
has-frame? (d/seek #(= (:type %) :frame) selected-objs)
page-selected (wsh/lookup-selected state)
wrapper (gsh/selection-rect selected-objs)
@@ -1594,12 +1621,12 @@
[frame-id parent-id delta index]))))
;; Change the indexes if the paste is done with an element selected
- (change-add-obj-index [objects selected index change]
+ (change-add-obj-index [paste-objects selected index change]
(let [set-index (fn [[result index] id]
[(assoc result id index) (inc index)])
map-ids (when index
- (->> (vals objects)
+ (->> (vals paste-objects)
(filter #(not (selected (:parent-id %))))
(map :id)
(reduce set-index [{} (inc index)])
@@ -1611,8 +1638,8 @@
;; Check if the shape is an instance whose master is defined in a
;; library that is not linked to the current file
- (foreign-instance? [shape objects state]
- (let [root (cph/get-root-shape shape objects)
+ (foreign-instance? [shape paste-objects state]
+ (let [root (cph/get-root-shape shape paste-objects)
root-file-id (:component-file root)]
(and (some? root)
(not= root-file-id (:current-file-id state))
@@ -1620,34 +1647,37 @@
;; Procceed with the standard shape paste procediment.
(do-paste [it state mouse-pos media]
- (let [media-idx (d/index-by :prev-id media)
+ (let [page-objects (wsh/lookup-page-objects state)
+ media-idx (d/index-by :prev-id media)
;; Calculate position for the pasted elements
[frame-id parent-id delta index] (calculate-paste-position state mouse-pos in-viewport?)
- objects (->> objects
- (d/mapm (fn [_ shape]
- (-> shape
- (assoc :frame-id frame-id)
- (assoc :parent-id parent-id)
+ paste-objects (->> paste-objects
+ (d/mapm (fn [_ shape]
+ (-> shape
+ (assoc :frame-id frame-id)
+ (assoc :parent-id parent-id)
- (cond->
- ;; if foreign instance, detach the shape
- (foreign-instance? shape objects state)
- (dissoc :component-id
- :component-file
- :component-root?
- :remote-synced?
- :shape-ref
- :touched))))))
+ (cond->
+ ;; if foreign instance, detach the shape
+ (foreign-instance? shape paste-objects state)
+ (dissoc :component-id
+ :component-file
+ :component-root?
+ :remote-synced?
+ :shape-ref
+ :touched))))))
+
+ all-objects (merge page-objects paste-objects)
page-id (:current-page-id state)
unames (-> (wsh/lookup-page-objects state page-id)
(dwc/retrieve-used-names)) ;; TODO: move this calculation inside prepare-duplcate-changes?
- rchanges (->> (dws/prepare-duplicate-changes objects page-id unames selected delta)
+ rchanges (->> (dws/prepare-duplicate-changes all-objects page-id unames selected delta)
(mapv (partial process-rchange media-idx))
- (mapv (partial change-add-obj-index objects selected index)))
+ (mapv (partial change-add-obj-index paste-objects selected index)))
uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
(reverse rchanges))
@@ -1751,69 +1781,12 @@
;; Interactions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(declare move-create-interaction)
-(declare finish-create-interaction)
-
-(defn start-create-interaction
- []
- (ptk/reify ::start-create-interaction
- ptk/WatchEvent
- (watch [_ state stream]
- (let [initial-pos @ms/mouse-position
- selected (wsh/lookup-selected state)
- stopper (rx/filter ms/mouse-up? stream)]
- (when (= 1 (count selected))
- (rx/concat
- (->> ms/mouse-position
- (rx/take-until stopper)
- (rx/map #(move-create-interaction initial-pos %)))
- (rx/of (finish-create-interaction initial-pos))))))))
-
-(defn move-create-interaction
- [initial-pos position]
- (ptk/reify ::move-create-interaction
- ptk/UpdateEvent
- (update [_ state]
- (let [page-id (:current-page-id state)
- objects (wsh/lookup-page-objects state page-id)
- selected-shape-id (-> state wsh/lookup-selected first)
- selected-shape (get objects selected-shape-id)
- selected-shape-frame-id (:frame-id selected-shape)
- start-frame (get objects selected-shape-frame-id)
- end-frame (dwc/get-frame-at-point objects position)]
- (cond-> state
- (not= position initial-pos) (assoc-in [:workspace-local :draw-interaction-to] position)
- (not= start-frame end-frame) (assoc-in [:workspace-local :draw-interaction-to-frame] end-frame))))))
-
-(defn finish-create-interaction
- [initial-pos]
- (ptk/reify ::finish-create-interaction
- ptk/UpdateEvent
- (update [_ state]
- (-> state
- (assoc-in [:workspace-local :draw-interaction-to] nil)
- (assoc-in [:workspace-local :draw-interaction-to-frame] nil)))
-
- ptk/WatchEvent
- (watch [_ state _]
- (let [position @ms/mouse-position
- page-id (:current-page-id state)
- objects (wsh/lookup-page-objects state page-id)
- frame (dwc/get-frame-at-point objects position)
-
- shape-id (-> state wsh/lookup-selected first)
- shape (get objects shape-id)]
-
- (when-not (= position initial-pos)
- (if (and frame shape-id
- (not= (:id frame) (:id shape))
- (not= (:id frame) (:frame-id shape)))
- (rx/of (update-shape shape-id
- {:interactions [{:event-type :click
- :action-type :navigate
- :destination (:id frame)}]}))
- (rx/of (update-shape shape-id
- {:interactions []}))))))))
+(d/export dwi/start-edit-interaction)
+(d/export dwi/move-edit-interaction)
+(d/export dwi/finish-edit-interaction)
+(d/export dwi/start-move-overlay-pos)
+(d/export dwi/move-overlay-pos)
+(d/export dwi/finish-move-overlay-pos)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CANVAS OPTIONS
@@ -1887,3 +1860,12 @@
(d/export dwg/unmask-group)
(d/export dwg/group-selected)
(d/export dwg/ungroup-selected)
+
+;; Boolean
+(d/export dwb/create-bool)
+(d/export dwb/group-to-bool)
+(d/export dwb/bool-to-group)
+(d/export dwb/change-bool-type)
+
+;; Shapes to path
+(d/export dwps/convert-selected-to-path)
diff --git a/frontend/src/app/main/data/workspace/booleans.cljs b/frontend/src/app/main/data/workspace/booleans.cljs
new file mode 100644
index 000000000..5c3c1af7b
--- /dev/null
+++ b/frontend/src/app/main/data/workspace/booleans.cljs
@@ -0,0 +1,131 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.data.workspace.booleans
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.shapes :as gsh]
+ [app.common.pages :as cp]
+ [app.common.pages.changes-builder :as cb]
+ [app.common.path.shapes-to-path :as stp]
+ [app.common.uuid :as uuid]
+ [app.main.data.workspace.changes :as dch]
+ [app.main.data.workspace.common :as dwc]
+ [app.main.data.workspace.state-helpers :as wsh]
+ [beicon.core :as rx]
+ [cuerdas.core :as str]
+ [potok.core :as ptk]))
+
+(defn selected-shapes
+ [state]
+ (let [objects (wsh/lookup-page-objects state)]
+ (->> (wsh/lookup-selected state)
+ (cp/clean-loops objects)
+ (map #(get objects %))
+ (filter #(not= :frame (:type %)))
+ (map #(assoc % ::index (cp/position-on-parent (:id %) objects)))
+ (sort-by ::index))))
+
+(defn create-bool-data
+ [bool-type name shapes objects]
+ (let [shapes (mapv #(stp/convert-to-path % objects) shapes)
+ head (if (= bool-type :difference) (first shapes) (last shapes))
+ head (cond-> head
+ (and (contains? head :svg-attrs) (nil? (:fill-color head)))
+ (assoc :fill-color "#000000"))
+
+ head-data (select-keys head stp/style-properties)]
+ [(-> {:id (uuid/next)
+ :type :bool
+ :bool-type bool-type
+ :frame-id (:frame-id head)
+ :parent-id (:parent-id head)
+ :name name
+ :shapes []}
+ (merge head-data)
+ (gsh/update-bool-selrect shapes objects))
+ (cp/position-on-parent (:id head) objects)]))
+
+(defn group->bool
+ [group bool-type objects]
+
+ (let [shapes (->> (:shapes group)
+ (map #(get objects %))
+ (mapv #(stp/convert-to-path % objects)))
+ head (if (= bool-type :difference) (first shapes) (last shapes))
+ head (cond-> head
+ (and (contains? head :svg-attrs) (nil? (:fill-color head)))
+ (assoc :fill-color "#000000"))
+ head-data (select-keys head stp/style-properties)]
+
+ (-> group
+ (assoc :type :bool)
+ (assoc :bool-type bool-type)
+ (merge head-data)
+ (gsh/update-bool-selrect shapes objects))))
+
+(defn bool->group
+ [shape objects]
+
+ (let [children (->> (:shapes shape)
+ (mapv #(get objects %)))]
+ (-> shape
+ (assoc :type :group)
+ (dissoc :bool-type)
+ (d/without-keys stp/style-group-properties)
+ (gsh/update-group-selrect children))))
+
+(defn create-bool
+ [bool-type]
+ (ptk/reify ::create-bool-union
+ ptk/WatchEvent
+ (watch [it state _]
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state)
+ base-name (-> bool-type d/name str/capital (str "-1"))
+ name (-> (dwc/retrieve-used-names objects)
+ (dwc/generate-unique-name base-name))
+ shapes (selected-shapes state)]
+
+ (when-not (empty? shapes)
+ (let [[boolean-data index] (create-bool-data bool-type name shapes objects)
+ shape-id (:id boolean-data)
+ changes (-> (cb/empty-changes it page-id)
+ (cb/with-objects objects)
+ (cb/add-obj boolean-data index)
+ (cb/change-parent shape-id shapes))]
+ (rx/of (dch/commit-changes changes)
+ (dwc/select-shapes (d/ordered-set shape-id)))))))))
+
+(defn group-to-bool
+ [shape-id bool-type]
+ (ptk/reify ::group-to-bool
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [objects (wsh/lookup-page-objects state)
+ change-to-bool
+ (fn [shape] (group->bool shape bool-type objects))]
+ (rx/of (dch/update-shapes [shape-id] change-to-bool {:reg-objects? true}))))))
+
+(defn bool-to-group
+ [shape-id]
+ (ptk/reify ::bool-to-group
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [objects (wsh/lookup-page-objects state)
+ change-to-group
+ (fn [shape] (bool->group shape objects))]
+ (rx/of (dch/update-shapes [shape-id] change-to-group {:reg-objects? true}))))))
+
+
+(defn change-bool-type
+ [shape-id bool-type]
+ (ptk/reify ::change-bool-type
+ ptk/WatchEvent
+ (watch [_ _ _]
+ (let [change-type
+ (fn [shape] (assoc shape :bool-type bool-type))]
+ (rx/of (dch/update-shapes [shape-id] change-type {:reg-objects? true}))))))
diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs
index 4edfa71fd..99c050db6 100644
--- a/frontend/src/app/main/data/workspace/changes.cljs
+++ b/frontend/src/app/main/data/workspace/changes.cljs
@@ -7,6 +7,7 @@
(ns app.main.data.workspace.changes
(:require
[app.common.data :as d]
+ [app.common.logging :as log]
[app.common.pages :as cp]
[app.common.pages.spec :as spec]
[app.common.spec :as us]
@@ -14,7 +15,6 @@
[app.main.data.workspace.undo :as dwu]
[app.main.store :as st]
[app.main.worker :as uw]
- [app.util.logging :as log]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[potok.core :as ptk]))
diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs
index 0fe0939d7..6cb0af4f7 100644
--- a/frontend/src/app/main/data/workspace/common.cljs
+++ b/frontend/src/app/main/data/workspace/common.cljs
@@ -9,15 +9,17 @@
[app.common.data :as d]
[app.common.geom.proportions :as gpr]
[app.common.geom.shapes :as gsh]
+ [app.common.logging :as log]
[app.common.pages :as cp]
[app.common.spec :as us]
+ [app.common.types.interactions :as cti]
+ [app.common.types.page-options :as cto]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
[app.main.streams :as ms]
[app.main.worker :as uw]
- [app.util.logging :as log]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[potok.core :as ptk]))
@@ -379,8 +381,10 @@
(watch [it state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
+ options (wsh/lookup-page-options state page-id)
ids (cp/clean-loops objects ids)
+ flows (:flows options)
groups-to-unmask
(reduce (fn [group-ids id]
@@ -399,9 +403,14 @@
interacting-shapes
(filter (fn [shape]
(let [interactions (:interactions shape)]
- (some ids (map :destination interactions))))
+ (some #(and (cti/has-destination %)
+ (contains? ids (:destination %)))
+ interactions)))
(vals objects))
+ starting-flows
+ (filter #(contains? ids (:starting-frame %)) flows)
+
empty-parents-xform
(comp
(map (fn [id] (get objects id)))
@@ -467,7 +476,8 @@
:operations [{:type :set
:attr :interactions
:val (vec (remove (fn [interaction]
- (contains? ids (:destination interaction)))
+ (and (cti/has-destination interaction)
+ (contains? ids (:destination interaction))))
(:interactions obj)))}]})))
mk-mod-int-add-xf
(comp (filter some?)
@@ -479,6 +489,22 @@
:attr :interactions
:val (:interactions obj)}]})))
+ mk-mod-del-flow-xf
+ (comp (filter some?)
+ (map (fn [flow]
+ {:type :set-option
+ :page-id page-id
+ :option :flows
+ :value (cto/remove-flow flows (:id flow))})))
+
+ mk-mod-add-flow-xf
+ (comp (filter some?)
+ (map (fn [_]
+ {:type :set-option
+ :page-id page-id
+ :option :flows
+ :value flows})))
+
mk-mod-unmask-xf
(comp (filter (partial contains? objects))
(map (fn [id]
@@ -508,7 +534,8 @@
:page-id page-id
:shapes (vec all-parents)})
(into mk-mod-unmask-xf groups-to-unmask)
- (into mk-mod-int-del-xf interacting-shapes))
+ (into mk-mod-int-del-xf interacting-shapes)
+ (into mk-mod-del-flow-xf starting-flows))
uchanges
(-> []
@@ -520,8 +547,8 @@
:shapes (vec all-parents)})
(into mk-mod-touched-xf (reverse all-parents))
(into mk-mod-mask-xf groups-to-unmask)
- (into mk-mod-int-add-xf interacting-shapes))
- ]
+ (into mk-mod-int-add-xf interacting-shapes)
+ (into mk-mod-add-flow-xf starting-flows))]
;; (println "================ rchanges")
;; (cljs.pprint/pprint rchanges)
diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs
index f3f92c2b3..11605a13b 100644
--- a/frontend/src/app/main/data/workspace/groups.cljs
+++ b/frontend/src/app/main/data/workspace/groups.cljs
@@ -198,7 +198,7 @@
group-id (first selected)
group (get objects group-id)]
(when (and (= 1 (count selected))
- (= (:type group) :group))
+ (contains? #{:group :bool} (:type group)))
(let [[rchanges uchanges]
(prepare-remove-group page-id group objects)]
(rx/of (dch/commit-changes {:redo-changes rchanges
diff --git a/frontend/src/app/main/data/workspace/interactions.cljs b/frontend/src/app/main/data/workspace/interactions.cljs
new file mode 100644
index 000000000..bf7ba200f
--- /dev/null
+++ b/frontend/src/app/main/data/workspace/interactions.cljs
@@ -0,0 +1,332 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.data.workspace.interactions
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.point :as gpt]
+ [app.common.pages.helpers :as cph]
+ [app.common.spec :as us]
+ [app.common.types.interactions :as cti]
+ [app.common.types.page-options :as cto]
+ [app.common.uuid :as uuid]
+ [app.main.data.workspace.changes :as dch]
+ [app.main.data.workspace.common :as dwc]
+ [app.main.data.workspace.state-helpers :as wsh]
+ [app.main.streams :as ms]
+ [beicon.core :as rx]
+ [potok.core :as ptk]))
+
+;; --- Flows
+
+(defn add-flow
+ [starting-frame]
+ (ptk/reify ::add-flow
+ ptk/WatchEvent
+ (watch [it state _]
+ (let [page-id (:current-page-id state)
+ flows (get-in state [:workspace-data
+ :pages-index
+ page-id
+ :options
+ :flows] [])
+
+ unames (into #{} (map :name flows))
+ name (dwc/generate-unique-name unames "Flow-1")
+
+ new-flow {:id (uuid/next)
+ :name name
+ :starting-frame starting-frame}]
+
+ (rx/of (dch/commit-changes
+ {:redo-changes [{:type :set-option
+ :page-id page-id
+ :option :flows
+ :value (cto/add-flow flows new-flow)}]
+ :undo-changes [{:type :set-option
+ :page-id page-id
+ :option :flows
+ :value flows}]
+ :origin it}))))))
+
+(defn add-flow-selected-frame
+ []
+ (ptk/reify ::add-flow-selected-frame
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [selected (wsh/lookup-selected state)]
+ (rx/of (add-flow (first selected)))))))
+
+(defn remove-flow
+ [flow-id]
+ (us/verify ::us/uuid flow-id)
+ (ptk/reify ::remove-flow
+ ptk/WatchEvent
+ (watch [it state _]
+ (let [page-id (:current-page-id state)
+ flows (get-in state [:workspace-data
+ :pages-index
+ page-id
+ :options
+ :flows] [])]
+ (rx/of (dch/commit-changes
+ {:redo-changes [{:type :set-option
+ :page-id page-id
+ :option :flows
+ :value (cto/remove-flow flows flow-id)}]
+ :undo-changes [{:type :set-option
+ :page-id page-id
+ :option :flows
+ :value flows}]
+ :origin it}))))))
+
+(defn rename-flow
+ [flow-id name]
+ (us/verify ::us/uuid flow-id)
+ (us/verify ::us/string name)
+ (ptk/reify ::rename-flow
+ ptk/WatchEvent
+ (watch [it state _]
+ (let [page-id (:current-page-id state)
+ flows (get-in state [:workspace-data
+ :pages-index
+ page-id
+ :options
+ :flows] [])]
+ (rx/of (dch/commit-changes
+ {:redo-changes [{:type :set-option
+ :page-id page-id
+ :option :flows
+ :value (cto/update-flow flows flow-id
+ #(cto/rename-flow % name))}]
+ :undo-changes [{:type :set-option
+ :page-id page-id
+ :option :flows
+ :value flows}]
+ :origin it}))))))
+
+
+(defn start-rename-flow
+ [id]
+ (us/verify ::us/uuid id)
+ (ptk/reify ::start-rename-flow
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:workspace-local :flow-for-rename] id))))
+
+(defn end-rename-flow
+ []
+ (ptk/reify ::end-rename-flow
+ ptk/UpdateEvent
+ (update [_ state]
+ (update state :workspace-local dissoc :flow-for-rename))))
+
+;; --- Interactions
+
+(defn add-new-interaction
+ ([shape] (add-new-interaction shape nil))
+ ([shape destination]
+ (ptk/reify ::add-new-interaction
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ frame (cph/get-frame shape objects)
+ flows (get-in state [:workspace-data
+ :pages-index
+ page-id
+ :options
+ :flows] [])
+ flow (cto/get-frame-flow flows (:id frame))]
+ (rx/concat
+ (rx/of (dch/update-shapes [(:id shape)]
+ (fn [shape]
+ (let [new-interaction (cti/set-destination
+ cti/default-interaction
+ destination)]
+ (update shape :interactions
+ cti/add-interaction new-interaction)))))
+ (when (and (not (cph/connected-frame? (:id frame) objects))
+ (nil? flow))
+ (rx/of (add-flow (:id frame))))))))))
+
+(defn remove-interaction
+ [shape index]
+ (ptk/reify ::remove-interaction
+ ptk/WatchEvent
+ (watch [_ _ _]
+ (rx/of (dch/update-shapes [(:id shape)]
+ (fn [shape]
+ (update shape :interactions
+ cti/remove-interaction index)))))))
+
+(defn update-interaction
+ [shape index update-fn]
+ (ptk/reify ::update-interaction
+ ptk/WatchEvent
+ (watch [_ _ _]
+ (rx/of (dch/update-shapes [(:id shape)]
+ (fn [shape]
+ (update shape :interactions
+ cti/update-interaction index update-fn)))))))
+
+(declare move-edit-interaction)
+(declare finish-edit-interaction)
+
+(defn start-edit-interaction
+ [index]
+ (ptk/reify ::start-edit-interaction
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:workspace-local :editing-interaction-index] index))
+
+ ptk/WatchEvent
+ (watch [_ state stream]
+ (let [initial-pos @ms/mouse-position
+ selected (wsh/lookup-selected state)
+ stopper (rx/filter ms/mouse-up? stream)]
+ (when (= 1 (count selected))
+ (rx/concat
+ (->> ms/mouse-position
+ (rx/take-until stopper)
+ (rx/map #(move-edit-interaction initial-pos %)))
+ (rx/of (finish-edit-interaction index initial-pos))))))))
+
+(defn move-edit-interaction
+ [initial-pos position]
+ (ptk/reify ::move-edit-interaction
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ selected-shape-id (-> state wsh/lookup-selected first)
+ selected-shape (get objects selected-shape-id)
+ selected-shape-frame-id (:frame-id selected-shape)
+ start-frame (get objects selected-shape-frame-id)
+ end-frame (dwc/get-frame-at-point objects position)]
+ (cond-> state
+ (not= position initial-pos) (assoc-in [:workspace-local :draw-interaction-to] position)
+ (not= start-frame end-frame) (assoc-in [:workspace-local :draw-interaction-to-frame] end-frame))))))
+
+(defn finish-edit-interaction
+ [index initial-pos]
+ (ptk/reify ::finish-edit-interaction
+ ptk/UpdateEvent
+ (update [_ state]
+ (-> state
+ (assoc-in [:workspace-local :editing-interaction-index] nil)
+ (assoc-in [:workspace-local :draw-interaction-to] nil)
+ (assoc-in [:workspace-local :draw-interaction-to-frame] nil)))
+
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [position @ms/mouse-position
+ page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ frame (dwc/get-frame-at-point objects position)
+
+ shape-id (-> state wsh/lookup-selected first)
+ shape (get objects shape-id)]
+
+ (when (and shape (not (= position initial-pos)))
+ (if (nil? frame)
+ (when index
+ (rx/of (remove-interaction shape index)))
+ (let [frame (if (or (= (:id frame) (:id shape))
+ (= (:id frame) (:frame-id shape)))
+ nil ;; Drop onto self frame -> set destination to none
+ frame)]
+ (if (nil? index)
+ (rx/of (add-new-interaction shape (:id frame)))
+ (rx/of (update-interaction shape index
+ (fn [interaction]
+ (cond-> interaction
+ (not (cti/has-destination interaction))
+ (cti/set-action-type :navigate)
+
+ :always
+ (cti/set-destination (:id frame))))))))))))))
+;; --- Overlays
+
+(declare move-overlay-pos)
+(declare finish-move-overlay-pos)
+
+(defn start-move-overlay-pos
+ [index]
+ (ptk/reify ::start-move-overlay-pos
+ ptk/UpdateEvent
+ (update [_ state]
+ (-> state
+ (assoc-in [:workspace-local :move-overlay-to] nil)
+ (assoc-in [:workspace-local :move-overlay-index] index)))
+
+ ptk/WatchEvent
+ (watch [_ state stream]
+ (let [initial-pos @ms/mouse-position
+ selected (wsh/lookup-selected state)
+ stopper (rx/filter ms/mouse-up? stream)]
+ (when (= 1 (count selected))
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ shape (->> state
+ wsh/lookup-selected
+ first
+ (get objects))
+ overlay-pos (-> shape
+ (get-in [:interactions index])
+ :overlay-position)
+ orig-frame (cph/get-frame shape objects)
+ frame-pos (gpt/point (:x orig-frame) (:y orig-frame))
+ offset (-> initial-pos
+ (gpt/subtract overlay-pos)
+ (gpt/subtract frame-pos))]
+ (rx/concat
+ (->> ms/mouse-position
+ (rx/take-until stopper)
+ (rx/map #(move-overlay-pos % frame-pos offset)))
+ (rx/of (finish-move-overlay-pos index frame-pos offset)))))))))
+
+(defn move-overlay-pos
+ [pos frame-pos offset]
+ (ptk/reify ::move-overlay-pos
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [pos (-> pos
+ (gpt/subtract frame-pos)
+ (gpt/subtract offset))]
+ (assoc-in state [:workspace-local :move-overlay-to] pos)))))
+
+(defn finish-move-overlay-pos
+ [index frame-pos offset]
+ (ptk/reify ::finish-move-overlay-pos
+ ptk/UpdateEvent
+ (update [_ state]
+ (-> state
+ (d/dissoc-in [:workspace-local :move-overlay-to])
+ (d/dissoc-in [:workspace-local :move-overlay-index])))
+
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [pos @ms/mouse-position
+ overlay-pos (-> pos
+ (gpt/subtract frame-pos)
+ (gpt/subtract offset))
+
+ page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ shape (->> state
+ wsh/lookup-selected
+ first
+ (get objects))
+
+ interactions (:interactions shape)
+
+ new-interactions
+ (update interactions index
+ #(cti/set-overlay-position % overlay-pos))]
+
+ (rx/of (dch/update-shapes [(:id shape)] #(merge % {:interactions new-interactions})))))))
+
diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs
index 995fb674c..9ba3a9a79 100644
--- a/frontend/src/app/main/data/workspace/libraries.cljs
+++ b/frontend/src/app/main/data/workspace/libraries.cljs
@@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as geom]
+ [app.common.logging :as log]
[app.common.pages :as cp]
[app.common.spec :as us]
[app.common.uuid :as uuid]
@@ -18,10 +19,10 @@
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.libraries-helpers :as dwlh]
[app.main.data.workspace.state-helpers :as wsh]
+ [app.main.data.workspace.undo :as dwu]
[app.main.repo :as rp]
[app.main.store :as st]
[app.util.i18n :refer [tr]]
- [app.util.logging :as log]
[app.util.router :as rt]
[app.util.time :as dt]
[beicon.core :as rx]
@@ -134,10 +135,12 @@
:color color}
uchg {:type :mod-color
:color prev}]
- (rx/of (dch/commit-changes {:redo-changes [rchg]
+ (rx/of (dwu/start-undo-transaction)
+ (dch/commit-changes {:redo-changes [rchg]
:undo-changes [uchg]
:origin it})
- (sync-file (:current-file-id state) file-id))))))
+ (sync-file (:current-file-id state) file-id)
+ (dwu/commit-undo-transaction))))))
(defn delete-color
[{:keys [id] :as params}]
@@ -244,10 +247,12 @@
:typography typography}
uchg {:type :mod-typography
:typography prev}]
- (rx/of (dch/commit-changes {:redo-changes [rchg]
+ (rx/of (dwu/start-undo-transaction)
+ (dch/commit-changes {:redo-changes [rchg]
:undo-changes [uchg]
:origin it})
- (sync-file (:current-file-id state) file-id))))))
+ (sync-file (:current-file-id state) file-id)
+ (dwu/commit-undo-transaction))))))
(defn delete-typography
[id]
@@ -516,12 +521,14 @@
(ptk/reify ::nav-to-component-file
ptk/WatchEvent
(watch [_ state _]
- (let [file (get-in state [:workspace-libraries file-id])
- pparams {:project-id (:project-id file)
- :file-id (:id file)}
- qparams {:page-id (first (get-in file [:data :pages]))
- :layout :assets}]
- (rx/of (rt/nav-new-window :workspace pparams qparams))))))
+ (let [file (get-in state [:workspace-libraries file-id])
+ path-params {:project-id (:project-id file)
+ :file-id (:id file)}
+ query-params {:page-id (first (get-in file [:data :pages]))
+ :layout :assets}]
+ (rx/of (rt/nav-new-window* {:rname :workspace
+ :path-params path-params
+ :query-params query-params}))))))
(defn ext-library-changed
[file-id modified-at revn changes]
diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs
index 226d5b2ba..0f6a4a2e8 100644
--- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs
+++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs
@@ -9,11 +9,11 @@
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as geom]
+ [app.common.logging :as log]
[app.common.pages :as cp]
[app.common.spec :as us]
[app.common.text :as txt]
[app.main.data.workspace.groups :as dwg]
- [app.util.logging :as log]
[cljs.spec.alpha :as s]
[clojure.set :as set]))
diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs
index 202266e79..8e7de7cae 100644
--- a/frontend/src/app/main/data/workspace/path/drawing.cljs
+++ b/frontend/src/app/main/data/workspace/path/drawing.cljs
@@ -7,7 +7,10 @@
(ns app.main.data.workspace.path.drawing
(:require
[app.common.geom.point :as gpt]
+ [app.common.geom.shapes.path :as upg]
[app.common.pages :as cp]
+ [app.common.path.commands :as upc]
+ [app.common.path.shapes-to-path :as upsp]
[app.common.spec :as us]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
@@ -21,9 +24,6 @@
[app.main.data.workspace.path.undo :as undo]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.streams :as ms]
- [app.util.path.commands :as upc]
- [app.util.path.geom :as upg]
- [app.util.path.shapes-to-path :as upsp]
[beicon.core :as rx]
[potok.core :as ptk]))
diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs
index 89331ebd2..9df8b6e9a 100644
--- a/frontend/src/app/main/data/workspace/path/edition.cljs
+++ b/frontend/src/app/main/data/workspace/path/edition.cljs
@@ -8,6 +8,10 @@
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
+ [app.common.geom.shapes.path :as upg]
+ [app.common.path.commands :as upc]
+ [app.common.path.shapes-to-path :as upsp]
+ [app.common.path.subpaths :as ups]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.path.changes :as changes]
@@ -19,10 +23,6 @@
[app.main.data.workspace.path.undo :as undo]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.streams :as ms]
- [app.util.path.commands :as upc]
- [app.util.path.geom :as upg]
- [app.util.path.shapes-to-path :as upsp]
- [app.util.path.subpaths :as ups]
[app.util.path.tools :as upt]
[beicon.core :as rx]
[potok.core :as ptk]))
diff --git a/frontend/src/app/main/data/workspace/path/helpers.cljs b/frontend/src/app/main/data/workspace/path/helpers.cljs
index 9b36e4099..a7d47d238 100644
--- a/frontend/src/app/main/data/workspace/path/helpers.cljs
+++ b/frontend/src/app/main/data/workspace/path/helpers.cljs
@@ -10,10 +10,10 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
+ [app.common.path.commands :as upc]
+ [app.common.path.subpaths :as ups]
[app.main.data.workspace.path.common :as common]
[app.main.streams :as ms]
- [app.util.path.commands :as upc]
- [app.util.path.subpaths :as ups]
[potok.core :as ptk]))
(defn end-path-event? [event]
diff --git a/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs b/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs
new file mode 100644
index 000000000..eae4dfb91
--- /dev/null
+++ b/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs
@@ -0,0 +1,36 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.data.workspace.path.shapes-to-path
+ (:require
+ [app.common.pages :as cp]
+ [app.common.pages.changes-builder :as cb]
+ [app.common.path.shapes-to-path :as upsp]
+ [app.main.data.workspace.changes :as dch]
+ [app.main.data.workspace.state-helpers :as wsh]
+ [beicon.core :as rx]
+ [potok.core :as ptk]))
+
+(defn convert-selected-to-path []
+ (ptk/reify ::convert-selected-to-path
+ ptk/WatchEvent
+ (watch [it state _]
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state)
+ selected (wsh/lookup-selected state)
+
+ children-ids
+ (into #{}
+ (mapcat #(cp/get-children % objects))
+ selected)
+
+ changes
+ (-> (cb/empty-changes it page-id)
+ (cb/with-objects objects)
+ (cb/remove-objects children-ids)
+ (cb/update-shapes selected #(upsp/convert-to-path % objects)))]
+
+ (rx/of (dch/commit-changes changes))))))
diff --git a/frontend/src/app/main/data/workspace/path/state.cljs b/frontend/src/app/main/data/workspace/path/state.cljs
index 229e46256..86ac1731e 100644
--- a/frontend/src/app/main/data/workspace/path/state.cljs
+++ b/frontend/src/app/main/data/workspace/path/state.cljs
@@ -7,7 +7,7 @@
(ns app.main.data.workspace.path.state
(:require
[app.common.data :as d]
- [app.util.path.shapes-to-path :as upsp]))
+ [app.common.path.shapes-to-path :as upsp]))
(defn get-path-id
"Retrieves the currently editing path id"
@@ -31,7 +31,8 @@
[state & ks]
(let [path-loc (get-path-location state)
shape (-> (get-in state path-loc)
- (upsp/convert-to-path))]
+ ;; Empty map because we know the current shape will not have children
+ (upsp/convert-to-path {}))]
(if (empty? ks)
shape
diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs
index b67607ae0..8a8f8a59b 100644
--- a/frontend/src/app/main/data/workspace/path/streams.cljs
+++ b/frontend/src/app/main/data/workspace/path/streams.cljs
@@ -7,12 +7,12 @@
(ns app.main.data.workspace.path.streams
(:require
[app.common.geom.point :as gpt]
+ [app.common.geom.shapes.path :as upg]
[app.common.math :as mth]
[app.main.data.workspace.path.state :as state]
[app.main.snap :as snap]
[app.main.store :as st]
[app.main.streams :as ms]
- [app.util.path.geom :as upg]
[beicon.core :as rx]
[okulary.core :as l]
[potok.core :as ptk]))
diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs
index 18f262743..fce88f9db 100644
--- a/frontend/src/app/main/data/workspace/path/tools.cljs
+++ b/frontend/src/app/main/data/workspace/path/tools.cljs
@@ -6,13 +6,13 @@
(ns app.main.data.workspace.path.tools
(:require
+ [app.common.path.shapes-to-path :as upsp]
+ [app.common.path.subpaths :as ups]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.path.changes :as changes]
[app.main.data.workspace.path.state :as st]
[app.main.data.workspace.state-helpers :as wsh]
- [app.util.path.shapes-to-path :as upsp]
- [app.util.path.subpaths :as ups]
[app.util.path.tools :as upt]
[beicon.core :as rx]
[potok.core :as ptk]))
diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs
index 86208467f..ce5ff4160 100644
--- a/frontend/src/app/main/data/workspace/selection.cljs
+++ b/frontend/src/app/main/data/workspace/selection.cljs
@@ -12,6 +12,7 @@
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.spec :as us]
+ [app.common.types.interactions :as cti]
[app.common.uuid :as uuid]
[app.main.data.modal :as md]
[app.main.data.workspace.changes :as dch]
@@ -288,12 +289,17 @@
fit."
[objects page-id unames ids delta]
(let [unames (volatile! unames)
- update-unames! (fn [new-name] (vswap! unames conj new-name))]
+ update-unames! (fn [new-name] (vswap! unames conj new-name))
+ all-ids (reduce (fn [ids-set id]
+ (into ids-set (cons id (cp/get-children id objects))))
+ #{}
+ ids)
+ ids-map (into {} (map #(vector % (uuid/next)) all-ids))]
(loop [ids (seq ids)
chgs []]
(if ids
(let [id (first ids)
- result (prepare-duplicate-change objects page-id unames update-unames! id delta)
+ result (prepare-duplicate-change objects page-id unames update-unames! ids-map id delta)
result (if (vector? result) result [result])]
(recur
(next ids)
@@ -313,22 +319,27 @@
(-> changes (update-indices index-map))))
(defn- prepare-duplicate-change
- [objects page-id unames update-unames! id delta]
+ [objects page-id unames update-unames! ids-map id delta]
(let [obj (get objects id)]
(if (= :frame (:type obj))
- (prepare-duplicate-frame-change objects page-id unames update-unames! obj delta)
- (prepare-duplicate-shape-change objects page-id unames update-unames! obj delta (:frame-id obj) (:parent-id obj)))))
+ (prepare-duplicate-frame-change objects page-id unames update-unames! ids-map obj delta)
+ (prepare-duplicate-shape-change objects page-id unames update-unames! ids-map obj delta (:frame-id obj) (:parent-id obj)))))
(defn- prepare-duplicate-shape-change
- [objects page-id unames update-unames! obj delta frame-id parent-id]
+ [objects page-id unames update-unames! ids-map obj delta frame-id parent-id]
(when (some? obj)
- (let [id (uuid/next)
+ (let [new-id (ids-map (:id obj))
+ parent-id (or parent-id frame-id)
name (dwc/generate-unique-name @unames (:name obj))
_ (update-unames! name)
- renamed-obj (assoc obj :id id :name name)
- moved-obj (geom/move renamed-obj delta)
- parent-id (or parent-id frame-id)
+ new-obj (-> obj
+ (assoc :id new-id
+ :name name
+ :frame-id frame-id)
+ (dissoc :shapes)
+ (geom/move delta)
+ (d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
children-changes
(loop [result []
@@ -337,47 +348,45 @@
(if (nil? cid)
result
(let [obj (get objects cid)
- changes (prepare-duplicate-shape-change objects page-id unames update-unames! obj delta frame-id id)]
+ changes (prepare-duplicate-shape-change objects page-id unames update-unames! ids-map obj delta frame-id new-id)]
(recur
(into result changes)
(first cids)
- (rest cids)))))
+ (rest cids)))))]
- reframed-obj (-> moved-obj
- (assoc :frame-id frame-id)
- (dissoc :shapes))]
(into [{:type :add-obj
- :id id
+ :id new-id
:page-id page-id
:old-id (:id obj)
:frame-id frame-id
:parent-id parent-id
:ignore-touched true
- :obj (dissoc reframed-obj :shapes)}]
+ :obj new-obj}]
children-changes))))
(defn- prepare-duplicate-frame-change
- [objects page-id unames update-unames! obj delta]
- (let [frame-id (uuid/next)
+ [objects page-id unames update-unames! ids-map obj delta]
+ (let [new-id (ids-map (:id obj))
frame-name (dwc/generate-unique-name @unames (:name obj))
_ (update-unames! frame-name)
sch (->> (map #(get objects %) (:shapes obj))
- (mapcat #(prepare-duplicate-shape-change objects page-id unames update-unames! % delta frame-id frame-id)))
+ (mapcat #(prepare-duplicate-shape-change objects page-id unames update-unames! ids-map % delta new-id new-id)))
- frame (-> obj
- (assoc :id frame-id)
- (assoc :name frame-name)
- (assoc :frame-id uuid/zero)
- (assoc :shapes [])
- (geom/move delta))
+ new-frame (-> obj
+ (assoc :id new-id
+ :name frame-name
+ :frame-id uuid/zero
+ :shapes [])
+ (geom/move delta)
+ (d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
fch {:type :add-obj
:old-id (:id obj)
:page-id page-id
- :id frame-id
+ :id new-id
:frame-id uuid/zero
- :obj frame}]
+ :obj new-frame}]
(into [fch] sch)))
@@ -420,48 +429,49 @@
(gpt/point (+ (:width obj) 50) 0)
(gpt/point 0 0))
- (let [obj-original (get objects id-original)
- obj-duplicated (get objects id-duplicated)
- distance (gpt/subtract (gpt/point obj-duplicated)
- (gpt/point obj-original))
- new-pos (gpt/add (gpt/point obj-duplicated) distance)
- delta (gpt/subtract new-pos (gpt/point obj))]
- delta))))
+ (let [pt-original (-> (get objects id-original) :selrect gpt/point)
+ pt-duplicated (-> (get objects id-duplicated) :selrect gpt/point)
+ pt-obj (-> obj :selrect gpt/point)
+ distance (gpt/subtract pt-duplicated pt-original)
+ new-pos (gpt/add pt-duplicated distance)]
-(def duplicate-selected
+ (gpt/subtract new-pos pt-obj)))))
+
+(defn duplicate-selected [move-delta?]
(ptk/reify ::duplicate-selected
ptk/WatchEvent
(watch [it state _]
- (let [page-id (:current-page-id state)
- objects (wsh/lookup-page-objects state page-id)
- selected (wsh/lookup-selected state)
- delta (if (= (count selected) 1)
- (let [obj (get objects (first selected))]
- (calc-duplicate-delta obj state objects))
- (gpt/point 0 0))
+ (when (nil? (get-in state [:workspace-local :transform]))
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ selected (wsh/lookup-selected state)
+ delta (if (and move-delta? (= (count selected) 1))
+ (let [obj (get objects (first selected))]
+ (calc-duplicate-delta obj state objects))
+ (gpt/point 0 0))
- unames (dwc/retrieve-used-names objects)
+ unames (dwc/retrieve-used-names objects)
- rchanges (->> (prepare-duplicate-changes objects page-id unames selected delta)
- (duplicate-changes-update-indices objects selected))
+ rchanges (->> (prepare-duplicate-changes objects page-id unames selected delta)
+ (duplicate-changes-update-indices objects selected))
- uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
- (reverse rchanges))
+ uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
+ (reverse rchanges))
- id-original (when (= (count selected) 1) (first selected))
+ id-original (when (= (count selected) 1) (first selected))
- selected (->> rchanges
- (filter #(selected (:old-id %)))
- (map #(get-in % [:obj :id]))
- (into (d/ordered-set)))
+ selected (->> rchanges
+ (filter #(selected (:old-id %)))
+ (map #(get-in % [:obj :id]))
+ (into (d/ordered-set)))
- id-duplicated (when (= (count selected) 1) (first selected))]
+ id-duplicated (when (= (count selected) 1) (first selected))]
- (rx/of (dch/commit-changes {:redo-changes rchanges
- :undo-changes uchanges
- :origin it})
- (select-shapes selected)
- (memorize-duplicated id-original id-duplicated))))))
+ (rx/of (dch/commit-changes {:redo-changes rchanges
+ :undo-changes uchanges
+ :origin it})
+ (select-shapes selected)
+ (memorize-duplicated id-original id-duplicated)))))))
(defn change-hover-state
[id value]
diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs
index d589df2e8..87d4735a7 100644
--- a/frontend/src/app/main/data/workspace/shortcuts.cljs
+++ b/frontend/src/app/main/data/workspace/shortcuts.cljs
@@ -119,7 +119,7 @@
:duplicate {:tooltip (ds/meta "D")
:command (ds/c-mod "d")
- :fn #(st/emit! dw/duplicate-selected)}
+ :fn #(st/emit! (dw/duplicate-selected true))}
:undo {:tooltip (ds/meta "Z")
:command (ds/c-mod "z")
@@ -260,6 +260,23 @@
:command ["alt" "."]
:type "keyup"
:fn #(st/emit! (dw/toggle-distances-display false))}
+
+ :boolean-union {:tooltip (ds/alt "U")
+ :command "alt+u"
+ :fn #(st/emit! (dw/create-bool :union))}
+
+ :boolean-difference {:tooltip (ds/alt "D")
+ :command "alt+d"
+ :fn #(st/emit! (dw/create-bool :difference))}
+
+ :boolean-intersection {:tooltip (ds/alt "I")
+ :command "alt+i"
+ :fn #(st/emit! (dw/create-bool :intersection))}
+
+ :boolean-exclude {:tooltip (ds/alt "E")
+ :command "alt+e"
+ :fn #(st/emit! (dw/create-bool :exclude))}
+
})
(defn get-tooltip [shortcut]
diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs
index 3193b549d..2b253738a 100644
--- a/frontend/src/app/main/data/workspace/svg_upload.cljs
+++ b/frontend/src/app/main/data/workspace/svg_upload.cljs
@@ -178,7 +178,8 @@
:y (+ y offset-y)}
(gsh/setup-selrect)
(assoc :svg-attrs (-> (:attrs svg-data)
- (dissoc :viewBox :xmlns))))))
+ (dissoc :viewBox :xmlns)
+ (d/without-keys usvg/inheritable-props))))))
(defn create-group [name frame-id svg-data {:keys [attrs]}]
(let [svg-transform (usvg/parse-transform (:transform attrs))
@@ -368,16 +369,16 @@
;; SVG graphic elements
;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use
(let [shape (-> (case tag
- (:g :a :svg) (create-group name frame-id svg-data element-data)
- :rect (create-rect-shape name frame-id svg-data element-data)
+ (:g :a :svg) (create-group name frame-id svg-data element-data)
+ :rect (create-rect-shape name frame-id svg-data element-data)
(:circle
- :ellipse) (create-circle-shape name frame-id svg-data element-data)
- :path (create-path-shape name frame-id svg-data element-data)
- :polyline (create-path-shape name frame-id svg-data (-> element-data usvg/polyline->path))
- :polygon (create-path-shape name frame-id svg-data (-> element-data usvg/polygon->path))
- :line (create-path-shape name frame-id svg-data (-> element-data usvg/line->path))
- :image (create-image-shape name frame-id svg-data element-data)
- #_other (create-raw-svg name frame-id svg-data element-data))
+ :ellipse) (create-circle-shape name frame-id svg-data element-data)
+ :path (create-path-shape name frame-id svg-data element-data)
+ :polyline (create-path-shape name frame-id svg-data (-> element-data usvg/polyline->path))
+ :polygon (create-path-shape name frame-id svg-data (-> element-data usvg/polygon->path))
+ :line (create-path-shape name frame-id svg-data (-> element-data usvg/line->path))
+ :image (create-image-shape name frame-id svg-data element-data)
+ #_other (create-raw-svg name frame-id svg-data element-data))
)
shape (when (some? shape)
@@ -387,7 +388,7 @@
(setup-stroke)))
children (cond->> (:content element-data)
- (= tag :g)
+ (or (= tag :g) (= tag :svg))
(mapv #(usvg/inherit-attributes attrs %)))]
[shape children]))))
@@ -487,11 +488,15 @@
;; Creates the root shape
changes (dwc/add-shape-changes page-id objects selected root-shape false)
+ root-attrs (-> (:attrs svg-data)
+ (usvg/format-styles))
+
;; Reduces the children to create the changes to add the children shapes
[_ [rchanges uchanges]]
(reduce (partial add-svg-child-changes page-id objects selected frame-id root-id svg-data)
[unames changes]
- (d/enumerate (:content svg-data)))
+ (d/enumerate (->> (:content svg-data)
+ (mapv #(usvg/inherit-attributes root-attrs %)))))
reg-objects-action {:type :reg-objects
:page-id page-id
diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs
index 841bf0181..53e123c16 100644
--- a/frontend/src/app/main/data/workspace/texts.cljs
+++ b/frontend/src/app/main/data/workspace/texts.cljs
@@ -180,12 +180,10 @@
shape (get objects id)
merge-fn (fn [node attrs]
- (reduce-kv (fn [node k v]
- (if (= (get node k) v)
- (dissoc node k)
- (assoc node k v)))
- node
- attrs))
+ (reduce-kv
+ (fn [node k v] (assoc node k v))
+ node
+ attrs))
update-fn #(update-shape % txt/is-paragraph-node? merge-fn attrs)
shape-ids (cond (= (:type shape) :text) [id]
diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs
index 9426033c8..d7574e330 100644
--- a/frontend/src/app/main/data/workspace/transforms.cljs
+++ b/frontend/src/app/main/data/workspace/transforms.cljs
@@ -70,23 +70,19 @@
(defn- fix-init-point
"Fix the initial point so the resizes are accurate"
[initial handler shape]
- (let [{:keys [x y width height]} (:selrect shape)
- {:keys [rotation]} shape
- rotation (or rotation 0)]
- (if (= rotation 0)
- (cond-> initial
- (contains? #{:left :top-left :bottom-left} handler)
- (assoc :x x)
+ (let [{:keys [x y width height]} (:selrect shape)]
+ (cond-> initial
+ (contains? #{:left :top-left :bottom-left} handler)
+ (assoc :x x)
- (contains? #{:right :top-right :bottom-right} handler)
- (assoc :x (+ x width))
+ (contains? #{:right :top-right :bottom-right} handler)
+ (assoc :x (+ x width))
- (contains? #{:top :top-right :top-left} handler)
- (assoc :y y)
+ (contains? #{:top :top-right :top-left} handler)
+ (assoc :y y)
- (contains? #{:bottom :bottom-right :bottom-left} handler)
- (assoc :y (+ y height)))
- initial)))
+ (contains? #{:bottom :bottom-right :bottom-left} handler)
+ (assoc :y (+ y height)))))
(defn finish-transform []
(ptk/reify ::finish-transform
@@ -117,8 +113,9 @@
(declare clear-local-transform)
(defn- set-modifiers
- ([ids] (set-modifiers ids nil))
- ([ids modifiers]
+ ([ids] (set-modifiers ids nil false))
+ ([ids modifiers] (set-modifiers ids modifiers false))
+ ([ids modifiers ignore-constraints]
(us/verify (s/coll-of uuid?) ids)
(ptk/reify ::set-modifiers
ptk/UpdateEvent
@@ -136,7 +133,8 @@
(get objects id)
modifiers
nil
- nil)))
+ nil
+ ignore-constraints)))
state
ids))))))
@@ -201,7 +199,7 @@
(dwu/commit-undo-transaction))))))
(defn- set-modifiers-recursive
- [modif-tree objects shape modifiers root transformed-root]
+ [modif-tree objects shape modifiers root transformed-root ignore-constraints]
(let [children (->> (get shape :shapes [])
(map #(get objects %)))
@@ -215,13 +213,15 @@
set-child (fn [modif-tree child]
(let [child-modifiers (gsh/calc-child-modifiers shape
child
- modifiers)]
+ modifiers
+ ignore-constraints)]
(set-modifiers-recursive modif-tree
objects
child
child-modifiers
root
- transformed-root)))]
+ transformed-root
+ ignore-constraints)))]
(reduce set-child
(assoc-in modif-tree [(:id shape) :modifiers] modifiers)
children)))
@@ -285,10 +285,19 @@
(letfn [(resize [shape initial layout [point lock? center? point-snap]]
(let [{:keys [width height]} (:selrect shape)
{:keys [rotation]} shape
+
+ shape-center (gsh/center-shape shape)
+ shape-transform (:transform shape (gmt/matrix))
+ shape-transform-inverse (:transform-inverse shape (gmt/matrix))
+
rotation (or rotation 0)
+ initial (gsh/transform-point-center initial shape-center shape-transform-inverse)
initial (fix-init-point initial handler shape)
+ point (gsh/transform-point-center (if (= rotation 0) point-snap point)
+ shape-center shape-transform-inverse)
+
shapev (-> (gpt/point width height))
scale-text (:scale-text layout)
@@ -300,8 +309,7 @@
handler-mult (let [[x y] (handler-multipliers handler)] (gpt/point x y))
;; Difference between the origin point in the coordinate system of the rotation
- deltav (-> (gpt/to-vec initial (if (= rotation 0) point-snap point))
- (gpt/transform (gmt/rotate-matrix (- rotation)))
+ deltav (-> (gpt/to-vec initial point)
(gpt/multiply handler-mult))
;; Resize vector
@@ -317,26 +325,25 @@
scalev)
;; Resize origin point given the selected handler
- origin (handler-resize-origin (:selrect shape) handler)
+ handler-origin (handler-resize-origin (:selrect shape) handler)
- shape-center (gsh/center-shape shape)
- shape-transform (:transform shape (gmt/matrix))
- shape-transform-inverse (:transform-inverse shape (gmt/matrix))
;; If we want resize from center, displace the shape
;; so it is still centered after resize.
- displacement (when center?
- (-> shape-center
- (gpt/subtract origin)
- (gpt/multiply scalev)
- (gpt/add origin)
- (gpt/subtract shape-center)
- (gpt/multiply (gpt/point -1 -1))
- (gpt/transform shape-transform)))
+ displacement
+ (when center?
+ (-> shape-center
+ (gpt/subtract handler-origin)
+ (gpt/multiply scalev)
+ (gpt/add handler-origin)
+ (gpt/subtract shape-center)
+ (gpt/multiply (gpt/point -1 -1))
+ (gpt/transform shape-transform)))
- origin (cond-> (gsh/transform-point-center origin shape-center shape-transform)
- (some? displacement)
- (gpt/add displacement))
+ resize-origin
+ (cond-> (gsh/transform-point-center handler-origin shape-center shape-transform)
+ (some? displacement)
+ (gpt/add displacement))
displacement (when (some? displacement)
(gmt/translate-matrix displacement))]
@@ -344,7 +351,7 @@
(rx/of (set-modifiers ids
{:displacement displacement
:resize-vector scalev
- :resize-origin origin
+ :resize-origin resize-origin
:resize-transform shape-transform
:resize-scale-text scale-text
:resize-transform-inverse shape-transform-inverse}))))
@@ -407,7 +414,8 @@
shape
modifiers
nil
- nil))))
+ nil
+ false))))
state
ids)))
@@ -505,7 +513,7 @@
(if alt?
;; When alt is down we start a duplicate+move
(rx/of (start-move-duplicate initial)
- dws/duplicate-selected)
+ (dws/duplicate-selected false))
;; Otherwise just plain old move
(rx/of (start-move initial selected)))))))))))
@@ -712,7 +720,8 @@
(rx/of (set-modifiers selected
{:resize-vector (gpt/point -1.0 1.0)
:resize-origin origin
- :displacement (gmt/translate-matrix (gpt/point (- (:width selrect)) 0))})
+ :displacement (gmt/translate-matrix (gpt/point (- (:width selrect)) 0))}
+ true)
(apply-modifiers selected))))))
(defn flip-vertical-selected []
@@ -728,7 +737,8 @@
(rx/of (set-modifiers selected
{:resize-vector (gpt/point 1.0 -1.0)
:resize-origin origin
- :displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))})
+ :displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))}
+ true)
(apply-modifiers selected))))))
diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs
new file mode 100644
index 000000000..39350c180
--- /dev/null
+++ b/frontend/src/app/main/errors.cljs
@@ -0,0 +1,172 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.errors
+ "Generic error handling"
+ (:require
+ [app.common.exceptions :as ex]
+ [app.config :as cf]
+ [app.main.data.messages :as dm]
+ [app.main.data.users :as du]
+ [app.main.sentry :as sentry]
+ [app.main.store :as st]
+ [app.util.router :as rt]
+ [app.util.timers :as ts]
+ [cljs.pprint :refer [pprint]]
+ [cuerdas.core :as str]
+ [expound.alpha :as expound]
+ [potok.core :as ptk]))
+
+(defn on-error
+ "A general purpose error handler."
+ [error]
+ (cond
+ (instance? ExceptionInfo error)
+ (-> error sentry/capture-exception ex-data ptk/handle-error)
+
+ (map? error)
+ (ptk/handle-error error)
+
+ :else
+ (let [hint (ex-message error)
+ msg (str "Internal Error: " hint)]
+ (sentry/capture-exception error)
+ (ts/schedule (st/emitf (rt/assign-exception error)))
+
+ (js/console.group msg)
+ (ex/ignoring (js/console.error error))
+ (js/console.groupEnd msg))))
+
+;; Set the main potok error handler
+(reset! st/on-error on-error)
+
+;; We receive a explicit authentication error; this explicitly clears
+;; all profile data and redirect the user to the login page. This is
+;; here and not in app.main.errors because of circular dependency.
+(defmethod ptk/handle-error :authentication
+ [_]
+ (ts/schedule (st/emitf (du/logout))))
+
+
+;; That are special case server-errors that should be treated
+;; differently.
+(derive :not-found ::exceptional-state)
+(derive :bad-gateway ::exceptional-state)
+(derive :service-unavailable ::exceptional-state)
+
+(defmethod ptk/handle-error ::exceptional-state
+ [error]
+ (ts/schedule
+ (st/emitf (rt/assign-exception error))))
+
+;; Error that happens on an active bussines model validation does not
+;; passes an validation (example: profile can't leave a team). From
+;; the user perspective a error flash message should be visualized but
+;; user can continue operate on the application.
+(defmethod ptk/handle-error :validation
+ [error]
+ (ts/schedule
+ (st/emitf
+ (dm/show {:content "Unexpected validation error."
+ :type :error
+ :timeout 3000})))
+
+ ;; Print to the console some debug info.
+ (js/console.group "Validation Error:")
+ (ex/ignoring
+ (js/console.info
+ (with-out-str
+ (pprint (dissoc error :explain))))
+ (when-let [explain (:explain error)]
+ (js/console.error explain)))
+ (js/console.groupEnd "Validation Error:"))
+
+
+;; Error on parsing an SVG
+(defmethod ptk/handle-error :svg-parser
+ [_]
+ (ts/schedule
+ (st/emitf
+ (dm/show {:content "SVG is invalid or malformed"
+ :type :error
+ :timeout 3000}))))
+
+(defmethod ptk/handle-error :comment-error
+ [_]
+ (ts/schedule
+ (st/emitf
+ (dm/show {:content "There was an error with the comment"
+ :type :error
+ :timeout 3000}))))
+
+;; This is a pure frontend error that can be caused by an active
+;; assertion (assertion that is preserved on production builds). From
+;; the user perspective this should be treated as internal error.
+(defmethod ptk/handle-error :assertion
+ [{:keys [data stack message hint context] :as error}]
+ (let [message (or message hint)
+ message (str "Internal Assertion Error: " message)
+ context (str/fmt "ns: '%s'\nname: '%s'\nfile: '%s:%s'"
+ (:ns context)
+ (:name context)
+ (str cf/public-uri "js/cljs-runtime/" (:file context))
+ (:line context))]
+ (ts/schedule
+ (st/emitf
+ (dm/show {:content "Internal error: assertion."
+ :type :error
+ :timeout 3000})))
+
+ ;; Print to the console some debugging info
+ (js/console.group message)
+ (js/console.info context)
+ (js/console.groupCollapsed "Stack Trace")
+ (js/console.info stack)
+ (js/console.groupEnd "Stack Trace")
+ (js/console.error (with-out-str (expound/printer data)))
+ (js/console.groupEnd message)))
+
+;; This happens when the backed server fails to process the
+;; request. This can be caused by an internal assertion or any other
+;; uncontrolled error.
+(defmethod ptk/handle-error :server-error
+ [{:keys [data hint] :as error}]
+ (let [hint (or hint (:hint data) (:message data))
+ info (with-out-str (pprint (dissoc data :explain)))
+ expl (:explain data)
+ msg (str "Internal Server Error: " hint)]
+
+ (ts/schedule
+ (st/emitf
+ (dm/show {:content "Something wrong has happened (on backend)."
+ :type :error
+ :timeout 3000})))
+
+ (js/console.group msg)
+ (js/console.info info)
+ (when expl (js/console.error expl))
+ (js/console.groupEnd msg)))
+
+(defn on-unhandled-error
+ [error]
+ (if (instance? ExceptionInfo error)
+ (-> error sentry/capture-exception ex-data ptk/handle-error)
+ (let [hint (ex-message error)
+ msg (str "Unhandled Internal Error: " hint)]
+ (sentry/capture-exception error)
+ (ts/schedule (st/emitf (rt/assign-exception error)))
+ (js/console.group msg)
+ (ex/ignoring (js/console.error error))
+ (js/console.groupEnd msg))))
+
+(defonce uncaught-error-handler
+ (letfn [(on-error [event]
+ (.preventDefault ^js event)
+ (some-> (unchecked-get event "error")
+ (on-unhandled-error)))]
+ (.addEventListener js/window "error" on-error)
+ (fn []
+ (.removeEventListener js/window "error" on-error))))
diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs
index 5c0217152..726562f4c 100644
--- a/frontend/src/app/main/exports.cljs
+++ b/frontend/src/app/main/exports.cljs
@@ -14,6 +14,7 @@
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.uuid :as uuid]
+ [app.main.ui.shapes.bool :as bool]
[app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.embed :as embed]
[app.main.ui.shapes.export :as use]
@@ -81,6 +82,18 @@
:is-child-selected? true
:childs childs}]))))
+(defn bool-wrapper-factory
+ [objects]
+ (let [shape-wrapper (shape-wrapper-factory objects)
+ bool-shape (bool/bool-shape shape-wrapper)]
+ (mf/fnc bool-wrapper
+ [{:keys [shape frame] :as props}]
+ (let [childs (->> (cp/get-children (:id shape) objects)
+ (select-keys objects))]
+ [:& bool-shape {:frame frame
+ :shape shape
+ :childs childs}]))))
+
(defn svg-raw-wrapper-factory
[objects]
(let [shape-wrapper (shape-wrapper-factory objects)
@@ -104,9 +117,10 @@
[objects]
(mf/fnc shape-wrapper
[{:keys [frame shape] :as props}]
- (let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper-factory objects))
+ (let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper-factory objects))
svg-raw-wrapper (mf/use-memo (mf/deps objects) #(svg-raw-wrapper-factory objects))
- frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
+ bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects))
+ frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
(when (and shape (not (:hidden shape)))
(let [shape (-> (gsh/transform-shape shape)
(gsh/translate-to-frame frame))
@@ -122,6 +136,7 @@
:circle [:> circle/circle-shape opts]
:frame [:> frame-wrapper {:shape shape}]
:group [:> group-wrapper {:shape shape :frame frame}]
+ :bool [:> bool-wrapper {:shape shape :frame frame}]
nil)]
;; Don't wrap svg elements inside a otherwise some can break
diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs
index dbd50cde3..94187a561 100644
--- a/frontend/src/app/main/fonts.cljs
+++ b/frontend/src/app/main/fonts.cljs
@@ -9,11 +9,11 @@
(:require-macros [app.main.fonts :refer [preload-gfonts]])
(:require
[app.common.data :as d]
+ [app.common.logging :as log]
[app.common.text :as txt]
[app.config :as cf]
[app.util.dom :as dom]
[app.util.http :as http]
- [app.util.logging :as log]
[app.util.object :as obj]
[beicon.core :as rx]
[clojure.set :as set]
@@ -23,7 +23,7 @@
[okulary.core :as l]
[promesa.core :as p]))
-(log/set-level! :trace)
+(log/set-level! :warn)
(def google-fonts
(preload-gfonts "fonts/gfonts.2020.04.23.json"))
diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs
index 4dcbac803..3612d8490 100644
--- a/frontend/src/app/main/refs.cljs
+++ b/frontend/src/app/main/refs.cljs
@@ -1,4 +1,3 @@
-
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
@@ -11,6 +10,7 @@
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
+ [app.common.path.commands :as upc]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.store :as st]
[okulary.core :as l]))
@@ -122,6 +122,10 @@
:show-distances?])
workspace-local =))
+(def local-displacement
+ (l/derived #(select-keys % [:modifiers :selected])
+ workspace-local =))
+
(def selected-zoom
(l/derived :zoom workspace-local))
@@ -239,16 +243,44 @@
([ids {:keys [with-modifiers?]
:or { with-modifiers? false }}]
- (l/derived (fn [state]
- (let [objects (wsh/lookup-page-objects state)
- modifiers (:workspace-modifiers state)
- objects (cond-> objects
- with-modifiers?
- (gsh/merge-modifiers modifiers))
- xform (comp (map #(get objects %))
- (remove nil?))]
- (into [] xform ids)))
- st/state =)))
+ (let [selector
+ (fn [state]
+ (let [objects (wsh/lookup-page-objects state)
+ modifiers (:workspace-modifiers state)
+ objects (cond-> objects
+ with-modifiers?
+ (gsh/merge-modifiers modifiers))
+ xform (comp (map #(get objects %))
+ (remove nil?))]
+ (into [] xform ids)))]
+ (l/derived selector st/state =))))
+
+(defn- set-content-modifiers [state]
+ (fn [id shape]
+ (let [content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers])]
+ (if (some? content-modifiers)
+ (update shape :content upc/apply-content-modifiers content-modifiers)
+ shape))))
+
+(defn select-children [id]
+ (let [selector
+ (fn [state]
+ (let [objects (wsh/lookup-page-objects state)
+
+ modifiers (-> (:workspace-modifiers state))
+ {selected :selected disp-modifiers :modifiers}
+ (-> (:workspace-local state)
+ (select-keys [:modifiers :selected]))
+
+ modifiers
+ (d/deep-merge
+ modifiers
+ (into {} (map #(vector % {:modifiers disp-modifiers})) selected))]
+
+ (as-> (cp/select-children id objects) $
+ (gsh/merge-modifiers $ modifiers)
+ (d/mapm (set-content-modifiers state) $))))]
+ (l/derived selector st/state =)))
(def selected-data
(l/derived #(let [selected (wsh/lookup-selected %)
diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs
index 7ec498c40..9fa42387c 100644
--- a/frontend/src/app/main/repo.cljs
+++ b/frontend/src/app/main/repo.cljs
@@ -48,6 +48,7 @@
[id params]
(->> (http/send! {:method :get
:uri (u/join base-uri "api/rpc/query/" (name id))
+ :credentials "include"
:query params})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))
@@ -58,6 +59,7 @@
[id params]
(->> (http/send! {:method :post
:uri (u/join base-uri "api/rpc/mutation/" (name id))
+ :credentials "include"
:body (http/transit-data params)})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))
@@ -87,7 +89,10 @@
[_ {:keys [provider] :as params}]
(let [uri (u/join base-uri "api/auth/oauth/" (d/name provider))
params (dissoc params :provider)]
- (->> (http/send! {:method :post :uri uri :query params})
+ (->> (http/send! {:method :post
+ :uri uri
+ :credentials "include"
+ :query params})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response))))
@@ -95,6 +100,7 @@
[_ params]
(->> (http/send! {:method :post
:uri (u/join base-uri "api/feedback")
+ :credentials "include"
:body (http/transit-data params)})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))
@@ -104,6 +110,7 @@
(->> (http/send! {:method :post
:uri (u/join base-uri "export")
:body (http/transit-data params)
+ :credentials "include"
:response-type :blob})
(rx/mapcat handle-response)))
@@ -112,6 +119,7 @@
(->> (http/send! {:method :post
:uri (u/join base-uri "export-frames")
:body (http/transit-data params)
+ :credentials "include"
:response-type :blob})
(rx/mapcat handle-response)))
@@ -123,6 +131,7 @@
[id params]
(->> (http/send! {:method :post
:uri (u/join base-uri "api/rpc/mutation/" (name id))
+ :credentials "include"
:body (http/form-data params)})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response)))
diff --git a/frontend/src/app/main/sentry.cljs b/frontend/src/app/main/sentry.cljs
new file mode 100644
index 000000000..d7cdcfd9a
--- /dev/null
+++ b/frontend/src/app/main/sentry.cljs
@@ -0,0 +1,60 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.sentry
+ "Sentry integration."
+ (:require
+ ["@sentry/browser" :as sentry]
+ [app.common.exceptions :as ex]
+ [app.common.uuid :as uuid]
+ [app.config :as cf]
+ [app.main.refs :as refs]))
+
+(defn- setup-profile!
+ [profile]
+ (if (or (= uuid/zero (:id profile))
+ (nil? profile))
+ (sentry/setUser nil)
+ (sentry/setUser #js {:id (str (:id profile))})))
+
+(defn init!
+ []
+ (setup-profile! @refs/profile)
+ (when cf/sentry-dsn
+ (sentry/init
+ #js {:dsn cf/sentry-dsn
+ :autoSessionTracking false
+ :attachStacktrace false
+ :release (str "frontend@" (:base @cf/version))
+ :maxBreadcrumbs 20
+ :beforeBreadcrumb (fn [breadcrumb _hint]
+ (let [category (.-category ^js breadcrumb)]
+ (if (= category "navigate")
+ breadcrumb
+ nil)))
+ :tracesSampleRate 1.0})
+
+ (add-watch refs/profile ::profile
+ (fn [_ _ _ profile]
+ (setup-profile! profile)))
+
+ (add-watch refs/route ::route
+ (fn [_ _ _ route]
+ (sentry/addBreadcrumb
+ #js {:category "navigate",
+ :message (str "path: " (:path route))
+ :level (.-Info ^js sentry/Severity)})))))
+
+(defn capture-exception
+ [err]
+ (when cf/sentry-dsn
+ (when (ex/ex-info? err)
+ (sentry/setContext "ex-data", (clj->js (ex-data err))))
+ (sentry/captureException err))
+ err)
+
+
+
diff --git a/frontend/src/app/main/store.cljs b/frontend/src/app/main/store.cljs
index 4fd743ecd..78a93c1a8 100644
--- a/frontend/src/app/main/store.cljs
+++ b/frontend/src/app/main/store.cljs
@@ -17,11 +17,15 @@
(enable-console-print!)
-(def ^:dynamic *on-error* identity)
-
(defonce loader (l/atom false))
-(defonce state (ptk/store {:resolve ptk/resolve}))
-(defonce stream (ptk/input-stream state))
+(defonce on-error (l/atom identity))
+
+(defonce state
+ (ptk/store {:resolve ptk/resolve
+ :on-error (fn [e] (@on-error e))}))
+
+(defonce stream
+ (ptk/input-stream state))
(defonce last-events
(let [buffer (atom #queue [])
diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs
index bcec7a1b7..b005dc11c 100644
--- a/frontend/src/app/main/ui.cljs
+++ b/frontend/src/app/main/ui.cljs
@@ -6,12 +6,6 @@
(ns app.main.ui
(:require
- [app.common.exceptions :as ex]
- [app.common.spec :as us]
- [app.config :as cf]
- [app.main.data.events :as ev]
- [app.main.data.messages :as dm]
- [app.main.data.users :as du]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.auth :refer [auth]]
@@ -28,77 +22,12 @@
[app.main.ui.static :as static]
[app.main.ui.viewer :as viewer]
[app.main.ui.workspace :as workspace]
- [app.util.timers :as ts]
- [cljs.pprint :refer [pprint]]
- [cljs.spec.alpha :as s]
- [cuerdas.core :as str]
- [expound.alpha :as expound]
- [potok.core :as ptk]
+ [app.util.router :as rt]
[rumext.alpha :as mf]))
-;; --- Routes
-
-(s/def ::page-id ::us/uuid)
-(s/def ::file-id ::us/uuid)
-(s/def ::section ::us/keyword)
-(s/def ::index ::us/integer)
-(s/def ::token (s/nilable ::us/not-empty-string))
-(s/def ::share-id ::us/uuid)
-
-(s/def ::viewer-path-params
- (s/keys :req-un [::file-id]))
-
-(s/def ::viewer-query-params
- (s/keys :req-un [::index]
- :opt-un [::share-id ::section ::page-id]))
-
-(def routes
- [["/auth"
- ["/login" :auth-login]
- (when (contains? @cf/flags :registration)
- ["/register" :auth-register])
- (when (contains? @cf/flags :registration)
- ["/register/validate" :auth-register-validate])
- (when (contains? @cf/flags :registration)
- ["/register/success" :auth-register-success])
- ["/recovery/request" :auth-recovery-request]
- ["/recovery" :auth-recovery]
- ["/verify-token" :auth-verify-token]]
-
- ["/settings"
- ["/profile" :settings-profile]
- ["/password" :settings-password]
- ["/feedback" :settings-feedback]
- ["/options" :settings-options]]
-
- ["/view/:file-id"
- {:name :viewer
- :conform
- {:path-params ::viewer-path-params
- :query-params ::viewer-query-params}}]
-
- (when *assert*
- ["/debug/icons-preview" :debug-icons-preview])
-
- ;; Used for export
- ["/render-object/:file-id/:page-id/:object-id" :render-object]
- ["/render-sprite/:file-id" :render-sprite]
-
- ["/dashboard/team/:team-id"
- ["/members" :dashboard-team-members]
- ["/settings" :dashboard-team-settings]
- ["/projects" :dashboard-projects]
- ["/search" :dashboard-search]
- ["/fonts" :dashboard-fonts]
- ["/fonts/providers" :dashboard-font-providers]
- ["/libraries" :dashboard-libraries]
- ["/projects/:project-id" :dashboard-files]]
-
- ["/workspace/:project-id/:file-id" :workspace]])
-
(mf/defc on-main-error
[{:keys [error] :as props}]
- (mf/use-effect #(ptk/handle-error error))
+ (mf/use-effect (st/emitf (rt/assign-exception error)))
[:span "Internal application errror"])
(mf/defc main-page
@@ -161,12 +90,14 @@
:render-object
(do
- (let [file-id (uuid (get-in route [:path-params :file-id]))
- page-id (uuid (get-in route [:path-params :page-id]))
- object-id (uuid (get-in route [:path-params :object-id]))]
+ (let [file-id (uuid (get-in route [:path-params :file-id]))
+ page-id (uuid (get-in route [:path-params :page-id]))
+ object-id (uuid (get-in route [:path-params :object-id]))
+ render-texts (get-in route [:query-params :render-texts])]
[:& render/render-object {:file-id file-id
:page-id page-id
- :object-id object-id}]))
+ :object-id object-id
+ :render-texts? (and (some? render-texts) (= render-texts "true"))}]))
:render-sprite
(do
@@ -199,143 +130,3 @@
[:& msgs/notifications]
(when route
[:& main-page {:route route}])])]))
-
-;; --- Error Handling
-
-;; That are special case server-errors that should be treated
-;; differently.
-(derive :not-found ::exceptional-state)
-(derive :bad-gateway ::exceptional-state)
-(derive :service-unavailable ::exceptional-state)
-
-(defmethod ptk/handle-error ::exceptional-state
- [error]
- (ts/schedule
- (st/emitf (dm/assign-exception error))))
-
-;; We receive a explicit authentication error; this explicitly clears
-;; all profile data and redirect the user to the login page.
-(defmethod ptk/handle-error :authentication
- [_]
- (ts/schedule (st/emitf (du/logout))))
-
-;; Error that happens on an active bussines model validation does not
-;; passes an validation (example: profile can't leave a team). From
-;; the user perspective a error flash message should be visualized but
-;; user can continue operate on the application.
-(defmethod ptk/handle-error :validation
- [error]
- (ts/schedule
- (st/emitf
- (dm/show {:content "Unexpected validation error."
- :type :error
- :timeout 3000})))
-
- ;; Print to the console some debug info.
- (js/console.group "Validation Error")
- (ex/ignoring
- (js/console.info
- (with-out-str
- (pprint (dissoc error :explain))))
- (when-let [explain (:explain error)]
- (js/console.error explain)))
- (js/console.groupEnd "Validation Error"))
-
-;; Error on parsing an SVG
-(defmethod ptk/handle-error :svg-parser
- [_]
- (ts/schedule
- (st/emitf
- (dm/show {:content "SVG is invalid or malformed"
- :type :error
- :timeout 3000}))))
-
-;; This is a pure frontend error that can be caused by an active
-;; assertion (assertion that is preserved on production builds). From
-;; the user perspective this should be treated as internal error.
-(defmethod ptk/handle-error :assertion
- [{:keys [data stack message hint context] :as error}]
- (let [message (or message hint)
- context (str/fmt "ns: '%s'\nname: '%s'\nfile: '%s:%s'"
- (:ns context)
- (:name context)
- (str cf/public-uri "js/cljs-runtime/" (:file context))
- (:line context))]
- (ts/schedule
- (st/emitf
- (dm/show {:content "Internal error: assertion."
- :type :error
- :timeout 3000})
- (ptk/event ::ev/event
- {::ev/type "exception"
- ::ev/name "assertion-error"
- :message message
- :context context
- :trace stack})))
-
- ;; Print to the console some debugging info
- (js/console.group message)
- (js/console.info context)
- (js/console.groupCollapsed "Stack Trace")
- (js/console.info stack)
- (js/console.groupEnd "Stack Trace")
- (js/console.error (with-out-str (expound/printer data)))
- (js/console.groupEnd message)))
-
-;; This happens when the backed server fails to process the
-;; request. This can be caused by an internal assertion or any other
-;; uncontrolled error.
-(defmethod ptk/handle-error :server-error
- [{:keys [data hint] :as error}]
- (let [hint (or hint (:hint data) (:message data))
- info (with-out-str (pprint (dissoc data :explain)))
- expl (:explain data)]
- (ts/schedule
- (st/emitf
- (dm/show {:content "Something wrong has happened (on backend)."
- :type :error
- :timeout 3000})
- (ptk/event ::ev/event
- {::ev/type "exception"
- ::ev/name "server-error"
- :hint hint
- :info info
- :explain expl})))
-
- (js/console.group "Internal Server Error:")
- (js/console.error "hint:" hint)
- (js/console.info info)
- (when expl (js/console.error expl))
- (js/console.groupEnd "Internal Server Error:")))
-
-(defmethod ptk/handle-error :default
- [error]
- (if (instance? ExceptionInfo error)
- (ptk/handle-error (ex-data error))
- (let [stack (.-stack error)
- hint (or (ex-message error)
- (:hint error)
- (:message error))]
- (ts/schedule
- (st/emitf
- (dm/assign-exception error)
- (ptk/event ::ev/event
- {::ev/type "exception"
- ::ev/name "unexpected-error"
- :message hint
- :trace (.-stack error)})))
-
- (js/console.group "Internal error:")
- (js/console.log "hint:" hint)
- (ex/ignoring
- (js/console.error (clj->js error))
- (js/console.error "stack:" stack))
- (js/console.groupEnd "Internal error:"))))
-
-(defonce uncaught-error-handler
- (letfn [(on-error [event]
- (ptk/handle-error (unchecked-get event "error"))
- (.preventDefault ^js event))]
- (.addEventListener js/window "error" on-error)
- (fn []
- (.removeEventListener js/window "error" on-error))))
diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs
index f10181e77..71cc32eeb 100644
--- a/frontend/src/app/main/ui/auth/register.cljs
+++ b/frontend/src/app/main/ui/auth/register.cljs
@@ -210,8 +210,13 @@
[:div.fields-row
[:& fm/input {:name :accept-terms-and-privacy
:class "check-primary"
- :label (tr "auth.terms-privacy-agreement")
- :type "checkbox"}]]
+ :type "checkbox"}
+ [:span
+ (tr "auth.terms-privacy-agreement")
+ [:div
+ [:a {:href "https://penpot.app/terms.html" :target "_blank"} (tr "auth.terms-of-service")]
+ [:span ",\u00A0"]
+ [:a {:href "https://penpot.app/privacy.html" :target "_blank"} (tr "auth.privacy-policy")]]]]]
;; (when (contains? @cf/flags :newsletter-registration-check)
;; [:div.fields-row
diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs
index 3ae683238..f65abba07 100644
--- a/frontend/src/app/main/ui/comments.cljs
+++ b/frontend/src/app/main/ui/comments.cljs
@@ -302,21 +302,22 @@
(when-let [node (mf/ref-val ref)]
(.scrollIntoViewIfNeeded ^js node))))
- [:div.thread-content
- {:style {:top (str pos-y "px")
- :left (str pos-x "px")}
- :on-click dom/stop-propagation}
+ (when (some? comment)
+ [:div.thread-content
+ {:style {:top (str pos-y "px")
+ :left (str pos-x "px")}
+ :on-click dom/stop-propagation}
- [:div.comments
- [:& comment-item {:comment comment
- :users users
- :thread thread}]
- (for [item (rest comments)]
- [:*
- [:hr]
- [:& comment-item {:comment item :users users}]])
- [:div {:ref ref}]]
- [:& reply-form {:thread thread}]]))
+ [:div.comments
+ [:& comment-item {:comment comment
+ :users users
+ :thread thread}]
+ (for [item (rest comments)]
+ [:*
+ [:hr]
+ [:& comment-item {:comment item :users users}]])
+ [:div {:ref ref}]]
+ [:& reply-form {:thread thread}]])))
(mf/defc thread-bubble
{::mf/wrap [mf/memo]}
diff --git a/frontend/src/app/main/ui/components/editable_select.cljs b/frontend/src/app/main/ui/components/editable_select.cljs
index d1a2e666f..49cea928f 100644
--- a/frontend/src/app/main/ui/components/editable_select.cljs
+++ b/frontend/src/app/main/ui/components/editable_select.cljs
@@ -76,6 +76,7 @@
handle-key-down
(mf/use-callback
+ (mf/deps set-value)
(fn [event]
(when (= type "number")
(let [up? (kbd/up-arrow? event)
diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs
index 2827f5a6e..fd15aa757 100644
--- a/frontend/src/app/main/ui/components/forms.cljs
+++ b/frontend/src/app/main/ui/components/forms.cljs
@@ -19,7 +19,7 @@
(def use-form fm/use-form)
(mf/defc input
- [{:keys [label help-icon disabled form hint trim] :as props}]
+ [{:keys [label help-icon disabled form hint trim children] :as props}]
(let [input-type (get props :type "text")
input-name (get props :name)
more-classes (get props :class)
@@ -82,7 +82,7 @@
(swap! form assoc-in [:touched input-name] true)))
props (-> props
- (dissoc :help-icon :form :trim)
+ (dissoc :help-icon :form :trim :children)
(assoc :id (name input-name)
:value value
:auto-focus auto-focus?
@@ -97,7 +97,13 @@
{:class klass}
[:*
[:> :input props]
- [:label {:for (name input-name)} label]
+ (cond
+ (some? label)
+ [:label {:for (name input-name)} label]
+
+ (some? children)
+ [:label {:for (name input-name)} children])
+
(when help-icon'
[:div.help-icon
{:style {:cursor "pointer"}
diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs
index e0c03033b..531df875e 100644
--- a/frontend/src/app/main/ui/dashboard/file_menu.cljs
+++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs
@@ -74,10 +74,12 @@
on-new-tab
(fn [_]
- (let [pparams {:project-id (:project-id file)
- :file-id (:id file)}
- qparams {:page-id (first (get-in file [:data :pages]))}]
- (st/emit! (rt/nav-new-window :workspace pparams qparams))))
+ (let [path-params {:project-id (:project-id file)
+ :file-id (:id file)}
+ query-params {:page-id (first (get-in file [:data :pages]))}]
+ (st/emit! (rt/nav-new-window* {:rname :workspace
+ :path-params path-params
+ :query-params query-params}))))
on-duplicate
(fn [_]
diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs
index 1b12fc298..dd35cd446 100644
--- a/frontend/src/app/main/ui/dashboard/fonts.cljs
+++ b/frontend/src/app/main/ui/dashboard/fonts.cljs
@@ -18,13 +18,10 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
- [app.util.logging :as log]
[beicon.core :as rx]
[cuerdas.core :as str]
[rumext.alpha :as mf]))
-(log/set-level! :trace)
-
(defn- use-set-page-title
[team section]
(mf/use-effect
diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs
index 1e4202632..9d8083fc9 100644
--- a/frontend/src/app/main/ui/dashboard/import.cljs
+++ b/frontend/src/app/main/ui/dashboard/import.cljs
@@ -7,6 +7,7 @@
(ns app.main.ui.dashboard.import
(:require
[app.common.data :as d]
+ [app.common.logging :as log]
[app.main.data.events :as ev]
[app.main.data.modal :as modal]
[app.main.store :as st]
@@ -16,12 +17,11 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
- [app.util.logging :as log]
[beicon.core :as rx]
[potok.core :as ptk]
[rumext.alpha :as mf]))
-(log/set-level! :debug)
+(log/set-level! :warn)
(def ^:const emit-delay 1000)
diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs
index 3f1e7cbd9..827f48265 100644
--- a/frontend/src/app/main/ui/dashboard/sidebar.cljs
+++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs
@@ -10,6 +10,7 @@
[app.common.spec :as us]
[app.config :as cf]
[app.main.data.dashboard :as dd]
+ [app.main.data.events :as ev]
[app.main.data.messages :as dm]
[app.main.data.modal :as modal]
[app.main.data.users :as du]
@@ -69,7 +70,8 @@
(mf/use-callback
(mf/deps item)
(fn [name]
- (st/emit! (dd/rename-project (assoc item :name name)))
+ (st/emit! (-> (dd/rename-project (assoc item :name name))
+ (with-meta {::ev/origin "dashboard:sidebar"})))
(swap! local assoc :edition? false)))
on-drag-enter
diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs
index 4c87aa4d1..bf0a8bf5a 100644
--- a/frontend/src/app/main/ui/dashboard/team.cljs
+++ b/frontend/src/app/main/ui/dashboard/team.cljs
@@ -26,12 +26,13 @@
(mf/defc header
{::mf/wrap [mf/memo]}
- [{:keys [section] :as props}]
+ [{:keys [section team] :as props}]
(let [go-members (st/emitf (dd/go-to-team-members))
go-settings (st/emitf (dd/go-to-team-settings))
- invite-member (st/emitf (modal/show {:type ::invite-member}))
+ invite-member (st/emitf (modal/show {:type ::invite-member :team team}))
members-section? (= section :dashboard-team-members)
- settings-section? (= section :dashboard-team-settings)]
+ settings-section? (= section :dashboard-team-settings)
+ permissions (:permissions team)]
[:header.dashboard-header
[:div.dashboard-title
@@ -46,20 +47,21 @@
[:li {:class (when settings-section? "active")}
[:a {:on-click go-settings} (tr "labels.settings")]]]]
- (if members-section?
+ (if (and members-section? (:is-admin permissions))
[:a.btn-secondary.btn-small {:on-click invite-member}
(tr "dashboard.invite-profile")]
[:div])]))
(defn get-available-roles
- []
- [{:value "" :label (tr "labels.role")}
- {:value "admin" :label (tr "labels.admin")}
- {:value "editor" :label (tr "labels.editor")}
- ;; Temporarily disabled viewer role
- ;; https://tree.taiga.io/project/uxboxproject/issue/1083
- ;; {:value "viewer" :label (tr "labels.viewer")}
- ])
+ [permissions]
+ (->> [{:value "editor" :label (tr "labels.editor")}
+ (when (:is-admin permissions)
+ {:value "admin" :label (tr "labels.admin")})
+ ;; Temporarily disabled viewer role
+ ;; https://tree.taiga.io/project/uxboxproject/issue/1083
+ ;; {:value "viewer" :label (tr "labels.viewer")}
+ ]
+ (filterv identity)))
(s/def ::email ::us/email)
(s/def ::role ::us/keyword)
@@ -69,8 +71,9 @@
(mf/defc invite-member-modal
{::mf/register modal/components
::mf/register-as ::invite-member}
- []
- (let [roles (mf/use-memo get-available-roles)
+ [{:keys [team]}]
+ (let [perms (:permissions team)
+ roles (mf/use-memo (mf/deps perms) #(get-available-roles perms))
initial (mf/use-memo (constantly {:role "editor"}))
form (fm/use-form :spec ::invite-member-form
:initial initial)
@@ -262,6 +265,7 @@
(tr "dashboard.your-penpot")
(:name team))))))
+
(mf/use-effect
(st/emitf (dd/fetch-team-members)
(dd/fetch-team-stats)))
diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs
index 257b3b239..484974c1d 100644
--- a/frontend/src/app/main/ui/hooks.cljs
+++ b/frontend/src/app/main/ui/hooks.cljs
@@ -11,13 +11,10 @@
[app.main.store :as st]
[app.util.dom :as dom]
[app.util.dom.dnd :as dnd]
- [app.util.logging :as log]
[app.util.timers :as ts]
[beicon.core :as rx]
[rumext.alpha :as mf]))
-(log/set-level! :warn)
-
(defn use-rxsub
[ob]
(let [[state reset-state!] (mf/useState @ob)]
@@ -101,7 +98,6 @@
subscribe-to-drag-end
(fn []
(when (nil? (:subscr @state))
- ;; (js/console.log "subscribing" (:name data))
(swap! state
#(assoc % :subscr (rx/sub! global-drag-end cleanup)))))
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index 47738e45a..fce2cdc65 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -9,6 +9,8 @@
(:require-macros [app.main.ui.icons :refer [icon-xref]])
(:require [rumext.alpha :as mf]))
+;; Keep the list of icons sorted
+
(def action (icon-xref :action))
(def actions (icon-xref :actions))
(def align-bottom (icon-xref :align-bottom))
@@ -23,6 +25,11 @@
(def auto-fix (icon-xref :auto-fix))
(def auto-height (icon-xref :auto-height))
(def auto-width (icon-xref :auto-width))
+(def boolean-difference (icon-xref :boolean-difference))
+(def boolean-exclude (icon-xref :boolean-exclude))
+(def boolean-flatten (icon-xref :boolean-flatten))
+(def boolean-intersection (icon-xref :boolean-intersection))
+(def boolean-union (icon-xref :boolean-union))
(def box (icon-xref :box))
(def chain (icon-xref :chain))
(def chat (icon-xref :chat))
@@ -67,8 +74,8 @@
(def listing-thumbs (icon-xref :listing-thumbs))
(def loader (icon-xref :loader))
(def lock (icon-xref :lock))
-(def logo (icon-xref :uxbox-logo))
-(def logo-icon (icon-xref :uxbox-logo-icon))
+(def logo (icon-xref :penpot-logo))
+(def logo-icon (icon-xref :penpot-logo-icon))
(def logout (icon-xref :logout))
(def lowercase (icon-xref :lowercase))
(def mail (icon-xref :mail))
@@ -101,6 +108,13 @@
(def play (icon-xref :play))
(def plus (icon-xref :plus))
(def pointer-inner (icon-xref :pointer-inner))
+(def position-bottom-center (icon-xref :position-bottom-center))
+(def position-bottom-left (icon-xref :position-bottom-left))
+(def position-bottom-right (icon-xref :position-bottom-right))
+(def position-center (icon-xref :position-center))
+(def position-top-center (icon-xref :position-top-center))
+(def position-top-left (icon-xref :position-top-left))
+(def position-top-right (icon-xref :position-top-right))
(def radius (icon-xref :radius))
(def radius-1 (icon-xref :radius-1))
(def radius-4 (icon-xref :radius-4))
@@ -145,6 +159,7 @@
(def uppercase (icon-xref :uppercase))
(def user (icon-xref :user))
+
(def loader-pencil
(mf/html
[:svg
diff --git a/frontend/src/app/main/ui/onboarding.cljs b/frontend/src/app/main/ui/onboarding.cljs
index d25c4b0bf..c980f45c0 100644
--- a/frontend/src/app/main/ui/onboarding.cljs
+++ b/frontend/src/app/main/ui/onboarding.cljs
@@ -20,6 +20,7 @@
[app.main.ui.releases.v1-6]
[app.main.ui.releases.v1-7]
[app.main.ui.releases.v1-8]
+ [app.main.ui.releases.v1-9]
[app.util.i18n :as i18n :refer [tr]]
[app.util.object :as obj]
[app.util.router :as rt]
@@ -298,5 +299,5 @@
(defmethod rc/render-release-notes "0.0"
[params]
- (rc/render-release-notes (assoc params :version "1.8")))
+ (rc/render-release-notes (assoc params :version "1.9")))
diff --git a/frontend/src/app/main/ui/releases/v1_9.cljs b/frontend/src/app/main/ui/releases/v1_9.cljs
new file mode 100644
index 000000000..7bf95fadb
--- /dev/null
+++ b/frontend/src/app/main/ui/releases/v1_9.cljs
@@ -0,0 +1,108 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.ui.releases.v1-9
+ (:require
+ [app.main.ui.releases.common :as c]
+ [rumext.alpha :as mf]))
+
+(defmethod c/render-release-notes "1.9"
+ [{:keys [slide klass next finish navigate version]}]
+ (mf/html
+ (case @slide
+ :start
+ [:div.modal-overlay
+ [:div.animated {:class @klass}
+ [:div.modal-container.onboarding.feature
+ [:div.modal-left
+ [:img {:src "images/login-on.jpg" :border "0" :alt "What's new Alpha release 1.9"}]]
+ [:div.modal-right
+ [:div.modal-title
+ [:h2 "What's new?"]]
+ [:span.release "Alpha version " version]
+ [:div.modal-content
+ [:p "Penpot continues growing with new features that improve performance, user experience and visual design."]
+ [:p "We are happy to show you a sneak peak of the most important stuff that the Alpha 1.9 version brings."]]
+ [:div.modal-navigation
+ [:button.btn-secondary {:on-click next} "Continue"]]]
+ [:img.deco {:src "images/deco-left.png" :border "0"}]
+ [:img.deco.right {:src "images/deco-right.png" :border "0"}]]]]
+
+ 0
+ [:div.modal-overlay
+ [:div.animated {:class @klass}
+ [:div.modal-container.onboarding.feature
+ [:div.modal-left
+ [:img {:src "images/features/advanced-proto.gif" :border "0" :alt "Advanced interactions"}]]
+ [:div.modal-right
+ [:div.modal-title
+ [:h2 "Prototyping triggers and actions"]]
+ [:div.modal-content
+ [:p "Prototyping options at last! Different triggers (like mouse events or time delays) and actions allow you to add complexity to the interactions of your prototypes."]
+ [:p "Create overlays, back buttons or links to URLs to mimic the behavior of the product you’re designing."]]
+ [:div.modal-navigation
+ [:button.btn-secondary {:on-click next} "Continue"]
+ [:& c/navigation-bullets
+ {:slide @slide
+ :navigate navigate
+ :total 4}]]]]]]
+
+ 1
+ [:div.modal-overlay
+ [:div.animated {:class @klass}
+ [:div.modal-container.onboarding.feature
+ [:div.modal-left
+ [:img {:src "images/features/flows-proto.gif" :border "0" :alt "Multiple flows"}]]
+ [:div.modal-right
+ [:div.modal-title
+ [:h2 "Multiple flows"]]
+ [:div.modal-content
+ [:p "Design projects usually need to define multiple casuistics for different devices and user journeys."]
+ [:p "Flows allow you to define multiple starting points within the same page so you can better organize and present your prototypes."]]
+ [:div.modal-navigation
+ [:button.btn-secondary {:on-click next} "Continue"]
+ [:& c/navigation-bullets
+ {:slide @slide
+ :navigate navigate
+ :total 4}]]]]]]
+
+ 2
+ [:div.modal-overlay
+ [:div.animated {:class @klass}
+ [:div.modal-container.onboarding.feature
+ [:div.modal-left
+ [:img {:src "images/features/booleans.gif" :border "0" :alt "Boolean shapes"}]]
+ [:div.modal-right
+ [:div.modal-title
+ [:h2 "Boolean operations"]]
+ [:div.modal-content
+ [:p "Now in Penpot you can combine shapes in different ways. There are five options: Union, difference, intersection, exclusion and flatten."]
+ [:p "Using boolean operations will lead to countless graphic possibilities for your designs."]]
+ [:div.modal-navigation
+ [:button.btn-secondary {:on-click next} "Continue"]
+ [:& c/navigation-bullets
+ {:slide @slide
+ :navigate navigate
+ :total 4}]]]]]]
+
+ 3
+ [:div.modal-overlay
+ [:div.animated {:class @klass}
+ [:div.modal-container.onboarding.feature
+ [:div.modal-left
+ [:img {:src "images/features/libraries-feature.gif" :border "0" :alt "Libraries & templates"}]]
+ [:div.modal-right
+ [:div.modal-title
+ [:h2 "Libraries & templates"]]
+ [:div.modal-content
+ [:p "We’ve created a new space on Penpot where you can share your libraries and templates and download the ones you like. Material Design, Cocomaterial or Penpot’s Design System are among them (and a lot more to come!)."]
+ [:p [:a {:alt "Explore libraries & templates" :target "_blank" :href "https://penpot.app/libraries-templates.html"} "Explore libraries & templates"]]]
+ [:div.modal-navigation
+ [:button.btn-secondary {:on-click finish} "Start!"]
+ [:& c/navigation-bullets
+ {:slide @slide
+ :navigate navigate
+ :total 4}]]]]]])))
diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs
index 9cea9c60a..0263a4826 100644
--- a/frontend/src/app/main/ui/render.cljs
+++ b/frontend/src/app/main/ui/render.cljs
@@ -25,9 +25,34 @@
[cuerdas.core :as str]
[rumext.alpha :as mf]))
+(defn calc-bounds
+ [object objects]
+
+ (let [xf-get-bounds (comp (map #(get objects %)) (map #(calc-bounds % objects)))
+ padding (filters/calculate-padding object)
+ obj-bounds
+ (-> (filters/get-filters-bounds object)
+ (update :x - padding)
+ (update :y - padding)
+ (update :width + (* 2 padding))
+ (update :height + (* 2 padding)))]
+
+ (cond
+ (and (= :group (:type object))
+ (:masked-group? object))
+ (calc-bounds (get objects (first (:shapes object))) objects)
+
+ (= :group (:type object))
+ (->> (:shapes object)
+ (into [obj-bounds] xf-get-bounds)
+ (gsh/join-rects))
+
+ :else
+ obj-bounds)))
+
(mf/defc object-svg
{::mf/wrap [mf/memo]}
- [{:keys [objects object-id zoom] :or {zoom 1} :as props}]
+ [{:keys [objects object-id zoom render-texts?] :or {zoom 1} :as props}]
(let [object (get objects object-id)
frame-id (if (= :frame (:type object))
(:id object)
@@ -47,20 +72,10 @@
objects (reduce updt-fn objects mod-ids)
object (get objects object-id)
- ;; We need to get the shadows/blurs paddings to create the viewbox properly
- {:keys [x y width height]} (filters/get-filters-bounds object)
+ {:keys [x y width height] :as bs} (calc-bounds object objects)
+ [_ _ width height :as coords] (->> [x y width height] (map #(* % zoom)))
- x (* x zoom)
- y (* y zoom)
- width (* width zoom)
- height (* height zoom)
-
- padding (* (filters/calculate-padding object) zoom)
-
- vbox (str/join " " [(- x padding)
- (- y padding)
- (+ width padding padding)
- (+ height padding padding)])
+ vbox (str/join " " coords)
frame-wrapper
(mf/use-memo
@@ -76,18 +91,22 @@
(mf/use-memo
(mf/deps objects)
#(exports/shape-wrapper-factory objects))
- ]
+
+ text-shapes
+ (->> objects
+ (filter (fn [[_ shape]] (= :text (:type shape))))
+ (mapv second))]
(mf/use-effect
(mf/deps width height)
- #(dom/set-page-style {:size (str (mth/ceil (+ width padding padding)) "px "
- (mth/ceil (+ height padding padding)) "px")}))
+ #(dom/set-page-style {:size (str (mth/ceil width) "px "
+ (mth/ceil height) "px")}))
[:& (mf/provider embed/context) {:value true}
[:svg {:id "screenshot"
:view-box vbox
- :width (+ width padding padding)
- :height (+ height padding padding)
+ :width width
+ :height height
:version "1.1"
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"
@@ -100,7 +119,19 @@
:frame [:& frame-wrapper {:shape object :view-box vbox}]
:group [:> shape-container {:shape object}
[:& group-wrapper {:shape object}]]
- [:& shape-wrapper {:shape object}])]]))
+ [:& shape-wrapper {:shape object}])]
+
+ ;; Auxiliary SVG for rendering text-shapes
+ (when render-texts?
+ (for [object text-shapes]
+ [:svg {:id (str "screenshot-text-" (:id object))
+ :view-box (str "0 0 " (:width object) " " (:height object))
+ :width (:width object)
+ :height (:height object)
+ :version "1.1"
+ :xmlns "http://www.w3.org/2000/svg"
+ :xmlnsXlink "http://www.w3.org/1999/xlink"}
+ [:& shape-wrapper {:shape (-> object (assoc :x 0 :y 0))}]]))]))
(defn- adapt-root-frame
[objects object-id]
@@ -120,7 +151,7 @@
;; backend entry point for download only the data of single page.
(mf/defc render-object
- [{:keys [file-id page-id object-id] :as props}]
+ [{:keys [file-id page-id object-id render-texts?] :as props}]
(let [objects (mf/use-state nil)]
(mf/use-effect
(mf/deps file-id page-id object-id)
@@ -140,6 +171,7 @@
(when @objects
[:& object-svg {:objects @objects
:object-id object-id
+ :render-texts? render-texts?
:zoom 1}])))
(mf/defc render-sprite
diff --git a/frontend/src/app/main/ui/routes.cljs b/frontend/src/app/main/ui/routes.cljs
new file mode 100644
index 000000000..28d402c85
--- /dev/null
+++ b/frontend/src/app/main/ui/routes.cljs
@@ -0,0 +1,122 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.ui.routes
+ (:require
+ [app.common.spec :as us]
+ [app.common.uuid :as uuid]
+ [app.config :as cf]
+ [app.main.data.users :as du]
+ [app.main.store :as st]
+ [app.util.router :as rt]
+ [app.util.storage :refer [storage]]
+ [beicon.core :as rx]
+ [cljs.spec.alpha :as s]
+ [potok.core :as ptk]))
+
+(s/def ::page-id ::us/uuid)
+(s/def ::file-id ::us/uuid)
+(s/def ::section ::us/keyword)
+(s/def ::index ::us/integer)
+(s/def ::token (s/nilable ::us/not-empty-string))
+(s/def ::share-id ::us/uuid)
+
+(s/def ::viewer-path-params
+ (s/keys :req-un [::file-id]))
+
+(s/def ::viewer-query-params
+ (s/keys :opt-un [::index ::share-id ::section ::page-id]))
+
+(s/def ::any any?)
+
+(def routes
+ [["/auth"
+ ["/login" :auth-login]
+ (when (contains? @cf/flags :registration)
+ ["/register" :auth-register])
+ (when (contains? @cf/flags :registration)
+ ["/register/validate" :auth-register-validate])
+ (when (contains? @cf/flags :registration)
+ ["/register/success" :auth-register-success])
+ ["/recovery/request" :auth-recovery-request]
+ ["/recovery" :auth-recovery]
+ ["/verify-token" :auth-verify-token]]
+
+ ["/settings"
+ ["/profile" :settings-profile]
+ ["/password" :settings-password]
+ ["/feedback" :settings-feedback]
+ ["/options" :settings-options]]
+
+ ["/view/:file-id"
+ {:name :viewer
+ :conform
+ {:path-params ::viewer-path-params
+ :query-params ::viewer-query-params}}]
+
+ (when *assert*
+ ["/debug/icons-preview" :debug-icons-preview])
+
+ ;; Used for export
+ ["/render-object/:file-id/:page-id/:object-id" :render-object]
+ ["/render-sprite/:file-id" :render-sprite]
+
+ ["/dashboard/team/:team-id"
+ ["/members" :dashboard-team-members]
+ ["/settings" :dashboard-team-settings]
+ ["/projects" :dashboard-projects]
+ ["/search" :dashboard-search]
+ ["/fonts" :dashboard-fonts]
+ ["/fonts/providers" :dashboard-font-providers]
+ ["/libraries" :dashboard-libraries]
+ ["/projects/:project-id" :dashboard-files]]
+
+ ["/workspace/:project-id/:file-id" :workspace]])
+
+(defn- match-path
+ [router path]
+ (when-let [match (rt/match router path)]
+ (if-let [conform (get-in match [:data :conform])]
+ (let [spath (get conform :path-params ::any)
+ squery (get conform :query-params ::any)]
+ (try
+ (-> (dissoc match :params)
+ (assoc :path-params (us/conform spath (get match :path-params))
+ :query-params (us/conform squery (get match :query-params))))
+ (catch :default _
+ nil)))
+ match)))
+
+(defn on-navigate
+ [router path]
+ (let [match (match-path router path)
+ profile (:profile @storage)
+ nopath? (or (= path "") (= path "/"))
+ authed? (and (not (nil? profile))
+ (not= (:id profile) uuid/zero))]
+
+ (cond
+ (and nopath? authed? (nil? match))
+ (if (not= uuid/zero profile)
+ (st/emit! (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)}))
+ (st/emit! (rt/nav :auth-login)))
+
+ (and (not authed?) (nil? match))
+ (st/emit! (rt/nav :auth-login))
+
+ (nil? match)
+ (st/emit! (rt/assign-exception {:type :not-found}))
+
+ :else
+ (st/emit! (rt/navigated match)))))
+
+(defn init-routes
+ []
+ (ptk/reify ::init-routes
+ ptk/WatchEvent
+ (watch [_ _ _]
+ (rx/of (rt/initialize-router routes)
+ (rt/initialize-history on-navigate)))))
diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs
index 973a155a4..e9ca5a966 100644
--- a/frontend/src/app/main/ui/shapes/attrs.cljs
+++ b/frontend/src/app/main/ui/shapes/attrs.cljs
@@ -14,12 +14,14 @@
[rumext.alpha :as mf]))
(defn- stroke-type->dasharray
- [style]
- (case style
- :mixed "5,5,1,5"
- :dotted "5,5"
- :dashed "10,10"
- nil))
+ [width style]
+ (let [values (case style
+ :mixed [5 5 1 5]
+ :dotted [5 5]
+ :dashed [10 10]
+ nil)]
+
+ (->> values (map #(+ % width)) (str/join ","))))
(defn- truncate-side
[shape ra-attr rb-attr dimension-attr]
@@ -102,10 +104,11 @@
(defn add-stroke [attrs shape render-id]
(let [stroke-style (:stroke-style shape :none)
- stroke-color-gradient-id (str "stroke-color-gradient_" render-id)]
+ stroke-color-gradient-id (str "stroke-color-gradient_" render-id)
+ stroke-width (:stroke-width shape 1)]
(if (not= stroke-style :none)
(let [stroke-attrs
- (cond-> {:strokeWidth (:stroke-width shape 1)}
+ (cond-> {:strokeWidth stroke-width}
(:stroke-color-gradient shape)
(assoc :stroke (str/format "url(#%s)" stroke-color-gradient-id))
@@ -118,7 +121,7 @@
(assoc :strokeOpacity (:stroke-opacity shape nil))
(not= stroke-style :svg)
- (assoc :strokeDasharray (stroke-type->dasharray stroke-style))
+ (assoc :strokeDasharray (stroke-type->dasharray stroke-width stroke-style))
;; For simple line caps we use svg stroke-line-cap attribute. This
;; only works if all caps are the same and we are not using the tricks
diff --git a/frontend/src/app/main/ui/shapes/bool.cljs b/frontend/src/app/main/ui/shapes/bool.cljs
new file mode 100644
index 000000000..fd9b58f94
--- /dev/null
+++ b/frontend/src/app/main/ui/shapes/bool.cljs
@@ -0,0 +1,114 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.ui.shapes.bool
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.shapes :as gsh]
+ [app.common.geom.shapes.path :as gsp]
+ [app.common.path.bool :as pb]
+ [app.common.path.shapes-to-path :as stp]
+ [app.main.ui.hooks :refer [use-equal-memo]]
+ [app.main.ui.shapes.export :as use]
+ [app.main.ui.shapes.path :refer [path-shape]]
+ [app.util.object :as obj]
+ [rumext.alpha :as mf]))
+
+(mf/defc debug-bool
+ {::mf/wrap-props false}
+ [props]
+
+ (let [frame (obj/get props "frame")
+ shape (obj/get props "shape")
+ childs (obj/get props "childs")
+
+ [content-a content-b]
+ (mf/use-memo
+ (mf/deps shape childs)
+ (fn []
+ (let [childs (d/mapm #(-> %2 (gsh/translate-to-frame frame) gsh/transform-shape) childs)
+ [content-a content-b]
+ (->> (:shapes shape)
+ (map #(get childs %))
+ (filter #(not (:hidden %)))
+ (map #(stp/convert-to-path % childs))
+ (map :content)
+ (map pb/close-paths)
+ (map pb/add-previous))]
+ (pb/content-intersect-split content-a content-b))))]
+ [:g.debug-bool
+ [:g.shape-a
+ [:& path-shape {:shape (-> shape
+ (assoc :type :path)
+ (assoc :stroke-color "blue")
+ (assoc :stroke-opacity 1)
+ (assoc :stroke-width 1)
+ (assoc :stroke-style :solid)
+ (dissoc :fill-color :fill-opacity)
+ (assoc :content content-b))
+ :frame frame}]
+ (for [{:keys [x y]} (gsp/content->points (pb/close-paths content-b))]
+ [:circle {:cx x
+ :cy y
+ :r 2.5
+ :style {:fill "blue"}}])]
+
+ [:g.shape-b
+ [:& path-shape {:shape (-> shape
+ (assoc :type :path)
+ (assoc :stroke-color "red")
+ (assoc :stroke-opacity 1)
+ (assoc :stroke-width 0.5)
+ (assoc :stroke-style :solid)
+ (dissoc :fill-color :fill-opacity)
+ (assoc :content content-a))
+ :frame frame}]
+ (for [{:keys [x y]} (gsp/content->points (pb/close-paths content-a))]
+ [:circle {:cx x
+ :cy y
+ :r 1.25
+ :style {:fill "red"}}])]])
+ )
+
+
+(defn bool-shape
+ [shape-wrapper]
+ (mf/fnc bool-shape
+ {::mf/wrap-props false}
+ [props]
+ (let [frame (obj/get props "frame")
+ shape (obj/get props "shape")
+ childs (obj/get props "childs")
+
+ childs (use-equal-memo childs)
+
+ include-metadata? (mf/use-ctx use/include-metadata-ctx)
+
+ bool-content
+ (mf/use-memo
+ (mf/deps shape childs)
+ (fn []
+ (let [childs (d/mapm #(-> %2 gsh/transform-shape (gsh/translate-to-frame frame)) childs)]
+ (->> (:shapes shape)
+ (map #(get childs %))
+ (filter #(not (:hidden %)))
+ (map #(stp/convert-to-path % childs))
+ (mapv :content)
+ (pb/content-bool (:bool-type shape))))))]
+
+ [:*
+ [:& path-shape {:shape (assoc shape :content bool-content)}]
+
+ (when include-metadata?
+ [:> "penpot:bool" {}
+ (for [item (->> (:shapes shape) (mapv #(get childs %)))]
+ [:& shape-wrapper {:frame frame
+ :shape item
+ :key (:id item)}])])
+
+ #_[:& debug-bool {:frame frame
+ :shape shape
+ :childs childs}]])))
diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs
index 6a5f92dcb..35d7691de 100644
--- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs
+++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs
@@ -7,6 +7,7 @@
(ns app.main.ui.shapes.custom-stroke
(:require
[app.common.data :as d]
+ [app.common.geom.shapes :as gsh]
[app.main.ui.context :as muc]
[app.util.object :as obj]
[cuerdas.core :as str]
@@ -34,8 +35,22 @@
[{:keys [shape render-id]}]
(let [stroke-mask-id (str "outer-stroke-" render-id)
shape-id (str "stroke-shape-" render-id)
- stroke-width (:stroke-width shape 0)]
- [:mask {:id stroke-mask-id}
+ stroke-width (case (:stroke-alignment shape :center)
+ :center (/ (:stroke-width shape 0) 2)
+ :outer (:stroke-width shape 0)
+ 0)
+ margin (gsh/shape-stroke-margin shape stroke-width)
+ bounding-box (-> (gsh/points->selrect (:points shape))
+ (update :x - (+ stroke-width margin))
+ (update :y - (+ stroke-width margin))
+ (update :width + (* 2 (+ stroke-width margin)))
+ (update :height + (* 2 (+ stroke-width margin))))]
+ [:mask {:id stroke-mask-id
+ :x (:x bounding-box)
+ :y (:y bounding-box)
+ :width (:width bounding-box)
+ :height (:height bounding-box)
+ :maskUnits "userSpaceOnUse"}
[:use {:xlinkHref (str "#" shape-id)
:style #js {:fill "none" :stroke "white" :strokeWidth (* stroke-width 2)}}]
@@ -60,8 +75,8 @@
:viewBox "0 0 3 6"
:refX "2"
:refY "3"
- :markerWidth "3"
- :markerHeight "6"
+ :markerWidth "8.5"
+ :markerHeight "8.5"
:orient "auto-start-reverse"
:fill stroke-color
:fillOpacity stroke-opacity}
@@ -72,8 +87,8 @@
:viewBox "0 0 3 6"
:refX "2"
:refY "3"
- :markerWidth "3"
- :markerHeight "6"
+ :markerWidth "8.5"
+ :markerHeight "8.5"
:orient "auto-start-reverse"
:fill stroke-color
:fillOpacity stroke-opacity}
@@ -82,10 +97,10 @@
(when (or (= cap-start :square-marker) (= cap-end :square-marker))
[:marker {:id (str marker-id-prefix "-square-marker")
:viewBox "0 0 6 6"
- :refX "5"
+ :refX "3"
:refY "3"
- :markerWidth "6"
- :markerHeight "6"
+ :markerWidth "4.2426" ;; diagonal length of a 3x3 square
+ :markerHeight "4.2426"
:orient "auto-start-reverse"
:fill stroke-color
:fillOpacity stroke-opacity}
@@ -94,10 +109,10 @@
(when (or (= cap-start :circle-marker) (= cap-end :circle-marker))
[:marker {:id (str marker-id-prefix "-circle-marker")
:viewBox "0 0 6 6"
- :refX "5"
+ :refX "3"
:refY "3"
- :markerWidth "6"
- :markerHeight "6"
+ :markerWidth "4"
+ :markerHeight "4"
:orient "auto-start-reverse"
:fill stroke-color
:fillOpacity stroke-opacity}
@@ -106,7 +121,7 @@
(when (or (= cap-start :diamond-marker) (= cap-end :diamond-marker))
[:marker {:id (str marker-id-prefix "-diamond-marker")
:viewBox "0 0 6 6"
- :refX "5"
+ :refX "3"
:refY "3"
:markerWidth "6"
:markerHeight "6"
@@ -145,22 +160,24 @@
(mf/defc stroke-defs
[{:keys [shape render-id]}]
- (cond
- (and (= :inner (:stroke-alignment shape :center))
- (> (:stroke-width shape 0) 0))
- [:& inner-stroke-clip-path {:shape shape
- :render-id render-id}]
+ (when (or (not= (:type shape) :path)
+ (not (gsh/open-path? shape)))
+ (cond
+ (and (= :inner (:stroke-alignment shape :center))
+ (> (:stroke-width shape 0) 0))
+ [:& inner-stroke-clip-path {:shape shape
+ :render-id render-id}]
- (and (= :outer (:stroke-alignment shape :center))
- (> (:stroke-width shape 0) 0))
- [:& outer-stroke-mask {:shape shape
- :render-id render-id}]
+ (and (= :outer (:stroke-alignment shape :center))
+ (> (:stroke-width shape 0) 0))
+ [:& outer-stroke-mask {:shape shape
+ :render-id render-id}]
- (and (or (some? (:stroke-cap-start shape))
- (some? (:stroke-cap-end shape)))
- (= (:stroke-alignment shape) :center))
- [:& cap-markers {:shape shape
- :render-id render-id}]))
+ (and (or (some? (:stroke-cap-start shape))
+ (some? (:stroke-cap-end shape)))
+ (= (:stroke-alignment shape) :center))
+ [:& cap-markers {:shape shape
+ :render-id render-id}])))
;; Outer alingmnent: display the shape in two layers. One
;; without stroke (only fill), and another one only with stroke
@@ -253,15 +270,17 @@
stroke-position (:stroke-alignment shape :center)
has-stroke? (and (> stroke-width 0)
(not= stroke-style :none))
- inner? (= :inner stroke-position)
- outer? (= :outer stroke-position)]
+ closed? (or (not= :path (:type shape))
+ (not (gsh/open-path? shape)))
+ inner? (= :inner stroke-position)
+ outer? (= :outer stroke-position)]
(cond
- (and has-stroke? inner?)
+ (and has-stroke? inner? closed?)
[:& inner-stroke {:shape shape}
child]
- (and has-stroke? outer?)
+ (and has-stroke? outer? closed?)
[:& outer-stroke {:shape shape}
child]
diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs
index 7f1d3bcb9..f20428298 100644
--- a/frontend/src/app/main/ui/shapes/export.cljs
+++ b/frontend/src/app/main/ui/shapes/export.cljs
@@ -64,6 +64,7 @@
text? (= :text (:type shape))
path? (= :path (:type shape))
mask? (and group? (:masked-group? shape))
+ bool? (= :bool (:type shape))
center (gsh/center-shape shape)]
(-> props
(add! :name)
@@ -102,7 +103,10 @@
(add! :content (comp json/encode uuid->string))))
(cond-> mask?
- (obj/set! "penpot:masked-group" "true")))))
+ (obj/set! "penpot:masked-group" "true"))
+
+ (cond-> bool?
+ (add! :bool-type)))))
(defn add-library-refs [props shape]
@@ -127,30 +131,41 @@
(mf/defc export-grid-data
[{:keys [grids]}]
- (when-not (empty? grids)
- [:> "penpot:grids" #js {}
- (for [{:keys [type display params]} grids]
- (let [props (->> (d/without-keys params [:color])
- (prefix-keys)
- (clj->js))]
- [:> "penpot:grid"
- (-> props
- (obj/set! "penpot:color" (get-in params [:color :color]))
- (obj/set! "penpot:opacity" (get-in params [:color :opacity]))
- (obj/set! "penpot:type" (d/name type))
- (cond-> (some? display)
- (obj/set! "penpot:display" (str display))))]))]))
+ [:> "penpot:grids" #js {}
+ (for [{:keys [type display params]} grids]
+ (let [props (->> (d/without-keys params [:color])
+ (prefix-keys)
+ (clj->js))]
+ [:> "penpot:grid"
+ (-> props
+ (obj/set! "penpot:color" (get-in params [:color :color]))
+ (obj/set! "penpot:opacity" (get-in params [:color :opacity]))
+ (obj/set! "penpot:type" (d/name type))
+ (cond-> (some? display)
+ (obj/set! "penpot:display" (str display))))]))])
+
+(mf/defc export-flows
+ [{:keys [flows]}]
+ [:> "penpot:flows" #js {}
+ (for [{:keys [id name starting-frame]} flows]
+ [:> "penpot:flow" #js {:id id
+ :name name
+ :starting-frame starting-frame}])])
(mf/defc export-page
[{:keys [options]}]
- (let [saved-grids (get options :saved-grids)]
- (when-not (empty? saved-grids)
- (let [parse-grid
- (fn [[type params]]
- {:type type :params params})
- grids (->> saved-grids (mapv parse-grid))]
- [:> "penpot:page" #js {}
- [:& export-grid-data {:grids grids}]]))))
+ (let [saved-grids (get options :saved-grids)
+ flows (get options :flows)]
+ (when (or (seq saved-grids) (seq flows))
+ (let [parse-grid
+ (fn [[type params]]
+ {:type type :params params})
+ grids (->> saved-grids (mapv parse-grid))]
+ [:> "penpot:page" #js {}
+ (when (seq saved-grids)
+ [:& export-grid-data {:grids grids}])
+ (when (seq flows)
+ [:& export-flows {:flows flows}])]))))
(mf/defc export-shadow-data
[{:keys [shadow]}]
@@ -216,11 +231,18 @@
[{:keys [interactions]}]
(when-not (empty? interactions)
[:> "penpot:interactions" #js {}
- (for [{:keys [action-type destination event-type]} interactions]
+ (for [interaction interactions]
[:> "penpot:interaction"
- #js {:penpot:action-type (d/name action-type)
- :penpot:destination (str destination)
- :penpot:event-type (d/name event-type)}])]))
+ #js {:penpot:event-type (d/name (:event-type interaction))
+ :penpot:action-type (d/name (:action-type interaction))
+ :penpot:delay ((d/nilf str) (:delay interaction))
+ :penpot:destination ((d/nilf str) (:destination interaction))
+ :penpot:overlay-pos-type ((d/nilf d/name) (:overlay-pos-type interaction))
+ :penpot:overlay-position-x ((d/nilf get-in) interaction [:overlay-position :x])
+ :penpot:overlay-position-y ((d/nilf get-in) interaction [:overlay-position :y])
+ :penpot:url (:url interaction)
+ :penpot:close-click-outside ((d/nilf str) (:close-click-outside interaction))
+ :penpot:background-overlay ((d/nilf str) (:background-overlay interaction))}])]))
(mf/defc export-data
[{:keys [shape]}]
diff --git a/frontend/src/app/main/ui/shapes/filters.cljs b/frontend/src/app/main/ui/shapes/filters.cljs
index 25af34796..60ba3fee6 100644
--- a/frontend/src/app/main/ui/shapes/filters.cljs
+++ b/frontend/src/app/main/ui/shapes/filters.cljs
@@ -202,17 +202,12 @@
:height (- y2 y1)})))))
(defn calculate-padding [shape]
- (let [{:keys [stroke-style stroke-alignment stroke-width]} shape]
- (cond
- (and (not= stroke-style :none)
- (= stroke-alignment :outer))
- stroke-width
-
- (and (not= stroke-style :none)
- (= stroke-alignment :center))
- (mth/ceil (/ stroke-width 2))
-
- :else 0)))
+ (let [stroke-width (case (:stroke-alignment shape :center)
+ :center (/ (:stroke-width shape 0) 2)
+ :outer (:stroke-width shape 0)
+ 0)
+ margin (gsh/shape-stroke-margin shape stroke-width)]
+ (+ stroke-width margin)))
(mf/defc filters
[{:keys [filter-id shape]}]
@@ -221,9 +216,7 @@
;; Adds the previous filter as `filter-in` parameter
filters (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters)))
-
bounds (get-filters-bounds shape filters (or (-> shape :blur :value) 0))
-
padding (calculate-padding shape)]
[:*
diff --git a/frontend/src/app/main/ui/shapes/group.cljs b/frontend/src/app/main/ui/shapes/group.cljs
index 2dafc7bff..49a628350 100644
--- a/frontend/src/app/main/ui/shapes/group.cljs
+++ b/frontend/src/app/main/ui/shapes/group.cljs
@@ -27,21 +27,31 @@
[(first childs) (rest childs)]
[nil childs])
+ ;; We need to separate mask and clip into two because a bug in Firefox
+ ;; breaks when the group has clip+mask+foreignObject
+ ;; Clip and mask separated will work in every platform
+ ; Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1734805
+ [clip-wrapper clip-props]
+ (if masked-group?
+ ["g" (-> (obj/new)
+ (obj/set! "clipPath" (clip-url render-id mask)))]
+ [mf/Fragment nil])
+
[mask-wrapper mask-props]
(if masked-group?
["g" (-> (obj/new)
- (obj/set! "clipPath" (clip-url render-id mask))
(obj/set! "mask" (mask-url render-id mask)))]
[mf/Fragment nil])]
- [:> mask-wrapper mask-props
- (when masked-group?
- [:> render-mask #js {:frame frame :mask mask}])
+ [:> clip-wrapper clip-props
+ [:> mask-wrapper mask-props
+ (when masked-group?
+ [:> render-mask #js {:frame frame :mask mask}])
- (for [item childs]
- [:& shape-wrapper {:frame frame
- :shape item
- :key (:id item)}])]))))
+ (for [item childs]
+ [:& shape-wrapper {:frame frame
+ :shape item
+ :key (:id item)}])]]))))
diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs
index eb26eb5a4..5b68b7ee9 100644
--- a/frontend/src/app/main/ui/shapes/shape.cljs
+++ b/frontend/src/app/main/ui/shapes/shape.cljs
@@ -72,6 +72,7 @@
[:> wrapper-tag wrapper-props
(when include-metadata?
[:& ed/export-data {:shape shape}])
+
[:defs
[:& defs/svg-defs {:shape shape :render-id render-id}]
[:& filters/filters {:shape shape :filter-id filter-id}]
diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs
index 2dca984ef..1ee53dd4d 100644
--- a/frontend/src/app/main/ui/shapes/text/styles.cljs
+++ b/frontend/src/app/main/ui/shapes/text/styles.cljs
@@ -18,7 +18,8 @@
(let [valign (:vertical-align node "top")
width (some-> (:width shape) (+ 1))
base #js {:height (or (:height shape) "100%")
- :width (or width "100%")}]
+ :width (or width "100%")
+ :fontFamily "sourcesanspro"}]
(cond-> base
(= valign "top") (obj/set! "justifyContent" "flex-start")
(= valign "center") (obj/set! "justifyContent" "center")
@@ -40,6 +41,7 @@
:justifyContent "inherit"
:minHeight (when-not (or auto-width? auto-height?) "100%")
:minWidth (when-not auto-width? "100%")
+ :marginRight "1px"
:verticalAlign "top"}))
(defn generate-paragraph-styles
diff --git a/frontend/src/app/main/ui/share_link.cljs b/frontend/src/app/main/ui/share_link.cljs
index b9a84565b..f8823ddd6 100644
--- a/frontend/src/app/main/ui/share_link.cljs
+++ b/frontend/src/app/main/ui/share_link.cljs
@@ -7,6 +7,7 @@
(ns app.main.ui.share-link
(:require
[app.common.data :as d]
+ [app.common.logging :as log]
[app.config :as cf]
[app.main.data.common :as dc]
[app.main.data.messages :as dm]
@@ -16,12 +17,11 @@
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
- [app.util.logging :as log]
[app.util.router :as rt]
[app.util.webapi :as wapi]
[rumext.alpha :as mf]))
-(log/set-level! :debug)
+(log/set-level! :warn)
(defn prepare-params
[{:keys [sections pages pages-mode]}]
diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs
index 6f630ce14..fc1fe6e08 100644
--- a/frontend/src/app/main/ui/static.cljs
+++ b/frontend/src/app/main/ui/static.cljs
@@ -6,7 +6,6 @@
(ns app.main.ui.static
(:require
- [app.main.data.messages :as dm]
[app.main.data.users :as du]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -72,7 +71,7 @@
[:div.desc-message (tr "labels.internal-error.desc-message")]
[:div.sign-info
[:a.btn-primary.btn-small
- {:on-click (st/emitf (dm/assign-exception nil))}
+ {:on-click (st/emitf (rt/assign-exception nil))}
(tr "labels.retry")]]])
(mf/defc exception-page
diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs
index c3b54d8bb..a17097db4 100644
--- a/frontend/src/app/main/ui/viewer.cljs
+++ b/frontend/src/app/main/ui/viewer.cljs
@@ -6,6 +6,8 @@
(ns app.main.ui.viewer
(:require
+ [app.common.exceptions :as ex]
+ [app.common.geom.point :as gpt]
[app.main.data.comments :as dcm]
[app.main.data.viewer :as dv]
[app.main.data.viewer.shortcuts :as sc]
@@ -13,6 +15,7 @@
[app.main.store :as st]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
+ [app.main.ui.shapes.filters :as filters]
[app.main.ui.share-link]
[app.main.ui.static :as static]
[app.main.ui.viewer.comments :refer [comments-layer]]
@@ -27,22 +30,19 @@
(defn- calculate-size
[frame zoom]
- {:width (* (:width frame) zoom)
- :height (* (:height frame) zoom)
- :vbox (str "0 0 " (:width frame 0) " " (:height frame 0))})
+ (let [{:keys [_ _ width height]} (filters/get-filters-bounds frame)]
+ {:width (* width zoom)
+ :height (* height zoom)
+ :vbox (str "0 0 " width " " height)}))
(mf/defc viewer
[{:keys [params data]}]
(let [{:keys [page-id section index]} params
+ {:keys [file users project permissions]} data
local (mf/deref refs/viewer-local)
- file (:file data)
- users (:users data)
- project (:project data)
- perms (:permissions data)
-
page-id (or page-id (-> file :data :pages first))
page (mf/use-memo
@@ -58,15 +58,26 @@
(mf/deps frame zoom)
(fn [] (calculate-size frame zoom)))
+ interactions-mode
+ (:interactions-mode local)
+
on-click
(mf/use-callback
(mf/deps section)
(fn [_]
(when (= section :comments)
- (st/emit! (dcm/close-thread)))))]
+ (st/emit! (dcm/close-thread)))))
+
+ close-overlay
+ (mf/use-callback
+ (fn [frame]
+ (st/emit! (dv/close-overlay (:id frame)))))]
(hooks/use-shortcuts ::viewer sc/shortcuts)
+ (when (nil? page)
+ (ex/raise :type :not-found))
+
;; Set the page title
(mf/use-effect
(mf/deps (:name file))
@@ -90,8 +101,8 @@
:file file
:page page
:frame frame
- :permissions perms
- :zoom (:zoom local)
+ :permissions permissions
+ :zoom zoom
:section section}]
[:div.viewer-content
@@ -118,7 +129,6 @@
:section section
:local local}]
-
[:div.viewport-container
{:style {:width (:width size)
:height (:height size)
@@ -133,11 +143,43 @@
[:& interactions/viewport
{:frame frame
+ :base-frame frame
+ :frame-offset (gpt/point 0 0)
:size size
:page page
:file file
:users users
- :local local}]]))]]]))
+ :interactions-mode interactions-mode}]
+
+ (for [overlay (:overlays local)]
+ (let [size-over (calculate-size (:frame overlay) zoom)]
+ [:*
+ (when (or (:close-click-outside overlay)
+ (:background-overlay overlay))
+ [:div.viewer-overlay-background
+ {:class (dom/classnames
+ :visible (:background-overlay overlay))
+ :style {:width (:width frame)
+ :height (:height frame)
+ :position "absolute"
+ :left 0
+ :top 0}
+ :on-click #(when (:close-click-outside overlay)
+ (close-overlay (:frame overlay)))}])
+ [:div.viewport-container.viewer-overlay
+ {:style {:width (:width size-over)
+ :height (:height size-over)
+ :left (* (:x (:position overlay)) zoom)
+ :top (* (:y (:position overlay)) zoom)}}
+ [:& interactions/viewport
+ {:frame (:frame overlay)
+ :base-frame frame
+ :frame-offset (:position overlay)
+ :size size-over
+ :page page
+ :file file
+ :users users
+ :interactions-mode interactions-mode}]]]))]))]]]))
;; --- Component: Viewer Page
diff --git a/frontend/src/app/main/ui/viewer/handoff/render.cljs b/frontend/src/app/main/ui/viewer/handoff/render.cljs
index 093cfc846..0f1854fc1 100644
--- a/frontend/src/app/main/ui/viewer/handoff/render.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/render.cljs
@@ -8,8 +8,10 @@
"The main container for a frame in handoff mode"
(:require
[app.common.geom.shapes :as geom]
+ [app.common.pages :as cp]
[app.main.data.viewer :as dv]
[app.main.store :as st]
+ [app.main.ui.shapes.bool :as bool]
[app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.frame :as frame]
[app.main.ui.shapes.group :as group]
@@ -106,6 +108,22 @@
(obj/merge! #js {:childs childs}))]
[:> group-wrapper props]))))
+(defn bool-container-factory
+ [objects]
+ (let [shape-container (shape-container-factory objects)
+ bool-shape (bool/bool-shape shape-container)
+ bool-wrapper (shape-wrapper-factory bool-shape)]
+ (mf/fnc bool-container
+ {::mf/wrap-props false}
+ [props]
+ (let [shape (unchecked-get props "shape")
+ children-ids (cp/get-children (:id shape) objects)
+ childs (select-keys objects children-ids)
+ props (-> (obj/new)
+ (obj/merge! props)
+ (obj/merge! #js {:childs childs}))]
+ [:> bool-wrapper props]))))
+
(defn svg-raw-container-factory
[objects]
(let [shape-container (shape-container-factory objects)
@@ -133,12 +151,18 @@
[props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
- group-container (mf/use-memo
- (mf/deps objects)
- #(group-container-factory objects))
- svg-raw-container (mf/use-memo
- (mf/deps objects)
- #(svg-raw-container-factory objects))]
+
+ group-container
+ (mf/use-memo (mf/deps objects)
+ #(group-container-factory objects))
+
+ bool-container
+ (mf/use-memo (mf/deps objects)
+ #(bool-container-factory objects))
+
+ svg-raw-container
+ (mf/use-memo (mf/deps objects)
+ #(svg-raw-container-factory objects))]
(when (and shape (not (:hidden shape)))
(let [shape (-> (geom/transform-shape shape)
(geom/translate-to-frame frame))
@@ -151,6 +175,7 @@
:image [:> image-wrapper opts]
:circle [:> circle-wrapper opts]
:group [:> group-container opts]
+ :bool [:> bool-container opts]
:svg-raw [:> svg-raw-container opts])))))))
(mf/defc render-frame-svg
diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs
index b0c869f90..2c838251e 100644
--- a/frontend/src/app/main/ui/viewer/header.cljs
+++ b/frontend/src/app/main/ui/viewer/header.cljs
@@ -13,14 +13,14 @@
[app.main.ui.components.fullscreen :as fs]
[app.main.ui.icons :as i]
[app.main.ui.viewer.comments :refer [comments-menu]]
- [app.main.ui.viewer.interactions :refer [interactions-menu]]
+ [app.main.ui.viewer.interactions :refer [flows-menu interactions-menu]]
[app.main.ui.workspace.header :refer [zoom-widget]]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[rumext.alpha :as mf]))
(mf/defc header-options
- [{:keys [section zoom page file permissions]}]
+ [{:keys [section zoom page file index permissions]}]
(let [fullscreen (mf/use-ctx fs/fullscreen-context)
toggle-fullscreen
@@ -43,7 +43,10 @@
[:div.options-zone
(case section
- :interactions [:& interactions-menu]
+ :interactions [:*
+ (when index
+ [:& flows-menu {:page page :index index}])
+ [:& interactions-menu]]
:comments [:& comments-menu]
[:div.view-options])
@@ -64,10 +67,10 @@
i/full-screen-off
i/full-screen)]
- (when (:edit permissions)
+ (when (:is-admin permissions)
[:span.btn-primary {:on-click open-share-dialog} (tr "labels.share-prototype")])
- (when (:edit permissions)
+ (when (:can-edit permissions)
[:span.btn-text-dark {:on-click go-to-workspace} (tr "labels.edit-file")])]))
(mf/defc header-sitemap
@@ -84,15 +87,23 @@
show-dropdown? (mf/use-state false)
+ open-dropdown
+ (fn []
+ (reset! show-dropdown? true)
+ (st/emit! dv/close-thumbnails-panel))
+
+ close-dropdown
+ (fn []
+ (reset! show-dropdown? false))
+
navigate-to
(fn [page-id]
(st/emit! (dv/go-to-page page-id))
- (reset! show-dropdown? false))
- ]
+ (reset! show-dropdown? false))]
[:div.sitemap-zone {:alt (tr "viewer.header.sitemap")}
[:div.breadcrumb
- {:on-click #(swap! show-dropdown? not)}
+ {:on-click open-dropdown}
[:span.project-name project-name]
[:span "/"]
[:span.file-name file-name]
@@ -101,7 +112,7 @@
[:span.icon i/arrow-down]
[:& dropdown {:show @show-dropdown?
- :on-close #(swap! show-dropdown? not)}
+ :on-close close-dropdown}
[:ul.dropdown
(for [id (get-in file [:data :pages])]
[:li {:id (str id)
@@ -125,7 +136,6 @@
(fn [section]
(st/emit! (dv/go-to-section section)))]
-
[:header.viewer-header
[:div.main-icon
[:a {:on-click go-to-dashboard
@@ -141,14 +151,16 @@
:alt (tr "viewer.header.interactions-section")}
i/play]
- (when (:edit permissions)
+ (when (:can-edit permissions)
[:button.mode-zone-button.tooltip.tooltip-bottom
{:on-click #(navigate :comments)
:class (dom/classnames :active (= section :comments))
:alt (tr "viewer.header.comments-section")}
i/chat])
- (when (:read permissions)
+ (when (or (= (:type permissions) :membership)
+ (and (= (:type permissions) :share-link)
+ (contains? (:flags permissions) :section-handoff)))
[:button.mode-zone-button.tooltip.tooltip-bottom
{:on-click #(navigate :handoff)
:class (dom/classnames :active (= section :handoff))
@@ -159,5 +171,6 @@
:permissions permissions
:page page
:file file
+ :index index
:zoom zoom}]]))
diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs
index 15846eca9..f4faa97fc 100644
--- a/frontend/src/app/main/ui/viewer/interactions.cljs
+++ b/frontend/src/app/main/ui/viewer/interactions.cljs
@@ -10,6 +10,7 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.pages :as cp]
+ [app.common.types.page-options :as cto]
[app.main.data.comments :as dcm]
[app.main.data.viewer :as dv]
[app.main.refs :as refs]
@@ -41,25 +42,23 @@
(mf/defc viewport
{::mf/wrap [mf/memo]}
- [{:keys [local page frame size]}]
- (let [interactions? (:interactions-show? local)
-
- objects (mf/use-memo
+ [{:keys [page interactions-mode frame base-frame frame-offset size]}]
+ (let [objects (mf/use-memo
(mf/deps page frame)
(prepare-objects page frame))
wrapper (mf/use-memo
- (mf/deps objects interactions?)
- #(shapes/frame-container-factory objects interactions?))
+ (mf/deps objects)
+ #(shapes/frame-container-factory objects))
- ;; Retrieve frame again with correct modifier
+ ;; Retrieve frames again with correct modifier
frame (get objects (:id frame))
+ base-frame (get objects (:id base-frame))
on-click
(fn [_]
- (let [mode (:interactions-mode local)]
- (when (= mode :show-on-click)
- (st/emit! dv/flash-interactions))))
+ (when (= interactions-mode :show-on-click)
+ (st/emit! dv/flash-interactions)))
on-mouse-wheel
(fn [event]
@@ -77,7 +76,7 @@
(st/emit! (dcm/close-thread))))]
(mf/use-effect
- (mf/deps local) ;; on-click event depends on local
+ (mf/deps interactions-mode) ;; on-click event depends on interactions-mode
(fn []
;; bind with passive=false to allow the event to be cancelled
;; https://stackoverflow.com/a/57582286/3219895
@@ -89,15 +88,50 @@
(events/unlistenByKey key2)
(events/unlistenByKey key3)))))
- [:svg {:view-box (:vbox size)
- :width (:width size)
- :height (:height size)
- :version "1.1"
- :xmlnsXlink "http://www.w3.org/1999/xlink"
- :xmlns "http://www.w3.org/2000/svg"}
- [:& wrapper {:shape frame
- :show-interactions? interactions?
- :view-box (:vbox size)}]]))
+ [:& (mf/provider shapes/base-frame-ctx) {:value base-frame}
+ [:& (mf/provider shapes/frame-offset-ctx) {:value frame-offset}
+ [:svg {:view-box (:vbox size)
+ :width (:width size)
+ :height (:height size)
+ :version "1.1"
+ :xmlnsXlink "http://www.w3.org/1999/xlink"
+ :xmlns "http://www.w3.org/2000/svg"}
+ [:& wrapper {:shape frame
+ :view-box (:vbox size)}]]]]))
+
+
+(mf/defc flows-menu
+ {::mf/wrap [mf/memo]}
+ [{:keys [page index]}]
+ (let [flows (get-in page [:options :flows])
+ frames (:frames page)
+ frame (get frames index)
+ current-flow (mf/use-state
+ (cto/get-frame-flow flows (:id frame)))
+
+ show-dropdown? (mf/use-state false)
+ toggle-dropdown (mf/use-fn #(swap! show-dropdown? not))
+ hide-dropdown (mf/use-fn #(reset! show-dropdown? false))
+
+ select-flow
+ (mf/use-callback
+ (fn [flow]
+ (reset! current-flow flow)
+ (st/emit! (dv/go-to-frame (:starting-frame flow)))))]
+
+ (when (seq flows)
+ [:div.view-options {:on-click toggle-dropdown}
+ [:span.icon i/play]
+ [:span.label (:name @current-flow)]
+ [:span.icon i/arrow-down]
+ [:& dropdown {:show @show-dropdown?
+ :on-close hide-dropdown}
+ [:ul.dropdown.with-check
+ (for [flow flows]
+ [:li {:class (dom/classnames :selected (= (:id flow) (:id @current-flow)))
+ :on-click #(select-flow flow)}
+ [:span.icon i/tick]
+ [:span.label (:name flow)]])]]])))
(mf/defc interactions-menu
diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs
index 39d90d175..dc10b4446 100644
--- a/frontend/src/app/main/ui/viewer/shapes.cljs
+++ b/frontend/src/app/main/ui/viewer/shapes.cljs
@@ -12,8 +12,11 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as geom]
[app.common.pages :as cp]
+ [app.common.types.interactions :as cti]
[app.main.data.viewer :as dv]
+ [app.main.refs :as refs]
[app.main.store :as st]
+ [app.main.ui.shapes.bool :as bool]
[app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.frame :as frame]
[app.main.ui.shapes.group :as group]
@@ -25,21 +28,162 @@
[app.main.ui.shapes.text :as text]
[app.util.dom :as dom]
[app.util.object :as obj]
+ [app.util.router :as rt]
+ [app.util.timers :as tm]
+ [okulary.core :as l]
[rumext.alpha :as mf]))
-(defn on-mouse-down
- [event interactions]
- (let [interaction (first (filter #(= (:event-type %) :click) interactions))]
- (case (:action-type interaction)
- :navigate
- (let [frame-id (:destination interaction)]
- (dom/stop-propagation event)
- (st/emit! (dv/go-to-frame frame-id)))
+(def base-frame-ctx (mf/create-context nil))
+(def frame-offset-ctx (mf/create-context nil))
- nil)))
+(def viewer-interactions-show?
+ (l/derived :interactions-show? refs/viewer-local))
+
+(defn activate-interaction
+ [interaction shape base-frame frame-offset objects]
+ (case (:action-type interaction)
+ :navigate
+ (when-let [frame-id (:destination interaction)]
+ (st/emit! (dv/go-to-frame frame-id)))
+
+ :open-overlay
+ (let [dest-frame-id (:destination interaction)
+ close-click-outside (:close-click-outside interaction)
+ background-overlay (:background-overlay interaction)
+
+ dest-frame (get objects dest-frame-id)
+ position (cti/calc-overlay-position interaction
+ base-frame
+ dest-frame
+ frame-offset)]
+ (when dest-frame-id
+ (st/emit! (dv/open-overlay dest-frame-id
+ position
+ close-click-outside
+ background-overlay))))
+
+ :toggle-overlay
+ (let [frame-id (:destination interaction)
+ position (:overlay-position interaction)
+ close-click-outside (:close-click-outside interaction)
+ background-overlay (:background-overlay interaction)]
+ (when frame-id
+ (st/emit! (dv/toggle-overlay frame-id
+ position
+ close-click-outside
+ background-overlay))))
+
+ :close-overlay
+ (let [frame-id (or (:destination interaction)
+ (if (= (:type shape) :frame)
+ (:id shape)
+ (:frame-id shape)))]
+ (st/emit! (dv/close-overlay frame-id)))
+
+ :prev-screen
+ (st/emit! (rt/nav-back-local))
+
+ :open-url
+ (st/emit! (dom/open-new-window (:url interaction)))
+
+ nil))
+
+;; Perform the opposite action of an interaction, if possible
+(defn deactivate-interaction
+ [interaction shape base-frame frame-offset objects]
+ (case (:action-type interaction)
+ :open-overlay
+ (let [frame-id (or (:destination interaction)
+ (if (= (:type shape) :frame)
+ (:id shape)
+ (:frame-id shape)))]
+ (st/emit! (dv/close-overlay frame-id)))
+
+ :toggle-overlay
+ (let [frame-id (:destination interaction)
+ position (:overlay-position interaction)
+ close-click-outside (:close-click-outside interaction)
+ background-overlay (:background-overlay interaction)]
+ (when frame-id
+ (st/emit! (dv/toggle-overlay frame-id
+ position
+ close-click-outside
+ background-overlay))))
+
+ :close-overlay
+ (let [dest-frame-id (:destination interaction)
+ close-click-outside (:close-click-outside interaction)
+ background-overlay (:background-overlay interaction)
+
+ dest-frame (get objects dest-frame-id)
+ position (cti/calc-overlay-position interaction
+ base-frame
+ dest-frame
+ frame-offset)]
+ (when dest-frame-id
+ (st/emit! (dv/open-overlay dest-frame-id
+ position
+ close-click-outside
+ background-overlay))))
+ nil))
+
+(defn on-mouse-down
+ [event shape base-frame frame-offset objects]
+ (let [interactions (->> (:interactions shape)
+ (filter #(or (= (:event-type %) :click)
+ (= (:event-type %) :mouse-press))))]
+ (when (seq interactions)
+ (dom/stop-propagation event)
+ (doseq [interaction interactions]
+ (activate-interaction interaction shape base-frame frame-offset objects)))))
+
+(defn on-mouse-up
+ [event shape base-frame frame-offset objects]
+ (let [interactions (->> (:interactions shape)
+ (filter #(= (:event-type %) :mouse-press)))]
+ (when (seq interactions)
+ (dom/stop-propagation event)
+ (doseq [interaction interactions]
+ (deactivate-interaction interaction shape base-frame frame-offset objects)))))
+
+(defn on-mouse-enter
+ [event shape base-frame frame-offset objects]
+ (let [interactions (->> (:interactions shape)
+ (filter #(or (= (:event-type %) :mouse-enter)
+ (= (:event-type %) :mouse-over))))]
+ (when (seq interactions)
+ (dom/stop-propagation event)
+ (doseq [interaction interactions]
+ (activate-interaction interaction shape base-frame frame-offset objects)))))
+
+(defn on-mouse-leave
+ [event shape base-frame frame-offset objects]
+ (let [interactions (->> (:interactions shape)
+ (filter #(= (:event-type %) :mouse-leave)))
+ interactions-inv (->> (:interactions shape)
+ (filter #(= (:event-type %) :mouse-over)))]
+ (when (or (seq interactions) (seq interactions-inv))
+ (dom/stop-propagation event)
+ (doseq [interaction interactions]
+ (activate-interaction interaction shape base-frame frame-offset objects))
+ (doseq [interaction interactions-inv]
+ (deactivate-interaction interaction shape base-frame frame-offset objects)))))
+
+(defn on-load
+ [shape base-frame frame-offset objects]
+ (let [interactions (->> (:interactions shape)
+ (filter #(= (:event-type %) :after-delay)))]
+ (loop [interactions (seq interactions)
+ sems []]
+ (if-let [interaction (first interactions)]
+ (let [sem (tm/schedule (:delay interaction)
+ #(activate-interaction interaction shape base-frame frame-offset objects))]
+ (recur (next interactions)
+ (conj sems sem)))
+ sems))))
(mf/defc interaction
- [{:keys [shape interactions show-interactions?]}]
+ [{:keys [shape interactions interactions-show?]}]
(let [{:keys [x y width height]} (:selrect shape)
frame? (= :frame (:type shape))]
(when-not (empty? interactions)
@@ -49,37 +193,44 @@
:height (+ height 2)
:fill "#31EFB8"
:stroke "#31EFB8"
- :stroke-width (if show-interactions? 1 0)
- :fill-opacity (if show-interactions? 0.2 0)
+ :stroke-width (if interactions-show? 1 0)
+ :fill-opacity (if interactions-show? 0.2 0)
:style {:pointer-events (when frame? "none")}
:transform (geom/transform-matrix shape)}])))
(defn generic-wrapper-factory
"Wrap some svg shape and add interaction controls"
- [component show-interactions?]
+ [component]
(mf/fnc generic-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
- objects (unchecked-get props "objects")
childs (unchecked-get props "childs")
frame (unchecked-get props "frame")
+ objects (unchecked-get props "objects")
- interactions (->> (:interactions shape)
- (filter #(contains? objects (:destination %))))
+ base-frame (mf/use-ctx base-frame-ctx)
+ frame-offset (mf/use-ctx frame-offset-ctx)
- on-mouse-down (mf/use-callback
- (mf/deps interactions)
- (fn [event]
- (on-mouse-down event interactions)))
+ interactions-show? (mf/deref viewer-interactions-show?)
+
+ interactions (:interactions shape)
svg-element? (and (= :svg-raw (:type shape))
(not= :svg (get-in shape [:content :tag])))]
+ (mf/use-effect
+ (fn []
+ (let [sems (on-load shape base-frame frame-offset objects)]
+ #(run! tm/dispose! sems))))
+
(if-not svg-element?
[:> shape-container {:shape shape
- :cursor (when-not (empty? interactions) "pointer")
- :on-mouse-down on-mouse-down}
+ :cursor (when (cti/actionable? interactions) "pointer")
+ :on-mouse-down #(on-mouse-down % shape base-frame frame-offset objects)
+ :on-mouse-up #(on-mouse-up % shape base-frame frame-offset objects)
+ :on-mouse-enter #(on-mouse-enter % shape base-frame frame-offset objects)
+ :on-mouse-leave #(on-mouse-leave % shape base-frame frame-offset objects)}
[:& component {:shape shape
:frame frame
@@ -88,7 +239,7 @@
[:& interaction {:shape shape
:interactions interactions
- :show-interactions? show-interactions?}]]
+ :interactions-show? interactions-show?}]]
;; Don't wrap svg elements inside a otherwise some can break
[:& component {:shape shape
@@ -96,60 +247,63 @@
:childs childs}]))))
(defn frame-wrapper
- [shape-container show-interactions?]
- (generic-wrapper-factory (frame/frame-shape shape-container) show-interactions?))
+ [shape-container]
+ (generic-wrapper-factory (frame/frame-shape shape-container)))
(defn group-wrapper
- [shape-container show-interactions?]
- (generic-wrapper-factory (group/group-shape shape-container) show-interactions?))
+ [shape-container]
+ (generic-wrapper-factory (group/group-shape shape-container)))
+
+(defn bool-wrapper
+ [shape-container]
+ (generic-wrapper-factory (bool/bool-shape shape-container)))
(defn svg-raw-wrapper
- [shape-container show-interactions?]
- (generic-wrapper-factory (svg-raw/svg-raw-shape shape-container) show-interactions?))
+ [shape-container]
+ (generic-wrapper-factory (svg-raw/svg-raw-shape shape-container)))
(defn rect-wrapper
- [show-interactions?]
- (generic-wrapper-factory rect/rect-shape show-interactions?))
+ []
+ (generic-wrapper-factory rect/rect-shape))
(defn image-wrapper
- [show-interactions?]
- (generic-wrapper-factory image/image-shape show-interactions?))
+ []
+ (generic-wrapper-factory image/image-shape))
(defn path-wrapper
- [show-interactions?]
- (generic-wrapper-factory path/path-shape show-interactions?))
+ []
+ (generic-wrapper-factory path/path-shape))
(defn text-wrapper
- [show-interactions?]
- (generic-wrapper-factory text/text-shape show-interactions?))
+ []
+ (generic-wrapper-factory text/text-shape))
(defn circle-wrapper
- [show-interactions?]
- (generic-wrapper-factory circle/circle-shape show-interactions?))
+ []
+ (generic-wrapper-factory circle/circle-shape))
(declare shape-container-factory)
(defn frame-container-factory
- [objects show-interactions?]
- (let [shape-container (shape-container-factory objects show-interactions?)
- frame-wrapper (frame-wrapper shape-container show-interactions?)]
+ [objects]
+ (let [shape-container (shape-container-factory objects)
+ frame-wrapper (frame-wrapper shape-container)]
(mf/fnc frame-container
{::mf/wrap-props false}
[props]
- (let [shape (obj/get props "shape")
- childs (mapv #(get objects %) (:shapes shape))
- shape (geom/transform-shape shape)
- props (obj/merge! #js {} props
- #js {:shape shape
- :childs childs
- :objects objects
- :show-interactions? show-interactions?})]
+ (let [shape (obj/get props "shape")
+ childs (mapv #(get objects %) (:shapes shape))
+ shape (geom/transform-shape shape)
+ props (obj/merge! #js {} props
+ #js {:shape shape
+ :childs childs
+ :objects objects})]
[:> frame-wrapper props]))))
(defn group-container-factory
- [objects show-interactions?]
- (let [shape-container (shape-container-factory objects show-interactions?)
- group-wrapper (group-wrapper shape-container show-interactions?)]
+ [objects]
+ (let [shape-container (shape-container-factory objects)
+ group-wrapper (group-wrapper shape-container)]
(mf/fnc group-container
{::mf/wrap-props false}
[props]
@@ -157,14 +311,27 @@
childs (mapv #(get objects %) (:shapes shape))
props (obj/merge! #js {} props
#js {:childs childs
- :objects objects
- :show-interactions? show-interactions?})]
+ :objects objects})]
[:> group-wrapper props]))))
+(defn bool-container-factory
+ [objects]
+ (let [shape-container (shape-container-factory objects)
+ bool-wrapper (bool-wrapper shape-container)]
+ (mf/fnc bool-container
+ {::mf/wrap-props false}
+ [props]
+ (let [shape (unchecked-get props "shape")
+ childs (select-keys objects (cp/get-children (:id shape) objects))
+ props (obj/merge! #js {} props
+ #js {:childs childs
+ :objects objects})]
+ [:> bool-wrapper props]))))
+
(defn svg-raw-container-factory
- [objects show-interactions?]
- (let [shape-container (shape-container-factory objects show-interactions?)
- svg-raw-wrapper (svg-raw-wrapper shape-container show-interactions?)]
+ [objects]
+ (let [shape-container (shape-container-factory objects)
+ svg-raw-wrapper (svg-raw-wrapper shape-container)]
(mf/fnc svg-raw-container
{::mf/wrap-props false}
[props]
@@ -172,26 +339,30 @@
childs (mapv #(get objects %) (:shapes shape))
props (obj/merge! #js {} props
#js {:childs childs
- :objects objects
- :show-interactions? show-interactions?})]
+ :objects objects})]
[:> svg-raw-wrapper props]))))
(defn shape-container-factory
- [objects show-interactions?]
- (let [path-wrapper (path-wrapper show-interactions?)
- text-wrapper (text-wrapper show-interactions?)
- rect-wrapper (rect-wrapper show-interactions?)
- image-wrapper (image-wrapper show-interactions?)
- circle-wrapper (circle-wrapper show-interactions?)]
+ [objects]
+ (let [path-wrapper (path-wrapper)
+ text-wrapper (text-wrapper)
+ rect-wrapper (rect-wrapper)
+ image-wrapper (image-wrapper)
+ circle-wrapper (circle-wrapper)]
(mf/fnc shape-container
{::mf/wrap-props false}
[props]
- (let [group-container (mf/use-memo
- (mf/deps objects)
- #(group-container-factory objects show-interactions?))
- svg-raw-container (mf/use-memo
- (mf/deps objects)
- #(svg-raw-container-factory objects show-interactions?))
+ (let [group-container
+ (mf/use-memo (mf/deps objects)
+ #(group-container-factory objects))
+
+ bool-container
+ (mf/use-memo (mf/deps objects)
+ #(bool-container-factory objects))
+
+ svg-raw-container
+ (mf/use-memo (mf/deps objects)
+ #(svg-raw-container-factory objects))
shape (unchecked-get props "shape")
frame (unchecked-get props "frame")]
(when (and shape (not (:hidden shape)))
@@ -207,11 +378,12 @@
:image [:> image-wrapper opts]
:circle [:> circle-wrapper opts]
:group [:> group-container {:shape shape :frame frame :objects objects}]
+ :bool [:> bool-container {:shape shape :frame frame :objects objects}]
:svg-raw [:> svg-raw-container {:shape shape :frame frame :objects objects}])))))))
(mf/defc frame-svg
{::mf/wrap [mf/memo]}
- [{:keys [objects frame zoom show-interactions?] :or {zoom 1} :as props}]
+ [{:keys [objects frame zoom] :or {zoom 1} :as props}]
(let [modifier (-> (gpt/point (:x frame) (:y frame))
(gpt/negate)
(gmt/translate-matrix))
@@ -229,7 +401,7 @@
" " (:height frame 0))
wrapper (mf/use-memo
(mf/deps objects)
- #(frame-container-factory objects show-interactions?))]
+ #(frame-container-factory objects))]
[:svg {:view-box vbox
:width width
@@ -238,6 +410,5 @@
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
[:& wrapper {:shape frame
- :show-interactions? show-interactions?
:view-box vbox}]]))
diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs
index e2eaf6136..1ccc80d5b 100644
--- a/frontend/src/app/main/ui/workspace.cljs
+++ b/frontend/src/app/main/ui/workspace.cljs
@@ -121,7 +121,6 @@
(fn []
(st/emit! (dw/setup-layout layout-name))))
-
(mf/use-effect
(mf/deps project-id file-id)
(fn []
diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs
index 7e71d5c4a..e0688ade1 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs
@@ -36,9 +36,6 @@
(def picked-color-select
(l/derived :picked-color-select refs/workspace-local))
-(def picked-shift?
- (l/derived :picked-shift? refs/workspace-local))
-
(def viewport
(l/derived (l/in [:workspace-local :vport]) st/state))
@@ -202,10 +199,10 @@
h
(str (* s 100) "%")
(str (* l 100) "%")))]
- (dom/set-css-property node "--color" (str/join ", " rgb))
- (dom/set-css-property node "--hue-rgb" (str/join ", " hue-rgb))
- (dom/set-css-property node "--saturation-grad-from" (format-hsl hsl-from))
- (dom/set-css-property node "--saturation-grad-to" (format-hsl hsl-to)))))
+ (dom/set-css-property! node "--color" (str/join ", " rgb))
+ (dom/set-css-property! node "--hue-rgb" (str/join ", " hue-rgb))
+ (dom/set-css-property! node "--saturation-grad-from" (format-hsl hsl-from))
+ (dom/set-css-property! node "--saturation-grad-to" (format-hsl hsl-to)))))
;; When closing the modal we update the recent-color list
(mf/use-effect
diff --git a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs
index ed86777bd..00a0c7851 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs
@@ -27,15 +27,27 @@
:v (mf/use-ref nil)
:alpha (mf/use-ref nil)}
+ setup-hex-color
+ (fn [hex]
+ (let [[r g b] (uc/hex->rgb hex)
+ [h s v] (uc/hex->hsv hex)]
+ (on-change {:hex hex
+ :h h :s s :v v
+ :r r :g g :b b})))
on-change-hex
(fn [e]
(let [val (-> e dom/get-target-val parse-hex)]
(when (uc/hex? val)
- (let [[r g b] (uc/hex->rgb val)
- [h s v] (uc/hex->hsv hex)]
- (on-change {:hex val
- :h h :s s :v v
- :r r :g g :b b})))))
+ (setup-hex-color val))))
+
+ on-blur-hex
+ (fn [e]
+ (let [val (-> e dom/get-target-val)
+ val (cond
+ (uc/color? val) (uc/parse-color val)
+ (uc/hex? (parse-hex val)) (parse-hex val))]
+ (when (some? val)
+ (setup-hex-color val))))
on-change-property
(fn [property max-value]
@@ -81,9 +93,10 @@
[:div.color-values
{:class (when disable-opacity "disable-opacity")}
[:input {:id "hex-value"
- :ref (:hex refs)
- :default-value hex
- :on-change on-change-hex}]
+ :ref (:hex refs)
+ :default-value hex
+ :on-change on-change-hex
+ :on-blur on-blur-hex}]
(if (= type :rgb)
[:*
diff --git a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs
index d4a2e1772..b886df4f1 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs
@@ -115,7 +115,8 @@
:on-mouse-down #(reset! dragging? true)
:on-mouse-up #(reset! dragging? false)
:on-pointer-down (partial dom/capture-pointer)
- :on-pointer-up (partial dom/release-pointer)
+ :on-lost-pointer-capture #(do (dom/release-pointer %)
+ (reset! dragging? false))
:on-click calculate-pos
:on-mouse-move #(when @dragging? (calculate-pos %))}]
[:div.handler {:style {:pointer-events "none"
diff --git a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs
index ea79bbd3c..8fbd1e534 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs
@@ -26,7 +26,8 @@
{:on-mouse-down #(reset! dragging? true)
:on-mouse-up #(reset! dragging? false)
:on-pointer-down (partial dom/capture-pointer)
- :on-pointer-up (partial dom/release-pointer)
+ :on-lost-pointer-capture #(do (dom/release-pointer %)
+ (reset! dragging? false))
:on-click calculate-pos
:on-mouse-move #(when @dragging? (calculate-pos %))}
[:div.handler {:style {:pointer-events "none"
diff --git a/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs
index e0e630e43..4518decda 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs
@@ -35,7 +35,8 @@
:on-mouse-down #(reset! dragging? true)
:on-mouse-up #(reset! dragging? false)
:on-pointer-down (partial dom/capture-pointer)
- :on-pointer-up (partial dom/release-pointer)
+ :on-lost-pointer-capture #(do (dom/release-pointer %)
+ (reset! dragging? false))
:on-click calculate-pos
:on-mouse-move #(when @dragging? (calculate-pos %))}
diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs
index 29180d9a1..9cdae0466 100644
--- a/frontend/src/app/main/ui/workspace/context_menu.cljs
+++ b/frontend/src/app/main/ui/workspace/context_menu.cljs
@@ -7,8 +7,10 @@
(ns app.main.ui.workspace.context-menu
"A workspace specific context menu (mouse right click)."
(:require
+ [app.common.types.page-options :as cto]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
+ [app.main.data.workspace.interactions :as dwi]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shortcuts :as sc]
[app.main.data.workspace.undo :as dwu]
@@ -16,6 +18,7 @@
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.context :as ctx]
+ [app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :refer [tr] :as i18n]
[app.util.timers :as timers]
@@ -31,10 +34,52 @@
(dom/stop-propagation event))
(mf/defc menu-entry
- [{:keys [title shortcut on-click] :as props}]
- [:li {:on-click on-click}
- [:span.title title]
- [:span.shortcut (or shortcut "")]])
+ [{:keys [title shortcut on-click children] :as props}]
+ (let [submenu-ref (mf/use-ref nil)
+ hovering? (mf/use-ref false)
+
+ on-pointer-enter
+ (mf/use-callback
+ (fn []
+ (mf/set-ref-val! hovering? true)
+ (let [submenu-node (mf/ref-val submenu-ref)]
+ (when (some? submenu-node)
+ (dom/set-css-property! submenu-node "display" "block")))))
+
+ on-pointer-leave
+ (mf/use-callback
+ (fn []
+ (mf/set-ref-val! hovering? false)
+ (let [submenu-node (mf/ref-val submenu-ref)]
+ (when (some? submenu-node)
+ (timers/schedule
+ 200
+ #(when-not (mf/ref-val hovering?)
+ (dom/set-css-property! submenu-node "display" "none")))))))
+
+ set-dom-node
+ (mf/use-callback
+ (fn [dom]
+ (let [submenu-node (mf/ref-val submenu-ref)]
+ (when (and (some? dom) (some? submenu-node))
+ (dom/set-css-property! submenu-node "top" (str (.-offsetTop dom) "px"))))))]
+
+ [:li {:ref set-dom-node
+ :on-click on-click
+ :on-pointer-enter on-pointer-enter
+ :on-pointer-leave on-pointer-leave}
+ [:span.title title]
+ [:span.shortcut (or shortcut "")]
+
+ (when (> (count children) 1)
+ [:span.submenu-icon i/arrow-slide])
+
+ (when (> (count children) 1)
+ [:ul.workspace-context-menu
+ {:ref submenu-ref
+ :style {:display "none" :left 250}
+ :on-context-menu prevent-default}
+ children])]))
(mf/defc menu-separator
[]
@@ -42,16 +87,36 @@
(mf/defc shape-context-menu
[{:keys [mdata] :as props}]
- (let [{:keys [id] :as shape} (:shape mdata)
- selected (:selected mdata)
+ (let [{:keys [shape selected disable-booleans? disable-flatten?]} mdata
+ {:keys [id type]} shape
single? (= (count selected) 1)
multiple? (> (count selected) 1)
- editable-shape? (#{:group :text :path} (:type shape))
+ editable-shape? (#{:group :text :path} type)
+
+ is-group? (and (some? shape) (= :group type))
+ is-bool? (and (some? shape) (= :bool type))
+
+ options (mf/deref refs/workspace-page-options)
+ flows (:flows options)
+
+ options-mode (mf/deref refs/options-mode)
+
+ set-bool
+ (fn [bool-type]
+ #(cond
+ (> (count selected) 1)
+ (st/emit! (dw/create-bool bool-type))
+
+ (and (= (count selected) 1) is-group?)
+ (st/emit! (dw/group-to-bool (:id shape) bool-type))
+
+ (and (= (count selected) 1) is-bool?)
+ (st/emit! (dw/change-bool-type (:id shape) bool-type))))
current-file-id (mf/use-ctx ctx/current-file-id)
- do-duplicate (st/emitf dw/duplicate-selected)
+ do-duplicate (st/emitf (dw/duplicate-selected false))
do-delete (st/emitf dw/delete-selected)
do-copy (st/emitf (dw/copy-selected))
do-cut (st/emitf (dw/copy-selected) dw/delete-selected)
@@ -64,6 +129,8 @@
do-hide-shape (st/emitf (dw/update-shape-flags id {:hidden true}))
do-lock-shape (st/emitf (dw/update-shape-flags id {:blocked true}))
do-unlock-shape (st/emitf (dw/update-shape-flags id {:blocked false}))
+ do-add-flow (st/emitf (dwi/add-flow-selected-frame))
+ do-remove-flow #(st/emitf (dwi/remove-flow (:id %)))
do-create-group (st/emitf dw/group-selected)
do-remove-group (st/emitf dw/ungroup-selected)
do-mask-group (st/emitf dw/mask-group)
@@ -98,7 +165,10 @@
:on-accept confirm-update-remote-component}))
do-show-component (st/emitf (dw/go-to-layout :assets))
do-navigate-component-file (st/emitf (dwl/nav-to-component-file
- (:component-file shape)))]
+ (:component-file shape)))
+
+ do-transform-to-path (st/emitf (dw/convert-selected-to-path))
+ do-flatten (st/emitf (dw/convert-selected-to-path))]
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.copy")
:shortcut (sc/get-tooltip :copy)
@@ -147,7 +217,7 @@
:on-click do-flip-horizontal}]
[:& menu-separator]])
- (when (and single? (= (:type shape) :group))
+ (when (and single? (or is-bool? is-group?))
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.ungroup")
:shortcut (sc/get-tooltip :ungroup)
@@ -165,6 +235,32 @@
:shortcut (sc/get-tooltip :start-editing)
:on-click do-start-editing}])
+ (when-not disable-flatten?
+ [:& menu-entry {:title (tr "workspace.shape.menu.transform-to-path")
+ :on-click do-transform-to-path}])
+
+ (when (and (not disable-booleans?)
+ (or multiple? (and single? (or is-group? is-bool?))))
+ [:& menu-entry {:title (tr "workspace.shape.menu.path")}
+ [:& menu-entry {:title (tr "workspace.shape.menu.union")
+ :shortcut (sc/get-tooltip :boolean-union)
+ :on-click (set-bool :union)}]
+ [:& menu-entry {:title (tr "workspace.shape.menu.difference")
+ :shortcut (sc/get-tooltip :boolean-difference)
+ :on-click (set-bool :difference)}]
+ [:& menu-entry {:title (tr "workspace.shape.menu.intersection")
+ :shortcut (sc/get-tooltip :boolean-intersection)
+ :on-click (set-bool :intersection)}]
+ [:& menu-entry {:title (tr "workspace.shape.menu.exclude")
+ :shortcut (sc/get-tooltip :boolean-exclude)
+ :on-click (set-bool :exclude)}]
+
+ (when (and single? is-bool? (not disable-flatten?))
+ [:*
+ [:& menu-separator]
+ [:& menu-entry {:title (tr "workspace.shape.menu.flatten")
+ :on-click do-flatten}]])])
+
(if (:hidden shape)
[:& menu-entry {:title (tr "workspace.shape.menu.show")
:on-click do-show-shape}]
@@ -177,9 +273,15 @@
[:& menu-entry {:title (tr "workspace.shape.menu.lock")
:on-click do-lock-shape}])
- (when (and (or (nil? (:shape-ref shape))
- (> (count selected) 1))
- (not= (:type shape) :frame))
+ (when (and (= options-mode :prototype) (= (:type shape) :frame))
+ (let [flow (cto/get-frame-flow flows (:id shape))]
+ (if (nil? flow)
+ [:& menu-entry {:title (tr "workspace.shape.menu.flow-start")
+ :on-click do-add-flow}]
+ [:& menu-entry {:title (tr "workspace.shape.menu.delete-flow-start")
+ :on-click (do-remove-flow flow)}])))
+
+ (when (not= (:type shape) :frame)
[:*
[:& menu-separator]
[:& menu-entry {:title (tr "workspace.shape.menu.create-component")
@@ -240,7 +342,7 @@
(when dropdown
(let [bounding-rect (dom/get-bounding-rect dropdown)
window-size (dom/get-window-size)
- delta-x (max (- (:right bounding-rect) (:width window-size)) 0)
+ delta-x (max (- (+ (:right bounding-rect) 250) (:width window-size)) 0)
delta-y (max (- (:bottom bounding-rect) (:height window-size)) 0)
new-style (str "top: " (- top delta-y) "px; "
"left: " (- left delta-x) "px;")]
diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs
index b310d2118..70efd1f49 100644
--- a/frontend/src/app/main/ui/workspace/shapes.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes.cljs
@@ -20,6 +20,7 @@
[app.main.ui.shapes.image :as image]
[app.main.ui.shapes.rect :as rect]
[app.main.ui.shapes.text.fontfaces :as ff]
+ [app.main.ui.workspace.shapes.bool :as bool]
[app.main.ui.workspace.shapes.bounding-box :refer [bounding-box]]
[app.main.ui.workspace.shapes.common :as common]
[app.main.ui.workspace.shapes.frame :as frame]
@@ -35,6 +36,7 @@
(declare shape-wrapper)
(declare group-wrapper)
(declare svg-raw-wrapper)
+(declare bool-wrapper)
(declare frame-wrapper)
(def circle-wrapper (common/generic-wrapper-factory circle/circle-shape))
@@ -92,13 +94,14 @@
[:*
(if-not svg-element?
(case (:type shape)
- :path [:> path/path-wrapper opts]
- :text [:> text/text-wrapper opts]
- :group [:> group-wrapper opts]
- :rect [:> rect-wrapper opts]
- :image [:> image-wrapper opts]
- :circle [:> circle-wrapper opts]
+ :path [:> path/path-wrapper opts]
+ :text [:> text/text-wrapper opts]
+ :group [:> group-wrapper opts]
+ :rect [:> rect-wrapper opts]
+ :image [:> image-wrapper opts]
+ :circle [:> circle-wrapper opts]
:svg-raw [:> svg-raw-wrapper opts]
+ :bool [:> bool-wrapper opts]
;; Only used when drawing a new frame.
:frame [:> frame-wrapper {:shape shape}]
@@ -113,5 +116,6 @@
(def group-wrapper (group/group-wrapper-factory shape-wrapper))
(def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))
+(def bool-wrapper (bool/bool-wrapper-factory shape-wrapper))
(def frame-wrapper (frame/frame-wrapper-factory shape-wrapper))
diff --git a/frontend/src/app/main/ui/workspace/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/shapes/bool.cljs
new file mode 100644
index 000000000..2cc8f6e00
--- /dev/null
+++ b/frontend/src/app/main/ui/workspace/shapes/bool.cljs
@@ -0,0 +1,47 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.ui.workspace.shapes.bool
+ (:require
+ [app.main.data.workspace :as dw]
+ [app.main.refs :as refs]
+ [app.main.store :as st]
+ [app.main.streams :as ms]
+ [app.main.ui.shapes.bool :as bool]
+ [app.main.ui.shapes.shape :refer [shape-container]]
+ [app.util.dom :as dom]
+ [rumext.alpha :as mf]))
+
+(defn use-double-click [{:keys [id]}]
+ (mf/use-callback
+ (mf/deps id)
+ (fn [event]
+ (dom/stop-propagation event)
+ (dom/prevent-default event)
+ (st/emit! (dw/select-inside-group id @ms/mouse-position)))))
+
+(defn bool-wrapper-factory
+ [shape-wrapper]
+ (let [shape-component (bool/bool-shape shape-wrapper)]
+ (mf/fnc bool-wrapper
+ {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
+ ::mf/wrap-props false}
+ [props]
+ (let [shape (unchecked-get props "shape")
+ frame (unchecked-get props "frame")
+
+ childs-ref (mf/use-memo
+ (mf/deps (:id shape))
+ #(refs/select-children (:id shape)))
+
+ childs (mf/deref childs-ref)]
+
+ [:> shape-container {:shape shape}
+ [:& shape-component
+ {:frame frame
+ :shape shape
+ :childs childs}]]))))
+
diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs
index bae6a5d99..a245d5bb2 100644
--- a/frontend/src/app/main/ui/workspace/shapes/path.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs
@@ -6,11 +6,11 @@
(ns app.main.ui.workspace.shapes.path
(:require
+ [app.common.path.commands :as upc]
[app.main.refs :as refs]
[app.main.ui.shapes.path :as path]
[app.main.ui.shapes.shape :refer [shape-container]]
[app.main.ui.workspace.shapes.path.common :as pc]
- [app.util.path.commands :as upc]
[rumext.alpha :as mf]))
(mf/defc path-wrapper
diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs
index aa683f708..0330f101f 100644
--- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs
@@ -8,7 +8,9 @@
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
- [app.common.geom.shapes.path :as gshp]
+ [app.common.geom.shapes.path :as gsp]
+ [app.common.path.commands :as upc]
+ [app.common.path.shapes-to-path :as ups]
[app.main.data.workspace.path :as drp]
[app.main.snap :as snap]
[app.main.store :as st]
@@ -18,10 +20,7 @@
[app.main.ui.workspace.shapes.path.common :as pc]
[app.util.dom :as dom]
[app.util.keyboard :as kbd]
- [app.util.path.commands :as upc]
[app.util.path.format :as upf]
- [app.util.path.geom :as upg]
- [app.util.path.shapes-to-path :as ups]
[clojure.set :refer [map-invert]]
[goog.events :as events]
[rumext.alpha :as mf])
@@ -217,16 +216,16 @@
shape (cond-> shape
(not= :path (:type shape))
- ups/convert-to-path
+ (ups/convert-to-path {})
:always
hooks/use-equal-memo)
base-content (:content shape)
- base-points (mf/use-memo (mf/deps base-content) #(->> base-content upg/content->points))
+ base-points (mf/use-memo (mf/deps base-content) #(->> base-content gsp/content->points))
content (upc/apply-content-modifiers base-content content-modifiers)
- content-points (mf/use-memo (mf/deps content) #(->> content upg/content->points))
+ content-points (mf/use-memo (mf/deps content) #(->> content gsp/content->points))
point->base (->> (map hash-map content-points base-points) (reduce merge))
base->point (map-invert point->base)
@@ -269,10 +268,14 @@
ms/mouse-position
(mf/deps shape zoom)
(fn [position]
- (when-let [point (gshp/path-closest-point shape position)]
+ (when-let [point (gsp/path-closest-point shape position)]
(reset! hover-point (when (< (gpt/distance position point) (/ 10 zoom)) point)))))
[:g.path-editor {:ref editor-ref}
+ [:path {:d (upf/format-path content)
+ :style {:fill "none"
+ :stroke pc/primary-color
+ :strokeWidth (/ 1 zoom)}}]
(when (and preview (not drag-handler))
[:& path-preview {:command preview
:from last-p
diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs
index 6f3b652ce..704920fba 100644
--- a/frontend/src/app/main/ui/workspace/shapes/text.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.workspace.shapes.text
(:require
+ [app.common.logging :as log]
[app.common.math :as mth]
[app.main.data.workspace.texts :as dwt]
[app.main.refs :as refs]
@@ -13,7 +14,6 @@
[app.main.ui.shapes.shape :refer [shape-container]]
[app.main.ui.shapes.text :as text]
[app.util.dom :as dom]
- [app.util.logging :as log]
[app.util.object :as obj]
[app.util.text-editor :as ted]
[app.util.timers :as timers]
diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs
index db9d70aad..d00ea37bd 100644
--- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs
@@ -174,7 +174,7 @@
handle-return
(mf/use-callback
(fn [_ state]
- (let [style (ted/get-editor-current-inline-styles state)
+ (let [style (ted/get-editor-current-block-data state)
state (-> (ted/insert-text state "\n" style)
(handle-change))]
(st/emit! (dwt/update-editor-state shape state)))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
index c759464b9..16889f759 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
@@ -230,7 +230,7 @@
on-fold-group
(mf/use-callback
- (mf/deps group-open?)
+ (mf/deps file-id box path group-open?)
(fn [event]
(dom/stop-propagation event)
(st/emit! (dwl/set-assets-group-open file-id
diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
index 0f147b1f2..928a3e9d8 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
@@ -19,6 +19,7 @@
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[app.util.timers :as ts]
+ [cuerdas.core :as str]
[okulary.core :as l]
[rumext.alpha :as mf]))
@@ -39,6 +40,11 @@
(if (:masked-group? shape)
i/mask
i/folder))
+ :bool (case (:bool-type shape)
+ :difference i/boolean-difference
+ :exclude i/boolean-exclude
+ :intersection i/boolean-intersection
+ #_:default i/boolean-union)
:svg-raw i/file-svg
nil))
@@ -63,7 +69,8 @@
(on-stop-edit)
(swap! local assoc :edition false)
(st/emit! (dw/end-rename-shape)
- (dw/update-shape (:id shape) {:name name}))))
+ (when-not (str/empty? name)
+ (dw/update-shape (:id shape) {:name name})))))
cancel-edit (fn []
(on-stop-edit)
@@ -292,7 +299,8 @@
:shape-ref
:touched
:metadata
- :masked-group?]))
+ :masked-group?
+ :bool-type]))
(defn- strip-objects
[objects]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs
index 89248b58b..e0a62cbff 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs
@@ -11,10 +11,12 @@
[app.main.store :as st]
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
[app.main.ui.context :as ctx]
- [app.main.ui.workspace.sidebar.align :refer [align-options]]
+ [app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]]
+ [app.main.ui.workspace.sidebar.options.menus.booleans :refer [booleans-options]]
[app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]]
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]]
[app.main.ui.workspace.sidebar.options.page :as page]
+ [app.main.ui.workspace.sidebar.options.shapes.bool :as bool]
[app.main.ui.workspace.sidebar.options.shapes.circle :as circle]
[app.main.ui.workspace.sidebar.options.shapes.frame :as frame]
[app.main.ui.workspace.sidebar.options.shapes.group :as group]
@@ -43,6 +45,7 @@
:path [:& path/options {:shape shape}]
:image [:& image/options {:shape shape}]
:svg-raw [:& svg-raw/options {:shape shape}]
+ :bool [:& bool/options {:shape shape}]
nil)
[:& exports-menu
{:shape shape
@@ -60,6 +63,7 @@
:title (tr "workspace.options.design")}
[:div.element-options
[:& align-options]
+ [:& booleans-options]
(case (count selected)
0 [:& page/options]
1 [:& shape-options {:shape (first shapes)
diff --git a/frontend/src/app/main/ui/workspace/sidebar/align.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs
similarity index 98%
rename from frontend/src/app/main/ui/workspace/sidebar/align.cljs
rename to frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs
index acb3c6a42..7b32c969a 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/align.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs
@@ -4,7 +4,7 @@
;;
;; Copyright (c) UXBOX Labs SL
-(ns app.main.ui.workspace.sidebar.align
+(ns app.main.ui.workspace.sidebar.options.menus.align
(:require
[app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs
new file mode 100644
index 000000000..80b2be852
--- /dev/null
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs
@@ -0,0 +1,87 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.ui.workspace.sidebar.options.menus.booleans
+ (:require
+ [app.main.data.workspace :as dw]
+ [app.main.refs :as refs]
+ [app.main.store :as st]
+ [app.main.ui.icons :as i]
+ [app.util.dom :as dom]
+ [app.util.i18n :as i18n :refer [tr]]
+ [rumext.alpha :as mf]))
+
+(mf/defc booleans-options
+ []
+ (let [selected (mf/deref refs/selected-objects)
+ selected-with-children (mf/deref refs/selected-objects-with-children)
+
+ has-invalid-shapes? (->> selected-with-children
+ (some (comp #{:frame :text} :type)))
+
+ first-not-group-like?
+ (and (= (count selected) 1)
+ (not (contains? #{:group :bool} (:type (first selected)))))
+
+ disabled-bool-btns (or (empty? selected) has-invalid-shapes? first-not-group-like?)
+ disabled-flatten (or (empty? selected) has-invalid-shapes?)
+
+ head (first selected)
+ is-group? (and (some? head) (= :group (:type head)))
+ is-bool? (and (some? head) (= :bool (:type head)))
+ head-bool-type (and (some? head) is-bool? (:bool-type head))
+
+ set-bool
+ (fn [bool-type]
+ #(cond
+ (> (count selected) 1)
+ (st/emit! (dw/create-bool bool-type))
+
+ (and (= (count selected) 1) is-group?)
+ (st/emit! (dw/group-to-bool (:id head) bool-type))
+
+ (and (= (count selected) 1) is-bool?)
+ (if (= head-bool-type bool-type)
+ (st/emit! (dw/bool-to-group (:id head)))
+ (st/emit! (dw/change-bool-type (:id head) bool-type)))))]
+
+ [:div.align-options
+ [:div.align-group
+ [:div.align-button.tooltip.tooltip-bottom
+ {:alt (tr "workspace.shape.menu.union")
+ :class (dom/classnames :disabled disabled-bool-btns
+ :selected (= head-bool-type :union))
+ :on-click (set-bool :union)}
+ i/boolean-union]
+
+ [:div.align-button.tooltip.tooltip-bottom
+ {:alt (tr "workspace.shape.menu.difference")
+ :class (dom/classnames :disabled disabled-bool-btns
+ :selected (= head-bool-type :difference))
+ :on-click (set-bool :difference)}
+ i/boolean-difference]
+
+ [:div.align-button.tooltip.tooltip-bottom
+ {:alt (tr "workspace.shape.menu.intersection")
+ :class (dom/classnames :disabled disabled-bool-btns
+ :selected (= head-bool-type :intersection))
+ :on-click (set-bool :intersection)}
+ i/boolean-intersection]
+
+ [:div.align-button.tooltip.tooltip-bottom
+ {:alt (tr "workspace.shape.menu.exclude")
+ :class (dom/classnames :disabled disabled-bool-btns
+ :selected (= head-bool-type :exclude))
+ :on-click (set-bool :exclude)}
+ i/boolean-exclude]]
+
+ [:div.align-group
+ [:div.align-button.tooltip.tooltip-bottom
+ {:alt (tr "workspace.shape.menu.flatten")
+ :class (dom/classnames :disabled disabled-flatten)
+ :on-click (st/emitf (dw/convert-selected-to-path))}
+ i/boolean-flatten]]]))
+
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
index 500b5e038..881b95236 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
@@ -8,70 +8,396 @@
(:require
[app.common.data :as d]
[app.common.pages :as cp]
+ [app.common.types.interactions :as cti]
+ [app.common.types.page-options :as cto]
+ [app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
+ [app.main.data.workspace.interactions :as dwi]
[app.main.refs :as refs]
[app.main.store :as st]
- [app.main.ui.components.dropdown :refer [dropdown]]
+ [app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.icons :as i]
+ [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
+ [app.util.keyboard :as kbd]
+ [cuerdas.core :as str]
+ [okulary.core :as l]
[rumext.alpha :as mf]))
-(mf/defc interactions-menu
- [{:keys [shape] :as props}]
- (let [objects (deref refs/workspace-page-objects)
- interaction (first (:interactions shape)) ; TODO: in the
- ; future we may
- ; have several
- ; interactions in
- ; one shape
+(defn- event-type-names
+ []
+ {:click (tr "workspace.options.interaction-on-click")
+ ; TODO: need more UX research
+ ;; :mouse-over (tr "workspace.options.interaction-while-hovering")
+ ;; :mouse-press (tr "workspace.options.interaction-while-pressing")
+ :mouse-enter (tr "workspace.options.interaction-mouse-enter")
+ :mouse-leave (tr "workspace.options.interaction-mouse-leave")
+ :after-delay (tr "workspace.options.interaction-after-delay")})
+(defn- event-type-name
+ [interaction]
+ (get (event-type-names) (:event-type interaction) "--"))
+
+(defn- action-type-names
+ []
+ {:navigate (tr "workspace.options.interaction-navigate-to")
+ :open-overlay (tr "workspace.options.interaction-open-overlay")
+ :toggle-overlay (tr "workspace.options.interaction-toggle-overlay")
+ :close-overlay (tr "workspace.options.interaction-close-overlay")
+ :prev-screen (tr "workspace.options.interaction-prev-screen")
+ :open-url (tr "workspace.options.interaction-open-url")})
+
+(defn- action-summary
+ [interaction destination]
+ (case (:action-type interaction)
+ :navigate (tr "workspace.options.interaction-navigate-to-dest"
+ (get destination :name (tr "workspace.options.interaction-none")))
+ :open-overlay (tr "workspace.options.interaction-open-overlay-dest"
+ (get destination :name (tr "workspace.options.interaction-none")))
+ :toggle-overlay (tr "workspace.options.interaction-toggle-overlay-dest"
+ (get destination :name (tr "workspace.options.interaction-none")))
+ :close-overlay (tr "workspace.options.interaction-close-overlay-dest"
+ (get destination :name (tr "workspace.options.interaction-self")))
+ :prev-screen (tr "workspace.options.interaction-prev-screen")
+ :open-url (tr "workspace.options.interaction-open-url")
+ "--"))
+
+(defn- overlay-pos-type-names
+ []
+ {:manual (tr "workspace.options.interaction-pos-manual")
+ :center (tr "workspace.options.interaction-pos-center")
+ :top-left (tr "workspace.options.interaction-pos-top-left")
+ :top-right (tr "workspace.options.interaction-pos-top-right")
+ :top-center (tr "workspace.options.interaction-pos-top-center")
+ :bottom-left (tr "workspace.options.interaction-pos-bottom-left")
+ :bottom-right (tr "workspace.options.interaction-pos-bottom-right")
+ :bottom-center (tr "workspace.options.interaction-pos-bottom-center")})
+
+(def flow-for-rename-ref
+ (l/derived (l/in [:workspace-local :flow-for-rename]) st/state))
+
+(mf/defc flow-item
+ [{:keys [flow]}]
+ (let [editing? (mf/use-state false)
+ flow-for-rename (mf/deref flow-for-rename-ref)
+ name-ref (mf/use-ref)
+
+ start-edit (fn []
+ (reset! editing? true))
+
+ accept-edit (fn []
+ (let [name-input (mf/ref-val name-ref)
+ name (dom/get-value name-input)]
+ (reset! editing? false)
+ (st/emit! (dwi/end-rename-flow)
+ (when-not (str/empty? name)
+ (dwi/rename-flow (:id flow) name)))))
+
+ cancel-edit (fn []
+ (reset! editing? false)
+ (st/emit! (dwi/end-rename-flow)))
+
+ on-key-down (fn [event]
+ (when (kbd/enter? event) (accept-edit))
+ (when (kbd/esc? event) (cancel-edit)))]
+
+ (mf/use-effect
+ (fn []
+ #(when editing?
+ (cancel-edit))))
+
+ (mf/use-effect
+ (mf/deps flow-for-rename)
+ #(when (and (= flow-for-rename (:id flow))
+ (not @editing?))
+ (start-edit)))
+
+ (mf/use-effect
+ (mf/deps @editing?)
+ #(when @editing?
+ (let [name-input (mf/ref-val name-ref)]
+ (dom/select-text! name-input))
+ nil))
+
+ [:div.flow-element
+ [:div.flow-button {:on-click (st/emitf (dw/select-shape (:starting-frame flow)))}
+ i/play]
+ (if @editing?
+ [:input.element-name
+ {:type "text"
+ :ref name-ref
+ :on-blur accept-edit
+ :on-key-down on-key-down
+ :auto-focus true
+ :default-value (:name flow "")}]
+ [:span.element-label.flow-name
+ {:on-double-click (st/emitf (dwi/start-rename-flow (:id flow)))}
+ (:name flow)])
+ [:div.add-page {:on-click (st/emitf (dwi/remove-flow (:id flow)))}
+ i/minus]]))
+
+(mf/defc page-flows
+ [{:keys [flows]}]
+ (when (seq flows)
+ [:div.element-set.interactions-options
+ [:div.element-set-title
+ [:span (tr "workspace.options.flows.flow-starts")]]
+ (for [flow flows]
+ [:& flow-item {:flow flow :key (str (:id flow))}])]))
+
+(mf/defc shape-flows
+ [{:keys [flows shape]}]
+ (when (= (:type shape) :frame)
+ (let [flow (cto/get-frame-flow flows (:id shape))]
+ [:div.element-set.interactions-options
+ [:div.element-set-title
+ [:span (tr "workspace.options.flows.flow-start")]]
+ (if (nil? flow)
+ [:div.flow-element
+ [:span.element-label (tr "workspace.options.flows.add-flow-start")]
+ [:div.add-page {:on-click (st/emitf (dwi/add-flow-selected-frame))}
+ i/plus]]
+ [:& flow-item {:flow flow :key (str (:id flow))}])])))
+
+(mf/defc interaction-entry
+ [{:keys [index shape interaction update-interaction remove-interaction]}]
+ (let [objects (deref refs/workspace-page-objects)
destination (get objects (:destination interaction))
frames (mf/use-memo (mf/deps objects)
#(cp/select-frames objects))
- show-frames-dropdown? (mf/use-state false)
+ overlay-pos-type (:overlay-pos-type interaction)
+ close-click-outside? (:close-click-outside interaction false)
+ background-overlay? (:background-overlay interaction false)
- on-set-blur #(reset! show-frames-dropdown? false)
- on-navigate #(when destination
- (st/emit! (dw/select-shapes (d/ordered-set (:id destination)))))
+ extended-open? (mf/use-state false)
- on-select-destination
- (fn [dest]
- (if (nil? dest)
- (st/emit! (dw/update-shape (:id shape) {:interactions []}))
- (st/emit! (dw/update-shape (:id shape) {:interactions [{:event-type :click
- :action-type :navigate
- :destination dest}]}))))]
+ ext-delay-ref (mf/use-ref nil)
- (if (not shape)
- [:*
- [:div.interactions-help-icon i/interaction]
- [:div.interactions-help (tr "workspace.options.select-a-shape")]
- [:div.interactions-help-icon i/play]
- [:div.interactions-help (tr "workspace.options.use-play-button")]]
+ select-text
+ (fn [ref] (fn [_] (dom/select-text! (mf/ref-val ref))))
- [:div.element-set {:on-blur on-set-blur}
- [:div.element-set-title
- [:span (tr "workspace.options.navigate-to")]]
- [:div.element-set-content
- [:div.row-flex
- [:div.custom-select.flex-grow {:on-click #(reset! show-frames-dropdown? true)}
- (if destination
- [:span (:name destination)]
- [:span (tr "workspace.options.select-artboard")])
- [:span.dropdown-button i/arrow-down]
- [:& dropdown {:show @show-frames-dropdown?
- :on-close #(reset! show-frames-dropdown? false)}
- [:ul.custom-select-dropdown
- [:li.dropdown-separator
- {:on-click #(on-select-destination nil)}
- (tr "workspace.options.none")]
+ change-event-type
+ (fn [event]
+ (let [value (-> event dom/get-target dom/get-value d/read-string)]
+ (update-interaction index #(cti/set-event-type % value shape))))
+ change-action-type
+ (fn [event]
+ (let [value (-> event dom/get-target dom/get-value d/read-string)]
+ (update-interaction index #(cti/set-action-type % value))))
+
+ change-delay
+ (fn [value]
+ (update-interaction index #(cti/set-delay % value)))
+
+ change-destination
+ (fn [event]
+ (let [value (-> event dom/get-target dom/get-value)
+ value (when (not= value "") (uuid/uuid value))]
+ (update-interaction index #(cti/set-destination % value))))
+
+ change-url
+ (fn [event]
+ (let [value (-> event dom/get-target dom/get-value)]
+ (update-interaction index #(cti/set-url % value))))
+
+ change-overlay-pos-type
+ (fn [event]
+ (let [value (-> event dom/get-target dom/get-value d/read-string)]
+ (update-interaction index #(cti/set-overlay-pos-type % value shape objects))))
+
+ toggle-overlay-pos-type
+ (fn [pos-type]
+ (update-interaction index #(cti/toggle-overlay-pos-type % pos-type shape objects)))
+
+ change-close-click-outside
+ (fn [event]
+ (let [value (-> event dom/get-target dom/checked?)]
+ (update-interaction index #(cti/set-close-click-outside % value))))
+
+ change-background-overlay
+ (fn [event]
+ (let [value (-> event dom/get-target dom/checked?)]
+ (update-interaction index #(cti/set-background-overlay % value))))]
+
+ [:*
+ [:div.element-set-options-group {:class (dom/classnames
+ :open @extended-open?)}
+
+ ; Summary
+ [:div.element-set-actions-button {:on-click #(swap! extended-open? not)}
+ i/actions]
+ [:div.interactions-summary {:on-click #(swap! extended-open? not)}
+ [:div.trigger-name (event-type-name interaction)]
+ [:div.action-summary (action-summary interaction destination)]]
+ [:div.elemen-set-actions {:on-click #(remove-interaction index)}
+ [:div.element-set-actions-button i/minus]]
+
+ (when @extended-open?
+ [:div.element-set-content
+
+ ; Trigger select
+ [:div.interactions-element.separator
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-trigger")]
+ [:select.input-select
+ {:value (str (:event-type interaction))
+ :on-change change-event-type}
+ (for [[value name] (event-type-names)]
+ (when-not (and (= value :after-delay)
+ (not= (:type shape) :frame))
+ [:option {:value (str value)} name]))]]
+
+ ; Delay
+ (when (cti/has-delay interaction)
+ [:div.interactions-element
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-delay")]
+ [:div.input-element
+ [:> numeric-input {:ref ext-delay-ref
+ :on-click (select-text ext-delay-ref)
+ :on-change change-delay
+ :value (:delay interaction)}]
+ [:span.after (tr "workspace.options.interaction-ms")]]])
+
+ ; Action select
+ [:div.interactions-element.separator
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-action")]
+ [:select.input-select
+ {:value (str (:action-type interaction))
+ :on-change change-action-type}
+ (for [[value name] (action-type-names)]
+ [:option {:value (str value)} name])]]
+
+ ; Destination
+ (when (cti/has-destination interaction)
+ [:div.interactions-element
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-destination")]
+ [:select.input-select
+ {:value (str (:destination interaction))
+ :on-change change-destination}
+ (if (= (:action-type interaction) :close-overlay)
+ [:option {:value ""} (tr "workspace.options.interaction-self")]
+ [:option {:value ""} (tr "workspace.options.interaction-none")])
(for [frame frames]
(when (and (not= (:id frame) (:id shape)) ; A frame cannot navigate to itself
(not= (:id frame) (:frame-id shape))) ; nor a shape to its container frame
- [:li {:key (:id frame)
- :on-click #(on-select-destination (:id frame))}
- (:name frame)]))]]]
- [:span.navigate-icon {:style {:visibility (when (not destination) "hidden")}
- :on-click on-navigate} i/navigate]]]])))
+ [:option {:value (str (:id frame))} (:name frame)]))]])
+
+ ; URL
+ (when (cti/has-url interaction)
+ [:div.interactions-element
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-url")]
+ [:input.input-text {:default-value (str (:url interaction))
+ :on-blur change-url}]])
+
+ (when (cti/has-overlay-opts interaction)
+ [:*
+ ; Overlay position (select)
+ [:div.interactions-element
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-position")]
+ [:select.input-select
+ {:value (str (:overlay-pos-type interaction))
+ :on-change change-overlay-pos-type}
+ (for [[value name] (overlay-pos-type-names)]
+ [:option {:value (str value)} name])]]
+
+ ; Overlay position (buttons)
+ [:div.interactions-element.interactions-pos-buttons
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :center))
+ :on-click #(toggle-overlay-pos-type :center)}
+ i/position-center]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :top-left))
+ :on-click #(toggle-overlay-pos-type :top-left)}
+ i/position-top-left]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :top-right))
+ :on-click #(toggle-overlay-pos-type :top-right)}
+ i/position-top-right]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :top-center))
+ :on-click #(toggle-overlay-pos-type :top-center)}
+ i/position-top-center]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :bottom-left))
+ :on-click #(toggle-overlay-pos-type :bottom-left)}
+ i/position-bottom-left]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :bottom-right))
+ :on-click #(toggle-overlay-pos-type :bottom-right)}
+ i/position-bottom-right]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :bottom-center))
+ :on-click #(toggle-overlay-pos-type :bottom-center)}
+ i/position-bottom-center]]
+
+ ; Overlay click outside
+ [:div.interactions-element
+ [:div.input-checkbox
+ [:input {:type "checkbox"
+ :id (str "close-" index)
+ :checked close-click-outside?
+ :on-change change-close-click-outside}]
+ [:label {:for (str "close-" index)}
+ (tr "workspace.options.interaction-close-outside")]]]
+
+ ; Overlay background
+ [:div.interactions-element
+ [:div.input-checkbox
+ [:input {:type "checkbox"
+ :id (str "background-" index)
+ :checked background-overlay?
+ :on-change change-background-overlay}]
+ [:label {:for (str "background-" index)}
+ (tr "workspace.options.interaction-background")]]]])])]]))
+
+(mf/defc interactions-menu
+ [{:keys [shape] :as props}]
+ (let [interactions (get shape :interactions [])
+
+ options (mf/deref refs/workspace-page-options)
+ flows (:flows options)
+
+ add-interaction
+ (fn []
+ (st/emit! (dwi/add-new-interaction shape)))
+
+ remove-interaction
+ (fn [index]
+ (st/emit! (dwi/remove-interaction shape index)))
+
+ update-interaction
+ (fn [index update-fn]
+ (st/emit! (dwi/update-interaction shape index update-fn)))]
+ [:*
+ (if shape
+ [:& shape-flows {:flows flows
+ :shape shape}]
+ [:& page-flows {:flows flows}])
+
+ [:div.element-set.interactions-options
+ (when (and shape (not (cp/unframed-shape? shape)))
+ [:div.element-set-title
+ [:span (tr "workspace.options.interactions")]
+ [:div.add-page {:on-click add-interaction}
+ i/plus]])
+ [:div.element-set-content
+ (when (= (count interactions) 0)
+ [:*
+ (when (and shape (not (cp/unframed-shape? shape)))
+ [:*
+ [:div.interactions-help-icon i/plus]
+ [:div.interactions-help.separator (tr "workspace.options.add-interaction")]])
+ [:div.interactions-help-icon i/interaction]
+ [:div.interactions-help (tr "workspace.options.select-a-shape")]
+ [:div.interactions-help-icon i/play]
+ [:div.interactions-help (tr "workspace.options.use-play-button")]])]
+ [:div.groups
+ (for [[index interaction] (d/enumerate interactions)]
+ [:& interaction-entry {:index index
+ :shape shape
+ :interaction interaction
+ :update-interaction update-interaction
+ :remove-interaction remove-interaction}])]]]))
+
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
index a5698e4c0..f0bd78126 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
@@ -73,7 +73,7 @@
:group (tr "workspace.options.group-stroke")
(tr "workspace.options.stroke"))
- show-options (not= (:stroke-style values :none) :none)
+ show-options (not= (or (:stroke-style values) :none) :none)
show-caps (and show-caps
(not (#{:inner :outer} (:stroke-alignment values))))
@@ -102,7 +102,10 @@
(mf/use-callback
(mf/deps ids)
(fn []
- (st/emit! (dc/change-stroke ids (dissoc current-stroke-color :id :file-id)))))
+ (let [remove-multiple (fn [[_ value]] (not= value :multiple))
+ current-stroke-color (-> (into {} (filter remove-multiple) current-stroke-color)
+ (assoc :id nil :file-id nil))]
+ (st/emit! (dc/change-stroke ids current-stroke-color)))))
on-stroke-style-change
(fn [event]
@@ -141,7 +144,10 @@
target (dom/get-current-target event)
rect (dom/get-bounding-rect target)
- top (+ (:bottom rect) 5)
+ top (if (< (+ (:bottom rect) 320) (:height window-size))
+ (+ (:bottom rect) 5)
+ (- (:height window-size) 325))
+
left (if (< (+ (:left rect) 200) (:width window-size))
(:left rect)
(- (:width window-size) 205))]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
index 45f313c73..b51955e96 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
@@ -74,7 +74,8 @@
(defn filter-fonts
[{:keys [term backends]} fonts]
- (let [xform (cond-> (map identity)
+ (let [term (str/lower term)
+ xform (cond-> (map identity)
(seq term)
(comp (filter #(str/includes? (str/lower (:name %)) term)))
@@ -175,7 +176,7 @@
[:div.font-selector
[:div.font-selector-dropdown
[:header
- [:input {:placeholder "Search font"
+ [:input {:placeholder (tr "workspace.options.search-font")
:value (:term @state)
:ref input
:spell-check false
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs
new file mode 100644
index 000000000..dc5a8fa8c
--- /dev/null
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs
@@ -0,0 +1,45 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.ui.workspace.sidebar.options.shapes.bool
+ (:require
+ [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
+ [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
+ [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
+ [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
+ [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]]
+ [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]]
+ [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]]
+ [rumext.alpha :as mf]))
+
+(mf/defc options
+ [{:keys [shape] :as props}]
+ (let [ids [(:id shape)]
+ type (:type shape)
+ measure-values (select-keys shape measure-attrs)
+ stroke-values (select-keys shape stroke-attrs)
+ layer-values (select-keys shape layer-attrs)
+ constraint-values (select-keys shape constraint-attrs)]
+ [:*
+ [:& measures-menu {:ids ids
+ :type type
+ :values measure-values}]
+ [:& constraints-menu {:ids ids
+ :values constraint-values}]
+ [:& layer-menu {:ids ids
+ :type type
+ :values layer-values}]
+ [:& fill-menu {:ids ids
+ :type type
+ :values (select-keys shape fill-attrs)}]
+ [:& stroke-menu {:ids ids
+ :type type
+ :show-caps true
+ :values stroke-values}]
+ [:& shadow-menu {:ids ids
+ :values (select-keys shape [:shadow])}]
+ [:& blur-menu {:ids ids
+ :values (select-keys shape [:blur])}]]))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs
index 07dbf09fb..bbad1e169 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs
@@ -93,6 +93,16 @@
:text :ignore}
:svg-raw
+ {:measure :shape
+ :layer :shape
+ :constraint :shape
+ :fill :shape
+ :shadow :shape
+ :blur :shape
+ :stroke :shape
+ :text :ignore}
+
+ :bool
{:measure :shape
:layer :shape
:constraint :shape
diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs
index c989136b5..53d82208a 100644
--- a/frontend/src/app/main/ui/workspace/viewport.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport.cljs
@@ -74,6 +74,7 @@
cursor (mf/use-state (utils/get-cursor :pointer-inner))
hover-ids (mf/use-state nil)
hover (mf/use-state nil)
+ hover-disabled? (mf/use-state false)
frame-hover (mf/use-state nil)
active-frames (mf/use-state {})
@@ -153,13 +154,14 @@
(hooks/setup-cursor cursor alt? panning drawing-tool drawing-path? node-editing?)
(hooks/setup-resize layout viewport-ref)
(hooks/setup-keyboard alt? ctrl? space?)
- (hooks/setup-hover-shapes page-id move-stream objects transform selected ctrl? hover hover-ids zoom)
+ (hooks/setup-hover-shapes page-id move-stream objects transform selected ctrl? hover hover-ids @hover-disabled? zoom)
(hooks/setup-viewport-modifiers modifiers selected objects render-ref)
(hooks/setup-shortcuts node-editing? drawing-path?)
(hooks/setup-active-frames objects vbox hover active-frames)
[:div.viewport
[:div.viewport-overlays
+
[:& wtr/frame-renderer {:objects objects
:background background}]
@@ -195,11 +197,12 @@
[:& use/export-page {:options options}]
- [:& (mf/provider embed/context) {:value true}
- ;; Render root shape
- [:& shapes/root-shape {:key page-id
- :objects objects
- :active-frames @active-frames}]]]
+ [:& (mf/provider use/include-metadata-ctx) {:value false}
+ [:& (mf/provider embed/context) {:value true}
+ ;; Render root shape
+ [:& shapes/root-shape {:key page-id
+ :objects objects
+ :active-frames @active-frames}]]]]
[:svg.viewport-controls
{:xmlns "http://www.w3.org/2000/svg"
@@ -228,7 +231,6 @@
:on-pointer-up on-pointer-up}
[:g {:style {:pointer-events (if disable-events? "none" "auto")}}
-
(when show-outlines?
[:& outline/shape-outlines
{:objects objects
@@ -267,6 +269,17 @@
:on-frame-leave on-frame-leave
:on-frame-select on-frame-select}]
+ (when show-prototypes?
+ [:& widgets/frame-flows
+ {:flows (:flows options)
+ :objects objects
+ :selected selected
+ :zoom zoom
+ :modifiers modifiers
+ :on-frame-enter on-frame-enter
+ :on-frame-leave on-frame-leave
+ :on-frame-select on-frame-select}])
+
(when show-gradient-handlers?
[:& gradients/gradient-handlers
{:id (first selected)
@@ -320,7 +333,8 @@
(when show-prototypes?
[:& interactions/interactions
- {:selected selected}])
+ {:selected selected
+ :hover-disabled? hover-disabled?}])
(when show-selrect?
[:& widgets/selection-rect {:data selrect
diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs
index 8ee77b05a..b5427fb4e 100644
--- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs
@@ -363,12 +363,14 @@
delta-y (-> (.-deltaY ^js event)
(* unit)
(/ zoom))
+
delta-x (-> (.-deltaX ^js event)
(* unit)
(/ zoom))]
(dom/prevent-default event)
(dom/stop-propagation event)
- (if (kbd/shift? event)
+ (if (and (not (cfg/check-platform? :macos)) ;; macos sends delta-x automaticaly, don't need to do it
+ (kbd/shift? event))
(st/emit! (dw/update-viewport-position {:x #(+ % delta-y)}))
(st/emit! (dw/update-viewport-position {:x #(+ % delta-x)
:y #(+ % delta-y)})))))))))
diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
index 9bebb02bd..b9df6b983 100644
--- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
@@ -89,36 +89,46 @@
(hooks/use-stream ms/keyboard-ctrl #(reset! ctrl? %))
(hooks/use-stream ms/keyboard-space #(reset! space? %)))
-(defn setup-hover-shapes [page-id move-stream objects transform selected ctrl? hover hover-ids zoom]
+(defn setup-hover-shapes [page-id move-stream objects transform selected ctrl? hover hover-ids hover-disabled? zoom]
(let [;; We use ref so we don't recreate the stream on a change
zoom-ref (mf/use-ref zoom)
+ ctrl-ref (mf/use-ref @ctrl?)
transform-ref (mf/use-ref nil)
selected-ref (mf/use-ref selected)
+ hover-disabled-ref (mf/use-ref hover-disabled?)
query-point
(mf/use-callback
(mf/deps page-id)
(fn [point]
(let [zoom (mf/ref-val zoom-ref)
+ ctrl? (mf/ref-val ctrl-ref)
rect (gsh/center->rect point (/ 5 zoom) (/ 5 zoom))]
- (uw/ask-buffered!
- {:cmd :selection/query
- :page-id page-id
- :rect rect
- :include-frames? true
- :reverse? true})))) ;; we want the topmost shape to be selected first
+ (if (mf/ref-val hover-disabled-ref)
+ (rx/of nil)
+ (uw/ask-buffered!
+ {:cmd :selection/query
+ :page-id page-id
+ :rect rect
+ :include-frames? true
+ :clip-children? (not ctrl?)
+ :reverse? true}))))) ;; we want the topmost shape to be selected first
over-shapes-stream
(mf/use-memo
- (fn []
- (->> move-stream
- ;; When transforming shapes we stop querying the worker
- (rx/filter #(not (some? (mf/ref-val transform-ref))))
- (rx/switch-map query-point))))
- ]
+ (fn []
+ (rx/merge
+ (->> move-stream
+ ;; When transforming shapes we stop querying the worker
+ (rx/filter #(not (some? (mf/ref-val transform-ref))))
+ (rx/switch-map query-point))
+
+ (->> move-stream
+ ;; When transforming shapes we stop querying the worker
+ (rx/filter #(some? (mf/ref-val transform-ref)))
+ (rx/map (constantly nil))))))]
;; Refresh the refs on a value change
-
(mf/use-effect
(mf/deps transform)
#(mf/set-ref-val! transform-ref transform))
@@ -127,23 +137,38 @@
(mf/deps zoom)
#(mf/set-ref-val! zoom-ref zoom))
+ (mf/use-effect
+ (mf/deps @ctrl?)
+ #(mf/set-ref-val! ctrl-ref @ctrl?))
+
(mf/use-effect
(mf/deps selected)
#(mf/set-ref-val! selected-ref selected))
+ (mf/use-effect
+ (mf/deps hover-disabled?)
+ #(mf/set-ref-val! hover-disabled-ref hover-disabled?))
+
(hooks/use-stream
over-shapes-stream
(mf/deps page-id objects @ctrl?)
(fn [ids]
- (let [selected (mf/ref-val selected-ref)
- remove-id? (into #{} (mapcat #(cp/get-parents % objects)) selected)
- remove-id? (if @ctrl?
- (d/concat remove-id?
- (->> ids
- (filterv #(= :group (get-in objects [% :type])))))
- remove-id?)
- ids (->> ids (filterv (comp not remove-id?)))]
- (reset! hover (get objects (first ids)))
+ (let [is-group?
+ (fn [id]
+ (contains? #{:group :bool} (get-in objects [id :type])))
+
+ selected (mf/ref-val selected-ref)
+
+ remove-xfm (mapcat #(cp/get-parents % objects))
+ remove-id? (cond-> (into #{} remove-xfm selected)
+ @ctrl?
+ (d/concat (filterv is-group? ids)))
+
+ ids (->> ids (filterv (comp not remove-id?)))
+
+ hover-shape (get objects (first ids))]
+
+ (reset! hover hover-shape)
(reset! hover-ids ids))))))
(defn setup-viewport-modifiers [modifiers selected objects render-ref]
diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
index ca0d1b0e2..ed2300b65 100644
--- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
@@ -7,24 +7,22 @@
(ns app.main.ui.workspace.viewport.interactions
"Visually show shape interactions in workspace"
(:require
+ [app.common.data :as d]
+ [app.common.pages :as cp]
+ [app.common.types.interactions :as cti]
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.workspace.viewport.outline :refer [outline]]
[app.util.dom :as dom]
[cuerdas.core :as str]
- [rumext.alpha :as mf]
- ))
-
-(defn- get-click-interaction
- [shape]
- (first (filter #(= (:event-type %) :click) (:interactions shape))))
+ [rumext.alpha :as mf]))
(defn- on-mouse-down
- [event {:keys [id] :as shape}]
+ [event index {:keys [id] :as shape}]
(dom/stop-propagation event)
(st/emit! (dw/select-shape id))
- (st/emit! (dw/start-create-interaction)))
+ (st/emit! (dw/start-edit-interaction index)))
(defn connect-to-shape
"Calculate the best position to draw an interaction line
@@ -84,38 +82,62 @@
(mf/defc interaction-marker
- [{:keys [x y arrow-dir zoom] :as props}]
- (let [arrow-pdata (case arrow-dir
- :right "M -5 0 l 8 0 l -4 -4 m 4 4 l -4 4"
- :left "M 5 0 l -8 0 l 4 -4 m -4 4 l 4 4"
- [])
+ [{:keys [x y stroke action-type arrow-dir zoom] :as props}]
+ (let [icon-pdata (case action-type
+ :navigate (case arrow-dir
+ :right "M -6.5 0 l 12 0 l -6 -6 m 6 6 l -6 6"
+ :left "M 6.5 0 l -12 0 l 6 -6 m -6 6 l 6 6"
+ nil)
+
+ :open-overlay "M-5 -5 h7 v7 h-7 z M2 -2 h3.5 v7 h-7 v-2.5"
+
+ :toggle-overlay "M-5 -5 h7 v7 h-7 z M2 -2 h3.5 v7 h-7 v-2.5"
+
+ :close-overlay "M -5 -5 L 5 5 M -5 5 L 5 -5"
+
+ :prev-screen (case arrow-dir
+ :left "M -6.5 0 l 12 0 l -6 -6 m 6 6 l -6 6"
+ :right "M 6.5 0 l -12 0 l 6 -6 m -6 6 l 6 6"
+ nil)
+
+ :open-url (str "M1 -5 L 3 -7 L 7 -3 L 1 3 L -1 1"
+ "M-1 5 L -3 7 L -7 3 L -1 -3 L 1 -1")
+
+ nil)
inv-zoom (/ 1 zoom)]
[:*
[:circle {:cx 0
:cy 0
- :r 8
- :stroke "#31EFB8"
- :stroke-width 2
- :fill "#FFFFFF"
+ :r (if (some? action-type) 11 4)
+ :fill stroke
:transform (str
"scale(" inv-zoom ", " inv-zoom ") "
"translate(" (* zoom x) ", " (* zoom y) ")")}]
- (when arrow-dir
- [:path {:stroke "#31EFB8"
- :fill "none"
+ (when icon-pdata
+ [:path {:fill stroke
:stroke-width 2
- :d arrow-pdata
+ :stroke "#FFFFFF"
+ :d icon-pdata
:transform (str
"scale(" inv-zoom ", " inv-zoom ") "
"translate(" (* zoom x) ", " (* zoom y) ")")}])]))
(mf/defc interaction-path
- [{:keys [orig-shape dest-shape dest-point selected? zoom] :as props}]
+ [{:keys [index level orig-shape dest-shape dest-point selected? action-type zoom] :as props}]
(let [[orig-pos orig-x orig-y dest-pos dest-x dest-y]
- (if dest-shape
+ (cond
+ dest-shape
(connect-to-shape orig-shape dest-shape)
- (connect-to-point orig-shape dest-point))
+
+ dest-point
+ (connect-to-point orig-shape dest-point)
+
+ :else
+ (connect-to-point orig-shape
+ {:x (+ (:x2 (:selrect orig-shape)) 100)
+ :y (+ (- (:y1 (:selrect orig-shape)) 50)
+ (/ (* level 32) zoom))}))
orig-dx (if (= orig-pos :right) 100 -100)
dest-dx (if (= dest-pos :right) 100 -100)
@@ -126,93 +148,183 @@
arrow-dir (if (= dest-pos :left) :right :left)]
(if-not selected?
- [:path {:stroke "#B1B2B5"
- :fill "none"
- :pointer-events "visible"
- :stroke-width (/ 2 zoom)
- :d pdata
- :on-mouse-down #(on-mouse-down % orig-shape)}]
+ [:g {:on-mouse-down #(on-mouse-down % index orig-shape)}
+ [:path {:stroke "#B1B2B5"
+ :fill "none"
+ :pointer-events "visible"
+ :stroke-width (/ 2 zoom)
+ :d pdata}]
+ (when (not dest-shape)
+ [:& interaction-marker {:index index
+ :x dest-x
+ :y dest-y
+ :stroke "#B1B2B5"
+ :action-type action-type
+ :arrow-dir arrow-dir
+ :zoom zoom}])]
- [:g {:on-mouse-down #(on-mouse-down % orig-shape)}
+ [:g {:on-mouse-down #(on-mouse-down % index orig-shape)}
[:path {:stroke "#31EFB8"
:fill "none"
:pointer-events "visible"
:stroke-width (/ 2 zoom)
:d pdata}]
- [:& interaction-marker {:x orig-x
- :y orig-y
- :arrow-dir nil
- :zoom zoom}]
- [:& interaction-marker {:x dest-x
- :y dest-y
- :arrow-dir arrow-dir
- :zoom zoom}]
(when dest-shape
[:& outline {:shape dest-shape
- :color "#31EFB8"}])])))
+ :color "#31EFB8"}])
+
+ [:& interaction-marker {:index index
+ :x orig-x
+ :y orig-y
+ :stroke "#31EFB8"
+ :zoom zoom}]
+ [:& interaction-marker {:index index
+ :x dest-x
+ :y dest-y
+ :stroke "#31EFB8"
+ :action-type action-type
+ :arrow-dir arrow-dir
+ :zoom zoom}]])))
(mf/defc interaction-handle
- [{:keys [shape zoom] :as props}]
+ [{:keys [index shape zoom] :as props}]
(let [shape-rect (:selrect shape)
handle-x (+ (:x shape-rect) (:width shape-rect))
handle-y (+ (:y shape-rect) (/ (:height shape-rect) 2))]
- [:g {:on-mouse-down #(on-mouse-down % shape)}
+ [:g {:on-mouse-down #(on-mouse-down % index shape)}
[:& interaction-marker {:x handle-x
:y handle-y
+ :stroke "#31EFB8"
+ :action-type :navigate
:arrow-dir :right
:zoom zoom}]]))
+(mf/defc overlay-marker
+ [{:keys [index orig-shape dest-shape position objects hover-disabled?] :as props}]
+ (let [start-move-position
+ (fn [_]
+ (st/emit! (dw/start-move-overlay-pos index)))]
+
+ (when dest-shape
+ (let [orig-frame (cp/get-frame orig-shape objects)
+ marker-x (+ (:x orig-frame) (:x position))
+ marker-y (+ (:y orig-frame) (:y position))
+ width (:width dest-shape)
+ height (:height dest-shape)]
+ [:g {:on-mouse-down start-move-position
+ :on-mouse-enter #(reset! hover-disabled? true)
+ :on-mouse-leave #(reset! hover-disabled? false)}
+ [:path {:stroke "#31EFB8"
+ :fill "#000000"
+ :fill-opacity 0.3
+ :stroke-width 1
+ :d (str "M" marker-x " " marker-y " "
+ "h " width " "
+ "v " height " "
+ "h -" width " z"
+ "M" marker-x " " marker-y " "
+ "l " width " " height " "
+ "M" marker-x " " (+ marker-y height) " "
+ "l " width " -" height " ")}]
+ [:circle {:cx (+ marker-x (/ width 2))
+ :cy (+ marker-y (/ height 2))
+ :r 8
+ :fill "#31EFB8"}]]))))
+
(mf/defc interactions
- [{:keys [selected] :as props}]
+ [{:keys [selected hover-disabled?] :as props}]
(let [local (mf/deref refs/workspace-local)
zoom (mf/deref refs/selected-zoom)
current-transform (:transform local)
objects (mf/deref refs/workspace-page-objects)
- active-shapes (filter #(first (get-click-interaction %)) (vals objects))
+ active-shapes (filter #(seq (:interactions %)) (vals objects))
selected-shapes (map #(get objects %) selected)
+ editing-interaction-index (:editing-interaction-index local)
draw-interaction-to (:draw-interaction-to local)
draw-interaction-to-frame (:draw-interaction-to-frame local)
- first-selected (first selected-shapes)]
+ move-overlay-to (:move-overlay-to local)
+ move-overlay-index (:move-overlay-index local)
+ first-selected (first selected-shapes)
+
+ calc-level (fn [index interactions]
+ (->> (subvec interactions 0 index)
+ (filter #(nil? (:destination %)))
+ (count)))]
[:g.interactions
[:g.non-selected
(for [shape active-shapes]
- (let [interaction (get-click-interaction shape)
- dest-shape (get objects (:destination interaction))
- selected? (contains? selected (:id shape))]
- (when-not (or selected? (not dest-shape))
- [:& interaction-path {:key (:id shape)
- :orig-shape shape
- :dest-shape dest-shape
- :selected selected
- :selected? false
- :zoom zoom}])))]
+ (for [[index interaction] (d/enumerate (:interactions shape))]
+ (let [dest-shape (when (cti/destination? interaction)
+ (get objects (:destination interaction)))
+ selected? (contains? selected (:id shape))
+ level (calc-level index (:interactions shape))]
+ (when-not selected?
+ [:& interaction-path {:key (str (:id shape) "-" index)
+ :index index
+ :level level
+ :orig-shape shape
+ :dest-shape dest-shape
+ :selected selected
+ :selected? false
+ :action-type (:action-type interaction)
+ :zoom zoom}]))))]
[:g.selected
- (if (and draw-interaction-to first-selected)
+ (when (and draw-interaction-to first-selected)
[:& interaction-path {:key "interactive"
+ :index nil
:orig-shape first-selected
:dest-point draw-interaction-to
:dest-shape draw-interaction-to-frame
:selected? true
- :zoom zoom}]
-
- (for [shape selected-shapes]
- (let [interaction (get-click-interaction shape)
- dest-shape (get objects (:destination interaction))]
- (if dest-shape
- [:& interaction-path {:key (:id shape)
- :orig-shape shape
- :dest-shape dest-shape
- :selected selected
- :selected? true
- :zoom zoom}]
- (when (not (#{:move :rotate} current-transform))
- [:& interaction-handle {:key (:id shape)
- :shape shape
+ :action-type :navigate
+ :zoom zoom}])
+ (for [shape selected-shapes]
+ (if (seq (:interactions shape))
+ (for [[index interaction] (d/enumerate (:interactions shape))]
+ (when-not (= index editing-interaction-index)
+ (let [dest-shape (when (cti/destination? interaction)
+ (get objects (:destination interaction)))
+ level (calc-level index (:interactions shape))]
+ [:*
+ [:& interaction-path {:key (str (:id shape) "-" index)
+ :index index
+ :level level
+ :orig-shape shape
+ :dest-shape dest-shape
:selected selected
- :zoom zoom}])))))]]))
+ :selected? true
+ :action-type (:action-type interaction)
+ :zoom zoom}]
+ (when (and (or (= (:action-type interaction) :open-overlay)
+ (= (:action-type interaction) :toggle-overlay))
+ (= (:overlay-pos-type interaction) :manual))
+ (if (and (some? move-overlay-to)
+ (= move-overlay-index index))
+ [:& overlay-marker {:key (str "pos" (:id shape) "-" index)
+ :index index
+ :orig-shape shape
+ :dest-shape dest-shape
+ :position move-overlay-to
+ :objects objects
+ :hover-disabled? hover-disabled?}]
+ [:& overlay-marker {:key (str "pos" (:id shape) "-" index)
+ :index index
+ :orig-shape shape
+ :dest-shape dest-shape
+ :position (:overlay-position interaction)
+ :objects objects
+ :hover-disabled? hover-disabled?}]))])))
+ (when (and shape
+ (not (cp/unframed-shape? shape))
+ (not (#{:move :rotate} current-transform)))
+ [:& interaction-handle {:key (:id shape)
+ :index nil
+ :shape shape
+ :selected selected
+ :zoom zoom}])))]]))
diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs
index b8282b5d8..9dc30ad5d 100644
--- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs
@@ -229,7 +229,12 @@
current-transform (mf/deref refs/current-transform)
selrect (:selrect shape)
- transform (geom/transform-matrix shape {:no-flip true})]
+ transform (geom/transform-matrix shape {:no-flip true})
+
+ rotation (-> (gpt/point 1 0)
+ (gpt/transform (:transform shape))
+ (gpt/angle)
+ (mod 360))]
(when (not (#{:move :rotate} current-transform))
[:g.controls {:pointer-events (if disable-handlers "none" "visible")}
@@ -249,7 +254,7 @@
:on-rotate on-rotate
:on-resize (partial on-resize position)
:transform transform
- :rotation (:rotation shape)
+ :rotation rotation
:color color
:overflow-text overflow-text}
props (map->obj (merge common-props props))]
diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs
index 9ca6c54a1..9d7f54ed4 100644
--- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs
@@ -10,10 +10,12 @@
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.main.data.workspace :as dw]
+ [app.main.data.workspace.interactions :as dwi]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.streams :as ms]
[app.main.ui.hooks :as hooks]
+ [app.main.ui.icons :as i]
[app.main.ui.workspace.viewport.path-actions :refer [path-actions]]
[app.util.dom :as dom]
[rumext.alpha :as mf]))
@@ -90,7 +92,7 @@
(mf/defc frame-title
[{:keys [frame modifiers selected? zoom on-frame-enter on-frame-leave on-frame-select]}]
(let [{:keys [width x y]} (gsh/transform-shape frame)
- label-pos (gpt/point x (- y (/ 10 zoom)))
+ label-pos (gpt/point x (- y (/ 10 zoom)))
on-mouse-down
(mf/use-callback
@@ -156,3 +158,75 @@
:on-frame-enter on-frame-enter
:on-frame-leave on-frame-leave
:on-frame-select on-frame-select}])]))
+
+(mf/defc frame-flow
+ [{:keys [flow frame modifiers selected? zoom on-frame-enter on-frame-leave on-frame-select]}]
+ (let [{:keys [x y]} (gsh/transform-shape frame)
+ flow-pos (gpt/point x (- y (/ 35 zoom)))
+
+ on-mouse-down
+ (mf/use-callback
+ (mf/deps (:id frame) on-frame-select)
+ (fn [bevent]
+ (let [event (.-nativeEvent bevent)]
+ (when (= 1 (.-which event))
+ (dom/prevent-default event)
+ (dom/stop-propagation event)
+ (on-frame-select event (:id frame))))))
+
+ on-double-click
+ (mf/use-callback
+ (mf/deps (:id frame))
+ (st/emitf (dwi/start-rename-flow (:id flow))))
+
+ on-pointer-enter
+ (mf/use-callback
+ (mf/deps (:id frame) on-frame-enter)
+ (fn [_]
+ (on-frame-enter (:id frame))))
+
+ on-pointer-leave
+ (mf/use-callback
+ (mf/deps (:id frame) on-frame-leave)
+ (fn [_]
+ (on-frame-leave (:id frame))))]
+
+ [:foreignObject {:x 0
+ :y -15
+ :width 100000
+ :height 24
+ :transform (str (when (and selected? modifiers)
+ (str (:displacement modifiers) " " ))
+ (text-transform flow-pos zoom))}
+ [:div.flow-badge {:class (dom/classnames :selected selected?)}
+ [:div.content {:on-mouse-down on-mouse-down
+ :on-double-click on-double-click
+ :on-pointer-enter on-pointer-enter
+ :on-pointer-leave on-pointer-leave}
+ i/play
+ [:span (:name flow)]]]]))
+
+(mf/defc frame-flows
+ {::mf/wrap-props false}
+ [props]
+ (let [flows (unchecked-get props "flows")
+ objects (unchecked-get props "objects")
+ zoom (unchecked-get props "zoom")
+ modifiers (unchecked-get props "modifiers")
+ selected (or (unchecked-get props "selected") #{})
+
+ on-frame-enter (unchecked-get props "on-frame-enter")
+ on-frame-leave (unchecked-get props "on-frame-leave")
+ on-frame-select (unchecked-get props "on-frame-select")]
+ [:g.frame-flows
+ (for [flow flows]
+ (let [frame (get objects (:starting-frame flow))]
+ [:& frame-flow {:flow flow
+ :frame frame
+ :selected? (contains? selected (:id frame))
+ :zoom zoom
+ :modifiers modifiers
+ :on-frame-enter on-frame-enter
+ :on-frame-leave on-frame-leave
+ :on-frame-select on-frame-select}]))]))
+
diff --git a/frontend/src/app/util/browser_history.js b/frontend/src/app/util/browser_history.js
index 380cb32ab..8663bd684 100644
--- a/frontend/src/app/util/browser_history.js
+++ b/frontend/src/app/util/browser_history.js
@@ -11,7 +11,6 @@
goog.provide("app.util.browser_history");
goog.require("goog.history.Html5History");
-
goog.scope(function() {
const self = app.util.browser_history;
const Html5History = goog.history.Html5History;
diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs
index 5c3074e14..b6c50eb1b 100644
--- a/frontend/src/app/util/dom.cljs
+++ b/frontend/src/app/util/dom.cljs
@@ -281,7 +281,7 @@
(defn set-text! [node text]
(set! (.-textContent node) text))
-(defn set-css-property [node property value]
+(defn set-css-property! [node property value]
(.setProperty (.-style ^js node) property value))
(defn capture-pointer [event]
@@ -394,3 +394,16 @@
(defn left-mouse? [bevent]
(let [event (.-nativeEvent ^js bevent)]
(= 1 (.-which event))))
+
+(defn open-new-window
+ ([uri]
+ (open-new-window uri "_blank"))
+ ([uri name]
+ ;; Warning: need to protect against reverse tabnabbing attack
+ ;; https://www.comparitech.com/blog/information-security/reverse-tabnabbing/
+ (.open js/window (str uri) name "noopener,noreferrer")))
+
+(defn browser-back
+ []
+ (.back (.-history js/window)))
+
diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs
index fe60147a3..41ac92a38 100644
--- a/frontend/src/app/util/http.cljs
+++ b/frontend/src/app/util/http.cljs
@@ -54,8 +54,10 @@
{"x-frontend-version" (:full @cfg/version)})
(defn fetch
- [{:keys [method uri query headers body mode omit-default-headers]
- :or {mode :cors headers {}}}]
+ [{:keys [method uri query headers body mode omit-default-headers credentials]
+ :or {mode :cors
+ headers {}
+ credentials "same-origin"}}]
(rx/Observable.create
(fn [subscriber]
(let [controller (js/AbortController.)
@@ -83,7 +85,7 @@
:body body
:mode (d/name mode)
:redirect "follow"
- :credentials "same-origin"
+ :credentials credentials
:referrerPolicy "no-referrer"
:signal signal}]
(-> (js/fetch (str uri) params)
@@ -165,7 +167,6 @@
:uri uri
:response-type :blob
:omit-default-headers true})
-
(rx/filter #(= 200 (:status %)))
(rx/map :body)
(rx/mapcat wapi/read-file-as-data-url)
diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs
index b7e942f94..fda05744b 100644
--- a/frontend/src/app/util/i18n.cljs
+++ b/frontend/src/app/util/i18n.cljs
@@ -27,6 +27,8 @@
{:label "Rumanian (communit)" :value "ro"}
{:label "Portuguese (Brazil, community)" :value "pt_br"}
{:label "Ελληνική γλώσσα (community)" :value "el"}
+ {:label "עִבְרִית (community)" :value "he"}
+ {:label "عربي/عربى (community)" :value "ar"}
{:label "简体中文 (community)" :value "zh_cn"}])
(defn- parse-locale
diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs
index 8481543e2..552ca311b 100644
--- a/frontend/src/app/util/import/parser.cljs
+++ b/frontend/src/app/util/import/parser.cljs
@@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
+ [app.common.types.interactions :as cti]
[app.common.uuid :as uuid]
[app.util.color :as uc]
[app.util.json :as json]
@@ -209,6 +210,13 @@
(->> node :content last))]
(merge (add-attrs {} (:attrs svg-node)) node-attrs))
+ (= type :bool)
+ (->> node
+ (:content)
+ (filter #(= :path (:tag %)))
+ (map #(:attrs %))
+ (reduce add-attrs node-attrs))
+
:else
node-attrs)))
@@ -443,6 +451,11 @@
mask?
(assoc :masked-group? true))))
+(defn add-bool-data
+ [props node]
+ (-> props
+ (assoc :bool-type (get-meta node :bool-type keyword))))
+
(defn parse-shadow [node]
{:id (uuid/next)
:style (get-meta node :shadow-type keyword)
@@ -465,7 +478,6 @@
:suffix (get-meta node :suffix)
:scale (get-meta node :scale d/parse-double)})
-
(defn parse-grid-node [node]
(let [attrs (-> node :attrs remove-penpot-prefix)
color {:color (:color attrs)
@@ -482,8 +494,18 @@
:params params}))
(defn parse-grids [node]
- (let [grid-node (get-data node :penpot:grids)]
- (->> grid-node :content (mapv parse-grid-node))))
+ (let [grids-node (get-data node :penpot:grids)]
+ (->> grids-node :content (mapv parse-grid-node))))
+
+(defn parse-flow-node [node]
+ (let [attrs (-> node :attrs remove-penpot-prefix)]
+ {:id (uuid/next)
+ :name (-> attrs :name)
+ :starting-frame (-> attrs :starting-frame uuid)}))
+
+(defn parse-flows [node]
+ (let [flows-node (get-data node :penpot:flows)]
+ (->> flows-node :content (mapv parse-flow-node))))
(defn extract-from-data
([node tag]
@@ -706,28 +728,51 @@
(add-image-data type node))
(cond-> (= :text type)
- (add-text-data node))))))
+ (add-text-data node))
+
+ (cond-> (= :bool type)
+ (add-bool-data node))))))
(defn parse-page-data
[node]
- (let [style (parse-style (get-in node [:attrs :style]))
+ (let [style (parse-style (get-in node [:attrs :style]))
background (:background style)
- grids (->> (parse-grids node)
- (group-by :type)
- (d/mapm (fn [_ v] (-> v first :params))))]
+ grids (->> (parse-grids node)
+ (group-by :type)
+ (d/mapm (fn [_ v] (-> v first :params))))
+ flows (parse-flows node)]
(cond-> {}
(some? background)
(assoc-in [:options :background] background)
(d/not-empty? grids)
- (assoc-in [:options :saved-grids] grids))))
+ (assoc-in [:options :saved-grids] grids)
+
+ (d/not-empty? flows)
+ (assoc-in [:options :flows] flows))))
(defn parse-interactions
[node]
(let [interactions-node (get-data node :penpot:interactions)]
(->> (find-all-nodes interactions-node :penpot:interaction)
(mapv (fn [node]
- {:destination (get-meta node :destination uuid/uuid)
- :action-type (get-meta node :action-type keyword)
- :event-type (get-meta node :event-type keyword)})))))
+ (let [interaction {:event-type (get-meta node :event-type keyword)
+ :action-type (get-meta node :action-type keyword)}]
+ (cond-> interaction
+ (cti/has-delay interaction)
+ (assoc :delay (get-meta node :delay d/parse-double))
+
+ (cti/has-destination interaction)
+ (assoc :destination (get-meta node :destination uuid/uuid))
+
+ (cti/has-url interaction)
+ (assoc :url (get-meta node :url str))
+
+ (cti/has-overlay-opts interaction)
+ (assoc :overlay-pos-type (get-meta node :overlay-pos-type keyword)
+ :overlay-position (gpt/point
+ (get-meta node :overlay-position-x d/parse-double)
+ (get-meta node :overlay-position-y d/parse-double))
+ :close-click-outside (get-meta node :close-click-outside str->bool)
+ :background-overlay (get-meta node :background-overlay str->bool)))))))))
diff --git a/frontend/src/app/util/logging.clj b/frontend/src/app/util/logging.clj
deleted file mode 100644
index f888f1bc8..000000000
--- a/frontend/src/app/util/logging.clj
+++ /dev/null
@@ -1,46 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-
-(ns app.util.logging)
-
-(defn- log-expr [_form level keyvals]
- (let [keyvals-map (apply array-map keyvals)
- ;;formatter (::formatter keyvals-map 'identity)
- ]
- `(log ~(::logger keyvals-map (str *ns*))
- ~level
- ~(-> keyvals-map
- (dissoc ::logger)
- #_(assoc :line (:line (meta form))))
- ~(:err keyvals-map))))
-
-(defmacro set-level!
- ([level]
- `(set-level* ~(str *ns*) ~level))
- ([n level]
- `(set-level* ~n ~level)))
-
-(defmacro error [& keyvals]
- (log-expr &form :error keyvals))
-
-(defmacro warn [& keyvals]
- (log-expr &form :warn keyvals))
-
-(defmacro info [& keyvals]
- (log-expr &form :info keyvals))
-
-(defmacro debug [& keyvals]
- (log-expr &form :debug keyvals))
-
-(defmacro trace [& keyvals]
- (log-expr &form :trace keyvals))
-
-(defmacro spy [form]
- (let [res (gensym)]
- `(let [~res ~form]
- ~(log-expr &form :debug [:spy `'~form
- :=> res])
- ~res)))
diff --git a/frontend/src/app/util/logging.cljs b/frontend/src/app/util/logging.cljs
deleted file mode 100644
index b2bfaaff9..000000000
--- a/frontend/src/app/util/logging.cljs
+++ /dev/null
@@ -1,189 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-;;
-;; This code is highly inspired on the lambdaisland/glogi library but
-;; adapted and simplified to our needs. The adapted code shares the
-;; same license. You can found the origianl source code here:
-;; https://github.com/lambdaisland/glogi
-
-(ns app.util.logging
- (:require
- [app.common.exceptions :as ex]
- [cuerdas.core :as str]
- [goog.log :as glog])
- (:require-macros [app.util.logging]))
-
-(defn- logger-name
- [s]
- (cond
- (string? s) s
- (= s :root) ""
- (simple-ident? s) (name s)
- (qualified-ident? s) (str (namespace s) "." (name s))
- :else (str s)))
-
-(defn get-logger
- [n]
- (glog/getLogger (logger-name n)))
-
-(def levels
- {:off (.-OFF ^js glog/Level)
- :shout (.-SHOUT ^js glog/Level)
- :error (.-SEVERE ^js glog/Level)
- :severe (.-SEVERE ^js glog/Level)
- :warning (.-WARNING ^js glog/Level)
- :warn (.-WARNING ^js glog/Level)
- :info (.-INFO ^js glog/Level)
- :config (.-CONFIG ^js glog/Level)
- :debug (.-FINE ^js glog/Level)
- :fine (.-FINE ^js glog/Level)
- :finer (.-FINER ^js glog/Level)
- :trace (.-FINER ^js glog/Level)
- :finest (.-FINEST ^js glog/Level)
- :all (.-ALL ^js glog/Level)})
-
-(def colors
- {:gray3 "#8e908c"
- :gray4 "#969896"
- :gray5 "#4d4d4c"
- :gray6 "#282a2e"
- :black "#1d1f21"
- :red "#c82829"
- :blue "#4271ae"
- :orange "#f5871f"})
-
-(defn- get-level-value
- [level]
- (if (instance? glog/Level level)
- (.-value ^js level)
- (.-value ^js (get levels level))))
-
-(defn- level->color
- [level]
- (condp <= (get-level-value level)
- (get-level-value :error) (get colors :red)
- (get-level-value :warn) (get colors :orange)
- (get-level-value :info) (get colors :blue)
- (get-level-value :debug) (get colors :gray4)
- (get-level-value :trace) (get colors :gray3)
- (get colors :gray2)))
-
-(defn- level->short-name
- [l]
- (case l
- :fine "DBG"
- :debug "DBG"
- :finer "TRC"
- :trace "TRC"
- :info "INF"
- :warn "WRN"
- :warning "WRN"
- :error "ERR"
- (subs (.-name ^js (get levels l)) 0 3)))
-
-(defn- make-log-record
- [level message name exception]
- (let [record (glog/LogRecord. level message name)]
- (when exception (.setException record exception))
- record))
-
-(defn log
- "Output a log message to the given logger, optionally with an exception to be
- logged."
- ([name lvl message]
- (log name lvl message nil))
- ([name lvl message exception]
- (when glog/ENABLED
- (when-let [l (get-logger name)]
- (glog/publishLogRecord l (make-log-record (get levels lvl) message name exception))))))
-
-(defn set-level*
- "Set the level (a keyword) of the given logger, identified by name."
- [name lvl]
- (assert (contains? levels lvl))
- (some-> (get-logger name)
- (glog/setLevel (get levels lvl))))
-
-(defn set-levels!
- [lvls]
- (doseq [[logger level] lvls
- :let [level (if (string? level) (keyword level) level)]]
- (set-level* logger level)))
-
-(defn record->map
- [^js record]
- {:seqn (.-sequenceNumber_ record)
- :time (.-time_ record)
- :level (keyword (str/lower (.-name (.-level_ record))))
- :message (.-msg_ record)
- :logger-name (.-loggerName_ record)
- :exception (.-exception_ record)})
-
-(defn add-handler!
- ([handler-fn]
- (add-handler! :root handler-fn))
- ([logger-or-name handler-fn]
- (when-let [l (get-logger logger-or-name)]
- (glog/removeHandler l handler-fn)
- (glog/addHandler l handler-fn))))
-
-(defn- prepare-message
- [message]
- (loop [kvpairs (seq message)
- message (array-map)
- specials []]
- (if (nil? kvpairs)
- [message specials]
- (let [[k v] (first kvpairs)]
- (cond
- (= k :err)
- (recur (next kvpairs)
- message
- (conj specials [:error nil v]))
-
- (and (qualified-ident? k)
- (= "js" (namespace k)))
- (recur (next kvpairs)
- message
- (conj specials [:js (name k) (if (object? v) v (clj->js v))]))
-
- :else
- (recur (next kvpairs)
- (assoc message k v)
- specials))))))
-
-(defn default-handler
- [{:keys [message level logger-name]}]
- (let [header-styles (str "font-weight: 600; color: " (level->color level))
- normal-styles (str "font-weight: 300; color: " (get colors :gray6))
- level-name (level->short-name level)
- header (str "%c" level-name " [" logger-name "] ")]
-
- (if (string? message)
- (let [message (str header "%c" message)]
- (js/console.log message header-styles normal-styles))
- (let [[message specials] (prepare-message message)]
- (if (seq specials)
- (let [message (str header "%c" (pr-str message))]
- (js/console.group message header-styles normal-styles)
- (doseq [[type n v] specials]
- (case type
- :js (js/console.log n v)
- :error (if (ex/ex-info? v)
- (js/console.error (pr-str v))
- (js/console.error v))))
- (js/console.groupEnd message))
- (let [message (str header "%c" (pr-str message))]
- (js/console.log message header-styles normal-styles)))))))
-
-(defonce default-console-handler
- #(default-handler (record->map %)))
-
-(defn initialize!
- []
- (add-handler! :root default-console-handler)
- nil)
-
diff --git a/frontend/src/app/util/path/format.cljs b/frontend/src/app/util/path/format.cljs
index 4b0640f4e..80b1f9214 100644
--- a/frontend/src/app/util/path/format.cljs
+++ b/frontend/src/app/util/path/format.cljs
@@ -6,7 +6,8 @@
(ns app.util.path.format
(:require
- [app.util.path.commands :as upc]
+ [app.common.path.commands :as upc]
+ [app.common.path.subpaths :refer [pt=]]
[cuerdas.core :as str]))
(defn command->param-list [command]
@@ -43,7 +44,9 @@
(:large-arc-flag params) ","
(:sweep-flag params) ","
(:x params) ","
- (:y params)))))
+ (:y params))
+
+ "")))
(defn command->string [{:keys [command relative] :as entry}]
(let [command-str (case command
@@ -56,12 +59,19 @@
:smooth-curve-to "S"
:quadratic-bezier-curve-to "Q"
:smooth-quadratic-bezier-curve-to "T"
- :elliptical-arc "A")
+ :elliptical-arc "A"
+ "")
command-str (if relative (str/lower command-str) command-str)
param-list (command->param-list entry)]
(str command-str param-list)))
+(defn set-point
+ [command point]
+ (-> command
+ (assoc-in [:params :x] (:x point))
+ (assoc-in [:params :y] (:y point))))
+
(defn format-path [content]
(with-out-str
(loop [last-move nil
@@ -72,9 +82,12 @@
(let [point (upc/command->point current)
current-move? (= :move-to (:command current))
last-move (if current-move? point last-move)]
- (print (command->string current))
- (when (and (not current-move?) (= last-move point))
+ (if (and (not current-move?) (pt= last-move point))
+ (print (command->string (set-point current last-move)))
+ (print (command->string current)))
+
+ (when (and (not current-move?) (pt= last-move point))
(print "Z"))
(recur last-move
diff --git a/frontend/src/app/util/path/geom.cljs b/frontend/src/app/util/path/geom.cljs
deleted file mode 100644
index 0478fff8c..000000000
--- a/frontend/src/app/util/path/geom.cljs
+++ /dev/null
@@ -1,55 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-
-(ns app.util.path.geom
- (:require
- [app.common.geom.point :as gpt]
- [app.common.geom.shapes.path :as gshp]
- [app.util.path.commands :as upc]))
-
-(defn calculate-opposite-handler
- "Given a point and its handler, gives the symetric handler"
- [point handler]
- (let [handler-vector (gpt/to-vec point handler)]
- (gpt/add point (gpt/negate handler-vector))))
-
-(defn split-line-to [from-p cmd val]
- (let [to-p (upc/command->point cmd)
- sp (gpt/line-val from-p to-p val)]
- [(upc/make-line-to sp) cmd]))
-
-(defn split-curve-to [from-p cmd val]
- (let [params (:params cmd)
- end (gpt/point (:x params) (:y params))
- h1 (gpt/point (:c1x params) (:c1y params))
- h2 (gpt/point (:c2x params) (:c2y params))
- [[_ to1 h11 h21]
- [_ to2 h12 h22]] (gshp/curve-split from-p end h1 h2 val)]
- [(upc/make-curve-to to1 h11 h21)
- (upc/make-curve-to to2 h12 h22)]))
-
-(defn opposite-handler
- "Calculates the coordinates of the opposite handler"
- [point handler]
- (let [phv (gpt/to-vec point handler)]
- (gpt/add point (gpt/negate phv))))
-
-(defn opposite-handler-keep-distance
- "Calculates the coordinates of the opposite handler but keeping the old distance"
- [point handler old-opposite]
- (let [old-distance (gpt/distance point old-opposite)
- phv (gpt/to-vec point handler)
- phv2 (gpt/multiply
- (gpt/unit (gpt/negate phv))
- (gpt/point old-distance))]
- (gpt/add point phv2)))
-
-(defn content->points [content]
- (->> content
- (map #(when (-> % :params :x) (gpt/point (-> % :params :x) (-> % :params :y))))
- (remove nil?)
- (into [])))
-
diff --git a/frontend/src/app/util/path/parser.cljs b/frontend/src/app/util/path/parser.cljs
index 7b68caf64..99e6435a5 100644
--- a/frontend/src/app/util/path/parser.cljs
+++ b/frontend/src/app/util/path/parser.cljs
@@ -8,9 +8,9 @@
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
+ [app.common.geom.shapes.path :as upg]
+ [app.common.path.commands :as upc]
[app.util.path.arc-to-curve :refer [a2c]]
- [app.util.path.commands :as upc]
- [app.util.path.geom :as upg]
[app.util.svg :as usvg]
[cuerdas.core :as str]))
@@ -292,6 +292,10 @@
[result next-pos next-start next-cc next-qc]))
start (first commands)
+ start (cond-> start
+ (:relative start)
+ (assoc :relative false))
+
start-pos (gpt/point (:params start))]
(->> (map vector (rest commands) commands)
diff --git a/frontend/src/app/util/path/shapes_to_path.cljs b/frontend/src/app/util/path/shapes_to_path.cljs
deleted file mode 100644
index 8d7c86cbd..000000000
--- a/frontend/src/app/util/path/shapes_to_path.cljs
+++ /dev/null
@@ -1,146 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-
-(ns app.util.path.shapes-to-path
- (:require
- [app.common.data :as d]
- [app.common.geom.matrix :as gmt]
- [app.common.geom.point :as gpt]
- [app.common.geom.shapes :as gsh]
- [app.common.geom.shapes.path :as gsp]
- [app.util.path.commands :as pc]))
-
-(def bezier-circle-c 0.551915024494)
-(def dissoc-attrs [:x :y :width :height
- :rx :ry :r1 :r2 :r3 :r4
- :medata])
-(def allowed-transform-types #{:rect
- :circle
- :image})
-
-(defn make-corner-arc
- "Creates a curvle corner for border radius"
- [from to corner radius]
- (let [x (case corner
- :top-left (:x from)
- :top-right (- (:x from) radius)
- :bottom-right (- (:x to) radius)
- :bottom-left (:x to))
-
- y (case corner
- :top-left (- (:y from) radius)
- :top-right (:y from)
- :bottom-right (- (:y to) (* 2 radius))
- :bottom-left (- (:y to) radius))
-
- width (* radius 2)
- height (* radius 2)
-
- c bezier-circle-c
- c1x (+ x (* (/ width 2) (- 1 c)))
- c2x (+ x (* (/ width 2) (+ 1 c)))
- c1y (+ y (* (/ height 2) (- 1 c)))
- c2y (+ y (* (/ height 2) (+ 1 c)))
-
- h1 (case corner
- :top-left (assoc from :y c1y)
- :top-right (assoc from :x c2x)
- :bottom-right (assoc from :y c2y)
- :bottom-left (assoc from :x c1x))
-
- h2 (case corner
- :top-left (assoc to :x c1x)
- :top-right (assoc to :y c1y)
- :bottom-right (assoc to :x c2x)
- :bottom-left (assoc to :y c2y))]
-
- (pc/make-curve-to to h1 h2)))
-
-(defn circle->path
- "Creates the bezier curves to approximate a circle shape"
- [x y width height]
- (let [mx (+ x (/ width 2))
- my (+ y (/ height 2))
- ex (+ x width)
- ey (+ y height)
-
- p1 (gpt/point mx y)
- p2 (gpt/point ex my)
- p3 (gpt/point mx ey)
- p4 (gpt/point x my)
-
- c bezier-circle-c
- c1x (+ x (* (/ width 2) (- 1 c)))
- c2x (+ x (* (/ width 2) (+ 1 c)))
- c1y (+ y (* (/ height 2) (- 1 c)))
- c2y (+ y (* (/ height 2) (+ 1 c)))]
-
- [(pc/make-move-to p1)
- (pc/make-curve-to p2 (assoc p1 :x c2x) (assoc p2 :y c1y))
- (pc/make-curve-to p3 (assoc p2 :y c2y) (assoc p3 :x c2x))
- (pc/make-curve-to p4 (assoc p3 :x c1x) (assoc p4 :y c2y))
- (pc/make-curve-to p1 (assoc p4 :y c1y) (assoc p1 :x c1x))]))
-
-(defn rect->path
- "Creates a bezier curve that approximates a rounded corner rectangle"
- [x y width height r1 r2 r3 r4]
- (let [p1 (gpt/point x (+ y r1))
- p2 (gpt/point (+ x r1) y)
-
- p3 (gpt/point (+ width x (- r2)) y)
- p4 (gpt/point (+ width x) (+ y r2))
-
- p5 (gpt/point (+ width x) (+ height y (- r3)))
- p6 (gpt/point (+ width x (- r3)) (+ height y))
-
- p7 (gpt/point (+ x r4) (+ height y))
- p8 (gpt/point x (+ height y (- r4)))]
- (-> []
- (conj (pc/make-move-to p1))
- (cond-> (not= p1 p2)
- (conj (make-corner-arc p1 p2 :top-left r1)))
- (conj (pc/make-line-to p3))
- (cond-> (not= p3 p4)
- (conj (make-corner-arc p3 p4 :top-right r2)))
- (conj (pc/make-line-to p5))
- (cond-> (not= p5 p6)
- (conj (make-corner-arc p5 p6 :bottom-right r3)))
- (conj (pc/make-line-to p7))
- (cond-> (not= p7 p8)
- (conj (make-corner-arc p7 p8 :bottom-left r4)))
- (conj (pc/make-line-to p1)))))
-
-(defn convert-to-path
- "Transforms the given shape to a path"
- [{:keys [type x y width height r1 r2 r3 r4 rx metadata] :as shape}]
-
- (if (contains? allowed-transform-types type)
- (let [r1 (or r1 rx 0)
- r2 (or r2 rx 0)
- r3 (or r3 rx 0)
- r4 (or r4 rx 0)
-
- new-content
- (case type
- :circle
- (circle->path x y width height)
- (rect->path x y width height r1 r2 r3 r4))
-
- ;; Apply the transforms that had the shape
- transform (:transform shape)
- new-content (cond-> new-content
- (some? transform)
- (gsp/transform-content (gmt/transform-in (gsh/center-shape shape) transform)))]
-
- (-> shape
- (d/without-keys dissoc-attrs)
- (assoc :type :path)
- (assoc :content new-content)
- (cond-> (= :image type) (-> (assoc :fill-image metadata)
- (dissoc :metadata)))))
- ;; Do nothing if the shape is not of a correct type
- shape))
-
diff --git a/frontend/src/app/util/path/tools.cljs b/frontend/src/app/util/path/tools.cljs
index f6409f221..9f97ab666 100644
--- a/frontend/src/app/util/path/tools.cljs
+++ b/frontend/src/app/util/path/tools.cljs
@@ -8,9 +8,9 @@
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
+ [app.common.geom.shapes.path :as upg]
[app.common.math :as mth]
- [app.util.path.commands :as upc]
- [app.util.path.geom :as upg]
+ [app.common.path.commands :as upc]
[clojure.set :as set]))
(defn remove-line-curves
@@ -210,7 +210,7 @@
(case (:command cmd)
:line-to [index (upg/split-line-to start cmd value)]
:curve-to [index (upg/split-curve-to start cmd value)]
- :close-path [index [(upc/make-line-to (gpt/line-val start end value)) cmd]]
+ :close-path [index [(upc/make-line-to (gpt/lerp start end value)) cmd]]
nil))
cmd-changes
diff --git a/frontend/src/app/util/router.cljs b/frontend/src/app/util/router.cljs
index 2329af2b6..27bfe4374 100644
--- a/frontend/src/app/util/router.cljs
+++ b/frontend/src/app/util/router.cljs
@@ -10,6 +10,7 @@
[app.common.uri :as u]
[app.config :as cfg]
[app.util.browser-history :as bhistory]
+ [app.util.dom :as dom]
[app.util.timers :as ts]
[beicon.core :as rx]
[goog.events :as e]
@@ -20,12 +21,12 @@
(defn resolve
([router id] (resolve router id {} {}))
- ([router id params] (resolve router id params {}))
- ([router id params qparams]
- (when-let [match (r/match-by-name router id params)]
- (if (empty? qparams)
+ ([router id path-params] (resolve router id path-params {}))
+ ([router id path-params query-params]
+ (when-let [match (r/match-by-name router id path-params)]
+ (if (empty? query-params)
(r/match->path match)
- (let [query (u/map->query-string qparams)]
+ (let [query (u/map->query-string query-params)]
(-> (u/uri (r/match->path match))
(assoc :query query)
(str)))))))
@@ -47,12 +48,12 @@
[router path]
(let [uri (u/uri path)]
(when-let [match (r/match-by-path router (:path uri))]
- (let [qparams (u/query-string->map (:query uri))
- params {:path (:path-params match)
- :query qparams}]
+ (let [query-params (u/query-string->map (:query uri))
+ params {:path (:path-params match)
+ :query query-params}]
(-> match
(assoc :params params)
- (assoc :query-params qparams))))))
+ (assoc :query-params query-params))))))
;; --- Navigate (Event)
@@ -64,60 +65,51 @@
ptk/UpdateEvent
(update [_ state]
- (assoc state :route match))))
+ (-> state
+ (assoc :route match)
+ (dissoc :exception)))))
(defn navigate*
- [id params qparams replace]
+ [id path-params query-params replace]
(ptk/reify ::navigate
IDeref
(-deref [_]
{:id id
- :path-params params
- :query-params qparams
+ :path-params path-params
+ :query-params query-params
:replace replace})
- ptk/UpdateEvent
- (update [_ state]
- (dissoc state :exception))
-
ptk/EffectEvent
(effect [_ state _]
- (ts/asap
- #(let [router (:router state)
- history (:history state)
- path (resolve router id params qparams)]
- (if ^boolean replace
+ (let [router (:router state)
+ history (:history state)
+ path (resolve router id path-params query-params)]
+ (ts/asap
+ #(if ^boolean replace
(bhistory/replace-token! history path)
(bhistory/set-token! history path)))))))
+(defn assign-exception
+ [error]
+ (ptk/reify ::assign-exception
+ ptk/UpdateEvent
+ (update [_ state]
+ (if (nil? error)
+ (dissoc state :exception)
+ (assoc state :exception error)))))
+
(defn nav
([id] (nav id nil nil))
- ([id params] (nav id params nil))
- ([id params qparams] (navigate* id params qparams false)))
+ ([id path-params] (nav id path-params nil))
+ ([id path-params query-params] (navigate* id path-params query-params false)))
(defn nav'
([id] (nav id nil nil))
- ([id params] (nav id params nil))
- ([id params qparams] (navigate* id params qparams true)))
+ ([id path-params] (nav id path-params nil))
+ ([id path-params query-params] (navigate* id path-params query-params true)))
(def navigate nav)
-(deftype NavigateNewWindow [id params qparams]
- ptk/EffectEvent
- (effect [_ state _]
- (let [router (:router state)
- path (resolve router id params qparams)
- uri (-> (u/uri cfg/public-uri)
- (assoc :fragment path))
- name (str (name id) "-" (:file-id params))]
- (js/window.open (str uri) name))))
-
-(defn nav-new-window
- ([id] (nav-new-window id nil nil))
- ([id params] (nav-new-window id params nil))
- ([id params qparams] (NavigateNewWindow. id params qparams)))
-
-
(defn nav-new-window*
[{:keys [rname path-params query-params name]}]
(ptk/reify ::nav-new-window
@@ -127,10 +119,23 @@
path (resolve router rname path-params query-params)
uri (-> (u/uri cfg/public-uri)
(assoc :fragment path))]
+ (dom/open-new-window (str uri) name)))))
+(defn nav-back
+ []
+ (ptk/reify ::nav-back
+ ptk/EffectEvent
+ (effect [_ _ _]
+ (ts/asap dom/browser-back))))
-
- (js/window.open (str uri) name)))))
+(defn nav-back-local
+ "Navigate back only if the previous page is in penpot app."
+ []
+ (let [location (.-location js/document)
+ referrer (u/uri (.-referrer js/document))]
+ (when (or (nil? (:host referrer))
+ (= (.-hostname location) (:host referrer)))
+ (nav-back))))
;; --- History API
diff --git a/frontend/src/app/util/time.cljs b/frontend/src/app/util/time.cljs
index a32a0e1a4..b6c470799 100644
--- a/frontend/src/app/util/time.cljs
+++ b/frontend/src/app/util/time.cljs
@@ -7,6 +7,8 @@
(ns app.util.time
(:require
["date-fns/formatDistanceToNowStrict" :default dateFnsFormatDistanceToNowStrict]
+ ["date-fns/locale/ar-SA" :default dateFnsLocalesAr]
+ ["date-fns/locale/he" :default dateFnsLocalesHe]
["date-fns/locale/ca" :default dateFnsLocalesCa]
["date-fns/locale/de" :default dateFnsLocalesDe]
["date-fns/locale/el" :default dateFnsLocalesEl]
@@ -202,6 +204,8 @@
(def ^:private locales
#js {:en dateFnsLocalesEnUs
+ :ar dateFnsLocalesAr
+ :he dateFnsLocalesHe
:fr dateFnsLocalesFr
:tr dateFnsLocalesTr
:es dateFnsLocalesEs
diff --git a/frontend/src/app/util/worker.cljs b/frontend/src/app/util/worker.cljs
index 914756e96..497a050d5 100644
--- a/frontend/src/app/util/worker.cljs
+++ b/frontend/src/app/util/worker.cljs
@@ -9,6 +9,8 @@
(:require
[app.common.transit :as t]
[app.common.uuid :as uuid]
+ [app.util.globals :refer [global]]
+ [app.util.object :as obj]
[beicon.core :as rx]))
(declare handle-response)
@@ -28,11 +30,13 @@
data (t/encode-str message)
instance (:instance worker)]
- (.postMessage instance data)
- (->> (:stream worker)
- (rx/filter #(= (:reply-to %) sender-id))
- (take-messages)
- (rx/map handle-response)))))
+ (if (some? instance)
+ (do (.postMessage instance data)
+ (->> (:stream worker)
+ (rx/filter #(= (:reply-to %) sender-id))
+ (take-messages)
+ (rx/map handle-response)))
+ (rx/empty)))))
(defn ask!
[worker message]
@@ -79,6 +83,11 @@
(.addEventListener instance "message" handle-message)
(.addEventListener instance "error" handle-error)
+ (ask! worker
+ {:cmd :configure
+ :params
+ {"penpotPublicURI" (obj/get global "penpotPublicURI")}})
+
worker))
(defn- handle-response
diff --git a/frontend/src/app/worker/impl.cljs b/frontend/src/app/worker/impl.cljs
index 9dfb3bba2..13b7d167f 100644
--- a/frontend/src/app/worker/impl.cljs
+++ b/frontend/src/app/worker/impl.cljs
@@ -7,6 +7,8 @@
(ns app.worker.impl
(:require
[app.common.pages.changes :as ch]
+ [app.util.globals :refer [global]]
+ [app.util.object :as obj]
[okulary.core :as l]))
(enable-console-print!)
@@ -50,3 +52,8 @@
(assoc :cmd :selection/update-index)))
(handler (-> message
(assoc :cmd :snaps/update-index))))))
+
+(defmethod handler :configure
+ [{:keys [params]}]
+ (doseq [[param-key param-value] params]
+ (obj/set! global param-key param-value)))
diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs
index 34c6ae3f5..d36ec13a6 100644
--- a/frontend/src/app/worker/import.cljs
+++ b/frontend/src/app/worker/import.cljs
@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.file-builder :as fb]
+ [app.common.logging :as log]
[app.common.pages :as cp]
[app.common.text :as ct]
[app.common.uuid :as uuid]
@@ -17,14 +18,13 @@
[app.util.http :as http]
[app.util.import.parser :as cip]
[app.util.json :as json]
- [app.util.logging :as log]
[app.util.zip :as uz]
[app.worker.impl :as impl]
[beicon.core :as rx]
[cuerdas.core :as str]
[tubax.core :as tubax]))
-(log/set-level! :trace)
+(log/set-level! :warn)
;; Upload changes batches size
(def ^:const change-batch-size 100)
@@ -238,6 +238,7 @@
(case type
:frame (fb/close-artboard file)
:group (fb/close-group file)
+ :bool (fb/close-bool file)
:svg-raw (fb/close-svg-raw file)
#_default file)
@@ -254,6 +255,7 @@
file (case type
:frame (fb/add-artboard file data)
:group (fb/add-group file data)
+ :bool (fb/add-bool file data)
:rect (fb/create-rect file data)
:circle (fb/create-circle file data)
:path (fb/create-path file data)
@@ -313,7 +315,10 @@
page-data (-> (cip/parse-page-data content)
(assoc :name page-name)
(assoc :id (resolve page-id)))
- file (-> file (fb/add-page page-data))]
+ flows (->> (get-in page-data [:options :flows])
+ (mapv #(update % :starting-frame resolve)))
+ page-data (d/assoc-in-when page-data [:options :flows] flows)
+ file (-> file (fb/add-page page-data))]
(->> (rx/from nodes)
(rx/filter cip/shape?)
(rx/mapcat (partial resolve-media context file-id))
diff --git a/frontend/src/app/worker/selection.cljs b/frontend/src/app/worker/selection.cljs
index d93fcfaf4..64217e014 100644
--- a/frontend/src/app/worker/selection.cljs
+++ b/frontend/src/app/worker/selection.cljs
@@ -18,13 +18,13 @@
(defonce state (l/atom {}))
(defn index-shape
- [objects parents-index masks-index]
+ [objects parents-index clip-parents-index]
(fn [index shape]
(let [{:keys [x y width height]} (gsh/points->selrect (:points shape))
shape-bound #js {:x x :y y :width width :height height}
- parents (get parents-index (:id shape))
- masks (get masks-index (:id shape))
+ parents (get parents-index (:id shape))
+ clip-parents (get clip-parents-index (:id shape))
frame (when (and (not= :frame (:type shape))
(not= (:frame-id shape) uuid/zero))
@@ -32,19 +32,22 @@
(qdt/insert index
(:id shape)
shape-bound
- (assoc shape :frame frame :masks masks :parents parents)))))
+ (assoc shape
+ :frame frame
+ :clip-parents clip-parents
+ :parents parents)))))
(defn- create-index
[objects]
- (let [shapes (-> objects (dissoc uuid/zero) (vals))
- parents-index (cp/generate-child-all-parents-index objects)
- masks-index (cp/create-mask-index objects parents-index)
+ (let [shapes (-> objects (dissoc uuid/zero) (vals))
+ parents-index (cp/generate-child-all-parents-index objects)
+ clip-parents-index (cp/create-clip-index objects parents-index)
bounds #js {:x (int -0.5e7)
:y (int -0.5e7)
:width (int 1e7)
:height (int 1e7)}
- index (reduce (index-shape objects parents-index masks-index)
+ index (reduce (index-shape objects parents-index clip-parents-index)
(qdt/create bounds)
shapes)
@@ -61,18 +64,19 @@
(get new-objects id)))
changed-ids (into #{}
- (comp (filter changes?)
- (filter #(not= % uuid/zero)))
+ (comp (filter #(not= % uuid/zero))
+ (filter changes?)
+ (mapcat #(d/concat [%] (cp/get-children % new-objects))))
(set/union (set (keys old-objects))
(set (keys new-objects))))
- shapes (->> changed-ids (mapv #(get new-objects %)) (filterv (comp not nil?)))
- parents-index (cp/generate-child-all-parents-index new-objects shapes)
- masks-index (cp/create-mask-index new-objects parents-index)
+ shapes (->> changed-ids (mapv #(get new-objects %)) (filterv (comp not nil?)))
+ parents-index (cp/generate-child-all-parents-index new-objects shapes)
+ clip-parents-index (cp/create-clip-index new-objects parents-index)
new-index (qdt/remove-all index changed-ids)
- index (reduce (index-shape new-objects parents-index masks-index)
+ index (reduce (index-shape new-objects parents-index clip-parents-index)
new-index
shapes)
@@ -84,7 +88,7 @@
(create-index new-objects)))
(defn- query-index
- [{index :index z-index :z-index} rect frame-id include-frames? full-frame? include-groups? reverse?]
+ [{index :index z-index :z-index} rect frame-id full-frame? include-frames? clip-children? reverse?]
(let [result (-> (qdt/search index (clj->js rect))
(es6-iterator-seq))
@@ -96,7 +100,6 @@
(or (not frame-id) (= frame-id (:frame-id shape)))
(case (:type shape)
:frame include-frames?
- :group include-groups?
true)
(or (not full-frame?)
@@ -107,11 +110,9 @@
(fn [shape]
(gsh/overlaps? shape rect))
- overlaps-masks?
- (fn [masks]
- (->> masks
- (some (comp not overlaps?))
- not))
+ overlaps-parent?
+ (fn [clip-parents]
+ (->> clip-parents (some (comp not overlaps?)) not))
add-z-index
(fn [{:keys [id frame-id] :as shape}]
@@ -125,7 +126,9 @@
(filter match-criteria?)
(filter overlaps?)
(filter (comp overlaps? :frame))
- (filter (comp overlaps-masks? :masks))
+ (filter (if clip-children?
+ (comp overlaps-parent? :clip-parents)
+ (constantly true)))
(map add-z-index))
result)
@@ -155,10 +158,10 @@
nil)
(defmethod impl/handler :selection/query
- [{:keys [page-id rect frame-id include-frames? full-frame? include-groups? reverse?]
- :or {include-groups? true reverse? false include-frames? false full-frame? false} :as message}]
+ [{:keys [page-id rect frame-id reverse? full-frame? include-frames? clip-children?]
+ :or {reverse? false full-frame? false include-frames? false clip-children? true} :as message}]
(when-let [index (get @state page-id)]
- (query-index index rect frame-id include-frames? full-frame? include-groups? reverse?)))
+ (query-index index rect frame-id full-frame? include-frames? clip-children? reverse?)))
(defmethod impl/handler :selection/query-z-index
[{:keys [page-id objects ids]}]
diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs
index 210dfe31b..606f72ec4 100644
--- a/frontend/src/app/worker/thumbnails.cljs
+++ b/frontend/src/app/worker/thumbnails.cljs
@@ -7,6 +7,8 @@
(ns app.worker.thumbnails
(:require
["react-dom/server" :as rds]
+ [app.common.uri :as u]
+ [app.config :as cfg]
[app.main.exports :as exports]
[app.main.fonts :as fonts]
[app.util.http :as http]
@@ -29,11 +31,15 @@
(defn- request-page
[file-id page-id]
- (let [uri "/api/rpc/query/page"]
+ (let [uri (u/join (cfg/get-public-uri) "api/rpc/query/page")
+ params {:file-id file-id
+ :id page-id
+ :strip-thumbnails true}]
(->> (http/send!
- {:uri uri
- :query {:file-id file-id :id page-id :strip-thumbnails true}
- :method :get})
+ {:method :get
+ :uri uri
+ :credentials "include"
+ :query params})
(rx/map http/conditional-decode-transit)
(rx/mapcat handle-response))))
diff --git a/frontend/test/app/components_basic_test.cljs b/frontend/test/app/components_basic_test.cljs
index e4ecfca84..30def79e5 100644
--- a/frontend/test/app/components_basic_test.cljs
+++ b/frontend/test/app/components_basic_test.cljs
@@ -19,356 +19,338 @@
(t/use-fixtures :each
{:before thp/reset-idmap!})
-(t/deftest test-add-component-from-single-shape
- (t/async done
- (try
- (let [state (-> thp/initial-state
- (thp/sample-page)
- (thp/sample-shape :shape1 :rect
- {:name "Rect 1"}))]
+;; Test using potok
+#_(t/deftest test-add-component-from-single-shape
+ (t/testing "test-add-component-from-single-shape"
+ (t/async
+ done
+ (let [state (-> thp/initial-state
+ (thp/sample-page)
+ (thp/sample-shape :shape1 :rect
+ {:name "Rect 1"}))
+ store (ptk/store {:state state})
+ stream (ptk/input-stream store)
+ end? (->> stream (rx/filter #(= ::end %)))]
- (->> state
- (the/do-update (dw/select-shape (thp/id :shape1)))
- (the/do-watch-update dwl/add-component)
- (rx/do
- (fn [new-state]
- (let [shape1 (thp/get-shape new-state :shape1)
+ (->> stream
+ (rx/take-until end?)
+ (rx/last)
+ (rx/do
+ (fn []
+ (let [new-state @store
+ shape1 (thp/get-shape new-state :shape1)
- [[group shape1] [c-group c-shape1] component]
- (thl/resolve-instance-and-main
+ [[group shape1] [c-group c-shape1] component]
+ (thl/resolve-instance-and-main
new-state
(:parent-id shape1))
- file (dwlh/get-local-file new-state)]
+ file (dwlh/get-local-file new-state)]
- (t/is (= (:name shape1) "Rect 1"))
- (t/is (= (:name group) "Component-1"))
- (t/is (= (:name component) "Component-1"))
- (t/is (= (:name c-shape1) "Rect 1"))
- (t/is (= (:name c-group) "Component-1"))
+ (t/is (= (:name shape1) "Rect 1"))
+ (t/is (= (:name group) "Component-1"))
+ (t/is (= (:name component) "Component-1"))
+ (t/is (= (:name c-shape1) "Rect 1"))
+ (t/is (= (:name c-group) "Component-1"))
- (thl/is-from-file group file))))
+ (thl/is-from-file group file))))
- (rx/subs
- done
- #(do
- (println (.-stack %))
- (done)))))
+ (rx/subs done #(throw %)))
- (catch :default e
- (println (.-stack e))
- (done)))))
+ (ptk/emit!
+ store
+ (dw/select-shape (thp/id :shape1))
+ (dwl/add-component)
+ ::end)))))
-(t/deftest test-add-component-from-several-shapes
- (t/async done
- (try
- (let [state (-> thp/initial-state
- (thp/sample-page)
- (thp/sample-shape :shape1 :rect
- {:name "Rect 1"})
- (thp/sample-shape :shape2 :rect
- {:name "Rect 2"}))]
+;; FAILING
+#_(t/deftest test-add-component-from-single-shape
+ (t/async
+ done
+ (let [state (-> thp/initial-state
+ (thp/sample-page)
+ (thp/sample-shape :shape1 :rect
+ {:name "Rect 1"}))]
- (->> state
- (the/do-update (dw/select-shapes (lks/set
- (thp/id :shape1)
- (thp/id :shape2))))
- (the/do-watch-update dwl/add-component)
- (rx/do
- (fn [new-state]
- (let [shape1 (thp/get-shape new-state :shape1)
+ (->> state
+ (the/do-update (dw/select-shape (thp/id :shape1)))
+ (the/do-watch-update dwl/add-component)
+ (rx/do
+ (fn [new-state]
+ (let [shape1 (thp/get-shape new-state :shape1)
- [[group shape1 shape2]
- [c-group c-shape1 c-shape2]
- component]
- (thl/resolve-instance-and-main
- new-state
- (:parent-id shape1))
+ [[group shape1] [c-group c-shape1] component]
+ (thl/resolve-instance-and-main
+ new-state
+ (:parent-id shape1))
- file (dwlh/get-local-file new-state)]
+ file (dwlh/get-local-file new-state)]
- ;; NOTE: the group name depends on having executed
- ;; the previous test.
- (t/is (= (:name group) "Component-1"))
- (t/is (= (:name shape1) "Rect 1"))
- (t/is (= (:name shape2) "Rect 2"))
- (t/is (= (:name component) "Component-1"))
- (t/is (= (:name c-group) "Component-1"))
- (t/is (= (:name c-shape1) "Rect 1"))
- (t/is (= (:name c-shape2) "Rect 2"))
+ (t/is (= (:name shape1) "Rect 1"))
+ (t/is (= (:name group) "Component-1"))
+ (t/is (= (:name component) "Component-1"))
+ (t/is (= (:name c-shape1) "Rect 1"))
+ (t/is (= (:name c-group) "Component-1"))
- (thl/is-from-file group file))))
+ (thl/is-from-file group file))))
- (rx/subs
- done
- #(do
- (println (.-stack %))
- (done)))))
+ (rx/subs done #(throw %))))))
- (catch :default e
- (println (.-stack e))
- (done)))))
+;; FAILING
+#_(t/deftest test-add-component-from-several-shapes
+ (t/async
+ done
+ (let [state (-> thp/initial-state
+ (thp/sample-page)
+ (thp/sample-shape :shape1 :rect
+ {:name "Rect 1"})
+ (thp/sample-shape :shape2 :rect
+ {:name "Rect 2"}))]
+
+ (->> state
+ (the/do-update (dw/select-shapes (lks/set
+ (thp/id :shape1)
+ (thp/id :shape2))))
+ (the/do-watch-update dwl/add-component)
+ (rx/do
+ (fn [new-state]
+ (let [shape1 (thp/get-shape new-state :shape1)
+
+ [[group shape1 shape2]
+ [c-group c-shape1 c-shape2]
+ component]
+ (thl/resolve-instance-and-main
+ new-state
+ (:parent-id shape1))
+
+ file (dwlh/get-local-file new-state)]
+
+ ;; NOTE: the group name depends on having executed
+ ;; the previous test.
+ (t/is (= (:name group) "Component-1"))
+ (t/is (= (:name shape1) "Rect 1"))
+ (t/is (= (:name shape2) "Rect 2"))
+ (t/is (= (:name component) "Component-1"))
+ (t/is (= (:name c-group) "Component-1"))
+ (t/is (= (:name c-shape1) "Rect 1"))
+ (t/is (= (:name c-shape2) "Rect 2"))
+
+ (thl/is-from-file group file))))
+
+ (rx/subs done #(throw %))))))
-(t/deftest test-add-component-from-group
- (t/async done
- (try
- (let [state (-> thp/initial-state
- (thp/sample-page)
- (thp/sample-shape :shape1 :rect
- {:name "Rect 1"})
- (thp/sample-shape :shape2 :rect
- {:name "Rect 2"})
- (thp/group-shapes :group1
- [(thp/id :shape1)
- (thp/id :shape2)]))]
+#_(t/deftest test-add-component-from-group
+ (t/async
+ done
+ (let [state (-> thp/initial-state
+ (thp/sample-page)
+ (thp/sample-shape :shape1 :rect
+ {:name "Rect 1"})
+ (thp/sample-shape :shape2 :rect
+ {:name "Rect 2"})
+ (thp/group-shapes :group1
+ [(thp/id :shape1)
+ (thp/id :shape2)]))]
- (->> state
- (the/do-update (dw/select-shape (thp/id :group1)))
- (the/do-watch-update dwl/add-component)
- (rx/do
- (fn [new-state]
- (let [[[group shape1 shape2]
- [c-group c-shape1 c-shape2]
- component]
- (thl/resolve-instance-and-main
- new-state
- (thp/id :group1))
+ (->> state
+ (the/do-update (dw/select-shape (thp/id :group1)))
+ (the/do-watch-update dwl/add-component)
+ (rx/do
+ (fn [new-state]
+ (let [[[group shape1 shape2]
+ [c-group c-shape1 c-shape2]
+ component]
+ (thl/resolve-instance-and-main
+ new-state
+ (thp/id :group1))
- file (dwlh/get-local-file new-state)]
+ file (dwlh/get-local-file new-state)]
- (t/is (= (:name shape1) "Rect 1"))
- (t/is (= (:name shape2) "Rect 2"))
- (t/is (= (:name group) "Group-1"))
- (t/is (= (:name component) "Group-1"))
- (t/is (= (:name c-shape1) "Rect 1"))
- (t/is (= (:name c-shape2) "Rect 2"))
- (t/is (= (:name c-group) "Group-1"))
+ (t/is (= (:name shape1) "Rect 1"))
+ (t/is (= (:name shape2) "Rect 2"))
+ (t/is (= (:name group) "Group-1"))
+ (t/is (= (:name component) "Group-1"))
+ (t/is (= (:name c-shape1) "Rect 1"))
+ (t/is (= (:name c-shape2) "Rect 2"))
+ (t/is (= (:name c-group) "Group-1"))
- (thl/is-from-file group file))))
+ (thl/is-from-file group file))))
- (rx/subs
- done
- #(do
- (println (.-stack %))
- (done)))))
-
- (catch :default e
- (println (.-stack e))
- (done)))))
+ (rx/subs done #(throw %))))))
(t/deftest test-rename-component
- (t/async done
- (try
- (let [state (-> thp/initial-state
- (thp/sample-page)
- (thp/sample-shape :shape1 :rect
- {:name "Rect 1"})
- (thp/make-component :instance1
- [(thp/id :shape1)]))
+ (t/async
+ done
+ (let [state (-> thp/initial-state
+ (thp/sample-page)
+ (thp/sample-shape :shape1 :rect
+ {:name "Rect 1"})
+ (thp/make-component :instance1
+ [(thp/id :shape1)]))
- instance1 (thp/get-shape state :instance1)]
+ instance1 (thp/get-shape state :instance1)]
- (->> state
- (the/do-watch-update (dwl/rename-component
- (:component-id instance1)
- "Renamed component"))
- (rx/do
- (fn [new-state]
- (let [file (dwlh/get-local-file new-state)
- component (cph/get-component
- (:component-id instance1)
- (:component-file instance1)
- file
- {})]
+ (->> state
+ (the/do-watch-update (dwl/rename-component
+ (:component-id instance1)
+ "Renamed component"))
+ (rx/do
+ (fn [new-state]
+ (let [file (dwlh/get-local-file new-state)
+ component (cph/get-component
+ (:component-id instance1)
+ (:component-file instance1)
+ file
+ {})]
- (t/is (= (:name component)
- "Renamed component")))))
+ (t/is (= (:name component)
+ "Renamed component")))))
- (rx/subs
- done
- #(do
- (println (.-stack %))
- (done)))))
-
- (catch :default e
- (println (.-stack e))
- (done)))))
+ (rx/subs done #(throw %))))))
(t/deftest test-duplicate-component
- (t/async done
- (try
- (let [state (-> thp/initial-state
- (thp/sample-page)
- (thp/sample-shape :shape1 :rect
- {:name "Rect 1"})
- (thp/make-component :instance1
- [(thp/id :shape1)]))
+ (t/async
+ done
+ (let [state (-> thp/initial-state
+ (thp/sample-page)
+ (thp/sample-shape :shape1 :rect
+ {:name "Rect 1"})
+ (thp/make-component :instance1
+ [(thp/id :shape1)]))
- instance1 (thp/get-shape state :instance1)
- component-id (:component-id instance1)]
+ instance1 (thp/get-shape state :instance1)
+ component-id (:component-id instance1)]
- (->> state
- (the/do-watch-update (dwl/duplicate-component
- {:id component-id}))
- (rx/do
- (fn [new-state]
- (let [new-component-id (->> (get-in new-state
- [:workspace-data
- :components])
- (keys)
- (filter #(not= % component-id))
- (first))
+ (->> state
+ (the/do-watch-update (dwl/duplicate-component
+ {:id component-id}))
+ (rx/do
+ (fn [new-state]
+ (let [new-component-id (->> (get-in new-state
+ [:workspace-data
+ :components])
+ (keys)
+ (filter #(not= % component-id))
+ (first))
- [[instance1 shape1]
- [c-instance1 c-shape1]
- component1]
- (thl/resolve-instance-and-main
- new-state
- (:id instance1))
+ [[instance1 shape1]
+ [c-instance1 c-shape1]
+ component1]
+ (thl/resolve-instance-and-main
+ new-state
+ (:id instance1))
- [[c-component2 c-shape2]
- component2]
- (thl/resolve-component
- new-state
- new-component-id)]
+ [[c-component2 c-shape2]
+ component2]
+ (thl/resolve-component
+ new-state
+ new-component-id)]
- (t/is (= (:name component2)
- "Component-2")))))
+ (t/is (= (:name component2)
+ "Component-2")))))
- (rx/subs
- done
- #(do
- (println (.-stack %))
- (done)))))
-
- (catch :default e
- (println (.-stack e))
- (done)))))
+ (rx/subs done #(throw %))))))
(t/deftest test-delete-component
- (t/async done
- (try
- (let [state (-> thp/initial-state
- (thp/sample-page)
- (thp/sample-shape :shape1 :rect
- {:name "Rect 1"})
- (thp/make-component :instance1
- [(thp/id :shape1)]))
+ (t/async
+ done
+ (let [state (-> thp/initial-state
+ (thp/sample-page)
+ (thp/sample-shape :shape1 :rect
+ {:name "Rect 1"})
+ (thp/make-component :instance1
+ [(thp/id :shape1)]))
- instance1 (thp/get-shape state :instance1)
- component-id (:component-id instance1)]
+ instance1 (thp/get-shape state :instance1)
+ component-id (:component-id instance1)]
- (->> state
- (the/do-watch-update (dwl/delete-component
- {:id component-id}))
- (rx/do
- (fn [new-state]
- (let [[instance1 shape1]
- (thl/resolve-instance
- new-state
- (:id instance1))
+ (->> state
+ (the/do-watch-update (dwl/delete-component
+ {:id component-id}))
+ (rx/do
+ (fn [new-state]
+ (let [[instance1 shape1]
+ (thl/resolve-instance
+ new-state
+ (:id instance1))
- file (dwlh/get-local-file new-state)
- component (cph/get-component
- (:component-id instance1)
- (:component-file instance1)
- file
- {})]
+ file (dwlh/get-local-file new-state)
+ component (cph/get-component
+ (:component-id instance1)
+ (:component-file instance1)
+ file
+ {})]
- (t/is (nil? component)))))
+ (t/is (nil? component)))))
- (rx/subs
- done
- #(do
- (println (.-stack %))
- (done)))))
-
- (catch :default e
- (println (.-stack e))
- (done)))))
+ (rx/subs done #(throw %))))))
(t/deftest test-instantiate-component
- (t/async done
- (try
- (let [state (-> thp/initial-state
- (thp/sample-page)
- (thp/sample-shape :shape1 :rect
- {:name "Rect 1"})
- (thp/make-component :instance1
- [(thp/id :shape1)]))
+ (t/async
+ done
+ (let [state (-> thp/initial-state
+ (thp/sample-page)
+ (thp/sample-shape :shape1 :rect
+ {:name "Rect 1"})
+ (thp/make-component :instance1
+ [(thp/id :shape1)]))
- file (dwlh/get-local-file state)
- instance1 (thp/get-shape state :instance1)
- component-id (:component-id instance1)]
+ file (dwlh/get-local-file state)
+ instance1 (thp/get-shape state :instance1)
+ component-id (:component-id instance1)]
- (->> state
- (the/do-watch-update (dwl/instantiate-component
- (:id file)
- (:component-id instance1)
- (gpt/point 100 100)))
- (rx/do
- (fn [new-state]
- (let [new-instance-id (-> new-state
- wsh/lookup-selected
- first)
+ (->> state
+ (the/do-watch-update (dwl/instantiate-component
+ (:id file)
+ (:component-id instance1)
+ (gpt/point 100 100)))
+ (rx/do
+ (fn [new-state]
+ (let [new-instance-id (-> new-state
+ wsh/lookup-selected
+ first)
- [[instance2 shape2]
- [c-instance2 c-shape2]
- component]
- (thl/resolve-instance-and-main
- new-state
- new-instance-id)]
+ [[instance2 shape2]
+ [c-instance2 c-shape2]
+ component]
+ (thl/resolve-instance-and-main
+ new-state
+ new-instance-id)]
- (t/is (not= (:id instance1) (:id instance2)))
- (t/is (= (:id component) component-id))
- (t/is (= (:name instance2) "Component-2"))
- (t/is (= (:name shape2) "Rect 1"))
- (t/is (= (:name c-instance2) "Component-1"))
- (t/is (= (:name c-shape2) "Rect 1")))))
+ (t/is (not= (:id instance1) (:id instance2)))
+ (t/is (= (:id component) component-id))
+ (t/is (= (:name instance2) "Component-2"))
+ (t/is (= (:name shape2) "Rect 1"))
+ (t/is (= (:name c-instance2) "Component-1"))
+ (t/is (= (:name c-shape2) "Rect 1")))))
- (rx/subs
- done
- #(do
- (println (.-stack %))
- (done)))))
-
- (catch :default e
- (println (.-stack e))
- (done)))))
+ (rx/subs done #(throw %))))))
(t/deftest test-detach-component
- (t/async done
- (try
- (let [state (-> thp/initial-state
- (thp/sample-page)
- (thp/sample-shape :shape1 :rect
- {:name "Rect 1"})
- (thp/make-component :instance1
- [(thp/id :shape1)]))
+ (t/async
+ done
+ (let [state (-> thp/initial-state
+ (thp/sample-page)
+ (thp/sample-shape :shape1 :rect
+ {:name "Rect 1"})
+ (thp/make-component :instance1
+ [(thp/id :shape1)]))
- instance1 (thp/get-shape state :instance1)
- component-id (:component-id instance1)]
+ instance1 (thp/get-shape state :instance1)
+ component-id (:component-id instance1)]
- (->> state
- (the/do-watch-update (dwl/detach-component
- (:id instance1)))
- (rx/do
- (fn [new-state]
- (let [[instance1 shape1]
- (thl/resolve-noninstance
- new-state
- (:id instance1))]
+ (->> state
+ (the/do-watch-update (dwl/detach-component
+ (:id instance1)))
+ (rx/do
+ (fn [new-state]
+ (let [[instance1 shape1]
+ (thl/resolve-noninstance
+ new-state
+ (:id instance1))]
- (t/is (= (:name "Rect 1"))))))
+ (t/is (= (:name "Rect 1"))))))
- (rx/subs
- done
- #(do
- (println (.-stack %))
- (done)))))
-
- (catch :default e
- (println (.-stack e))
- (done)))))
+ (rx/subs done #(throw %))))))
diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po
index ea4f2ff9e..3027caa6f 100644
--- a/frontend/translations/ar.po
+++ b/frontend/translations/ar.po
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
-"PO-Revision-Date: 2021-08-10 23:33+0000\n"
-"Last-Translator: Mahmoud A. Rabo \n"
+"PO-Revision-Date: 2021-09-14 13:35+0000\n"
+"Last-Translator: Amine Gdoura \n"
"Language-Team: Arabic "
"\n"
"Language: ar\n"
@@ -10,7 +10,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
-"X-Generator: Weblate 4.8-dev\n"
+"X-Generator: Weblate 4.9-dev\n"
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"
@@ -88,6 +88,10 @@ msgstr "تسجيل الدخول باستخدام OpenID (SSO)"
msgid "auth.new-password"
msgstr "اكتب كلمة مرور جديدة"
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.newsletter-subscription"
+msgstr "أوافق على الاشتراك في قائمة Penpot البريدية."
+
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.notifications.invalid-token-error"
msgstr "رمز الاسترداد غير صالح."
@@ -160,6 +164,20 @@ msgstr "عند إنشاء حساب جديد ، فإنك توافق على شرو
msgid "auth.verification-email-sent"
msgstr "لقد أرسلنا رسالة تحقق إلى بريدك الالكتروني"
+msgid "common.share-link.confirm-deletion-link-description"
+msgstr ""
+"هل أنت متأكد أنك تريد إزالة هذا الرابط؟ إذا قمت بذلك ، فلن يكون متاحًا لأي "
+"شخص"
+
+msgid "common.share-link.get-link"
+msgstr "خذ رابطا إلكتروني"
+
+msgid "common.share-link.link-copied-success"
+msgstr "تم نسخ الرابط بنجاح"
+
+msgid "common.share-link.link-deleted-success"
+msgstr "تم حذف الرابط بنجاح"
+
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"
msgstr "أضف كمكتبة مشتركة"
@@ -193,7 +211,7 @@ msgstr "تكرير"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.duplicate-multi"
-msgstr "تكرير ٪s الملفات"
+msgstr "تكرير %s الملفات"
#: src/app/main/ui/dashboard/grid.cljs
msgid "dashboard.empty-files"
@@ -255,7 +273,7 @@ msgstr "الانتقال إلى"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.move-to-multi"
-msgstr "أنقل ٪s الملفات إلى"
+msgstr "أنقل %s الملفات إلى"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.move-to-other-team"
@@ -279,7 +297,7 @@ msgstr "مشروع جديد"
#: src/app/main/ui/dashboard/search.cljs
msgid "dashboard.no-matches-for"
-msgstr "لم يتم العثور على مطابقات ل \"٪s\""
+msgstr "لم يتم العثور على مطابقات ل \"%s\""
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "dashboard.no-projects-placeholder"
@@ -299,7 +317,7 @@ msgstr "تم حفظ كلمة المرور بنجاح!"
#: src/app/main/ui/dashboard/team.cljs
msgid "dashboard.num-of-members"
-msgstr "٪s الأعضاء"
+msgstr "%s الأعضاء"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.open-in-new-tab"
@@ -338,7 +356,7 @@ msgstr "بحث…"
#: src/app/main/ui/dashboard/search.cljs
msgid "dashboard.searching-for"
-msgstr "البحث عن \"٪s\"…"
+msgstr "البحث عن \"%s\"…"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.select-ui-language"
@@ -442,7 +460,7 @@ msgstr "هل أنت متأكد؟"
#: src/app/main/ui/dashboard/grid.cljs
msgid "ds.updated-at"
-msgstr "محدث: ٪s"
+msgstr "محدث: %s"
#: src/app/main/data/workspace.cljs
msgid "errors.clipboard-not-implemented"
@@ -802,7 +820,7 @@ msgid "handoff.tabs.info"
msgstr "معلومات"
msgid "history.alert-message"
-msgstr "أنت ترى الإصدار٪ s"
+msgstr "أنت ترى الإصدار %s"
msgid "labels.accept"
msgstr "إقبل"
@@ -1228,7 +1246,7 @@ msgstr "حذف الملفات"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-file-multi-confirm.message"
-msgstr "هل تريد بالتأكيد حذف ٪s من الملفات؟"
+msgstr "هل تريد بالتأكيد حذف %s من الملفات؟"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-file-multi-confirm.title"
@@ -1417,7 +1435,7 @@ msgstr "موفرو الخطوط - %s - Penpot"
#: src/app/main/ui/dashboard/fonts.cljs
msgid "title.dashboard.fonts"
-msgstr "الخطوط -٪ s - Penpot"
+msgstr "الخطوط - %s - Penpot"
#: src/app/main/ui/dashboard/projects.cljs
msgid "title.dashboard.projects"
@@ -1425,11 +1443,11 @@ msgstr "المشاريع - %s - Penpot"
#: src/app/main/ui/dashboard/search.cljs
msgid "title.dashboard.search"
-msgstr "بحث - ٪s - بينبوت"
+msgstr "بحث - %s - بينبوت"
#: src/app/main/ui/dashboard/libraries.cljs
msgid "title.dashboard.shared-libraries"
-msgstr "المكتبات المشتركة - ٪s - Penpot"
+msgstr "المكتبات المشتركة - %s - Penpot"
#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs
msgid "title.default"
@@ -1453,15 +1471,15 @@ msgstr "الملف الشخصي - Penpot"
#: src/app/main/ui/dashboard/team.cljs
msgid "title.team-members"
-msgstr "الأعضاء - ٪s - Penpot"
+msgstr "الأعضاء - %s - Penpot"
#: src/app/main/ui/dashboard/team.cljs
msgid "title.team-settings"
-msgstr "الإعدادات - ٪s - Penpot"
+msgstr "الإعدادات - %s - Penpot"
#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
msgid "title.viewer"
-msgstr "٪s - وضع العرض - Penpot"
+msgstr "%s - وضع العرض - Penpot"
#: src/app/main/ui/workspace.cljs
msgid "title.workspace"
@@ -1782,7 +1800,7 @@ msgstr "التغييرات غير المحفوظة"
#: src/app/main/ui/workspace/header.cljs
msgid "workspace.header.viewer"
-msgstr "عرض الوضع (٪s)"
+msgstr "عرض الوضع (%s)"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.add"
@@ -1790,7 +1808,7 @@ msgstr "أضف"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.colors"
-msgstr "٪s الألوان"
+msgstr "%s الألوان"
#: src/app/main/ui/workspace/colorpalette.cljs
msgid "workspace.libraries.colors.big-thumbnails"
@@ -1814,7 +1832,7 @@ msgstr "صور مصغرة صغيرة"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.components"
-msgstr "٪s المكونات"
+msgstr "%s المكونات"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.file-library"
@@ -1822,7 +1840,7 @@ msgstr "مكتبة الملف"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.graphics"
-msgstr "٪s الرسومات"
+msgstr "%s الرسومات"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.in-this-file"
@@ -1866,7 +1884,7 @@ msgstr "قم بفك ارتباط كافة الأنماط المطبعية"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.typography"
-msgstr "٪s الطباعة"
+msgstr "%s الطباعة"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.update"
diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po
index 5ab7d6d07..8d59bd3be 100644
--- a/frontend/translations/ca.po
+++ b/frontend/translations/ca.po
@@ -1,6 +1,6 @@
msgid ""
msgstr ""
-"PO-Revision-Date: 2021-09-04 15:33+0000\n"
+"PO-Revision-Date: 2021-09-11 09:32+0000\n"
"Last-Translator: Rubén \n"
"Language-Team: Catalan "
"\n"
@@ -9,7 +9,7 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.8.1-dev\n"
+"X-Generator: Weblate 4.9-dev\n"
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"
@@ -91,6 +91,10 @@ msgstr "Entreu amb OpenID (SSO)"
msgid "auth.new-password"
msgstr "Escriviu la nova contrasenya"
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.newsletter-subscription"
+msgstr "Accepto subscriure'm a la llista de correu de Penpot."
+
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.notifications.invalid-token-error"
msgstr "El codi de recuperació no és vàlid."
@@ -165,6 +169,44 @@ msgstr ""
msgid "auth.verification-email-sent"
msgstr "S'ha enviat un correu de verificació a"
+msgid "common.share-link.confirm-deletion-link-description"
+msgstr ""
+"Segur que voleu eliminar l'enllaç? Si ho feu, ja no estarà disponible per a "
+"ningú"
+
+msgid "common.share-link.get-link"
+msgstr "Obtén l'enllaç"
+
+msgid "common.share-link.link-copied-success"
+msgstr "S'ha copiat l'enllaç correctament"
+
+msgid "common.share-link.link-deleted-success"
+msgstr "S'ha eliminat l'enllaç correctament"
+
+msgid "common.share-link.permissions-can-access"
+msgstr "Pot accedir"
+
+msgid "common.share-link.permissions-can-view"
+msgstr "Lector"
+
+msgid "common.share-link.permissions-hint"
+msgstr "Qualsevol persona amb l'enllaç hi tindrà accés"
+
+msgid "common.share-link.remove-link"
+msgstr "Elimina l'enllaç"
+
+msgid "common.share-link.title"
+msgstr "Compartiu prototips"
+
+msgid "common.share-link.view-all-pages"
+msgstr "Totes les pàgines"
+
+msgid "common.share-link.view-current-page"
+msgstr "Només aquesta pàgina"
+
+msgid "common.share-link.view-selected-pages"
+msgstr "Pàgines seleccionades"
+
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"
msgstr "Afegeix com a biblioteca compartida"
@@ -204,12 +246,52 @@ msgstr "Duplica %s fitxers"
msgid "dashboard.empty-files"
msgstr "Encara no teniu cap arxiu aquí"
+msgid "dashboard.export-frames"
+msgstr "Exporta les taules de treball a PDF..."
+
msgid "dashboard.export-multi"
msgstr "Exporta %s fitxers"
msgid "dashboard.export-single"
msgstr "Exporta el fitxer"
+msgid "dashboard.export.detail"
+msgstr "* Pot incloure components, gràfics, colors i/o tipografies."
+
+msgid "dashboard.export.explain"
+msgstr ""
+"Un o més fitxers que voleu exportar utilitzen biblioteques compartides. Què "
+"voleu fer amb els seus recursos*?"
+
+msgid "dashboard.export.options.all.message"
+msgstr ""
+"els fitxers amb biblioteques compartides s’inclouran a l’exportació, "
+"mantenint la vinculació."
+
+msgid "dashboard.export.options.all.title"
+msgstr "Exporta les biblioteques compartides"
+
+msgid "dashboard.export.options.detach.message"
+msgstr ""
+"Les biblioteques compartides no s'inclouran a l'exportació i no s'afegiran "
+"recursos a la biblioteca. "
+
+msgid "dashboard.export.options.detach.title"
+msgstr "Tracta els recursos de la biblioteca compartida com a objectes bàsics"
+
+msgid "dashboard.export.options.merge.message"
+msgstr ""
+"El fitxer s'exportarà amb tots els recursos externs fusionats a la "
+"biblioteca de fitxers."
+
+msgid "dashboard.export.options.merge.title"
+msgstr ""
+"Inclou els recursos de les biblioteques compartides a les biblioteques del "
+"fitxer"
+
+msgid "dashboard.export.title"
+msgstr "Exporta els fitxers"
+
msgid "dashboard.fonts.deleted-placeholder"
msgstr "S'ha eliminat el tipus de lletra"
@@ -237,6 +319,15 @@ msgstr ""
msgid "dashboard.import"
msgstr "Importa fitxers"
+msgid "dashboard.import.analyze-error"
+msgstr "Vaja! No s'ha pogut importar aquest fitxer"
+
+msgid "dashboard.import.import-error"
+msgstr "S'ha produït un problema en importar el fitxer i no s'ha importat."
+
+msgid "dashboard.import.import-message"
+msgstr "S'han importat %s fitxers correctament."
+
#: src/app/main/ui/dashboard/team.cljs
msgid "dashboard.invite-profile"
msgstr "Convida a l'equip"
@@ -847,6 +938,9 @@ msgstr "Cancel·la"
msgid "labels.centered"
msgstr "Centrat"
+msgid "labels.close"
+msgstr "Tanca"
+
#: src/app/main/ui/dashboard/comments.cljs
msgid "labels.comments"
msgstr "Comentaris"
@@ -858,6 +952,9 @@ msgstr "Confirma la contrasenya"
msgid "labels.content"
msgstr "Contingut"
+msgid "labels.continue"
+msgstr "Continua"
+
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "labels.create"
msgstr "Crea"
@@ -877,6 +974,9 @@ msgstr "Tipus de lletra personalitzats"
msgid "labels.dashboard"
msgstr "Panell"
+msgid "labels.default"
+msgstr "predeterminat"
+
#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "labels.delete"
msgstr "Elimina"
@@ -901,6 +1001,9 @@ msgstr "Esborranys"
msgid "labels.edit"
msgstr "Edita"
+msgid "labels.edit-file"
+msgstr "Edita el fitxer"
+
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
msgid "labels.editor"
msgstr "Editor"
@@ -909,6 +1012,9 @@ msgstr "Editor"
msgid "labels.email"
msgstr "Correu electrònic"
+msgid "labels.export"
+msgstr "Exporta"
+
#: src/app/main/ui/settings/feedback.cljs
msgid "labels.feedback-disabled"
msgstr "Opinions desactivades"
@@ -963,6 +1069,9 @@ msgstr "Error intern"
msgid "labels.language"
msgstr "Llengua"
+msgid "labels.link"
+msgstr "Enllaç"
+
#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.logout"
msgstr "Tanca la sessió"
@@ -1101,6 +1210,9 @@ msgstr "Servei no disponible"
msgid "labels.settings"
msgstr "Configuració"
+msgid "labels.share-prototype"
+msgstr "Comparteix el prototip"
+
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.shared-libraries"
msgstr "Biblioteques compartides"
@@ -1111,12 +1223,18 @@ msgstr "Mostra tots els comentaris"
#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs
msgid "labels.show-your-comments"
-msgstr "Mostra només els vostres comentaris"
+msgstr "Mostra només els meus comentaris"
#: src/app/main/ui/static.cljs
msgid "labels.sign-out"
msgstr "Tanca la sessió"
+msgid "labels.skip"
+msgstr "Omet"
+
+msgid "labels.start"
+msgstr "Inicia"
+
#: src/app/main/ui/settings/profile.cljs
msgid "labels.update"
msgstr "Actualitza"
@@ -1138,6 +1256,9 @@ msgstr "S'està pujant…"
msgid "labels.viewer"
msgstr "Espectador"
+msgid "labels.workspace"
+msgstr "Espai de treball"
+
#: src/app/main/ui/comments.cljs
msgid "labels.write-new-comment"
msgstr "Escriu un comentari nou"
@@ -1406,6 +1527,120 @@ msgstr "El perfil s'ha desat correctament!"
msgid "notifications.validation-email-sent"
msgstr "S'ha enviat un correu electrònic de verificació a %s. Reviseu el correu!"
+msgid "onboarding.contrib.alt"
+msgstr "Codi obert"
+
+msgid "onboarding.contrib.desc1"
+msgstr ""
+"Penpot és de codi obert, fet per i per a la comunitat. Si voleu "
+"col·laborar, sou més que benvinguts!"
+
+msgid "onboarding.contrib.desc2.1"
+msgstr "Podeu accedir al"
+
+msgid "onboarding.contrib.desc2.2"
+msgstr "i seguir les instruccions de contribució :)"
+
+msgid "onboarding.contrib.link"
+msgstr "projecte a github"
+
+msgid "onboarding.contrib.title"
+msgstr "Ets contribuïdor de codi obert?"
+
+msgid "onboarding.slide.0.alt"
+msgstr "Crea dissenys"
+
+msgid "onboarding.slide.0.desc1"
+msgstr ""
+"Creeu interfícies d'usuari boniques en col·laboració amb tots els membres "
+"de l'equip."
+
+msgid "onboarding.slide.0.desc2"
+msgstr ""
+"Manteniu la consistència a escala amb components, biblioteques i sistemes "
+"de disseny."
+
+msgid "onboarding.slide.1.alt"
+msgstr "Prototips interactius"
+
+msgid "onboarding.slide.1.desc1"
+msgstr "Creeu interaccions enriquides per a imitar el comportament del producte."
+
+msgid "onboarding.slide.1.desc2"
+msgstr ""
+"Compartiu amb els grups d'interés, presenteu propostes a l'equip i comenceu "
+"a provar els usuaris amb els vostres dissenys, tot en un sol lloc."
+
+msgid "onboarding.slide.1.title"
+msgstr "Doneu vida als vostres dissenys amb interaccions"
+
+msgid "onboarding.slide.2.alt"
+msgstr "Obteniu opinions"
+
+msgid "onboarding.slide.2.desc1"
+msgstr ""
+"Tot l'equip treballant simultàniament amb disseny en temps real i "
+"comentaris, idees i opinions sobre els dissenys de forma centralitzada."
+
+msgid "onboarding.slide.2.title"
+msgstr "Obteniu opinions, presenteu i compartiu el vostre treball"
+
+msgid "onboarding.slide.3.alt"
+msgstr "Lliurament i codi baix"
+
+msgid "onboarding.slide.3.desc1"
+msgstr ""
+"Sincronitzeu el disseny i el codi de tots els components i estils i obteniu "
+"fragments de codi."
+
+msgid "onboarding.slide.3.desc2"
+msgstr ""
+"Obteniu i proporcioneu especificacions de codi d'etiquetatge (SVG, HTML) o "
+"d'estils (CSS, Less, Stylus...)."
+
+msgid "onboarding.slide.3.title"
+msgstr "Una font compartida de veritat"
+
+msgid "onboarding.team.create.button"
+msgstr "Crea un equip"
+
+msgid "onboarding.team.create.desc1"
+msgstr ""
+"Esteu treballant amb algú? Creeu un equip per a treballar junts en "
+"projectes i compartir recursos de disseny."
+
+msgid "onboarding.team.create.input-placeholder"
+msgstr "Introduïu el nom de l'equip nou"
+
+#, fuzzy
+msgid "onboarding.team.create.title"
+msgstr "Creeu un equip"
+
+msgid "onboarding.team.start.button"
+msgstr "Comenceu de seguida"
+
+msgid "onboarding.team.start.desc1"
+msgstr ""
+"Salteu ara al Penpot i comenceu a dissenyar pel vostre compte. Tindreu "
+"l'oportunitat de crear més equips després."
+
+msgid "onboarding.team.start.title"
+msgstr "Comenceu a dissenyar"
+
+msgid "onboarding.welcome.alt"
+msgstr "Penpot"
+
+msgid "onboarding.welcome.desc1"
+msgstr "Estem molt contents de presentar-vos la primera versió alfa."
+
+msgid "onboarding.welcome.desc2"
+msgstr ""
+"Penpot encara està en fase de desenvolupament i hi haurà actualitzacions "
+"constants. Esperem que gaudiu de la primera versió estable."
+
+msgid "onboarding.welcome.title"
+msgstr "Vos donem la benvinguda a Penpot!"
+
#: src/app/main/ui/auth/recovery.cljs
msgid "profile.recovery.go-to-login"
msgstr "Vés a l'inici de sessió"
@@ -1483,6 +1718,9 @@ msgstr "No s'ha trobat cap taula de treball a la pàgina."
msgid "viewer.frame-not-found"
msgstr "No s'ha trobat la taula de treball."
+msgid "viewer.header.comments-section"
+msgstr "Comentaris"
+
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.dont-show-interactions"
msgstr "No mostris les interaccions"
@@ -1495,10 +1733,16 @@ msgstr "Edita el fitxer"
msgid "viewer.header.fullscreen"
msgstr "Pantalla completa"
+msgid "viewer.header.handsoff-section"
+msgstr "Lliurament"
+
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.interactions"
msgstr "Interaccions"
+msgid "viewer.header.interactions-section"
+msgstr "Interaccions"
+
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.copy-link"
msgstr "Copia l'enllaç"
@@ -1806,10 +2050,22 @@ msgstr "Miniatures grans"
msgid "workspace.libraries.colors.file-library"
msgstr "Biblioteca del fitxer"
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.hsv"
+msgstr "HSV"
+
#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs
msgid "workspace.libraries.colors.recent-colors"
msgstr "Colors recents"
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.rgb-complementary"
+msgstr "Complementari d'RGB"
+
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.rgba"
+msgstr "RGBA"
+
#: src/app/main/ui/workspace/colorpicker.cljs
msgid "workspace.libraries.colors.save-color"
msgstr "Desa l'estil de color"
@@ -2073,8 +2329,9 @@ msgid "workspace.options.group-fill"
msgstr "Emplenament de grup"
#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+#, fuzzy
msgid "workspace.options.group-stroke"
-msgstr "Vora del grup"
+msgstr "Traç del grup"
#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
msgid "workspace.options.layer-options.blend-mode.color"
@@ -2152,14 +2409,6 @@ msgstr "Agrupa les capes"
msgid "workspace.options.layer-options.title.multiple"
msgstr "Capes seleccionades"
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.navigate-to"
-msgstr "Vés a"
-
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.none"
-msgstr "Cap"
-
#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.position"
msgstr "Posició"
@@ -2198,8 +2447,9 @@ msgid "workspace.options.selection-fill"
msgstr "Emplenament de selecció"
#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+#, fuzzy
msgid "workspace.options.selection-stroke"
-msgstr "Vora de selecció"
+msgstr "Traç de la selecció"
#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
msgid "workspace.options.shadow-options.blur"
@@ -2247,11 +2497,44 @@ msgstr "Mides predefinides"
#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
msgid "workspace.options.stroke"
-msgstr "Vora"
+msgstr "Traç"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.circle-marker"
+msgstr "Marcador circular"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.diamond-marker"
+msgstr "Marcador de diamant"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+#, fuzzy
+msgid "workspace.options.stroke-cap.line-arrow"
+msgstr "Fletxa de línia"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.none"
+msgstr "Cap"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.round"
+msgstr "Redó"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.square"
+msgstr "Quadrat"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.square-marker"
+msgstr "Marcador quadrat"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.triangle-arrow"
+msgstr "Fletxa triangular"
#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
msgid "workspace.options.stroke.center"
-msgstr "Centre"
+msgstr "Centrat"
#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
msgid "workspace.options.stroke.dashed"
diff --git a/frontend/translations/de.po b/frontend/translations/de.po
index b66d51f92..53dc3c6df 100644
--- a/frontend/translations/de.po
+++ b/frontend/translations/de.po
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
-"PO-Revision-Date: 2021-06-27 08:33+0000\n"
-"Last-Translator: nautilusx \n"
+"PO-Revision-Date: 2021-10-08 20:58+0000\n"
+"Last-Translator: Marius \n"
"Language-Team: German "
"\n"
"Language: de\n"
@@ -9,7 +9,7 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.7.1-dev\n"
+"X-Generator: Weblate 4.9-dev\n"
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"
@@ -91,6 +91,10 @@ msgstr "Einloggen mit OpenID (SSO)"
msgid "auth.new-password"
msgstr "Geben Sie ein neues Passwort ein"
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.newsletter-subscription"
+msgstr "Ich bin damit einverstanden, die Penpot-Mailingliste zu abonnieren."
+
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.notifications.invalid-token-error"
msgstr "Der Wiederherstellungscode ist ungültig."
@@ -167,6 +171,44 @@ msgstr ""
msgid "auth.verification-email-sent"
msgstr "Wir haben eine Bestätigungs-E-Mail gesendet an"
+msgid "common.share-link.confirm-deletion-link-description"
+msgstr ""
+"Sind Sie sicher, dass Sie diesen Link löschen möchten? Wenn Sie das tun, "
+"wird er für niemanden mehr verfügbar sein"
+
+msgid "common.share-link.get-link"
+msgstr "Link erstellen"
+
+msgid "common.share-link.link-copied-success"
+msgstr "Link wurde erfolgreich kopiert"
+
+msgid "common.share-link.link-deleted-success"
+msgstr "Link wurde erfolgreich gelöscht"
+
+msgid "common.share-link.permissions-can-access"
+msgstr "Freigabe für"
+
+msgid "common.share-link.permissions-can-view"
+msgstr "Sichtbar"
+
+msgid "common.share-link.permissions-hint"
+msgstr "Jeder mit dem Link kann auf die Datei zugreifen"
+
+msgid "common.share-link.remove-link"
+msgstr "Link entfernen"
+
+msgid "common.share-link.title"
+msgstr "Prototypen teilen"
+
+msgid "common.share-link.view-all-pages"
+msgstr "Alle Seiten"
+
+msgid "common.share-link.view-current-page"
+msgstr "Nur diese Seite"
+
+msgid "common.share-link.view-selected-pages"
+msgstr "Ausgewählte Seiten"
+
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"
msgstr "Hinzufügen als gemeinsam genutzte Bibliothek"
@@ -206,12 +248,51 @@ msgstr "%s Dateien duplizieren"
msgid "dashboard.empty-files"
msgstr "Sie haben hier noch keine Dateien"
+msgid "dashboard.export-frames"
+msgstr "Zeichenflächen als PDF exportieren…"
+
msgid "dashboard.export-multi"
msgstr "%s Dateien exportieren"
msgid "dashboard.export-single"
msgstr "Datei exportieren"
+msgid "dashboard.export.detail"
+msgstr "* kann Komponenten, Grafiken, Farben und/oder Textstile enthalten."
+
+msgid "dashboard.export.explain"
+msgstr ""
+"Eine oder mehrere Dateien, die Sie exportieren möchten, verwenden geteilte "
+"Bibliotheken. Was möchten Sie mit den Assets* aus diesen Bibliotheken "
+"machen?"
+
+msgid "dashboard.export.options.all.message"
+msgstr ""
+"Dateien mit geteilten Bibliotheken werden exportiert, und ihre "
+"Verknüpfungen bleiben erhalten."
+
+msgid "dashboard.export.options.all.title"
+msgstr "Geteilte Bibliotheken exportieren"
+
+msgid "dashboard.export.options.detach.message"
+msgstr ""
+"Geteilte Bibliotheken werden nicht exportiert und der Bibliothek werden "
+"keine Assets hinzugefügt. "
+
+msgid "dashboard.export.options.detach.title"
+msgstr "Assets aus geteilten Bibliotheken als gewöhnliche Objekte behandeln"
+
+msgid "dashboard.export.options.merge.message"
+msgstr ""
+"Ihre Datei wird exportiert, und alle externen Assets werden der "
+"Dateibibliothek hinzugefügt."
+
+msgid "dashboard.export.options.merge.title"
+msgstr "Assets aus geteilten Bibliotheken in die Dateibibliothek aufnehmen"
+
+msgid "dashboard.export.title"
+msgstr "Dateien exportieren"
+
msgid "dashboard.fonts.deleted-placeholder"
msgstr "Schriftart gelöscht"
@@ -228,9 +309,29 @@ msgstr ""
"den folgenden Formaten hochladen: **TTF, OTF und WOFF** (nur eine wird "
"benötigt)."
+#, markdown
+msgid "dashboard.fonts.hero-text2"
+msgstr ""
+"Sie sollten nur Schriftarten hochladen, die Sie besitzen oder für die Sie "
+"eine Lizenz zur Verwendung in Penpot verfügen. Weitere Informationen finden "
+"Sie im Abschnitt über Inhaltsrechte in den [Nutzungsbedingungen von "
+"Penpot](https://penpot.app/terms.html). Mehr über die [Lizenzierung von "
+"Schriftarten erfahren Sie hier](https://www.typography.com/faq)."
+
msgid "dashboard.import"
msgstr "Dateien importieren"
+msgid "dashboard.import.analyze-error"
+msgstr "Huch! Wir konnten diese Datei nicht importieren"
+
+msgid "dashboard.import.import-error"
+msgstr ""
+"Beim Importieren der Datei ist ein Fehler aufgetreten. Die Datei wurde "
+"nicht importiert."
+
+msgid "dashboard.import.import-message"
+msgstr "%s Dateien wurden erfolgreich importiert."
+
#: src/app/main/ui/dashboard/team.cljs
msgid "dashboard.invite-profile"
msgstr "Zum Team einladen"
@@ -254,6 +355,10 @@ msgstr "laden Ihrer Schriftarten …"
msgid "dashboard.move-to"
msgstr "Verschieben nach"
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.move-to-multi"
+msgstr "%s Dateien verschieben"
+
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.move-to-other-team"
msgstr "Zu anderem Team verschieben"
@@ -369,6 +474,10 @@ msgstr "Ihr Projekt wurde erfolgreich dupliziert"
msgid "dashboard.success-move-file"
msgstr "Ihre Datei wurde erfolgreich verschoben"
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.success-move-files"
+msgstr "Ihre Dateien wurden erfolgreich verschoben"
+
#: src/app/main/ui/dashboard/project_menu.cljs
msgid "dashboard.success-move-project"
msgstr "Ihr Projekt wurde erfolgreich verschoben"
@@ -522,6 +631,10 @@ msgstr ""
"Sie müssen unsere Nutzungsbedingungen und Datenschutzrichtlinien "
"akzeptieren."
+#: src/app/main/ui/auth/verify_token.cljs
+msgid "errors.token-expired"
+msgstr "Token abgelaufen"
+
#: src/app/main/data/media.cljs, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs
msgid "errors.unexpected-error"
msgstr "Ein unerwarteter Fehler ist aufgetreten."
@@ -801,6 +914,9 @@ msgstr "Sie sehen Version %s"
msgid "labels.accept"
msgstr "Akzeptieren"
+msgid "labels.add-custom-font"
+msgstr "Eigene Schriftart hinzufügen"
+
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
msgid "labels.admin"
msgstr "Admin"
@@ -826,6 +942,9 @@ msgstr "Abbrechen"
msgid "labels.centered"
msgstr "Zentriert"
+msgid "labels.close"
+msgstr "Schließen"
+
#: src/app/main/ui/dashboard/comments.cljs
msgid "labels.comments"
msgstr "Kommentare"
@@ -837,6 +956,9 @@ msgstr "Passwort bestätigen"
msgid "labels.content"
msgstr "Inhalt"
+msgid "labels.continue"
+msgstr "Weiter"
+
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "labels.create"
msgstr "Erstellen"
@@ -849,10 +971,16 @@ msgstr "Neues Team erstellen"
msgid "labels.create-team.placeholder"
msgstr "Neuen Teamnamen eingeben"
+msgid "labels.custom-fonts"
+msgstr "Eigene Schriftarten"
+
#: src/app/main/ui/settings/sidebar.cljs
msgid "labels.dashboard"
msgstr "Dashboard"
+msgid "labels.default"
+msgstr "Standardeinstellung"
+
#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "labels.delete"
msgstr "Löschen"
@@ -877,6 +1005,9 @@ msgstr "Entwürfe"
msgid "labels.edit"
msgstr "Bearbeiten"
+msgid "labels.edit-file"
+msgstr "Datei bearbeiten"
+
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
msgid "labels.editor"
msgstr "Editor"
@@ -885,6 +1016,9 @@ msgstr "Editor"
msgid "labels.email"
msgstr "E-Mail"
+msgid "labels.export"
+msgstr "Exportieren"
+
#: src/app/main/ui/settings/feedback.cljs
msgid "labels.feedback-disabled"
msgstr "Feedback deaktiviert"
@@ -893,6 +1027,12 @@ msgstr "Feedback deaktiviert"
msgid "labels.feedback-sent"
msgstr "Feedback gesendet"
+msgid "labels.font-family"
+msgstr "Schriftfamilie"
+
+msgid "labels.font-providers"
+msgstr "Schriftenhersteller"
+
msgid "labels.font-variants"
msgstr "Stile"
@@ -933,6 +1073,9 @@ msgstr "Interner Fehler"
msgid "labels.language"
msgstr "Sprache"
+msgid "labels.link"
+msgstr "Link"
+
#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.logout"
msgstr "Abmelden"
@@ -976,6 +1119,11 @@ msgid_plural "labels.num-of-files"
msgstr[0] "1 Datei"
msgstr[1] "%s Dateien"
+msgid "labels.num-of-frames"
+msgid_plural "labels.num-of-frames"
+msgstr[0] "1 Zeichenfläche"
+msgstr[1] "%s Zeichenflächen"
+
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.num-of-projects"
msgid_plural "labels.num-of-projects"
@@ -1086,6 +1234,12 @@ msgstr "Nur eigene Kommentare anzeigen"
msgid "labels.sign-out"
msgstr "Abmelden"
+msgid "labels.skip"
+msgstr "Überspringen"
+
+msgid "labels.start"
+msgstr "Start"
+
#: src/app/main/ui/settings/profile.cljs
msgid "labels.update"
msgstr "Aktualisieren"
@@ -1094,10 +1248,22 @@ msgstr "Aktualisieren"
msgid "labels.update-team"
msgstr "Team aktualisieren"
+msgid "labels.upload"
+msgstr "Hochladen"
+
+msgid "labels.upload-custom-fonts"
+msgstr "Eigene Schriftarten hochladen"
+
+msgid "labels.uploading"
+msgstr "Lädt hoch…"
+
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.viewer"
msgstr "Zuschauer"
+msgid "labels.workspace"
+msgstr "Arbeitsbereich"
+
#: src/app/main/ui/comments.cljs
msgid "labels.write-new-comment"
msgstr "Neuen Kommentar schreiben"
@@ -1198,6 +1364,19 @@ msgstr "Sind Sie sicher, dass Sie %s Dateien löschen möchten?"
msgid "modals.delete-file-multi-confirm.title"
msgstr "Lösche %s Dateien"
+msgid "modals.delete-font-variant.message"
+msgstr ""
+"Sind Sie sicher, dass Sie diesen Schriftschnitt löschen möchten? Sie wird "
+"nicht mehr geladen, wenn sie bereits in einer Datei verwendet wird."
+
+msgid "modals.delete-font-variant.title"
+msgstr "Schriftschnitt löschen"
+
+msgid "modals.delete-font.message"
+msgstr ""
+"Sind Sie sicher, dass Sie diese Schriftart löschen möchten? Sie wird nicht "
+"mehr geladen, wenn sie bereits in einer Datei verwendet wird."
+
msgid "modals.delete-font.title"
msgstr "Schriftart löschen"
@@ -1255,6 +1434,11 @@ msgstr "Einladung senden"
msgid "modals.invite-member.title"
msgstr "Einladen, dem Team beizutreten"
+msgid "modals.leave-and-reassign.forbiden"
+msgstr ""
+"Sie können das Team nicht verlassen, wenn es kein anderes Mitglied gibt, "
+"das Sie zum Besitzer ernennen können. Sie können das Team jedoch löschen."
+
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.leave-and-reassign.hint1"
msgstr "Sie sind der Eigentümer von %s."
@@ -1352,6 +1536,101 @@ msgstr "Profil erfolgreich gespeichert!"
msgid "notifications.validation-email-sent"
msgstr "Verifizierungs-E-Mail an %s gesendet. Prüfen Sie Ihren Posteingang!"
+msgid "onboarding.contrib.alt"
+msgstr "Open Source"
+
+msgid "onboarding.slide.0.desc1"
+msgstr ""
+"Erstellen Sie ansprechende Benutzeroberflächen gemeinsam mit anderen "
+"Teammitgliedern."
+
+msgid "onboarding.slide.0.desc2"
+msgstr ""
+"Schaffen Sie ein einheitliches Erscheinungsbild mit Hilfe von Komponenten, "
+"Bibliotheken und Design-Systemen."
+
+msgid "onboarding.slide.0.title"
+msgstr "Design-Bibliotheken, Stile und Komponenten"
+
+msgid "onboarding.slide.1.alt"
+msgstr "Interaktive Prototypen"
+
+msgid "onboarding.slide.1.desc1"
+msgstr ""
+"Erstellen Sie umfangreiche Interaktionen, um das reale Verhalten ihres "
+"Produktes zu simulieren."
+
+msgid "onboarding.slide.1.desc2"
+msgstr ""
+"Teilen Sie Ihre Vorschläge mit allen Beteiligten und testen Sie Entwürfe "
+"direkt mit Ihren Nutzern - alles an einem Ort."
+
+msgid "onboarding.slide.1.title"
+msgstr "Hauchen Sie Ihren Entwürfen mit Interaktionen Leben ein"
+
+msgid "onboarding.slide.2.alt"
+msgstr "Erhalten Sie Feedback"
+
+msgid "onboarding.slide.2.desc1"
+msgstr ""
+"Alle Teammitglieder können in Echtzeit an Entwürfen arbeiten – und erhalten "
+"direktes Feedback über die integrierte Kommentarfunktion."
+
+msgid "onboarding.slide.2.title"
+msgstr "Holen Sie sich Feedback, präsentieren und teilen Sie Ihre Entwürfe"
+
+msgid "onboarding.slide.3.desc1"
+msgstr ""
+"Synchronisieren Sie das Design und den Code all Ihrer Komponenten und Stile "
+"und nutzen Sie Code-Snippets."
+
+msgid "onboarding.slide.3.desc2"
+msgstr ""
+"Erstellen und erhalten Sie Spezifikationen in Auszeichnungs- (SVG, HTML) "
+"oder Stylesheet-Sprachen (CSS, Less, Stylus…)."
+
+msgid "onboarding.slide.3.title"
+msgstr "Eine zentrale Anlaufstelle für alle"
+
+msgid "onboarding.team.create.button"
+msgstr "Team erstellen"
+
+msgid "onboarding.team.create.desc1"
+msgstr ""
+"Arbeiten Sie mit jemandem zusammen? Erstellen Sie ein Team, um gemeinsam an "
+"Projekten zu arbeiten und Design-Assets zu teilen."
+
+msgid "onboarding.team.create.input-placeholder"
+msgstr "Neuen Teamnamen eingeben"
+
+msgid "onboarding.team.create.title"
+msgstr "Team erstellen"
+
+msgid "onboarding.team.start.button"
+msgstr "Gleich loslegen"
+
+msgid "onboarding.team.start.desc1"
+msgstr ""
+"Legen Sie gleich los und gestalten Sie erstmal alleine. Sie können später "
+"immer noch Teams erstellen."
+
+msgid "onboarding.team.start.title"
+msgstr "Mit der Gestaltung beginnen"
+
+msgid "onboarding.welcome.alt"
+msgstr "Penpot"
+
+msgid "onboarding.welcome.desc1"
+msgstr "Wir freuen uns sehr, Ihnen die erste Alpha-Version vorstellen zu können."
+
+msgid "onboarding.welcome.desc2"
+msgstr ""
+"Penpot befindet sich noch in der Entwicklungsphase und wird ständig "
+"aktualisiert. Wir hoffen, dass Ihnen die erste stabile Version gefällt."
+
+msgid "onboarding.welcome.title"
+msgstr "Willkommen bei Penpot!"
+
#: src/app/main/ui/auth/recovery.cljs
msgid "profile.recovery.go-to-login"
msgstr "Zur Anmeldung"
@@ -1360,6 +1639,18 @@ msgstr "Zur Anmeldung"
msgid "settings.multiple"
msgstr "Mehrere"
+#: src/app/main/ui/dashboard/files.cljs
+msgid "title.dashboard.files"
+msgstr "%s - Penpot"
+
+#: src/app/main/ui/dashboard/fonts.cljs
+msgid "title.dashboard.font-providers"
+msgstr "Schriftenhersteller - %s - Penpot"
+
+#: src/app/main/ui/dashboard/fonts.cljs
+msgid "title.dashboard.fonts"
+msgstr "Schriftarten - %s - Penpot"
+
#: src/app/main/ui/dashboard/projects.cljs
msgid "title.dashboard.projects"
msgstr "Projekte - %s - Penpot"
@@ -1372,6 +1663,10 @@ msgstr "Suchen - %s - Penpot"
msgid "title.dashboard.shared-libraries"
msgstr "Gemeinsam genutzte Bibliotheken - %s - Penpot"
+#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs
+msgid "title.default"
+msgstr "Penpot - Gestaltungsfreiheit für Teams"
+
#: src/app/main/ui/settings/feedback.cljs
msgid "title.settings.feedback"
msgstr "Feedback geben - Penpot"
@@ -1400,6 +1695,10 @@ msgstr "Einstellungen - %s - Penpot"
msgid "title.viewer"
msgstr "%s - Ansichtsmodus - Penpot"
+#: src/app/main/ui/workspace.cljs
+msgid "title.workspace"
+msgstr "%s - Penpot"
+
#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
msgid "viewer.empty-state"
msgstr "Keine Zeichenflächen auf der Seite gefunden."
@@ -1408,6 +1707,9 @@ msgstr "Keine Zeichenflächen auf der Seite gefunden."
msgid "viewer.frame-not-found"
msgstr "Keine Zeichenfläche gefunden."
+msgid "viewer.header.comments-section"
+msgstr "Kommentare"
+
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.dont-show-interactions"
msgstr "Interaktionen nicht anzeigen"
@@ -1420,10 +1722,16 @@ msgstr "Seite bearbeiten"
msgid "viewer.header.fullscreen"
msgstr "Vollbildmodus"
+msgid "viewer.header.handsoff-section"
+msgstr "Übergabe"
+
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.interactions"
msgstr "Interaktionen"
+msgid "viewer.header.interactions-section"
+msgstr "Interaktionen"
+
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.copy-link"
msgstr "Link kopieren"
@@ -1507,6 +1815,10 @@ msgstr "Farben"
msgid "workspace.assets.components"
msgstr "Komponente"
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.create-group"
+msgstr "Gruppe erstellen"
+
#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.delete"
msgstr "Löschen"
@@ -1527,6 +1839,14 @@ msgstr "Dateibibliothek"
msgid "workspace.assets.graphics"
msgstr "Grafiken"
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.group"
+msgstr "Gruppieren"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.group-name"
+msgstr "Name der Gruppe"
+
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.libraries"
msgstr "Bibliotheken"
@@ -1539,10 +1859,20 @@ msgstr "Keine Assets gefunden"
msgid "workspace.assets.rename"
msgstr "Umbenennen"
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.rename-group"
+msgstr "Gruppe umbenennen"
+
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.search"
msgstr "Assets suchen"
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.selected-count"
+msgid_plural "workspace.assets.selected-count"
+msgstr[0] "%s Element ausgewählt"
+msgstr[1] "%s Elemente ausgewählt"
+
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.shared"
msgstr "GETEILT"
@@ -1583,6 +1913,10 @@ msgstr "Ag"
msgid "workspace.assets.typography.text-transform"
msgstr "Texttransformation"
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.ungroup"
+msgstr "Gruppierung aufheben"
+
#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs
msgid "workspace.gradients.linear"
msgstr "Linearer Farbverlauf"
@@ -1595,6 +1929,10 @@ msgstr "Radialer Farbverlauf"
msgid "workspace.header.menu.disable-dynamic-alignment"
msgstr "Dynamische Ausrichtung deaktivieren"
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.disable-scale-text"
+msgstr "Textskalierung ausschalten"
+
#: src/app/main/ui/workspace/header.cljs
msgid "workspace.header.menu.disable-snap-grid"
msgstr "Am Raster ausrichten deaktivieren"
@@ -1603,6 +1941,10 @@ msgstr "Am Raster ausrichten deaktivieren"
msgid "workspace.header.menu.enable-dynamic-alignment"
msgstr "Dynamische Ausrichtung aktivieren"
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.enable-scale-text"
+msgstr "Textskalierung einschalten"
+
#: src/app/main/ui/workspace/header.cljs
msgid "workspace.header.menu.enable-snap-grid"
msgstr "Am Raster ausrichten"
@@ -1687,10 +2029,18 @@ msgstr "Große Miniaturen"
msgid "workspace.libraries.colors.file-library"
msgstr "Dateibibliothek"
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.hsv"
+msgstr "HSV"
+
#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs
msgid "workspace.libraries.colors.recent-colors"
msgstr "Aktuelle Farben"
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.rgba"
+msgstr "RGBA"
+
#: src/app/main/ui/workspace/colorpicker.cljs
msgid "workspace.libraries.colors.save-color"
msgstr "Farbstil speichern"
@@ -1803,6 +2153,46 @@ msgstr "Hintergrundfarbe"
msgid "workspace.options.component"
msgstr "Komponente"
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints"
+msgstr "Constraints"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.bottom"
+msgstr "Unten"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.center"
+msgstr "Mittig"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.fix-when-scrolling"
+msgstr "Beim Scrollen fixieren"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.left"
+msgstr "Links"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.leftright"
+msgstr "Links & Rechts"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.right"
+msgstr "Rechts"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.scale"
+msgstr "Skalieren"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.top"
+msgstr "Oben"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.topbottom"
+msgstr "Oben & Unten"
+
#: src/app/main/ui/workspace/sidebar/options.cljs
msgid "workspace.options.design"
msgstr "Design"
@@ -1995,14 +2385,6 @@ msgstr "Ebenen gruppieren"
msgid "workspace.options.layer-options.title.multiple"
msgstr "Ausgewählte Ebenen"
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.navigate-to"
-msgstr "Navigiere zu"
-
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.none"
-msgstr "Keine"
-
#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.position"
msgstr "Position"
@@ -2092,6 +2474,38 @@ msgstr "Größenvoreinstellungen"
msgid "workspace.options.stroke"
msgstr "Rahmen"
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.circle-marker"
+msgstr "Punkt"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.diamond-marker"
+msgstr "Diamant"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.line-arrow"
+msgstr "Pfeil"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.none"
+msgstr "Keine"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.round"
+msgstr "Rund"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.square"
+msgstr "Rechteckig"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.square-marker"
+msgstr "Quadrat"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.triangle-arrow"
+msgstr "Dreieckiger Pfeil"
+
#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
msgid "workspace.options.stroke.center"
msgstr "Zentriert"
@@ -2151,6 +2565,14 @@ msgstr "Oben ausrichten"
msgid "workspace.options.text-options.decoration"
msgstr "Textdekoration"
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.direction-ltr"
+msgstr "von links nach rechts"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.direction-rtl"
+msgstr "von rechts nach links"
+
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
msgid "workspace.options.text-options.google"
msgstr "Google"
@@ -2227,6 +2649,36 @@ msgstr ""
"Verwenden Sie die Wiedergabetaste in der Kopfzeile, um die Prototypansicht "
"zu wechseln."
+msgid "workspace.path.actions.add-node"
+msgstr "Ankerpunkt hinzufügen (%s)"
+
+msgid "workspace.path.actions.delete-node"
+msgstr "Ankerpunkt entfernen (%s)"
+
+msgid "workspace.path.actions.draw-nodes"
+msgstr "Ankerpunkte zeichnen (%s)"
+
+msgid "workspace.path.actions.join-nodes"
+msgstr "Ankerpunkte verbinden (%s)"
+
+msgid "workspace.path.actions.make-corner"
+msgstr "in Ecke umwandeln (%s)"
+
+msgid "workspace.path.actions.make-curve"
+msgstr "in Kurve umwandeln (%s)"
+
+msgid "workspace.path.actions.merge-nodes"
+msgstr "Ankerpunkte zusammenlegen (%s)"
+
+msgid "workspace.path.actions.move-nodes"
+msgstr "Ankerpunkte verschieben (%s)"
+
+msgid "workspace.path.actions.separate-nodes"
+msgstr "Ankerpunkte trennen (%s)"
+
+msgid "workspace.path.actions.snap-nodes"
+msgstr "An Ankerpunkten ausrichten (%s)"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.back"
msgstr "In den Hintergrund"
diff --git a/frontend/translations/el.po b/frontend/translations/el.po
index 3812b4182..0ede3c579 100644
--- a/frontend/translations/el.po
+++ b/frontend/translations/el.po
@@ -1832,14 +1832,6 @@ msgstr "στρώματα Ομάδα"
msgid "workspace.options.layer-options.title.multiple"
msgstr "Επιλεγμένα επίπεδα"
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.navigate-to"
-msgstr "Μεταβείτε στο"
-
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.none"
-msgstr "Κανένας"
-
#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.position"
msgstr "Θέση"
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index 2a92e779b..691cc14e6 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -121,6 +121,9 @@ msgstr "Password"
msgid "auth.password-length-hint"
msgstr "At least 8 characters"
+msgid "auth.privacy-policy"
+msgstr "Privacy policy"
+
#: src/app/main/ui/auth/recovery_request.cljs
msgid "auth.recovery-request-submit"
msgstr "Recover Password"
@@ -157,6 +160,9 @@ msgstr "Create an account"
msgid "auth.sidebar-tagline"
msgstr "The open-source solution for design and prototyping."
+msgid "auth.terms-of-service"
+msgstr "Terms of service"
+
#: src/app/main/ui/auth/register.cljs
msgid "auth.terms-privacy-agreement"
msgstr ""
@@ -323,27 +329,27 @@ msgstr "There was a problem importing the file. The file wasn't imported."
msgid "dashboard.import.import-message"
msgstr "%s files have been imported succesfully."
+msgid "dashboard.import.progress.process-colors"
+msgstr "Processing colors"
+
+msgid "dashboard.import.progress.process-components"
+msgstr "Processing components"
+
+msgid "dashboard.import.progress.process-media"
+msgstr "Processing media"
+
+msgid "dashboard.import.progress.process-page"
+msgstr "Processing page: %s"
+
+msgid "dashboard.import.progress.process-typographies"
+msgstr "Processing typographies"
+
msgid "dashboard.import.progress.upload-data"
msgstr "Uploading data to server (%s/%s)"
msgid "dashboard.import.progress.upload-media"
msgstr "Uploading file: %s"
-msgid "dashboard.import.progress.process-page"
-msgstr "Processing page: %s"
-
-msgid "dashboard.import.progress.process-colors"
-msgstr "Processing colors"
-
-msgid "dashboard.import.progress.process-typographies"
-msgstr "Processing typographies"
-
-msgid "dashboard.import.progress.process-media"
-msgstr "Processing media"
-
-msgid "dashboard.import.progress.process-components"
-msgstr "Processing components"
-
#: src/app/main/ui/dashboard/team.cljs
msgid "dashboard.invite-profile"
msgstr "Invite to team"
@@ -1707,6 +1713,14 @@ msgstr "%s - View mode - Penpot"
msgid "title.workspace"
msgstr "%s - Penpot"
+msgid "viewer.breaking-change.description"
+msgstr ""
+"This shareable link is no longer valid. Create a new one or ask the owner "
+"for a new one."
+
+msgid "viewer.breaking-change.message"
+msgstr "Sorry!"
+
#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
msgid "viewer.empty-state"
msgstr "No artboards found on the page."
@@ -2137,6 +2151,10 @@ msgstr "My libraries"
msgid "workspace.library.store"
msgstr "Store libraries"
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.add-interaction"
+msgstr "Click the + button to add interactions."
+
msgid "workspace.options.blur-options.background-blur"
msgstr "Background"
@@ -2227,6 +2245,18 @@ msgstr "Exporting…"
msgid "workspace.options.fill"
msgstr "Fill"
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.flows.add-flow-start"
+msgstr "Add flow start"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.flows.flow-start"
+msgstr "Flow start"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.flows.flow-starts"
+msgstr "Flow starts"
+
#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
msgid "workspace.options.grid.auto"
msgstr "Auto"
@@ -2319,6 +2349,150 @@ msgstr "Group fill"
msgid "workspace.options.group-stroke"
msgstr "Group stroke"
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-action"
+msgstr "Action"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-after-delay"
+msgstr "After delay"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-background"
+msgstr "Add background overlay"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-close-outside"
+msgstr "Close when clicking outside"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-close-overlay"
+msgstr "Close overlay"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-close-overlay-dest"
+msgstr "Close overlay: %s"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-delay"
+msgstr "Delay"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-destination"
+msgstr "Destination"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-mouse-enter"
+msgstr "Mouse enter"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-mouse-leave"
+msgstr "Mouse leave"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-ms"
+msgstr "ms"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-navigate-to"
+msgstr "Navigate to"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-navigate-to-dest"
+msgstr "Navigate to: %s"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-none"
+msgstr "(not set)"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-on-click"
+msgstr "On Click"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-open-overlay"
+msgstr "Open overlay"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-open-overlay-dest"
+msgstr "Open overlay: %s"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-open-url"
+msgstr "Open url"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-center"
+msgstr "Bottom center"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-left"
+msgstr "Bottom left"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-right"
+msgstr "Bottom right"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-center"
+msgstr "Center"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-manual"
+msgstr "Manual"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-center"
+msgstr "Top center"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-left"
+msgstr "Top left"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-right"
+msgstr "Top right"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-position"
+msgstr "Position"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-prev-screen"
+msgstr "Previous screen"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-self"
+msgstr "self"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-toggle-overlay"
+msgstr "Toggle overlay"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-toggle-overlay-dest"
+msgstr "Toggle overlay: %s"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-trigger"
+msgstr "Trigger"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-url"
+msgstr "URL"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-while-hovering"
+msgstr "While Hovering"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-while-pressing"
+msgstr "While Pressing"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interactions"
+msgstr "Interactions"
+
#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
msgid "workspace.options.layer-options.blend-mode.color"
msgstr "Color"
@@ -2395,14 +2569,6 @@ msgstr "Group layers"
msgid "workspace.options.layer-options.title.multiple"
msgstr "Selected layers"
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.navigate-to"
-msgstr "Navigate to"
-
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.none"
-msgstr "None"
-
#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.position"
msgstr "Position"
@@ -2426,6 +2592,9 @@ msgstr "Single corners"
msgid "workspace.options.rotation"
msgstr "Rotation"
+msgid "workspace.options.search-font"
+msgstr "Search font"
+
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.select-a-shape"
msgstr "Select a shape, artboard or group to drag a connection to other artboard."
@@ -2717,10 +2886,17 @@ msgstr "Cut"
msgid "workspace.shape.menu.delete"
msgstr "Delete"
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.delete-flow-start"
+msgstr "Delete flow start"
+
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.detach-instance"
msgstr "Detach instance"
+msgid "workspace.shape.menu.difference"
+msgstr "Difference"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.duplicate"
msgstr "Duplicate"
@@ -2729,6 +2905,12 @@ msgstr "Duplicate"
msgid "workspace.shape.menu.edit"
msgstr "Edit"
+msgid "workspace.shape.menu.exclude"
+msgstr "Exclude"
+
+msgid "workspace.shape.menu.flatten"
+msgstr "Flatten"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.flip-horizontal"
msgstr "Flip horizontal"
@@ -2737,6 +2919,10 @@ msgstr "Flip horizontal"
msgid "workspace.shape.menu.flip-vertical"
msgstr "Flip vertical"
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.flow-start"
+msgstr "Flow start"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.forward"
msgstr "Bring forward"
@@ -2757,6 +2943,9 @@ msgstr "Group"
msgid "workspace.shape.menu.hide"
msgstr "Hide"
+msgid "workspace.shape.menu.intersection"
+msgstr "Intersection"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.lock"
msgstr "Lock"
@@ -2769,6 +2958,9 @@ msgstr "Mask"
msgid "workspace.shape.menu.paste"
msgstr "Paste"
+msgid "workspace.shape.menu.path"
+msgstr "Path"
+
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.reset-overrides"
msgstr "Reset overrides"
@@ -2781,10 +2973,16 @@ msgstr "Show"
msgid "workspace.shape.menu.show-main"
msgstr "Show main component"
+msgid "workspace.shape.menu.transform-to-path"
+msgstr "Transform to path"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.ungroup"
msgstr "Ungroup"
+msgid "workspace.shape.menu.union"
+msgstr "Union"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.unlock"
msgstr "Unlock"
@@ -2989,11 +3187,4 @@ msgid "workspace.updates.update"
msgstr "Update"
msgid "workspace.viewport.click-to-close-path"
-msgstr "Click to close the path"
-
-msgid "viewer.breaking-change.message"
-msgstr "Sorry!"
-
-msgid "viewer.breaking-change.description"
-msgstr "This shareable link is no longer valid. Create a new one or ask the owner for a new one.
-
+msgstr "Click to close the path"
\ No newline at end of file
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index 265eb513c..a7a3afe5f 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
-"PO-Revision-Date: 2021-09-04 15:33+0000\n"
-"Last-Translator: Rubén \n"
+"PO-Revision-Date: 2021-09-24 13:39+0000\n"
+"Last-Translator: andy \n"
"Language-Team: Spanish "
"\n"
"Language: es\n"
@@ -9,7 +9,7 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.8.1-dev\n"
+"X-Generator: Weblate 4.9-dev\n"
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"
@@ -65,7 +65,7 @@ msgstr "Introduce tus datos aquí"
#: src/app/main/ui/auth/login.cljs
msgid "auth.login-title"
-msgstr "¡Encantados de verte de nuevo!"
+msgstr "¡Un placer verte de nuevo!"
#: src/app/main/ui/auth/login.cljs
msgid "auth.login-with-github-submit"
@@ -93,7 +93,7 @@ msgstr "Introduce la nueva contraseña"
#: src/app/main/ui/auth/register.cljs
msgid "auth.newsletter-subscription"
-msgstr "Estoy de acuerdo en suscribirme a la lista de correo de Penpot"
+msgstr "Estoy de acuerdo en suscribirme a la lista de correo de Penpot."
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.notifications.invalid-token-error"
@@ -125,6 +125,9 @@ msgstr "Contraseña"
msgid "auth.password-length-hint"
msgstr "8 caracteres como mínimo"
+msgid "auth.privacy-policy"
+msgstr "Política de privacidad"
+
#: src/app/main/ui/auth/recovery_request.cljs
msgid "auth.recovery-request-submit"
msgstr "Recuperar contraseña"
@@ -161,6 +164,9 @@ msgstr "Crear una cuenta"
msgid "auth.sidebar-tagline"
msgstr "La solución de código abierto para diseñar y prototipar"
+msgid "auth.terms-of-service"
+msgstr "Terminos de servicio"
+
#: src/app/main/ui/auth/register.cljs
msgid "auth.terms-privacy-agreement"
msgstr ""
@@ -174,7 +180,7 @@ msgstr "Hemos enviado un email de verificación a"
msgid "common.share-link.confirm-deletion-link-description"
msgstr ""
"¿Estas seguro que quieres eliminar el enlace? Si lo haces, el enlace dejara "
-"de funcionar para todos"
+"de funcionar para cualquiera"
msgid "common.share-link.get-link"
msgstr "Obtener enlace"
@@ -276,10 +282,10 @@ msgstr "Exportar librerias compartidas"
msgid "dashboard.export.options.detach.message"
msgstr ""
"Las librerias compartidas no se incluirán en la exportación y ningún "
-"recurso será incluido en la librería."
+"recurso será incluido en la librería. "
msgid "dashboard.export.options.detach.title"
-msgstr "Usar los recursos como objetos básicos."
+msgstr "Usar los recursos como objetos básicos"
msgid "dashboard.export.options.merge.message"
msgstr ""
@@ -287,7 +293,7 @@ msgstr ""
"propio fichero."
msgid "dashboard.export.options.merge.title"
-msgstr "Incluir librerias compartidas dentro de las librerias del fichero."
+msgstr "Incluir librerias compartidas dentro de las librerias del fichero"
msgid "dashboard.export.title"
msgstr "Exportar ficheros"
@@ -316,39 +322,39 @@ msgstr ""
"te puede interesar leer más sobre licencias tipográficas: [font "
"licensing](2)."
+msgid "dashboard.import"
+msgstr "Importar archivos"
+
msgid "dashboard.import.analyze-error"
-msgstr "¡Vaya! No hemos podido importar el fichero."
+msgstr "¡Vaya! No hemos podido importar el fichero"
msgid "dashboard.import.import-error"
-msgstr "Hubo un problema importando el fichero. No se ha creado el fichero."
-
-msgid "dashboard.import.import-message"
-msgstr "%s ficheros han sido importados con éxito."
+msgstr "Hubo un problema importando el fichero. No ha podido ser importado."
msgid "dashboard.import.import-message"
msgstr "%s files have been imported succesfully."
+msgid "dashboard.import.progress.process-colors"
+msgstr "Procesando colores"
+
+msgid "dashboard.import.progress.process-components"
+msgstr "Procesando componentes"
+
+msgid "dashboard.import.progress.process-media"
+msgstr "Procesando media"
+
+msgid "dashboard.import.progress.process-page"
+msgstr "Procesando página: %s"
+
+msgid "dashboard.import.progress.process-typographies"
+msgstr "Procesando tipografías"
+
msgid "dashboard.import.progress.upload-data"
msgstr "Enviando datos al servidor (%s/%s)"
msgid "dashboard.import.progress.upload-media"
msgstr "Enviando fichero: %s"
-msgid "dashboard.import.progress.process-page"
-msgstr "Procesando página: %s"
-
-msgid "dashboard.import.progress.process-colors"
-msgstr "Procesando colores"
-
-msgid "dashboard.import.progress.process-typographies"
-msgstr "Procesando tipografías"
-
-msgid "dashboard.import.progress.process-media"
-msgstr "Procesando media"
-
-msgid "dashboard.import.progress.process-components"
-msgstr "Procesando componentes"
-
#: src/app/main/ui/dashboard/team.cljs
msgid "dashboard.invite-profile"
msgstr "Invitar al equipo"
@@ -424,6 +430,9 @@ msgstr "%s integrantes"
msgid "dashboard.open-in-new-tab"
msgstr "Abrir en una pestaña nueva"
+msgid "dashboard.options"
+msgstr "Opciones"
+
#: src/app/main/ui/settings/password.cljs
msgid "dashboard.password-change"
msgstr "Cambiar contraseña"
@@ -927,7 +936,7 @@ msgid "labels.accept"
msgstr "Aceptar"
msgid "labels.add-custom-font"
-msgstr "Añadir fuentes personalizada"
+msgstr "Añadir fuente personalizada"
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
msgid "labels.admin"
@@ -1244,6 +1253,12 @@ msgstr "Mostrar sólo tus comentarios"
msgid "labels.sign-out"
msgstr "Salir"
+msgid "labels.skip"
+msgstr "Omitir"
+
+msgid "labels.start"
+msgstr "Comenzar"
+
#: src/app/main/ui/settings/profile.cljs
msgid "labels.update"
msgstr "Actualizar"
@@ -1531,6 +1546,118 @@ msgstr "Perfil guardado correctamente!"
msgid "notifications.validation-email-sent"
msgstr "Verificación de email enviada a %s. Comprueba tu correo."
+msgid "onboarding.contrib.alt"
+msgstr "Código Abierto"
+
+msgid "onboarding.contrib.desc1"
+msgstr ""
+"Penpot es Open Source, hecho por y para la comunidad. Si decides colaborar "
+"nos encantará :)"
+
+msgid "onboarding.contrib.desc2.1"
+msgstr "Puedes acceder al"
+
+msgid "onboarding.contrib.desc2.2"
+msgstr "y seguir las instrucciones de contribución :)"
+
+msgid "onboarding.contrib.link"
+msgstr "proyecto en github"
+
+msgid "onboarding.contrib.title"
+msgstr "Contribuyes en Open Source?"
+
+msgid "onboarding.slide.0.alt"
+msgstr "Crea diseños"
+
+msgid "onboarding.slide.0.desc1"
+msgstr "Crea bellos interfaces en colaboración con todas las personas del equipo."
+
+msgid "onboarding.slide.0.desc2"
+msgstr ""
+"Mantén consistencia a escala usando componentes, bibliotecas y sistemas de "
+"diseño."
+
+msgid "onboarding.slide.0.title"
+msgstr "Bibliotecas de diseño, estilos y componentes"
+
+msgid "onboarding.slide.1.alt"
+msgstr "Prototipos interactivos"
+
+msgid "onboarding.slide.1.desc1"
+msgstr "Crea interacciones completas para imitar el comportamiento del producto."
+
+msgid "onboarding.slide.1.desc2"
+msgstr ""
+"Comparte con stakeholders, presenta propuestas a tu equipo y realiza tests "
+"de usabilidad sobre tus diseños, todo en uno."
+
+msgid "onboarding.slide.1.title"
+msgstr "Lleva tus diseños a la vida con interacciones"
+
+msgid "onboarding.slide.2.alt"
+msgstr "Obtén feedback"
+
+msgid "onboarding.slide.2.desc1"
+msgstr ""
+"Todas las personas del equipo trabajando simultáneamente en archivos de "
+"diseño con edición conjunta en tiempo real y herramientas para comentar "
+"directamente sobre los diseños."
+
+msgid "onboarding.slide.2.title"
+msgstr "Obtén feedback, presenta y comparte tu trabajo"
+
+msgid "onboarding.slide.3.alt"
+msgstr "Especificaciones de código"
+
+msgid "onboarding.slide.3.desc1"
+msgstr ""
+"Sincroniza diseño y código de todos tus estilos y componentes con a "
+"snippets de código."
+
+msgid "onboarding.slide.3.desc2"
+msgstr ""
+"Obtén y entrega especificaciones de código para marcado (SVG, HTML) y "
+"estilos (CSS, Less, Stylus…)."
+
+msgid "onboarding.slide.3.title"
+msgstr "Una \"fuente de la verdad\" compartida"
+
+msgid "onboarding.team.create.button"
+msgstr "Crea un equipo"
+
+msgid "onboarding.team.create.desc1"
+msgstr ""
+"¿Trabajando con alguien más? Crea un equipo donde compartir proyectos y "
+"elementos de diseño."
+
+msgid "onboarding.team.create.input-placeholder"
+msgstr "Introduce el nombre del equipo"
+
+msgid "onboarding.team.create.title"
+msgstr "Crear equipo"
+
+msgid "onboarding.team.start.button"
+msgstr "Comenzar directamente"
+
+msgid "onboarding.team.start.desc1"
+msgstr ""
+"Entra directamente a Penpot y comienza a diseñar individualmente. Siempre "
+"tendrás la opción de crear equipos más adelante."
+
+msgid "onboarding.team.start.title"
+msgstr "Comienza a diseñar"
+
+msgid "onboarding.welcome.desc1"
+msgstr "Estamos felices de presentarte la primera Alpha release."
+
+msgid "onboarding.welcome.desc2"
+msgstr ""
+"Penpot está en fase de desarrollo y tendrá actualizaciones constantes. "
+"Esperamos que disfrutes esta version."
+
+msgid "onboarding.welcome.title"
+msgstr "Te damos la bienvenida a Penpot!"
+
#: src/app/main/ui/auth/recovery.cljs
msgid "profile.recovery.go-to-login"
msgstr "Ir al login"
@@ -1591,6 +1718,14 @@ msgstr "Configuración - %s - Penpot"
msgid "title.viewer"
msgstr "%s - Modo de visualización - Penpot"
+msgid "viewer.breaking-change.description"
+msgstr ""
+"Este link compartido ya no funciona. Crea uno nuevo o pídelo a la persona "
+"que lo creó."
+
+msgid "viewer.breaking-change.message"
+msgstr "¡Lo sentimos!"
+
#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
msgid "viewer.empty-state"
msgstr "No se ha encontrado ningún tablero."
@@ -1611,7 +1746,7 @@ msgid "viewer.header.fullscreen"
msgstr "Pantalla completa"
msgid "viewer.header.handsoff-section"
-msgstr "Handsoff"
+msgstr "Handoff"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.interactions"
@@ -2023,6 +2158,10 @@ msgstr "Mis bibliotecas"
msgid "workspace.library.store"
msgstr "Predefinidas"
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.add-interaction"
+msgstr "Pulsa el botón + para añadir interacciones."
+
msgid "workspace.options.blur-options.background-blur"
msgstr "Fondo"
@@ -2113,6 +2252,18 @@ msgstr "Exportando"
msgid "workspace.options.fill"
msgstr "Relleno"
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.flows.add-flow-start"
+msgstr "Añadir inicio de flujo"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.flows.flow-start"
+msgstr "Inicio de flujo"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.flows.flow-starts"
+msgstr "Inicios de flujo"
+
#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
msgid "workspace.options.grid.auto"
msgstr "Automático"
@@ -2205,6 +2356,150 @@ msgstr "Relleno de grupo"
msgid "workspace.options.group-stroke"
msgstr "Borde de grupo"
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-action"
+msgstr "Acción"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-after-delay"
+msgstr "Tiempo"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-background"
+msgstr "Añadir sombreado de fondo"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-close-outside"
+msgstr "Cerrar al pulsar fuera"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-close-overlay"
+msgstr "Cerrar superposición"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-close-overlay-dest"
+msgstr "Cerrar superposición: %s"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-delay"
+msgstr "Tiempo"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-destination"
+msgstr "Destino"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-mouse-enter"
+msgstr "Pasar encima"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-mouse-leave"
+msgstr "Retirar encima"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-ms"
+msgstr "ms"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-navigate-to"
+msgstr "Navegar a"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-navigate-to-dest"
+msgstr "Navegar a: %s"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-none"
+msgstr "(sin definir)"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-on-click"
+msgstr "En click"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-open-overlay"
+msgstr "Superposición"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-open-overlay-dest"
+msgstr "Superposición: %s"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-open-url"
+msgstr "Abrir url"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-center"
+msgstr "Abajo centro"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-left"
+msgstr "Abajo izquierda"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-right"
+msgstr "Abajo derecha"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-center"
+msgstr "Centro"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-manual"
+msgstr "Manual"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-center"
+msgstr "Arriba centro"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-left"
+msgstr "Arriba izquierda"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-right"
+msgstr "Arriba derecha"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-position"
+msgstr "Posición"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-prev-screen"
+msgstr "Pantalla anterior"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-self"
+msgstr "mismo"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-toggle-overlay"
+msgstr "Alternar superpos."
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-toggle-overlay-dest"
+msgstr "Alternar superpos.: %s"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-trigger"
+msgstr "Disparador"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-url"
+msgstr "URL"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-while-hovering"
+msgstr "Mientras pasa encima"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-while-pressing"
+msgstr "Mientras pulsa"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interactions"
+msgstr "Interacciones"
+
#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
msgid "workspace.options.layer-options.blend-mode.color"
msgstr "Color"
@@ -2281,14 +2576,6 @@ msgstr "Capas de grupo"
msgid "workspace.options.layer-options.title.multiple"
msgstr "Capas seleccionadas"
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.navigate-to"
-msgstr "Navegar a"
-
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.none"
-msgstr "Ninguno"
-
#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.position"
msgstr "Posición"
@@ -2312,6 +2599,9 @@ msgstr "Esquinas individuales"
msgid "workspace.options.rotation"
msgstr "Rotación"
+msgid "workspace.options.search-font"
+msgstr "Buscar fuente"
+
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.select-a-shape"
msgstr ""
@@ -2605,10 +2895,17 @@ msgstr "Cortar"
msgid "workspace.shape.menu.delete"
msgstr "Eliminar"
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.delete-flow-start"
+msgstr "Eliminar inicio de flujo"
+
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.detach-instance"
msgstr "Desacoplar instancia"
+msgid "workspace.shape.menu.difference"
+msgstr "Diferencia"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.duplicate"
msgstr "Duplicar"
@@ -2617,6 +2914,12 @@ msgstr "Duplicar"
msgid "workspace.shape.menu.edit"
msgstr "Editar"
+msgid "workspace.shape.menu.exclude"
+msgstr "Exclusión"
+
+msgid "workspace.shape.menu.flatten"
+msgstr "Aplanar"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.flip-horizontal"
msgstr "Voltear horizontal"
@@ -2625,6 +2928,10 @@ msgstr "Voltear horizontal"
msgid "workspace.shape.menu.flip-vertical"
msgstr "Voltear vertical"
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.flow-start"
+msgstr "Inicio de flujo"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.forward"
msgstr "Mover hacia delante"
@@ -2645,6 +2952,9 @@ msgstr "Grupo"
msgid "workspace.shape.menu.hide"
msgstr "Ocultar"
+msgid "workspace.shape.menu.intersection"
+msgstr "Intersección"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.lock"
msgstr "Bloquear"
@@ -2657,6 +2967,9 @@ msgstr "Máscara"
msgid "workspace.shape.menu.paste"
msgstr "Pegar"
+msgid "workspace.shape.menu.path"
+msgstr "Path"
+
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.reset-overrides"
msgstr "Deshacer modificaciones"
@@ -2669,10 +2982,16 @@ msgstr "Mostrar"
msgid "workspace.shape.menu.show-main"
msgstr "Ver componente principal"
+msgid "workspace.shape.menu.transform-to-path"
+msgstr "Convertir en vector"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.ungroup"
msgstr "Desagrupar"
+msgid "workspace.shape.menu.union"
+msgstr "Unión"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.unlock"
msgstr "Desbloquear"
@@ -2877,10 +3196,4 @@ msgid "workspace.updates.update"
msgstr "Actualizar"
msgid "workspace.viewport.click-to-close-path"
-msgstr "Pulsar para cerrar la ruta"
-
-msgid "viewer.breaking-change.message"
-msgstr "¡Lo sentimos!"
-
-msgid "viewer.breaking-change.description"
-msgstr "Este link compartido ya no funciona. Crea uno nuevo o pídelo a la persona que lo creó."
+msgstr "Pulsar para cerrar la ruta"
\ No newline at end of file
diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po
index 3b98ea239..f80abca9d 100644
--- a/frontend/translations/fr.po
+++ b/frontend/translations/fr.po
@@ -119,6 +119,9 @@ msgstr "Mot de passe"
msgid "auth.password-length-hint"
msgstr "Au moins 8 caractères"
+msgid "auth.privacy-policy"
+msgstr "Politique de confidentialité"
+
#: src/app/main/ui/auth/recovery_request.cljs
msgid "auth.recovery-request-submit"
msgstr "Récupérer le mot de passe"
@@ -155,6 +158,9 @@ msgstr "Créer un compte"
msgid "auth.sidebar-tagline"
msgstr "La solution Open Source pour la conception et le prototypage."
+msgid "auth.terms-of-service"
+msgstr "Conditions générales d'utilisation"
+
#: src/app/main/ui/auth/register.cljs
msgid "auth.terms-privacy-agreement"
msgstr ""
@@ -2157,14 +2163,6 @@ msgstr "Grouper les calques"
msgid "workspace.options.layer-options.title.multiple"
msgstr "Calques sélectionnés"
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.navigate-to"
-msgstr "Naviguer vers"
-
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.none"
-msgstr "Aucun"
-
#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.position"
msgstr "Position"
diff --git a/frontend/translations/he.po b/frontend/translations/he.po
new file mode 100644
index 000000000..f4c03a8da
--- /dev/null
+++ b/frontend/translations/he.po
@@ -0,0 +1,2959 @@
+msgid ""
+msgstr ""
+"PO-Revision-Date: 2021-10-18 07:35+0000\n"
+"Last-Translator: Yaron Shahrabani \n"
+"Language-Team: Hebrew "
+"\n"
+"Language: he\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && "
+"n % 10 == 0) ? 2 : 3));\n"
+"X-Generator: Weblate 4.9-dev\n"
+
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.already-have-account"
+msgstr "כבר יש לך חשבון?"
+
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.check-your-email"
+msgstr "נא לחפש בדוא״ל הנכנס שלך וללחוץ על הקישור כדי לאמת ולהתחיל להשתמש ב־Penpot."
+
+#: src/app/main/ui/auth/recovery.cljs
+msgid "auth.confirm-password"
+msgstr "אישור הסיסמה"
+
+#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
+msgid "auth.create-demo-account"
+msgstr "יצירת חשבון הדגמה"
+
+#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
+msgid "auth.create-demo-profile"
+msgstr "מעניין אותך רק להתנסות?"
+
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.demo-warning"
+msgstr "זה שירות ניסיוני, לא להשתמש בו לעבודה אמתית, המיזמים יימחקו מדי פעם בפעם."
+
+#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs
+msgid "auth.email"
+msgstr "דוא״ל"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "auth.forgot-password"
+msgstr "שכחת סיסמה?"
+
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.fullname"
+msgstr "שם מלא"
+
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.login-here"
+msgstr "כניסה מכאן"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "auth.login-submit"
+msgstr "כניסה"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "auth.login-subtitle"
+msgstr "נא למלא את הפרטים שלך להלן"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "auth.login-title"
+msgstr "שמחים לראות אותך שוב!"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "auth.login-with-github-submit"
+msgstr "כניסה עם GitHub"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "auth.login-with-gitlab-submit"
+msgstr "כניסה עם GitLab"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "auth.login-with-google-submit"
+msgstr "כניסה עם Google"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "auth.login-with-ldap-submit"
+msgstr "כניסה עם LDAP"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "auth.login-with-oidc-submit"
+msgstr "כניסה עם OpenID (SSO)"
+
+#: src/app/main/ui/auth/recovery.cljs
+msgid "auth.new-password"
+msgstr "נא להקליד סיסמה חדשה"
+
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.newsletter-subscription"
+msgstr "ההרשמה לרשימת הדיוור של Penpot מקובלת עלי."
+
+#: src/app/main/ui/auth/recovery.cljs
+msgid "auth.notifications.invalid-token-error"
+msgstr "אסימון השחזור שגוי."
+
+#: src/app/main/ui/auth/recovery.cljs
+msgid "auth.notifications.password-changed-succesfully"
+msgstr "הסיסמה הוחלפה בהצלחה"
+
+#: src/app/main/ui/auth/recovery_request.cljs
+msgid "auth.notifications.profile-not-verified"
+msgstr "הפרופיל לא עבר אימות, נא לאמת את הפרופיל לפני המשך התהליך."
+
+#: src/app/main/ui/auth/recovery_request.cljs
+msgid "auth.notifications.recovery-token-sent"
+msgstr "קישור לשחזור סיסמה נשלח לתיבת הדוא״ל הנכנס שלך."
+
+#: src/app/main/ui/auth/verify_token.cljs
+msgid "auth.notifications.team-invitation-accepted"
+msgstr "הצטרפת לצוות בהצלחה"
+
+#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
+msgid "auth.password"
+msgstr "סיסמה"
+
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.password-length-hint"
+msgstr "8 תווים לפחות"
+
+#: src/app/main/ui/auth/recovery_request.cljs
+msgid "auth.recovery-request-submit"
+msgstr "שחזור סיסמה"
+
+#: src/app/main/ui/auth/recovery_request.cljs
+msgid "auth.recovery-request-subtitle"
+msgstr "נשלח לך הודעה בדוא״ל עם ההנחיות"
+
+#: src/app/main/ui/auth/recovery_request.cljs
+msgid "auth.recovery-request-title"
+msgstr "שכחת סיסמה?"
+
+#: src/app/main/ui/auth/recovery.cljs
+msgid "auth.recovery-submit"
+msgstr "החלפת הסיסמה שלך"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "auth.register"
+msgstr "אין לך חשבון עדיין?"
+
+#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs
+msgid "auth.register-submit"
+msgstr "יצירת חשבון"
+
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.register-subtitle"
+msgstr "זה חינם, בקוד פתוח"
+
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.register-title"
+msgstr "יצירת חשבון"
+
+#: src/app/main/ui/auth.cljs
+msgid "auth.sidebar-tagline"
+msgstr "הפתרון בקוד פתוח לעיצוב ויצירת אבטיפוס."
+
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.terms-privacy-agreement"
+msgstr "יצירת חשבון חדש מהווה את הסכמתך לתנאי השירות ולמדיניות הפרטיות."
+
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.verification-email-sent"
+msgstr "שלחנו הודעת דוא״ל לאימות אל"
+
+msgid "common.share-link.confirm-deletion-link-description"
+msgstr "להסיר את הקישור הזה? ביצוע פעולה זו תמנע מכולם לגשת אליו"
+
+msgid "common.share-link.get-link"
+msgstr "קבלת קישור"
+
+msgid "common.share-link.link-copied-success"
+msgstr "הקישור הועתק בהצלחה"
+
+msgid "common.share-link.link-deleted-success"
+msgstr "הקישור נמחק בהצלחה"
+
+msgid "common.share-link.permissions-can-access"
+msgstr "יש גישה"
+
+msgid "common.share-link.permissions-can-view"
+msgstr "אפשר לצפות"
+
+msgid "common.share-link.permissions-hint"
+msgstr "כל מי שיש לו את הקישור יכול לגשת"
+
+msgid "common.share-link.remove-link"
+msgstr "הסרת קישור"
+
+msgid "common.share-link.title"
+msgstr "שיתוף אבות טיפוס"
+
+msgid "common.share-link.view-all-pages"
+msgstr "כל העמודים"
+
+msgid "common.share-link.view-current-page"
+msgstr "רק העמוד הזה"
+
+msgid "common.share-link.view-selected-pages"
+msgstr "עמודים נבחרים"
+
+#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.add-shared"
+msgstr "הוספת ספריה משותפת"
+
+#: src/app/main/ui/settings/profile.cljs
+msgid "dashboard.change-email"
+msgstr "החלפת דוא״ל"
+
+#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs
+msgid "dashboard.copy-suffix"
+msgstr "(עותק)"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "dashboard.create-new-team"
+msgstr "+ יצירת צוות חדש"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "dashboard.default-team-name"
+msgstr "ה־Penpot שלך"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "dashboard.delete-team"
+msgstr "מחיקת צוות"
+
+msgid "dashboard.draft-title"
+msgstr "טיוטה"
+
+#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.duplicate"
+msgstr "שכפול"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.duplicate-multi"
+msgstr "שכפול %s קבצים"
+
+#: src/app/main/ui/dashboard/grid.cljs
+msgid "dashboard.empty-files"
+msgstr "עדיין אין לך כאן קבצים"
+
+msgid "dashboard.export-frames"
+msgstr "ייצוא לוחות אומנות ל־PDF…"
+
+msgid "dashboard.export-multi"
+msgstr "ייצוא קובצי %s"
+
+msgid "dashboard.export-single"
+msgstr "ייצוא קובץ"
+
+msgid "dashboard.export.detail"
+msgstr "* עשוי לכלול רכיבים, גרפיקה, צבעים ו/או טיפוגרפיות."
+
+msgid "dashboard.export.explain"
+msgstr ""
+"אחד או יותר מהקבצים שברצונך לייצא משתמשים בספריות משותפות. מה לעשות עם "
+"המשאבים שלהן*?"
+
+msgid "dashboard.export.options.all.message"
+msgstr "קבצים עם ספריות משותפות יצורפו לייצוא, תוך שימור הקישוריות שלהם."
+
+msgid "dashboard.export.options.all.title"
+msgstr "ייצוא ספריות משותפות"
+
+msgid "dashboard.export.options.detach.message"
+msgstr "ספריות משותפות לא יצורפו לייצוא ואף משאב לא יתווסף לספריה. "
+
+msgid "dashboard.export.options.detach.title"
+msgstr "להתייחס למשאבים בספריות משותפות כעצמים בסיסיים"
+
+msgid "dashboard.export.options.merge.message"
+msgstr "הקובץ שלך ייוצא כשכל המשאבים החיצוניים ממוזגים לספריית הקבצים."
+
+msgid "dashboard.export.options.merge.title"
+msgstr "לכלול משאבי ספריה משותפת בספריות הקבצים"
+
+msgid "dashboard.export.title"
+msgstr "ייצוא קבצים"
+
+msgid "dashboard.fonts.deleted-placeholder"
+msgstr "הגופן נמחק"
+
+msgid "dashboard.fonts.empty-placeholder"
+msgstr "עדיין לא מותקנים אצלך גופנים משלך."
+
+#, markdown
+msgid "dashboard.fonts.hero-text1"
+msgstr ""
+"כל גופן דפדפן שיועלה כאן יתווסף לרשימת משפחת הגופנים שזמין במאפייני הטקסט "
+"של הקבצים של הצוות הזה. גופנים מאותו שם של משפחת גופנים יקובצו תחת **משפחת "
+"גופנים יחידה**. ניתן להעלות גופנים מהסוגים הבאים: **TTF, OTF ו־WOFF** (אחד "
+"הסוגים יספיק)."
+
+#, markdown
+msgid "dashboard.fonts.hero-text2"
+msgstr ""
+"עליך להעלות גופנים בבעלותך או שיש לך רישיון להשתמש בהם ב־Penpot. ניתן למצוא "
+"על כך מידע נוסף בסעיף זכויות התוכן של [תנאי השירות של "
+"Penpot](https://penpot.app/terms.html). אפשר גם לקרוא גם על [רישוי "
+"גופנים](https://www.typography.com/faq)."
+
+msgid "dashboard.import"
+msgstr "ייבוא קבצים"
+
+msgid "dashboard.import.analyze-error"
+msgstr "אופס! לא הצלחנו לייבא את הקובץ הזה"
+
+msgid "dashboard.import.import-error"
+msgstr "אירעה תקלה בייבוא הקובץ. הוא לא ייובא."
+
+msgid "dashboard.import.import-message"
+msgstr "%s קבצים עברו ייבוא כרגיל."
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "dashboard.invite-profile"
+msgstr "הזמנה לצוות"
+
+#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs
+msgid "dashboard.leave-team"
+msgstr "עזיבת הצוות"
+
+#: src/app/main/ui/dashboard/libraries.cljs
+msgid "dashboard.libraries-title"
+msgstr "ספריות משותפות"
+
+#: src/app/main/ui/dashboard/grid.cljs
+msgid "dashboard.loading-files"
+msgstr "הקבצים שלך נטענים…"
+
+msgid "dashboard.loading-fonts"
+msgstr "הגופנים שלך נטענים…"
+
+#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.move-to"
+msgstr "העברה אל"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.move-to-multi"
+msgstr "העברה של %s קבצים אל"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.move-to-other-team"
+msgstr "העברה לצוות אחר"
+
+#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs
+msgid "dashboard.new-file"
+msgstr "+ קובץ חדש"
+
+#: src/app/main/data/dashboard.cljs
+msgid "dashboard.new-file-prefix"
+msgstr "קובץ חדש"
+
+#: src/app/main/ui/dashboard/projects.cljs
+msgid "dashboard.new-project"
+msgstr "+ מיזם חדש"
+
+#: src/app/main/data/dashboard.cljs
+msgid "dashboard.new-project-prefix"
+msgstr "מיזם חדש"
+
+#: src/app/main/ui/dashboard/search.cljs
+msgid "dashboard.no-matches-for"
+msgstr "לא נמצאו תוצאות לחיפוש אחר „%s”"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "dashboard.no-projects-placeholder"
+msgstr "מיזמים נעוצים יופיעו כאן"
+
+#: src/app/main/ui/auth/verify_token.cljs
+msgid "dashboard.notifications.email-changed-successfully"
+msgstr "כתובת הדוא״ל שלך עודכנה בהצלחה"
+
+#: src/app/main/ui/auth/verify_token.cljs
+msgid "dashboard.notifications.email-verified-successfully"
+msgstr "כתובת הדוא״ל שלך אומתה בהצלחה"
+
+#: src/app/main/ui/settings/password.cljs
+msgid "dashboard.notifications.password-saved"
+msgstr "הסיסמה נשמרה בהצלחה!"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "dashboard.num-of-members"
+msgstr "%s חברים"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.open-in-new-tab"
+msgstr "פתיחת קובץ בלשונית חדשה"
+
+msgid "dashboard.options"
+msgstr "אפשרויות"
+
+#: src/app/main/ui/settings/password.cljs
+msgid "dashboard.password-change"
+msgstr "החלפת סיסמה"
+
+#: src/app/main/ui/dashboard/project_menu.cljs
+msgid "dashboard.pin-unpin"
+msgstr "נעיצה/שחרור"
+
+#: src/app/main/ui/dashboard/projects.cljs
+msgid "dashboard.projects-title"
+msgstr "מיזמים"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "dashboard.promote-to-owner"
+msgstr "קידום לבעלות"
+
+#: src/app/main/ui/settings/profile.cljs
+msgid "dashboard.remove-account"
+msgstr "להסיר את החשבון שלך?"
+
+#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.remove-shared"
+msgstr "הסרה כספריה משותפת"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "dashboard.search-placeholder"
+msgstr "חיפוש…"
+
+#: src/app/main/ui/dashboard/search.cljs
+msgid "dashboard.searching-for"
+msgstr "מתבצע חיפוש אחר „%s”…"
+
+#: src/app/main/ui/settings/options.cljs
+msgid "dashboard.select-ui-language"
+msgstr "בחירת שפת מנשק משתמש"
+
+#: src/app/main/ui/settings/options.cljs
+msgid "dashboard.select-ui-theme"
+msgstr "בחירת ערכת עיצוב"
+
+#: src/app/main/ui/dashboard/grid.cljs
+msgid "dashboard.show-all-files"
+msgstr "הצגת כל הקבצים"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.success-delete-file"
+msgstr "הקובץ שלך נמחק בהצלחה"
+
+#: src/app/main/ui/dashboard/project_menu.cljs
+msgid "dashboard.success-delete-project"
+msgstr "המיזם שלך נמחק בהצלחה"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.success-duplicate-file"
+msgstr "הקובץ שלך שוכפל בהצלחה"
+
+#: src/app/main/ui/dashboard/project_menu.cljs
+msgid "dashboard.success-duplicate-project"
+msgstr "המיזם שלך שוכפל בהצלחה"
+
+#: src/app/main/ui/dashboard/grid.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.success-move-file"
+msgstr "הקובץ שלך הועבר בהצלחה"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.success-move-files"
+msgstr "הקבצים שלך הועברו בהצלחה"
+
+#: src/app/main/ui/dashboard/project_menu.cljs
+msgid "dashboard.success-move-project"
+msgstr "המיזם שלך הועבר בהצלחה"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "dashboard.switch-team"
+msgstr "לעבור צוות"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "dashboard.team-info"
+msgstr "פרטי הצוות"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "dashboard.team-members"
+msgstr "חברי הצוות"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "dashboard.team-projects"
+msgstr "מיזמי הצוות"
+
+#: src/app/main/ui/settings/options.cljs
+msgid "dashboard.theme-change"
+msgstr "ערכת עיצוב מנשק משתמש"
+
+#: src/app/main/ui/dashboard/search.cljs
+msgid "dashboard.title-search"
+msgstr "תוצאות חיפוש"
+
+#: src/app/main/ui/dashboard/search.cljs
+msgid "dashboard.type-something"
+msgstr "נא להקליד כדי לחפש"
+
+#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs
+msgid "dashboard.update-settings"
+msgstr "עדכון הגדרות"
+
+#: src/app/main/ui/settings.cljs
+msgid "dashboard.your-account-title"
+msgstr "החשבון שלך"
+
+#: src/app/main/ui/settings/profile.cljs
+msgid "dashboard.your-email"
+msgstr "דוא״ל"
+
+#: src/app/main/ui/settings/profile.cljs
+msgid "dashboard.your-name"
+msgstr "שמך"
+
+#: src/app/main/ui/dashboard/search.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/libraries.cljs, src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "dashboard.your-penpot"
+msgstr "ה־Penpot שלך"
+
+#: src/app/main/ui/confirm.cljs
+msgid "ds.confirm-cancel"
+msgstr "ביטול"
+
+#: src/app/main/ui/confirm.cljs
+msgid "ds.confirm-ok"
+msgstr "אישור"
+
+#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs
+msgid "ds.confirm-title"
+msgstr "להמשיך?"
+
+#: src/app/main/ui/dashboard/grid.cljs
+msgid "ds.updated-at"
+msgstr "עדכון: %s"
+
+#: src/app/main/data/workspace.cljs
+msgid "errors.clipboard-not-implemented"
+msgstr "הדפדפן שלך לא יכול לבצע את הפעולה הזאת"
+
+#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs
+msgid "errors.email-already-exists"
+msgstr "כתובת הדוא״ל כבר בשימוש"
+
+#: src/app/main/ui/auth/verify_token.cljs
+msgid "errors.email-already-validated"
+msgstr "כתובת הדוא״ל כבר אומתה."
+
+#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs
+msgid "errors.email-has-permanent-bounces"
+msgstr "לכתובת הדוא״ל „%s” יש יותר מדי דוחות החזרה קבועים."
+
+#: src/app/main/ui/settings/change_email.cljs
+msgid "errors.email-invalid-confirmation"
+msgstr "כתובת הדוא״ל לאימות חייבת להיות תואמת"
+
+#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs
+msgid "errors.generic"
+msgstr "קרה משהו לא טוב."
+
+#: src/app/main/ui/auth/login.cljs
+msgid "errors.google-auth-not-enabled"
+msgstr "אימות מול Google הושבת במנגנון"
+
+#: src/app/main/ui/components/color_input.cljs
+msgid "errors.invalid-color"
+msgstr "צבע שגוי"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "errors.ldap-disabled"
+msgstr "אימות מול LDAP הושבת."
+
+msgid "errors.media-format-unsupported"
+msgstr "סוג התמונה אינו נתמך (חייב להיות svg, jpg או png)."
+
+#: src/app/main/data/workspace/persistence.cljs
+msgid "errors.media-too-large"
+msgstr "התמונה גדולה מכדי להוסיף אותה (חייבת להיות פחות מ־5 מ״ב)."
+
+#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs
+msgid "errors.media-type-mismatch"
+msgstr "נראה כי תוכן התמונה לא תואם לסיומת הקובץ."
+
+#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs
+msgid "errors.media-type-not-allowed"
+msgstr "נראה כי זאת תמונה שגויה."
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "errors.member-is-muted"
+msgstr "הודעות הדוא״ל לפרופיל שהזמנת מושתקות (דיווחים על דואר זבל או הרבה החזרות)."
+
+msgid "errors.network"
+msgstr "לא ניתן ליצור קשר עם שרת המנגנון."
+
+#: src/app/main/ui/settings/password.cljs
+msgid "errors.password-invalid-confirmation"
+msgstr "סיסמת האימות חייבת להיות תואמת"
+
+#: src/app/main/ui/settings/password.cljs
+msgid "errors.password-too-short"
+msgstr "הסיסמה חייבת להיות באורך 8 תווים לפחות"
+
+#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs
+msgid "errors.profile-is-muted"
+msgstr "הודעות הדוא״ל לפרופיל שלך מושתקות (דיווחי דואר זבל או הרבה החזרות)."
+
+#: src/app/main/ui/auth/register.cljs
+msgid "errors.registration-disabled"
+msgstr "ההרשמה מושבתת כרגע."
+
+msgid "errors.terms-privacy-agreement-invalid"
+msgstr "עליך לקבל את תנאי השירות ואת מדיניות הפרטיות."
+
+#: src/app/main/ui/auth/verify_token.cljs
+msgid "errors.token-expired"
+msgstr "תוקף האסימון פג"
+
+#: src/app/main/data/media.cljs, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs
+msgid "errors.unexpected-error"
+msgstr "אירעה שגיאה בלתי צפויה."
+
+#: src/app/main/ui/auth/verify_token.cljs
+msgid "errors.unexpected-token"
+msgstr "אסימון בלתי ידוע"
+
+#: src/app/main/ui/auth/login.cljs
+msgid "errors.wrong-credentials"
+msgstr "כנראה ששם המשתמש והסיסמה שגויים."
+
+#: src/app/main/ui/settings/password.cljs
+msgid "errors.wrong-old-password"
+msgstr "הסיסמה הישנה שגויה"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "feedback.chat-start"
+msgstr "הצטרפות לצ׳אט"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "feedback.chat-subtitle"
+msgstr "מעניין אותך לדבר? מזמינים אותו ל־Gitter"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "feedback.description"
+msgstr "תיאור"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "feedback.discussions-go-to"
+msgstr "מעבר לדיונים"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "feedback.discussions-subtitle1"
+msgstr "מזמינים אותך להצטרף לפורום התקשורת של Penpot."
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "feedback.discussions-subtitle2"
+msgstr ""
+"ניתן לשאול ולענות על שאלות, לנהל דיונים פתוחים ולעקוב אחר החלטות שמשפיעות "
+"על המיזם."
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "feedback.discussions-title"
+msgstr "דיונים צוותיים"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "feedback.subject"
+msgstr "נושא"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "feedback.subtitle"
+msgstr ""
+"נא לתאר את הסיבה שלך לשליחת ההודעה תוך פירוט האם זאת תקלה, רעיון או בספק. "
+"אחד מחברי הצוות שלנו יגיב לך במהירות האפשרית."
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "feedback.title"
+msgstr "דוא״ל"
+
+#: src/app/main/ui/settings/password.cljs
+msgid "generic.error"
+msgstr "אירעה שגיאה"
+
+#: src/app/main/ui/handoff/attributes/blur.cljs
+msgid "handoff.attributes.blur"
+msgstr "טשטוש"
+
+#: src/app/main/ui/handoff/attributes/blur.cljs
+msgid "handoff.attributes.blur.value"
+msgstr "ערך"
+
+#: src/app/main/ui/handoff/attributes/common.cljs
+msgid "handoff.attributes.color.hex"
+msgstr "הקסדצימלי"
+
+#: src/app/main/ui/handoff/attributes/common.cljs
+msgid "handoff.attributes.color.hsla"
+msgstr "HSLA"
+
+#: src/app/main/ui/handoff/attributes/common.cljs
+msgid "handoff.attributes.color.rgba"
+msgstr "RGBA"
+
+#: src/app/main/ui/handoff/attributes/fill.cljs
+msgid "handoff.attributes.fill"
+msgstr "מילוי"
+
+#: src/app/main/ui/handoff/attributes/image.cljs
+msgid "handoff.attributes.image.download"
+msgstr "הורדת תמונת מקור"
+
+#: src/app/main/ui/handoff/attributes/image.cljs
+msgid "handoff.attributes.image.height"
+msgstr "גובה"
+
+#: src/app/main/ui/handoff/attributes/image.cljs
+msgid "handoff.attributes.image.width"
+msgstr "רוחב"
+
+#: src/app/main/ui/handoff/attributes/layout.cljs
+msgid "handoff.attributes.layout"
+msgstr "פריסה"
+
+#: src/app/main/ui/handoff/attributes/layout.cljs
+msgid "handoff.attributes.layout.height"
+msgstr "גובה"
+
+#: src/app/main/ui/handoff/attributes/layout.cljs
+msgid "handoff.attributes.layout.left"
+msgstr "שמאל"
+
+#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs
+msgid "handoff.attributes.layout.radius"
+msgstr "רדיוס"
+
+#: src/app/main/ui/handoff/attributes/layout.cljs
+msgid "handoff.attributes.layout.rotation"
+msgstr "סיבוב"
+
+#: src/app/main/ui/handoff/attributes/layout.cljs
+msgid "handoff.attributes.layout.top"
+msgstr "עליון"
+
+#: src/app/main/ui/handoff/attributes/layout.cljs
+msgid "handoff.attributes.layout.width"
+msgstr "רוחב"
+
+#: src/app/main/ui/handoff/attributes/shadow.cljs
+msgid "handoff.attributes.shadow"
+msgstr "צל"
+
+#: src/app/main/ui/handoff/attributes/shadow.cljs
+msgid "handoff.attributes.shadow.shorthand.blur"
+msgstr "ט"
+
+#: src/app/main/ui/handoff/attributes/shadow.cljs
+msgid "handoff.attributes.shadow.shorthand.offset-x"
+msgstr "X"
+
+#: src/app/main/ui/handoff/attributes/shadow.cljs
+msgid "handoff.attributes.shadow.shorthand.offset-y"
+msgstr "Y"
+
+#: src/app/main/ui/handoff/attributes/shadow.cljs
+msgid "handoff.attributes.shadow.shorthand.spread"
+msgstr "פ"
+
+#: src/app/main/ui/handoff/attributes/stroke.cljs
+msgid "handoff.attributes.stroke"
+msgstr "מתאר"
+
+#, permanent
+msgid "handoff.attributes.stroke.alignment.center"
+msgstr "מרכז"
+
+#, permanent
+msgid "handoff.attributes.stroke.alignment.inner"
+msgstr "בפנים"
+
+#, permanent
+msgid "handoff.attributes.stroke.alignment.outer"
+msgstr "בחוץ"
+
+msgid "handoff.attributes.stroke.style.dotted"
+msgstr "מנוקד"
+
+msgid "handoff.attributes.stroke.style.mixed"
+msgstr "מעורב"
+
+msgid "handoff.attributes.stroke.style.none"
+msgstr "ללא"
+
+msgid "handoff.attributes.stroke.style.solid"
+msgstr "אחיד"
+
+#: src/app/main/ui/handoff/attributes/stroke.cljs
+msgid "handoff.attributes.stroke.width"
+msgstr "רוחב"
+
+#: src/app/main/ui/handoff/attributes/text.cljs
+msgid "handoff.attributes.typography"
+msgstr "טיפוגרפיה"
+
+#: src/app/main/ui/handoff/attributes/text.cljs
+msgid "handoff.attributes.typography.font-family"
+msgstr "משפחת גופנים"
+
+#: src/app/main/ui/handoff/attributes/text.cljs
+msgid "handoff.attributes.typography.font-size"
+msgstr "גודל גופן"
+
+#: src/app/main/ui/handoff/attributes/text.cljs
+msgid "handoff.attributes.typography.font-style"
+msgstr "סגנון גופן"
+
+#: src/app/main/ui/handoff/attributes/text.cljs
+msgid "handoff.attributes.typography.letter-spacing"
+msgstr "ריווח תווים"
+
+#: src/app/main/ui/handoff/attributes/text.cljs
+msgid "handoff.attributes.typography.line-height"
+msgstr "גובה שורה"
+
+#: src/app/main/ui/handoff/attributes/text.cljs
+msgid "handoff.attributes.typography.text-decoration"
+msgstr "עיטור טקסט"
+
+msgid "handoff.attributes.typography.text-decoration.none"
+msgstr "ללא"
+
+msgid "handoff.attributes.typography.text-decoration.strikethrough"
+msgstr "קו חוצה"
+
+msgid "handoff.attributes.typography.text-decoration.underline"
+msgstr "קו תחתי"
+
+#: src/app/main/ui/handoff/attributes/text.cljs
+msgid "handoff.attributes.typography.text-transform"
+msgstr "התמרת טקסט"
+
+msgid "handoff.attributes.typography.text-transform.lowercase"
+msgstr "אותיות קטנות"
+
+msgid "handoff.attributes.typography.text-transform.none"
+msgstr "ללא"
+
+msgid "handoff.attributes.typography.text-transform.titlecase"
+msgstr "ראשונות גדולות"
+
+msgid "handoff.attributes.typography.text-transform.uppercase"
+msgstr "אותיות גדולות"
+
+#: src/app/main/ui/handoff/right_sidebar.cljs
+msgid "handoff.tabs.code"
+msgstr "קוד"
+
+msgid "handoff.tabs.code.selected.circle"
+msgstr "עיגול"
+
+msgid "handoff.tabs.code.selected.curve"
+msgstr "עיקול"
+
+msgid "handoff.tabs.code.selected.frame"
+msgstr "לוח אומנות"
+
+msgid "handoff.tabs.code.selected.group"
+msgstr "קבוצה"
+
+msgid "handoff.tabs.code.selected.image"
+msgstr "תמונה"
+
+#: src/app/main/ui/handoff/right_sidebar.cljs
+msgid "handoff.tabs.code.selected.multiple"
+msgstr "%s נבחרו"
+
+msgid "handoff.tabs.code.selected.path"
+msgstr "נתיב"
+
+msgid "handoff.tabs.code.selected.rect"
+msgstr "ריבוע"
+
+msgid "handoff.tabs.code.selected.svg-raw"
+msgstr "SVG"
+
+msgid "handoff.tabs.code.selected.text"
+msgstr "טקסט"
+
+#: src/app/main/ui/handoff/right_sidebar.cljs
+msgid "handoff.tabs.info"
+msgstr "מידע"
+
+msgid "history.alert-message"
+msgstr "הגרסה שמוצגת היא %s"
+
+msgid "labels.accept"
+msgstr "מקובל"
+
+msgid "labels.add-custom-font"
+msgstr "הוספת גופן משלך"
+
+#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
+msgid "labels.admin"
+msgstr "ניהול"
+
+#: src/app/main/ui/workspace/comments.cljs
+msgid "labels.all"
+msgstr "הכול"
+
+#: src/app/main/ui/static.cljs
+msgid "labels.bad-gateway.desc-message"
+msgstr ""
+"נראה כאילו עליך להמתין מעט ולנסות שוב, אנו מבצעים עבודות תחזוקה פשוטות "
+"בשרתים שלנו."
+
+#: src/app/main/ui/static.cljs
+msgid "labels.bad-gateway.main-message"
+msgstr "שער גישה שגוי"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "labels.cancel"
+msgstr "ביטול"
+
+msgid "labels.centered"
+msgstr "מרכז"
+
+msgid "labels.close"
+msgstr "סגירה"
+
+#: src/app/main/ui/dashboard/comments.cljs
+msgid "labels.comments"
+msgstr "הערות"
+
+#: src/app/main/ui/settings/password.cljs
+msgid "labels.confirm-password"
+msgstr "אישור סיסמה"
+
+msgid "labels.content"
+msgstr "תוכן"
+
+msgid "labels.continue"
+msgstr "להמשיך"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "labels.create"
+msgstr "יצירה"
+
+#: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs
+msgid "labels.create-team"
+msgstr "יצירת צוות חדש"
+
+#: src/app/main/ui/dashboard/team_form.cljs
+msgid "labels.create-team.placeholder"
+msgstr "נא למלא שם לצוות החדש"
+
+msgid "labels.custom-fonts"
+msgstr "גופנים משלך"
+
+#: src/app/main/ui/settings/sidebar.cljs
+msgid "labels.dashboard"
+msgstr "לוח בקרה"
+
+msgid "labels.default"
+msgstr "ברירת מחדל"
+
+#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "labels.delete"
+msgstr "מחיקה"
+
+#: src/app/main/ui/comments.cljs
+msgid "labels.delete-comment"
+msgstr "מחיקת הערה"
+
+#: src/app/main/ui/comments.cljs
+msgid "labels.delete-comment-thread"
+msgstr "מחיקת שרשור"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "labels.delete-multi-files"
+msgstr "מחיקת %s קבצים"
+
+#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "labels.drafts"
+msgstr "טיוטות"
+
+#: src/app/main/ui/comments.cljs
+msgid "labels.edit"
+msgstr "עריכה"
+
+msgid "labels.edit-file"
+msgstr "עריכת קובץ"
+
+#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
+msgid "labels.editor"
+msgstr "עורך"
+
+#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
+msgid "labels.email"
+msgstr "דוא״ל"
+
+msgid "labels.export"
+msgstr "ייצוא"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "labels.feedback-disabled"
+msgstr "המשוב הושבת"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "labels.feedback-sent"
+msgstr "המשוב נשלח"
+
+msgid "labels.font-family"
+msgstr "משפחת גופנים"
+
+msgid "labels.font-providers"
+msgstr "ספקי גופנים"
+
+msgid "labels.font-variants"
+msgstr "סגנונות"
+
+msgid "labels.fonts"
+msgstr "גופנים"
+
+#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs
+msgid "labels.give-feedback"
+msgstr "הגשת משוב"
+
+msgid "labels.go-back"
+msgstr "חזרה"
+
+#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs
+msgid "labels.hide-resolved-comments"
+msgstr "הסתרת הערות שנפתרו"
+
+msgid "labels.icons"
+msgstr "סמלים"
+
+msgid "labels.images"
+msgstr "תמונות"
+
+msgid "labels.installed-fonts"
+msgstr "גופנים מותקנים"
+
+#: src/app/main/ui/static.cljs
+msgid "labels.internal-error.desc-message"
+msgstr ""
+"משהו לא טוב קרה. נא לנסות לבצע את הפעולה שוב ואם הבעיה חוזרת, נא ליצור קשר "
+"עם התמיכה."
+
+#: src/app/main/ui/static.cljs
+msgid "labels.internal-error.main-message"
+msgstr "שגיאה פנימית"
+
+#: src/app/main/ui/settings/options.cljs
+msgid "labels.language"
+msgstr "שפה"
+
+msgid "labels.link"
+msgstr "קישור"
+
+#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs
+msgid "labels.logout"
+msgstr "יציאה"
+
+msgid "labels.manage-fonts"
+msgstr "ניהול גופנים"
+
+#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
+msgid "labels.members"
+msgstr "חברים"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "labels.name"
+msgstr "שם"
+
+#: src/app/main/ui/settings/password.cljs
+msgid "labels.new-password"
+msgstr "סיסמה חדשה"
+
+#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs
+msgid "labels.no-comments-available"
+msgstr "אין לך התראות ממתינות על הערות"
+
+#: src/app/main/ui/static.cljs
+msgid "labels.not-found.auth-info"
+msgstr "נכנסת בשם"
+
+#: src/app/main/ui/static.cljs
+msgid "labels.not-found.desc-message"
+msgstr "יכול להיות שהעמוד לא קיים או שאין לך הרשאות לגשת אליו."
+
+#: src/app/main/ui/static.cljs
+msgid "labels.not-found.main-message"
+msgstr "אופס!"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "labels.num-of-files"
+msgid_plural "labels.num-of-files"
+msgstr[0] "קובץ"
+msgstr[1] "%s קבצים"
+msgstr[2] "%s קבצים"
+msgstr[3] "%s קבצים"
+
+msgid "labels.num-of-frames"
+msgid_plural "labels.num-of-frames"
+msgstr[0] "לוח אומנות"
+msgstr[1] "%s לוחות אומנות"
+msgstr[2] "%s לוחות אומנות"
+msgstr[3] "%s לוחות אומנות"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "labels.num-of-projects"
+msgid_plural "labels.num-of-projects"
+msgstr[0] "מיזם"
+msgstr[1] "%s מיזמים"
+msgstr[2] "%s מיזמים"
+msgstr[3] "%s מיזמים"
+
+#: src/app/main/ui/settings/password.cljs
+msgid "labels.old-password"
+msgstr "סיסמה ישנה"
+
+#: src/app/main/ui/workspace/comments.cljs
+msgid "labels.only-yours"
+msgstr "שלך בלבד"
+
+msgid "labels.or"
+msgstr "או"
+
+#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
+msgid "labels.owner"
+msgstr "בעלים"
+
+#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs
+msgid "labels.password"
+msgstr "סיסמה"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "labels.permissions"
+msgstr "הרשאות"
+
+#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs
+msgid "labels.profile"
+msgstr "פרופיל"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "labels.projects"
+msgstr "מיזמים"
+
+msgid "labels.recent"
+msgstr "אחרונים"
+
+#: src/app/main/ui/settings/sidebar.cljs
+msgid "labels.release-notes"
+msgstr "הודעות מהדורה"
+
+#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs
+msgid "labels.remove"
+msgstr "הסרה"
+
+#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "labels.rename"
+msgstr "שינוי שם"
+
+#: src/app/main/ui/dashboard/team_form.cljs
+msgid "labels.rename-team"
+msgstr "שינוי שם לצוות"
+
+#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs
+msgid "labels.retry"
+msgstr "ניסיון חוזר"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "labels.role"
+msgstr "תפקיד"
+
+msgid "labels.save"
+msgstr "שמירה"
+
+msgid "labels.search-font"
+msgstr "חיפוש גופן"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "labels.send"
+msgstr "שליחה"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "labels.sending"
+msgstr "מתבצעת שליחה…"
+
+#: src/app/main/ui/static.cljs
+msgid "labels.service-unavailable.desc-message"
+msgstr "אנחנו בהפוגת תחזוקה מתוכננת של המערכות שלנו."
+
+#: src/app/main/ui/static.cljs
+msgid "labels.service-unavailable.main-message"
+msgstr "השירות אינו זמין"
+
+#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
+msgid "labels.settings"
+msgstr "הגדרות"
+
+msgid "labels.share-prototype"
+msgstr "שיתוף אבטיפוס"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "labels.shared-libraries"
+msgstr "ספריות משותפות"
+
+#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs
+msgid "labels.show-all-comments"
+msgstr "הצגת כל ההערות"
+
+#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs
+msgid "labels.show-your-comments"
+msgstr "הצגת ההערות שלך בלבד"
+
+#: src/app/main/ui/static.cljs
+msgid "labels.sign-out"
+msgstr "יציאה"
+
+msgid "labels.skip"
+msgstr "דילוג"
+
+msgid "labels.start"
+msgstr "התחלה"
+
+#: src/app/main/ui/settings/profile.cljs
+msgid "labels.update"
+msgstr "עדכון"
+
+#: src/app/main/ui/dashboard/team_form.cljs
+msgid "labels.update-team"
+msgstr "עדכון צוות"
+
+msgid "labels.upload"
+msgstr "העלאה"
+
+msgid "labels.upload-custom-fonts"
+msgstr "העלאת גופנים משלך"
+
+msgid "labels.uploading"
+msgstr "מתבצעת העלאה…"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "labels.viewer"
+msgstr "מציג"
+
+msgid "labels.workspace"
+msgstr "סביבת עבודה"
+
+#: src/app/main/ui/comments.cljs
+msgid "labels.write-new-comment"
+msgstr "כתיבת הערה חדשה"
+
+#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs
+msgid "media.loading"
+msgstr "התמונה נטענת…"
+
+#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.add-shared-confirm.accept"
+msgstr "הוספה כספריה משותפת"
+
+#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.add-shared-confirm.hint"
+msgstr ""
+"לאחר שנוספה כספריה משותפת, המשאבים בספריית הקבצים הזאת יהיו זמינים בנוסף "
+"לשאר הקבצים שלך."
+
+#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.add-shared-confirm.message"
+msgstr "הוספת „%s” כספריה משותפת"
+
+#: src/app/main/ui/settings/change_email.cljs
+msgid "modals.change-email.confirm-email"
+msgstr "אימות כתובת דוא״ל חדשה"
+
+#: src/app/main/ui/settings/change_email.cljs
+msgid "modals.change-email.info"
+msgstr "נשלח הודעה לכתובת הדוא״ל הנוכחית שלך „%s” כדי לאמת את הזהות שלך."
+
+#: src/app/main/ui/settings/change_email.cljs
+msgid "modals.change-email.new-email"
+msgstr "כתובת דוא״ל חדשה"
+
+#: src/app/main/ui/settings/change_email.cljs
+msgid "modals.change-email.submit"
+msgstr "החלפת כתובת דוא״ל"
+
+#: src/app/main/ui/settings/change_email.cljs
+msgid "modals.change-email.title"
+msgstr "החלפת כתובת הדוא״ל שלך"
+
+#: src/app/main/ui/settings/delete_account.cljs
+msgid "modals.delete-account.cancel"
+msgstr "ביטול ושמירה על החשבון שלי"
+
+#: src/app/main/ui/settings/delete_account.cljs
+msgid "modals.delete-account.confirm"
+msgstr "כן, למחוק את החשבון שלי"
+
+#: src/app/main/ui/settings/delete_account.cljs
+msgid "modals.delete-account.info"
+msgstr "הסרת החשבון שלך תוביל לאיבוד כל המיזמים והארכיונים הקיימים שלך."
+
+#: src/app/main/ui/settings/delete_account.cljs
+msgid "modals.delete-account.title"
+msgstr "למחוק את החשבון שלך?"
+
+#: src/app/main/ui/comments.cljs
+msgid "modals.delete-comment-thread.accept"
+msgstr "מחיקת דיון"
+
+#: src/app/main/ui/comments.cljs
+msgid "modals.delete-comment-thread.message"
+msgstr "למחוק את הדיון הזה? כל התגובות בשרשור תימחקנה."
+
+#: src/app/main/ui/comments.cljs
+msgid "modals.delete-comment-thread.title"
+msgstr "מחיקת דיון"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.delete-file-confirm.accept"
+msgstr "מחיקת קובץ"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.delete-file-confirm.message"
+msgstr "למחוק את הקובץ?"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.delete-file-confirm.title"
+msgstr "הקובץ נמחק"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.delete-file-multi-confirm.accept"
+msgstr "מחיקת קבצים"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.delete-file-multi-confirm.message"
+msgstr "למחוק %s קבצים?"
+
+#: src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.delete-file-multi-confirm.title"
+msgstr "%s קבצים נמחקים"
+
+msgid "modals.delete-font-variant.message"
+msgstr "למחוק את סגנון הגופן הזה? הוא לא ייטען אם נעשה בו שימוש בקובץ."
+
+msgid "modals.delete-font-variant.title"
+msgstr "סגנון גופן נמחק"
+
+msgid "modals.delete-font.message"
+msgstr "למחוק את הגופן הזה? הוא לא ייטען אם נעשה בו שימוש בקובץ."
+
+msgid "modals.delete-font.title"
+msgstr "גופן נמחק"
+
+#: src/app/main/ui/workspace/sidebar/sitemap.cljs
+msgid "modals.delete-page.body"
+msgstr "למחוק את העמוד הזה?"
+
+#: src/app/main/ui/workspace/sidebar/sitemap.cljs
+msgid "modals.delete-page.title"
+msgstr "מחיקת עמוד"
+
+#: src/app/main/ui/dashboard/project_menu.cljs
+msgid "modals.delete-project-confirm.accept"
+msgstr "מחיקת מיזם"
+
+#: src/app/main/ui/dashboard/project_menu.cljs
+msgid "modals.delete-project-confirm.message"
+msgstr "למחוק את המיזם הזה?"
+
+#: src/app/main/ui/dashboard/project_menu.cljs
+msgid "modals.delete-project-confirm.title"
+msgstr "מחיקת מיזם"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "modals.delete-team-confirm.accept"
+msgstr "מחיקת צוות"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "modals.delete-team-confirm.message"
+msgstr "למחוק את הצוות הזה? כל המיזמים והקבצים שמשויכים לצוות יימחקו לצמיתות."
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "modals.delete-team-confirm.title"
+msgstr "צוות נמחק"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "modals.delete-team-member-confirm.accept"
+msgstr "מחיקת חבר"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "modals.delete-team-member-confirm.message"
+msgstr "למחוק את החבר הזה מהצוות?"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "modals.delete-team-member-confirm.title"
+msgstr "למחוק חבר בצוות"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "modals.invite-member-confirm.accept"
+msgstr "שליחת הזמנה"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "modals.invite-member.title"
+msgstr "הזמנה להצטרף לצוות"
+
+msgid "modals.leave-and-reassign.forbiden"
+msgstr ""
+"אי אפשר לעזוב צוות אם אין חברים שאפשר לקדם לבעלות עליה. אולי עדיף למחוק את "
+"הצוות."
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "modals.leave-and-reassign.hint1"
+msgstr "הבעלות על %s בידיך."
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "modals.leave-and-reassign.hint2"
+msgstr "נא לבחור חבר אחר לקידום לבעלות בטרם עזיבתך"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "modals.leave-and-reassign.promote-and-leave"
+msgstr "קידום ועזיבה"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "modals.leave-and-reassign.select-memeber-to-promote"
+msgstr "נא לבחור חבר לקידום"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "modals.leave-and-reassign.title"
+msgstr "נא לבחור חבר לקידום"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "modals.leave-confirm.accept"
+msgstr "עזיבת צוות"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "modals.leave-confirm.message"
+msgstr "לעזוב את הצוות הזה?"
+
+#: src/app/main/ui/dashboard/sidebar.cljs
+msgid "modals.leave-confirm.title"
+msgstr "עזיבת הצוות"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "modals.promote-owner-confirm.accept"
+msgstr "קידום"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "modals.promote-owner-confirm.message"
+msgstr "לקדם את המשתמש הזה לבעלים?"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "modals.promote-owner-confirm.title"
+msgstr "קידום לבעלים"
+
+#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.remove-shared-confirm.accept"
+msgstr "הסרה כספריה משותפת"
+
+#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.remove-shared-confirm.hint"
+msgstr ""
+"לאחר הסרה כספריה משותפת, ספריית הקבצים של הקובץ הזה לא תהיה זמינה עוד "
+"לשימוש בקרב שאר הקבצים שלך."
+
+#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.remove-shared-confirm.message"
+msgstr "הסרת „%s” כספריה משותפת"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "modals.update-remote-component.accept"
+msgstr "עדכון רכיב"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "modals.update-remote-component.cancel"
+msgstr "ביטול"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "modals.update-remote-component.hint"
+msgstr ""
+"פעולה זו תעדכן רכיב בספריה משותפת. זה עשוי להשפיע על הקבצים האחרים שמשתמשים "
+"בה."
+
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "modals.update-remote-component.message"
+msgstr "עדכון רכיב בספריה משותפת"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "notifications.invitation-email-sent"
+msgstr "ההזמנה נשלחה בהצלחה"
+
+#: src/app/main/ui/settings/delete_account.cljs
+msgid "notifications.profile-deletion-not-allowed"
+msgstr ""
+"אין אפשר למחוק את הפרופיל שלך. יש להקצות את הצוותים שלך מחדש בטרם המשך "
+"בתהליך."
+
+#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs
+msgid "notifications.profile-saved"
+msgstr "הפרופיל נשמר בהצלחה!"
+
+#: src/app/main/ui/settings/change_email.cljs
+msgid "notifications.validation-email-sent"
+msgstr "הודעת האימות נשלחה בדוא״ל אל %s. נא לבדוק את הדוא״ל שלך!"
+
+msgid "onboarding.contrib.alt"
+msgstr "קוד פתוח"
+
+msgid "onboarding.contrib.desc1"
+msgstr ""
+"Penpot הוא בקוד פתוח, נוצר על ידי הקהילה ועבורה. אנו מזמינים אותך לקחת חלק, "
+"בשמחה!"
+
+msgid "onboarding.contrib.desc2.1"
+msgstr "אפשר לגשת אל"
+
+msgid "onboarding.contrib.desc2.2"
+msgstr "ולעקוב אחר הנחיות ההתנדבות :)"
+
+msgid "onboarding.contrib.link"
+msgstr "מיזם ב־github"
+
+msgid "onboarding.contrib.title"
+msgstr "התנדבות בקוד פתוח?"
+
+msgid "onboarding.slide.0.alt"
+msgstr "יצירת עיצובים"
+
+msgid "onboarding.slide.0.desc1"
+msgstr "אפשר ליצור מנשקי משתמש יפהפיים בשיתוף פעולה של כל חברי הצוות."
+
+msgid "onboarding.slide.0.desc2"
+msgstr "שמירה על אחידות גם בעת גדילה מול כל הרכיבים, הספריות ומערכות העיצוב."
+
+msgid "onboarding.slide.0.title"
+msgstr "ספריות עיצוב, סגנונות ורכיבים"
+
+msgid "onboarding.slide.1.alt"
+msgstr "אבות טיפוס אינטראקטיביים"
+
+msgid "onboarding.slide.1.desc1"
+msgstr "יצירת אינטראקציות עשירות לחיקוי התנהגות המוצר."
+
+msgid "onboarding.slide.1.desc2"
+msgstr ""
+"שיתוף עם בעלי עניין, הגשת הצעות לצוות והתחלת בדיקות עם העיצובים שלך, הכול "
+"באותו המקום."
+
+msgid "onboarding.slide.1.title"
+msgstr "העיצובים שלך קמים לחיים עם אינטראקציות"
+
+msgid "onboarding.slide.2.alt"
+msgstr "קבלת משוב"
+
+msgid "onboarding.slide.2.desc1"
+msgstr ""
+"כל חברי הצוות עובדים במקביל עם הערות, רעיונות ממגוון גורמים במקום מרכזי על "
+"העיצוב בזמן אמת ישירות על העיצובים."
+
+msgid "onboarding.slide.2.title"
+msgstr "קבלת משוב, הצגה ושיתוף העבודה שלך"
+
+msgid "onboarding.slide.3.alt"
+msgstr "הגשה וקוד מקור"
+
+msgid "onboarding.slide.3.desc1"
+msgstr "ניתן לסנכרן את העיצוב ואת הקוד של כל הרכיבים והעיצובים שלך ולקבל מקטעי קוד."
+
+msgid "onboarding.slide.3.desc2"
+msgstr ""
+"לקבל ולספק מפרטי קוד כגון שפת סימון (SVG, HTML) או סגנונות (CSS, Less, "
+"Stylus…)."
+
+msgid "onboarding.slide.3.title"
+msgstr "מקור משותף יחיד לאמת"
+
+msgid "onboarding.team.create.button"
+msgstr "יצירת צוות"
+
+msgid "onboarding.team.create.desc1"
+msgstr ""
+"יש לך עבודה בשיתוף עם גורם נוסף? ניתן ליצור צוות כדי לעבוד יחד על מיזמים "
+"ולשתף משאבי עיצוב."
+
+msgid "onboarding.team.create.input-placeholder"
+msgstr "נא למלא שם לצוות"
+
+msgid "onboarding.team.create.title"
+msgstr "יצירת צוות"
+
+msgid "onboarding.team.start.button"
+msgstr "להתחיל כבר עכשיו"
+
+msgid "onboarding.team.start.desc1"
+msgstr ""
+"אפשר לצלול עמוק ישירות אל Penpot ולהתחיל לעצב בעצמך. עדיין תהיה לך הזדמנות "
+"ליצור צוותים בהמשך."
+
+msgid "onboarding.team.start.title"
+msgstr "מתחילים לעצב"
+
+msgid "onboarding.welcome.alt"
+msgstr "Penpot"
+
+msgid "onboarding.welcome.desc1"
+msgstr "אנו שמחים להציג בפניך את גרסת האלפא הראשונית."
+
+msgid "onboarding.welcome.desc2"
+msgstr ""
+"Penpot עדיין בשלבי פיתוח וייתכנו עדכונים תכופים. אנו מקווים שהגרסה היציבה "
+"הראשונה תשרת אותך כראוי."
+
+msgid "onboarding.welcome.title"
+msgstr "ברוך בואך ל־Penpot!"
+
+#: src/app/main/ui/auth/recovery.cljs
+msgid "profile.recovery.go-to-login"
+msgstr "מעבר למסך הכניסה"
+
+#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
+msgid "settings.multiple"
+msgstr "מעורב"
+
+#: src/app/main/ui/dashboard/files.cljs
+msgid "title.dashboard.files"
+msgstr "%s - Penpot"
+
+#: src/app/main/ui/dashboard/fonts.cljs
+msgid "title.dashboard.font-providers"
+msgstr "ספקי גופנים - %s - Penpot"
+
+#: src/app/main/ui/dashboard/fonts.cljs
+msgid "title.dashboard.fonts"
+msgstr "גופנים - %s - Penpot"
+
+#: src/app/main/ui/dashboard/projects.cljs
+msgid "title.dashboard.projects"
+msgstr "מיזמים - %s - Penpot"
+
+#: src/app/main/ui/dashboard/search.cljs
+msgid "title.dashboard.search"
+msgstr "חיפוש - %s - Penpot"
+
+#: src/app/main/ui/dashboard/libraries.cljs
+msgid "title.dashboard.shared-libraries"
+msgstr "ספריות משותפות - %s - Penpot"
+
+#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs
+msgid "title.default"
+msgstr "Penpot - חופש עיצובי לצוותים"
+
+#: src/app/main/ui/settings/feedback.cljs
+msgid "title.settings.feedback"
+msgstr "הגשת משוב - Penpot"
+
+#: src/app/main/ui/settings/options.cljs
+msgid "title.settings.options"
+msgstr "הגדרות - Penpot"
+
+#: src/app/main/ui/settings/password.cljs
+msgid "title.settings.password"
+msgstr "סיסמה - Penpot"
+
+#: src/app/main/ui/settings/profile.cljs
+msgid "title.settings.profile"
+msgstr "פרופיל - Penpot"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "title.team-members"
+msgstr "חברים - %s - Penpot"
+
+#: src/app/main/ui/dashboard/team.cljs
+msgid "title.team-settings"
+msgstr "הגדרות - %s - Penpot"
+
+#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
+msgid "title.viewer"
+msgstr "%s - מצב תצוגה - Penpot"
+
+#: src/app/main/ui/workspace.cljs
+msgid "title.workspace"
+msgstr "%s - Penpot"
+
+#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
+msgid "viewer.empty-state"
+msgstr "לא נמצאו לוחות אומנות בעמוד."
+
+#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
+msgid "viewer.frame-not-found"
+msgstr "לוח האומנות לא נמצא."
+
+msgid "viewer.header.comments-section"
+msgstr "הערות"
+
+#: src/app/main/ui/viewer/header.cljs
+msgid "viewer.header.dont-show-interactions"
+msgstr "לא להציג אינטראקציות"
+
+#: src/app/main/ui/viewer/header.cljs
+msgid "viewer.header.fullscreen"
+msgstr "מסך מלא"
+
+msgid "viewer.header.handsoff-section"
+msgstr "מסירות"
+
+#: src/app/main/ui/viewer/header.cljs
+msgid "viewer.header.interactions"
+msgstr "אינטראקציות"
+
+msgid "viewer.header.interactions-section"
+msgstr "אינטראקציות"
+
+#: src/app/main/ui/viewer/header.cljs
+msgid "viewer.header.share.copy-link"
+msgstr "העתקת קישור"
+
+#: src/app/main/ui/viewer/header.cljs
+msgid "viewer.header.share.create-link"
+msgstr "יצירת קישור"
+
+#: src/app/main/ui/viewer/header.cljs
+msgid "viewer.header.share.placeholder"
+msgstr "הקישור לשיתוף יופיע כאן"
+
+#: src/app/main/ui/viewer/header.cljs
+msgid "viewer.header.share.remove-link"
+msgstr "הסרת קישור"
+
+#: src/app/main/ui/viewer/header.cljs
+msgid "viewer.header.share.subtitle"
+msgstr "לכל מי שיש את הקישור יש גישה"
+
+#: src/app/main/ui/viewer/header.cljs
+msgid "viewer.header.show-interactions"
+msgstr "הצגת פעילויות"
+
+#: src/app/main/ui/viewer/header.cljs
+msgid "viewer.header.show-interactions-on-click"
+msgstr "הצגת פעילויות בקליק"
+
+#: src/app/main/ui/viewer/header.cljs
+msgid "viewer.header.sitemap"
+msgstr "מפת אתר"
+
+#: src/app/main/ui/workspace/sidebar/align.cljs
+msgid "workspace.align.hcenter"
+msgstr "יישור למרכז האופקי"
+
+#: src/app/main/ui/workspace/sidebar/align.cljs
+msgid "workspace.align.hdistribute"
+msgstr "פיזור ריווח אופקי"
+
+#: src/app/main/ui/workspace/sidebar/align.cljs
+msgid "workspace.align.hleft"
+msgstr "יישור שמאלה"
+
+#: src/app/main/ui/workspace/sidebar/align.cljs
+msgid "workspace.align.hright"
+msgstr "יישור ימינה"
+
+#: src/app/main/ui/workspace/sidebar/align.cljs
+msgid "workspace.align.vbottom"
+msgstr "יישור למטה"
+
+#: src/app/main/ui/workspace/sidebar/align.cljs
+msgid "workspace.align.vcenter"
+msgstr "יישור למרכז אנכית"
+
+#: src/app/main/ui/workspace/sidebar/align.cljs
+msgid "workspace.align.vdistribute"
+msgstr "פיזור בריווח אנכי"
+
+#: src/app/main/ui/workspace/sidebar/align.cljs
+msgid "workspace.align.vtop"
+msgstr "יישור למעלה"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.assets"
+msgstr "משאבים"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.box-filter-all"
+msgstr "כל המשאבים"
+
+msgid "workspace.assets.box-filter-graphics"
+msgstr "גרפיקה"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.colors"
+msgstr "צבעים"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.components"
+msgstr "רכיבים"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.create-group"
+msgstr "יצירת קבוצה"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.create-group-hint"
+msgstr "הפריטים שלך יקבלו אוטומטית שם בסגנון „שם קבוצה / שם פריט”"
+
+#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.delete"
+msgstr "מחיקה"
+
+#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.duplicate"
+msgstr "שכפול"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.edit"
+msgstr "עריכה"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.file-library"
+msgstr "ספריית קבצים"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.graphics"
+msgstr "גרפיקה"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.group"
+msgstr "קבוצה"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.group-name"
+msgstr "שם קבוצה"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.libraries"
+msgstr "ספריות"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.not-found"
+msgstr "לא נמצאו משאבים"
+
+#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.rename"
+msgstr "שינוי שם"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.rename-group"
+msgstr "שינוי שם קבוצה"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.search"
+msgstr "חיפוש במשאבים"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.selected-count"
+msgid_plural "workspace.assets.selected-count"
+msgstr[0] "פריט נבחר"
+msgstr[1] "%s פריטים נבחרו"
+msgstr[2] "%s פריטים נבחרו"
+msgstr[3] "%s פריטים נבחרו"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.shared"
+msgstr "משותף"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.typography"
+msgstr "טיפוגרפיות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.assets.typography.font-id"
+msgstr "גופן"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.assets.typography.font-size"
+msgstr "גודל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.assets.typography.font-variant-id"
+msgstr "הגוון"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.assets.typography.go-to-edit"
+msgstr "מעבר לקובץ ספריית סגנון כדי לערוך"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.assets.typography.letter-spacing"
+msgstr "ריווח תווים"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.assets.typography.line-height"
+msgstr "גובה שורה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs
+msgid "workspace.assets.typography.sample"
+msgstr "שצ"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.assets.typography.text-transform"
+msgstr "התמרת טקסט"
+
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.ungroup"
+msgstr "פירוק קבוצה"
+
+#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs
+msgid "workspace.gradients.linear"
+msgstr "מדרג קווי"
+
+#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs
+msgid "workspace.gradients.radial"
+msgstr "מדרג מעגלי"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.disable-dynamic-alignment"
+msgstr "השבתת יישור דינמי"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.disable-scale-text"
+msgstr "השבתת שינוי גודל טקסט"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.disable-snap-grid"
+msgstr "השבתת הצמדה לרשת"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.enable-dynamic-alignment"
+msgstr "הפעלת יישור דינמי"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.enable-scale-text"
+msgstr "הפעלת שינוי גודל טקסט"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.enable-snap-grid"
+msgstr "הצמדה לרשת"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.hide-assets"
+msgstr "הסתרת משאבים"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.hide-grid"
+msgstr "הסתרת רשתות"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.hide-layers"
+msgstr "הסתרת שכבות"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.hide-palette"
+msgstr "הסתרת ערכת צבעים"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.hide-rules"
+msgstr "הסתרת כללים"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.select-all"
+msgstr "לבחור הכול"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.show-assets"
+msgstr "הצגת משאבים"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.show-grid"
+msgstr "הצגת רשת"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.show-layers"
+msgstr "הצגת שכבות"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.show-palette"
+msgstr "הצגת ערכת צבעים"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.menu.show-rules"
+msgstr "הצגת כללים"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.save-error"
+msgstr "שגיאה בשמירה"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.saved"
+msgstr "נשמר"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.saving"
+msgstr "בשמירה"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.unsaved"
+msgstr "שינויים שלא נשמרו"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.header.viewer"
+msgstr "מצב תצוגה (%s)"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.add"
+msgstr "הוספה"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.colors"
+msgstr "%s צבעים"
+
+#: src/app/main/ui/workspace/colorpalette.cljs
+msgid "workspace.libraries.colors.big-thumbnails"
+msgstr "תצוגה מקדימה גדולה"
+
+#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs
+msgid "workspace.libraries.colors.file-library"
+msgstr "ספריית קבצים"
+
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.hsv"
+msgstr "HSV"
+
+#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs
+msgid "workspace.libraries.colors.recent-colors"
+msgstr "צבעים אחרונים"
+
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.rgb-complementary"
+msgstr "RGB משלים"
+
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.rgba"
+msgstr "RGBA"
+
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.save-color"
+msgstr "שמירת סגנון צבע"
+
+#: src/app/main/ui/workspace/colorpalette.cljs
+msgid "workspace.libraries.colors.small-thumbnails"
+msgstr "תצוגה מקדימה קטנה"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.components"
+msgstr "%s רכיבים"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.file-library"
+msgstr "ספריית קבצים"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.graphics"
+msgstr "%s גרפיקה"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.in-this-file"
+msgstr "ספריות בקובץ הזה"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.libraries"
+msgstr "ספריות"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.library"
+msgstr "ספרייה"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.no-libraries-need-sync"
+msgstr "אין ספריות משותפות שדורשות עדכון"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.no-matches-for"
+msgstr "לא נמצאו תוצאות לחיפוש אחר „%s”"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.no-shared-libraries-available"
+msgstr "אין ספריות משותפות זמינות"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.search-shared-libraries"
+msgstr "חיפוש בספריות המשותפות"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.shared-libraries"
+msgstr "ספריות משותפות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.libraries.text.multiple-typography"
+msgstr "מגוון טיפוגרפיות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.libraries.text.multiple-typography-tooltip"
+msgstr "ניתוק כל הטיפוגרפיות"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.typography"
+msgstr "%s טיפוגרפיות"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.update"
+msgstr "עדכון"
+
+#: src/app/main/ui/workspace/libraries.cljs
+msgid "workspace.libraries.updates"
+msgstr "עדכונים"
+
+msgid "workspace.library.all"
+msgstr "כל הספריות"
+
+msgid "workspace.library.libraries"
+msgstr "ספריות"
+
+msgid "workspace.library.own"
+msgstr "הספריות שלי"
+
+msgid "workspace.library.store"
+msgstr "אחסון ספריות"
+
+msgid "workspace.options.blur-options.background-blur"
+msgstr "רקע"
+
+msgid "workspace.options.blur-options.layer-blur"
+msgstr "שכבה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
+msgid "workspace.options.blur-options.title"
+msgstr "טשטוש"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
+msgid "workspace.options.blur-options.title.group"
+msgstr "טשטוש קבוצה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
+msgid "workspace.options.blur-options.title.multiple"
+msgstr "טשטוש בחירה"
+
+#: src/app/main/ui/workspace/sidebar/options/page.cljs
+msgid "workspace.options.canvas-background"
+msgstr "רקע משטח ציור"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs
+msgid "workspace.options.component"
+msgstr "רכיב"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints"
+msgstr "הגבלות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.bottom"
+msgstr "תחתית"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.center"
+msgstr "מרכז"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.fix-when-scrolling"
+msgstr "תיקון בעת גלילה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.left"
+msgstr "שמאל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.leftright"
+msgstr "שמאל וימין"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.right"
+msgstr "ימין"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.scale"
+msgstr "שינוי גודל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.top"
+msgstr "עליון"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.constraints.topbottom"
+msgstr "עליון ותחתון"
+
+#: src/app/main/ui/workspace/sidebar/options.cljs
+msgid "workspace.options.design"
+msgstr "עיצוב"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs
+msgid "workspace.options.export"
+msgstr "ייצוא"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs
+msgid "workspace.options.export-object"
+msgstr "ייצוא צורה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs
+msgid "workspace.options.export.suffix"
+msgstr "סיומת"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs
+msgid "workspace.options.exporting-object"
+msgstr "מתבצע ייצוא…"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs
+msgid "workspace.options.fill"
+msgstr "מילוי"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.auto"
+msgstr "אוטומטי"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.column"
+msgstr "עמודות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.columns"
+msgstr "עמודות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.gutter"
+msgstr "מרזב"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.height"
+msgstr "גובה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.margin"
+msgstr "שול"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.rows"
+msgstr "שורות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.set-default"
+msgstr "הגדרה כבררת מחדל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.size"
+msgstr "גודל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.type"
+msgstr "סוג"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.type.bottom"
+msgstr "תחתון"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.type.center"
+msgstr "מרכז"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.type.left"
+msgstr "שמאל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.type.right"
+msgstr "ימין"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.type.stretch"
+msgstr "מתיחה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.type.top"
+msgstr "עליון"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.use-default"
+msgstr "להשתמש בבררת המחדל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.width"
+msgstr "רוחב"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.row"
+msgstr "שורות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.square"
+msgstr "ריבוע"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.title"
+msgstr "רשת ופריסות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs
+msgid "workspace.options.group-fill"
+msgstr "מילוי קבוצה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.group-stroke"
+msgstr "מתאר קבוצה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.color"
+msgstr "צבע"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.color-burn"
+msgstr "צריבת צבע"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.color-dodge"
+msgstr "בריחת צבע"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.darken"
+msgstr "החשכה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.difference"
+msgstr "הבדל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.exclusion"
+msgstr "הדרה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.hard-light"
+msgstr "אור קשיח"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.hue"
+msgstr "גוון"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.lighten"
+msgstr "הבהרה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.luminosity"
+msgstr "תאורה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.multiply"
+msgstr "הכפלה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.normal"
+msgstr "רגיל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.overlay"
+msgstr "שכבת על"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.saturation"
+msgstr "רוויה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.screen"
+msgstr "מסך"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.soft-light"
+msgstr "אור רך"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.title"
+msgstr "שכבה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.title.group"
+msgstr "קיבוץ שכבות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.title.multiple"
+msgstr "שכבות נבחרות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.navigate-to"
+msgstr "ניווט אל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.none"
+msgstr "ללא"
+
+#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.position"
+msgstr "מיקום"
+
+#: src/app/main/ui/workspace/sidebar/options.cljs
+msgid "workspace.options.prototype"
+msgstr "אבטיפוס"
+
+msgid "workspace.options.radius"
+msgstr "רדיוס"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.radius.all-corners"
+msgstr "כל הפינות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.radius.single-corners"
+msgstr "פינות בודדות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.rotation"
+msgstr "סיבוב"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.select-a-shape"
+msgstr "נא לבחור צורה, לוח אומנות או קבוצה כדי לגרור חיבור ללוח אומנות אחר."
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.select-artboard"
+msgstr "בחירת לוח אומנות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs
+msgid "workspace.options.selection-fill"
+msgstr "מילוי בחירה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.selection-stroke"
+msgstr "מתאר בחירה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
+msgid "workspace.options.shadow-options.blur"
+msgstr "טשטוש"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
+msgid "workspace.options.shadow-options.drop-shadow"
+msgstr "הטלת צל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
+msgid "workspace.options.shadow-options.inner-shadow"
+msgstr "צל פנימי"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
+msgid "workspace.options.shadow-options.offsetx"
+msgstr "X"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
+msgid "workspace.options.shadow-options.offsety"
+msgstr "Y"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
+msgid "workspace.options.shadow-options.spread"
+msgstr "פיזור"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
+msgid "workspace.options.shadow-options.title"
+msgstr "צל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
+msgid "workspace.options.shadow-options.title.group"
+msgstr "צל של קבוצה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
+msgid "workspace.options.shadow-options.title.multiple"
+msgstr "צללים של בחירה"
+
+#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+msgid "workspace.options.size"
+msgstr "גודל"
+
+#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs
+msgid "workspace.options.size-presets"
+msgstr "תבניות גודל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke"
+msgstr "מתאר"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.circle-marker"
+msgstr "סמן עגול"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.diamond-marker"
+msgstr "סמן יהלום"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.line-arrow"
+msgstr "חץ קו"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.none"
+msgstr "ללא"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.round"
+msgstr "עגול"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.square"
+msgstr "ריבוע"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.square-marker"
+msgstr "סמן ריבוע"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.triangle-arrow"
+msgstr "חץ משולש"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke.center"
+msgstr "מרכז"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke.dashed"
+msgstr "מקווקוו"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke.dotted"
+msgstr "מנוקד"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke.inner"
+msgstr "בפנים"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke.mixed"
+msgstr "מעורב"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke.outer"
+msgstr "בחוץ"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke.solid"
+msgstr "אחיד"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.align-bottom"
+msgstr "יישור לתחתית"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.align-center"
+msgstr "יישור למרכז"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.align-justify"
+msgstr "יישור לשני הצדדים"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.align-left"
+msgstr "יישור שמאלה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.align-middle"
+msgstr "יישור לאמצע"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.align-right"
+msgstr "יישור ימינה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.align-top"
+msgstr "יישור לראש"
+
+msgid "workspace.options.text-options.decoration"
+msgstr "עיטור"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.direction-ltr"
+msgstr "משמאל לימין"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.direction-rtl"
+msgstr "מימין לשמאל"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.options.text-options.google"
+msgstr "Google"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.grow-auto-height"
+msgstr "גובה אוטומטי"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.grow-auto-width"
+msgstr "רוחב אוטומטי"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.grow-fixed"
+msgstr "קבוע"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.options.text-options.letter-spacing"
+msgstr "ריווח תווים"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.options.text-options.line-height"
+msgstr "גובה שורה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.options.text-options.lowercase"
+msgstr "אותיות קטנות"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.options.text-options.none"
+msgstr "ללא"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.options.text-options.preset"
+msgstr "תבנית"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.strikethrough"
+msgstr "קו חוצה"
+
+msgid "workspace.options.text-options.text-case"
+msgstr "גדול/קטן"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.title"
+msgstr "טקסט"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.title-group"
+msgstr "קיבוץ טקסט"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.title-selection"
+msgstr "טקסט בחירה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.options.text-options.titlecase"
+msgstr "רישיות כותרת"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+msgid "workspace.options.text-options.underline"
+msgstr "קו תחתי"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.options.text-options.uppercase"
+msgstr "אותיות גדולות"
+
+msgid "workspace.options.text-options.vertical-align"
+msgstr "יישור אנכי"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.use-play-button"
+msgstr "ניתן להשתמש בכפתור הנגינה שבכותרת כדי להריץ את תצוגת האבטיפוס."
+
+msgid "workspace.path.actions.add-node"
+msgstr "הוספת מפרק (%s)"
+
+msgid "workspace.path.actions.delete-node"
+msgstr "מחיקת מפרק (%s)"
+
+msgid "workspace.path.actions.draw-nodes"
+msgstr "ציור מפרקים (%s)"
+
+msgid "workspace.path.actions.join-nodes"
+msgstr "צירוף מפרקים (%s)"
+
+msgid "workspace.path.actions.make-corner"
+msgstr "לפינה (%s)"
+
+msgid "workspace.path.actions.make-curve"
+msgstr "לעיקול (%s)"
+
+msgid "workspace.path.actions.merge-nodes"
+msgstr "מיזוג מפרקים (%s)"
+
+msgid "workspace.path.actions.move-nodes"
+msgstr "העברת מפרקים (%s)"
+
+msgid "workspace.path.actions.separate-nodes"
+msgstr "הפרדת מפרקים (%s)"
+
+msgid "workspace.path.actions.snap-nodes"
+msgstr "הצמדת מפרקים (%s)"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.back"
+msgstr "הרחקה"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.backward"
+msgstr "הרחקה לאחור"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.copy"
+msgstr "העתקה"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.create-component"
+msgstr "יצירת רכיב"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.cut"
+msgstr "גזירה"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.delete"
+msgstr "מחיקה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.detach-instance"
+msgstr "ניתוק מופע"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.duplicate"
+msgstr "שכפול"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.edit"
+msgstr "עריכה"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.flip-horizontal"
+msgstr "היפוך אופקי"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.flip-vertical"
+msgstr "היפוך אנכי"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.forward"
+msgstr "קידום"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.front"
+msgstr "קידום לחזית"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.go-main"
+msgstr "מעבר לקובץ הרכיב הראשי"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.group"
+msgstr "קבוצה"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.hide"
+msgstr "הסתרה"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.lock"
+msgstr "נעילה"
+
+#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.mask"
+msgstr "מסכה"
+
+#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.paste"
+msgstr "הדבקה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.reset-overrides"
+msgstr "איפוס מעקפים"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.show"
+msgstr "הצגה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.show-main"
+msgstr "הצגת הרכיב הראשי"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.ungroup"
+msgstr "פירוק קבוצה"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.unlock"
+msgstr "שחרור נעילה"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.unmask"
+msgstr "ביטול מסכה"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.update-main"
+msgstr "עדכון הרכיב הראשי"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.sidebar.history"
+msgstr "היסטוריה (%s)"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.sidebar.layers"
+msgstr "שכבות (%s)"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs
+msgid "workspace.sidebar.options.svg-attrs.title"
+msgstr "מאפייני SVG יובאו"
+
+#: src/app/main/ui/workspace/sidebar/sitemap.cljs
+msgid "workspace.sidebar.sitemap"
+msgstr "עמודים"
+
+#: src/app/main/ui/workspace/header.cljs
+msgid "workspace.sitemap"
+msgstr "מפת אתר"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.assets"
+msgstr "משאבים (%s)"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.color-palette"
+msgstr "ערכת צבעים (%s)"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.comments"
+msgstr "הערות (%s)"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.curve"
+msgstr "עיקול (%s)"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.ellipse"
+msgstr "אליפסה (%s)"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.frame"
+msgstr "לוח אומנות (%s)"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.image"
+msgstr "תמונה (%s)"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.move"
+msgstr "העברה"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.path"
+msgstr "נתיב (%s)"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.rect"
+msgstr "ריבוע (%s)"
+
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.text"
+msgstr "טקסט (%s)"
+
+#: src/app/main/ui/workspace/sidebar/history.cljs
+msgid "workspace.undo.empty"
+msgstr "אין שינויים היסטוריים עד כה"
+
+#: src/app/main/ui/workspace/sidebar/history.cljs
+msgid "workspace.undo.entry.delete"
+msgstr "%s נמחק"
+
+#: src/app/main/ui/workspace/sidebar/history.cljs
+msgid "workspace.undo.entry.modify"
+msgstr "%s נערך"
+
+#: src/app/main/ui/workspace/sidebar/history.cljs
+msgid "workspace.undo.entry.move"
+msgstr "פריטים שהועברו"
+
+msgid "workspace.undo.entry.multiple.circle"
+msgstr "עיגולים"
+
+msgid "workspace.undo.entry.multiple.color"
+msgstr "משאבי צבע"
+
+msgid "workspace.undo.entry.multiple.component"
+msgstr "רכיבים"
+
+msgid "workspace.undo.entry.multiple.curve"
+msgstr "עיקולים"
+
+msgid "workspace.undo.entry.multiple.frame"
+msgstr "לוח אומנות"
+
+msgid "workspace.undo.entry.multiple.group"
+msgstr "קבוצות"
+
+msgid "workspace.undo.entry.multiple.media"
+msgstr "משאבים גרפיים"
+
+msgid "workspace.undo.entry.multiple.multiple"
+msgstr "פריטים"
+
+msgid "workspace.undo.entry.multiple.page"
+msgstr "עמודים"
+
+msgid "workspace.undo.entry.multiple.path"
+msgstr "נתיבים"
+
+msgid "workspace.undo.entry.multiple.rect"
+msgstr "ריבועים"
+
+msgid "workspace.undo.entry.multiple.shape"
+msgstr "צורות"
+
+msgid "workspace.undo.entry.multiple.text"
+msgstr "טקסטים"
+
+msgid "workspace.undo.entry.multiple.typography"
+msgstr "משאבים טיפוגרפיים"
+
+#: src/app/main/ui/workspace/sidebar/history.cljs
+msgid "workspace.undo.entry.new"
+msgstr "%s חדש"
+
+msgid "workspace.undo.entry.single.circle"
+msgstr "עיגול"
+
+msgid "workspace.undo.entry.single.color"
+msgstr "משאב צבע"
+
+msgid "workspace.undo.entry.single.component"
+msgstr "רכיב"
+
+msgid "workspace.undo.entry.single.curve"
+msgstr "עיקול"
+
+msgid "workspace.undo.entry.single.frame"
+msgstr "לוח אומנות"
+
+msgid "workspace.undo.entry.single.group"
+msgstr "קבוצה"
+
+msgid "workspace.undo.entry.single.image"
+msgstr "תמונה"
+
+msgid "workspace.undo.entry.single.media"
+msgstr "משאב גרפי"
+
+msgid "workspace.undo.entry.single.multiple"
+msgstr "פריט"
+
+msgid "workspace.undo.entry.single.page"
+msgstr "עמוד"
+
+msgid "workspace.undo.entry.single.path"
+msgstr "נתיב"
+
+msgid "workspace.undo.entry.single.rect"
+msgstr "ריבוע"
+
+msgid "workspace.undo.entry.single.shape"
+msgstr "צורה"
+
+msgid "workspace.undo.entry.single.text"
+msgstr "טקסט"
+
+msgid "workspace.undo.entry.single.typography"
+msgstr "משאב טיפוגרפי"
+
+#: src/app/main/ui/workspace/sidebar/history.cljs
+msgid "workspace.undo.entry.unknown"
+msgstr "פעולה על %s"
+
+#: src/app/main/ui/workspace/sidebar/history.cljs
+msgid "workspace.undo.title"
+msgstr "היסטוריה"
+
+#: src/app/main/data/workspace/libraries.cljs
+msgid "workspace.updates.dismiss"
+msgstr "התעלמות"
+
+#: src/app/main/data/workspace/libraries.cljs
+msgid "workspace.updates.there-are-updates"
+msgstr "יש עדכונים בספריות המשותפות"
+
+#: src/app/main/data/workspace/libraries.cljs
+msgid "workspace.updates.update"
+msgstr "עדכון"
+
+msgid "workspace.viewport.click-to-close-path"
+msgstr "לחיצה תסגור את הנתיב"
\ No newline at end of file
diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po
index 8e8046458..6cd4fdd6b 100644
--- a/frontend/translations/pt_BR.po
+++ b/frontend/translations/pt_BR.po
@@ -1662,14 +1662,6 @@ msgstr "Camadas do grupo"
msgid "workspace.options.layer-options.title.multiple"
msgstr "Camadas selecionadas"
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.navigate-to"
-msgstr "Navegar para"
-
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.none"
-msgstr "Nenhum"
-
#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.position"
msgstr "Posição"
diff --git a/frontend/translations/ro.po b/frontend/translations/ro.po
index 2dd4d6e5d..c9feb0fb1 100644
--- a/frontend/translations/ro.po
+++ b/frontend/translations/ro.po
@@ -2048,14 +2048,6 @@ msgstr "Grupează layere"
msgid "workspace.options.layer-options.title.multiple"
msgstr "Layere selectate"
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.navigate-to"
-msgstr "Navighează la"
-
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.none"
-msgstr "Nici unul"
-
#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.position"
msgstr "Poziţie"
diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po
index 6f0d9cc98..ad4d9f0f6 100644
--- a/frontend/translations/ru.po
+++ b/frontend/translations/ru.po
@@ -957,14 +957,6 @@ msgstr "Заливка для группы"
msgid "workspace.options.group-stroke"
msgstr "Обводка для группы"
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.navigate-to"
-msgstr "Перейти к"
-
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.none"
-msgstr "Не задано"
-
#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.position"
msgstr "Позиция"
diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po
index afaf33288..33f368fb3 100644
--- a/frontend/translations/tr.po
+++ b/frontend/translations/tr.po
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
-"PO-Revision-Date: 2021-07-05 17:02+0000\n"
-"Last-Translator: Çağlar Yeşilyurt \n"
+"PO-Revision-Date: 2021-09-10 10:57+0000\n"
+"Last-Translator: Oğuz Ersen \n"
"Language-Team: Turkish "
"\n"
"Language: tr\n"
@@ -9,7 +9,7 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.8-dev\n"
+"X-Generator: Weblate 4.8.1-dev\n"
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"
@@ -91,13 +91,17 @@ msgstr "OpenID (SSO) ile Giriş Yap"
msgid "auth.new-password"
msgstr "Yeni bir parola gir"
+#: src/app/main/ui/auth/register.cljs
+msgid "auth.newsletter-subscription"
+msgstr "Penpot e-posta listesine abone olmayı kabul ediyorum."
+
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.notifications.invalid-token-error"
-msgstr "Kurtarma jetonu geçerli değil"
+msgstr "Kurtarma jetonu geçerli değil."
#: src/app/main/ui/auth/recovery.cljs
msgid "auth.notifications.password-changed-succesfully"
-msgstr "Parola başarıyla değiştirldi"
+msgstr "Parola başarıyla değiştirildi"
#: src/app/main/ui/auth/recovery_request.cljs
msgid "auth.notifications.profile-not-verified"
@@ -165,6 +169,44 @@ msgstr ""
msgid "auth.verification-email-sent"
msgstr "Onay e-postanı şu adrese gönderdik"
+msgid "common.share-link.confirm-deletion-link-description"
+msgstr ""
+"Bu bağlantıyı kaldırmak istediğinizden emin misiniz? Bunu yaparsanız, artık "
+"kimse tarafından kullanılamayacak"
+
+msgid "common.share-link.get-link"
+msgstr "Bağlantıyı al"
+
+msgid "common.share-link.link-copied-success"
+msgstr "Bağlantı başarıyla kopyalandı"
+
+msgid "common.share-link.link-deleted-success"
+msgstr "Bağlantı başarıyla silindi"
+
+msgid "common.share-link.permissions-can-access"
+msgstr "Erişebilir"
+
+msgid "common.share-link.permissions-can-view"
+msgstr "Görüntüleyebilir"
+
+msgid "common.share-link.permissions-hint"
+msgstr "Bağlantıya sahip olan herkes erişebilir"
+
+msgid "common.share-link.remove-link"
+msgstr "Bağlantıyı kaldır"
+
+msgid "common.share-link.title"
+msgstr "Prototipleri paylaş"
+
+msgid "common.share-link.view-all-pages"
+msgstr "Tüm sayfalar"
+
+msgid "common.share-link.view-current-page"
+msgstr "Yalnızca bu sayfa"
+
+msgid "common.share-link.view-selected-pages"
+msgstr "Seçili sayfalar"
+
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"
msgstr "Paylaşılan Kitaplık olarak ekle"
@@ -179,7 +221,7 @@ msgstr "(kopya)"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "dashboard.create-new-team"
-msgstr "Yeni takım oluştur"
+msgstr "+ Yeni takım oluştur"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "dashboard.default-team-name"
@@ -204,31 +246,87 @@ msgstr "%s dosyanın kopyasını oluştur"
msgid "dashboard.empty-files"
msgstr "Burada hiç dosyan yok"
+msgid "dashboard.export-frames"
+msgstr "Çalışma yüzeylerini PDF olarak dışarı aktar..."
+
msgid "dashboard.export-multi"
-msgstr "%s dosyalarını dışarı aktar"
+msgstr "%s dosyayı dışarı aktar"
msgid "dashboard.export-single"
msgstr "Dosyayı dışarı aktar"
+msgid "dashboard.export.detail"
+msgstr "* Bileşenleri, grafikleri, renkleri ve/veya tipografileri içerebilir."
+
+msgid "dashboard.export.explain"
+msgstr ""
+"Dışarı aktarmak istediğiniz bir veya daha fazla dosya, paylaşılan "
+"kütüphaneleri kullanıyor. Bunların varlıklarıyla ne yapmak istiyorsunuz*?"
+
+msgid "dashboard.export.options.all.message"
+msgstr ""
+"paylaşılan kütüphanelere sahip dosyalar, bağlantılarını koruyarak dışarı "
+"aktarmaya dahil edilecek."
+
+msgid "dashboard.export.options.all.title"
+msgstr "Paylaşılan kütüphaneleri dışarı aktar"
+
+msgid "dashboard.export.options.detach.message"
+msgstr ""
+"Paylaşılan kütüphaneler dışarı aktarmaya dahil edilmeyecek ve kütüphaneye "
+"hiçbir varlık eklenmeyecek. "
+
+msgid "dashboard.export.options.detach.title"
+msgstr "Paylaşılan kütüphane varlıklarını temel nesneler olarak ele al"
+
+msgid "dashboard.export.options.merge.message"
+msgstr ""
+"Dosyanız, tüm harici varlıklar kütüphane dosyasına birleştirilmiş olarak "
+"dışarı aktarılacak."
+
+msgid "dashboard.export.options.merge.title"
+msgstr "Dosya kütüphanelerine paylaşılan kütüphane varlıklarını dahil et"
+
+msgid "dashboard.export.title"
+msgstr "Dosyaları dışarı aktar"
+
msgid "dashboard.fonts.deleted-placeholder"
msgstr "Yazı tipi silindi"
msgid "dashboard.fonts.empty-placeholder"
msgstr "Kurulu özel yazı tipiniz bulunmamaktadır."
+#, markdown
+msgid "dashboard.fonts.hero-text1"
+msgstr ""
+"Buraya yüklediğiniz herhangi bir web yazı tipi, bu takımın dosyalarının "
+"metin özelliklerinde bulunan yazı tipi ailesi listesine eklenecek. Aynı "
+"yazı tipi ailesi adına sahip yazı tipleri, **tek yazı tipi ailesi** olarak "
+"gruplandırılacak. Yazı tiplerini şu biçimlerde yükleyebilirsiniz: **TTF, "
+"OTF ve WOFF** (yalnızca bir tane gerekli olacak)."
+
#, markdown
msgid "dashboard.fonts.hero-text2"
msgstr ""
"Sadece kendinize ait veya Penpot'ta kullanılabilecek bir lisansa sahip olan "
-"yazi tiplerini yükleyebilirsiniz. [Penpot'un Kullanım Şartları] içindeki "
-"İçerik hakları bölümünden detaylı bilgi alabilirsiniz "
-"(https://penpot.app/terms.html). Ayrıca [yazı tipi "
+"yazi tiplerini yükleyebilirsiniz. [Penpot'un Kullanım "
+"Şartları](https://penpot.app/terms.html) içindeki İçerik hakları bölümünden "
+"ayrıntılı bilgi alabilirsiniz. Ayrıca [yazı tipi "
"lisanslama](https://www.typography.com/faq) hakkında daha fazla bilgi almak "
"isteyebilirsiniz."
msgid "dashboard.import"
msgstr "Dosyaları içeri aktar"
+msgid "dashboard.import.analyze-error"
+msgstr "Oops! Bu dosyayı içeri aktaramadık"
+
+msgid "dashboard.import.import-error"
+msgstr "Dosya içeri aktarılırken bir sorun oluştu. Dosya içeri aktarılmadı."
+
+msgid "dashboard.import.import-message"
+msgstr "%s dosya başarıyla içeri aktarıldı."
+
#: src/app/main/ui/dashboard/team.cljs
msgid "dashboard.invite-profile"
msgstr "Takıma davet et"
@@ -239,7 +337,7 @@ msgstr "Takımdan ayrıl"
#: src/app/main/ui/dashboard/libraries.cljs
msgid "dashboard.libraries-title"
-msgstr "Paylaşılan Kitaplıklar"
+msgstr "Paylaşılan Kütüphaneler"
#: src/app/main/ui/dashboard/grid.cljs
msgid "dashboard.loading-files"
@@ -258,11 +356,11 @@ msgstr "%s dosyayı şuraya taşı"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.move-to-other-team"
-msgstr "Başkta takıma taşı"
+msgstr "Başka takıma taşı"
#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs
msgid "dashboard.new-file"
-msgstr "Yeni Dosya"
+msgstr "+ Yeni Dosya"
#: src/app/main/data/dashboard.cljs
msgid "dashboard.new-file-prefix"
@@ -270,7 +368,7 @@ msgstr "Yeni Dosya"
#: src/app/main/ui/dashboard/projects.cljs
msgid "dashboard.new-project"
-msgstr "Yeni Proje"
+msgstr "+ Yeni Proje"
#: src/app/main/data/dashboard.cljs
msgid "dashboard.new-project-prefix"
@@ -305,7 +403,7 @@ msgid "dashboard.open-in-new-tab"
msgstr "Dosyayı yeni sekmede aç"
msgid "dashboard.options"
-msgstr "Ayarlar"
+msgstr "Seçenekler"
#: src/app/main/ui/settings/password.cljs
msgid "dashboard.password-change"
@@ -325,11 +423,11 @@ msgstr "Sahibi olarak belirle"
#: src/app/main/ui/settings/profile.cljs
msgid "dashboard.remove-account"
-msgstr "Hesabını silmek istediğinden emin misin?"
+msgstr "Hesabınızı kaldırmak mı istiyorsunuz?"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.remove-shared"
-msgstr "Paylaşılan Kitaplık olarak sil"
+msgstr "Paylaşılan Kütüphane olarak sil"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "dashboard.search-placeholder"
@@ -337,7 +435,7 @@ msgstr "Ara…"
#: src/app/main/ui/dashboard/search.cljs
msgid "dashboard.searching-for"
-msgstr "%s aranıyor…"
+msgstr "“%s“ aranıyor…"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.select-ui-language"
@@ -397,7 +495,7 @@ msgstr "Takım projeleri"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.theme-change"
-msgstr "Önyüz teması"
+msgstr "Kullanıcı arayüzü teması"
#: src/app/main/ui/dashboard/search.cljs
msgid "dashboard.title-search"
@@ -429,7 +527,7 @@ msgstr "Penpot'un"
#: src/app/main/ui/confirm.cljs
msgid "ds.confirm-cancel"
-msgstr "Vazgeç"
+msgstr "İptal"
#: src/app/main/ui/confirm.cljs
msgid "ds.confirm-ok"
@@ -480,7 +578,7 @@ msgid "errors.ldap-disabled"
msgstr "LDAP ile oturum açma devre dışı bırakıldı."
msgid "errors.media-format-unsupported"
-msgstr "Görsel formatı desteklenmiyor (svg, jpg veya png olmalı)."
+msgstr "Görsel biçimi desteklenmiyor (svg, jpg veya png olmalı)."
#: src/app/main/data/workspace/persistence.cljs
msgid "errors.media-too-large"
@@ -488,7 +586,7 @@ msgstr "Bu görsel eklemek için çok büyük (5MB altında olmalı)."
#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs
msgid "errors.media-type-mismatch"
-msgstr "Dosya içeriği, uzantısı ile eşleşmiyor gibi görünüyor."
+msgstr "Görselin içeriği, dosya uzantısı ile eşleşmiyor gibi görünüyor."
#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs
msgid "errors.media-type-not-allowed"
@@ -594,7 +692,7 @@ msgstr "Bir hata oluştu"
#: src/app/main/ui/handoff/attributes/blur.cljs
msgid "handoff.attributes.blur"
-msgstr "Bulanıklaştır"
+msgstr "Bulanıklık"
#: src/app/main/ui/handoff/attributes/blur.cljs
msgid "handoff.attributes.blur.value"
@@ -628,6 +726,10 @@ msgstr "Yükseklik"
msgid "handoff.attributes.image.width"
msgstr "Genişlik"
+#: src/app/main/ui/handoff/attributes/layout.cljs
+msgid "handoff.attributes.layout"
+msgstr "Yerleşim düzeni"
+
#: src/app/main/ui/handoff/attributes/layout.cljs
msgid "handoff.attributes.layout.height"
msgstr "Yükseklik"
@@ -638,7 +740,7 @@ msgstr "Sol"
#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs
msgid "handoff.attributes.layout.radius"
-msgstr "Yarı Çap"
+msgstr "Yarıçap"
#: src/app/main/ui/handoff/attributes/layout.cljs
msgid "handoff.attributes.layout.rotation"
@@ -670,7 +772,7 @@ msgstr "Y"
#: src/app/main/ui/handoff/attributes/shadow.cljs
msgid "handoff.attributes.shadow.shorthand.spread"
-msgstr "Y"
+msgstr "S"
#: src/app/main/ui/handoff/attributes/stroke.cljs
msgid "handoff.attributes.stroke"
@@ -710,15 +812,15 @@ msgstr "Tipografi"
#: src/app/main/ui/handoff/attributes/text.cljs
msgid "handoff.attributes.typography.font-family"
-msgstr "Font Ailesi"
+msgstr "Yazı Tipi Ailesi"
#: src/app/main/ui/handoff/attributes/text.cljs
msgid "handoff.attributes.typography.font-size"
-msgstr "Font Boyutu"
+msgstr "Yazı Tipi Boyutu"
#: src/app/main/ui/handoff/attributes/text.cljs
msgid "handoff.attributes.typography.font-style"
-msgstr "Font Stili"
+msgstr "Yazı Tipi Biçimi"
#: src/app/main/ui/handoff/attributes/text.cljs
msgid "handoff.attributes.typography.letter-spacing"
@@ -830,6 +932,9 @@ msgstr "İptal"
msgid "labels.centered"
msgstr "Orta"
+msgid "labels.close"
+msgstr "Kapat"
+
#: src/app/main/ui/dashboard/comments.cljs
msgid "labels.comments"
msgstr "Yorumlar"
@@ -841,6 +946,9 @@ msgstr "Parolayı onayla"
msgid "labels.content"
msgstr "İçerik"
+msgid "labels.continue"
+msgstr "Devam et"
+
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "labels.create"
msgstr "Oluştur"
@@ -854,11 +962,14 @@ msgid "labels.create-team.placeholder"
msgstr "Yeni takım adı gir"
msgid "labels.custom-fonts"
-msgstr "Özel Fontlar"
+msgstr "Özel yazı tipleri"
#: src/app/main/ui/settings/sidebar.cljs
msgid "labels.dashboard"
-msgstr "Kontrol paneli"
+msgstr "Denetim paneli"
+
+msgid "labels.default"
+msgstr "varsayılan"
#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "labels.delete"
@@ -884,14 +995,20 @@ msgstr "Taslak"
msgid "labels.edit"
msgstr "Düzenle"
+msgid "labels.edit-file"
+msgstr "Dosya düzenle"
+
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
msgid "labels.editor"
-msgstr "Editör"
+msgstr "Düzenleyici"
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
msgid "labels.email"
msgstr "E-posta"
+msgid "labels.export"
+msgstr "Dışarı aktar"
+
#: src/app/main/ui/settings/feedback.cljs
msgid "labels.feedback-disabled"
msgstr "Geri bildirim devre dışı bırakıldı"
@@ -901,10 +1018,10 @@ msgid "labels.feedback-sent"
msgstr "Geri bildirim gönderildi"
msgid "labels.font-family"
-msgstr "Font Ailesi"
+msgstr "Yazı Tipi Ailesi"
msgid "labels.font-providers"
-msgstr "Font sağlayıcısı"
+msgstr "Yazı tipi sağlayıcıları"
msgid "labels.font-variant"
msgstr "Stil"
@@ -913,7 +1030,7 @@ msgid "labels.font-variants"
msgstr "Biçimler"
msgid "labels.fonts"
-msgstr "Fontlar"
+msgstr "Yazı tipleri"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.give-feedback"
@@ -933,7 +1050,7 @@ msgid "labels.images"
msgstr "Görseller"
msgid "labels.installed-fonts"
-msgstr "Yüklenmiş fontlar"
+msgstr "Kurulu yazı tipleri"
#: src/app/main/ui/static.cljs
msgid "labels.internal-error.desc-message"
@@ -949,12 +1066,15 @@ msgstr "İç Hata"
msgid "labels.language"
msgstr "Dil"
+msgid "labels.link"
+msgstr "Bağlantı"
+
#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.logout"
msgstr "Çıkış Yap"
msgid "labels.manage-fonts"
-msgstr "Fontları yönet"
+msgstr "Yazı tiplerini yönet"
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.members"
@@ -990,6 +1110,11 @@ msgid_plural "labels.num-of-files"
msgstr[0] "1 dosya"
msgstr[1] "%s dosya"
+msgid "labels.num-of-frames"
+msgid_plural "labels.num-of-frames"
+msgstr[0] "1 çalışma yüzeyi"
+msgstr[1] "%s çalışma yüzeyi"
+
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.num-of-projects"
msgid_plural "labels.num-of-projects"
@@ -1004,6 +1129,9 @@ msgstr "Eski parola"
msgid "labels.only-yours"
msgstr "Sadece seninkiler"
+msgid "labels.or"
+msgstr "veya"
+
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs
msgid "labels.owner"
msgstr "Sahip"
@@ -1055,7 +1183,7 @@ msgid "labels.save"
msgstr "Kaydet"
msgid "labels.search-font"
-msgstr "Font ara"
+msgstr "Yazı tipi ara"
#: src/app/main/ui/settings/feedback.cljs
msgid "labels.send"
@@ -1083,7 +1211,7 @@ msgstr "Prototipi paylaş"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "labels.shared-libraries"
-msgstr "Paylaşılan Kitaplıklar"
+msgstr "Paylaşılan Kütüphaneler"
#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs
msgid "labels.show-all-comments"
@@ -1097,6 +1225,12 @@ msgstr "Yalnızca kendi yorumlarımı göster"
msgid "labels.sign-out"
msgstr "Çıkış yap"
+msgid "labels.skip"
+msgstr "Atla"
+
+msgid "labels.start"
+msgstr "Başla"
+
#: src/app/main/ui/settings/profile.cljs
msgid "labels.update"
msgstr "Güncelle"
@@ -1116,7 +1250,10 @@ msgstr "Yükleniyor…"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.viewer"
-msgstr "Görüntüler"
+msgstr "Görüntüleyici"
+
+msgid "labels.workspace"
+msgstr "Çalışma alanı"
#: src/app/main/ui/comments.cljs
msgid "labels.write-new-comment"
@@ -1124,7 +1261,7 @@ msgstr "Yeni yorum yaz"
#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs
msgid "media.loading"
-msgstr "Resim yükleniyor…"
+msgstr "Görsel yükleniyor…"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.add-shared-confirm.accept"
@@ -1206,7 +1343,7 @@ msgstr "Dosya siliniyor"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-file-multi-confirm.accept"
-msgstr "Dosyalar sil"
+msgstr "Dosyaları sil"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-file-multi-confirm.message"
@@ -1214,15 +1351,23 @@ msgstr "%s dosyayı silmek istediğinden emin misin?"
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-file-multi-confirm.title"
-msgstr "%s dosyanın silinmesi"
+msgstr "%s dosyayı sil"
+
+msgid "modals.delete-font-variant.message"
+msgstr ""
+"Bu yazı tipi biçimini silmek istediğinizden emin misiniz? Bir dosyada "
+"kullanılıyorsa yüklenmeyecektir."
+
+msgid "modals.delete-font-variant.title"
+msgstr "Yazı tipi biçimini sil"
msgid "modals.delete-font.message"
msgstr ""
-"Bu fontu silmek istediğine emin misin? Bir dosyada kullanılıyorsa "
+"Bu yazı tipini silmek istediğinize emin misiniz? Bir dosyada kullanılıyorsa "
"yüklenmeyecektir."
msgid "modals.delete-font.title"
-msgstr "Fontu sil"
+msgstr "Yazı tipini sil"
#: src/app/main/ui/workspace/sidebar/sitemap.cljs
msgid "modals.delete-page.body"
@@ -1256,7 +1401,7 @@ msgstr ""
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.delete-team-confirm.title"
-msgstr "Takımın silinmesi"
+msgstr "Takımı sil"
#: src/app/main/ui/dashboard/team.cljs
msgid "modals.delete-team-member-confirm.accept"
@@ -1289,19 +1434,19 @@ msgstr "%s sahibisiniz."
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.leave-and-reassign.hint2"
-msgstr "Ayrılmadan önce terfi etmek için başka bir üye seçin"
+msgstr "Ayrılmadan önce terfi ettirmek için başka bir üye seçin"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.leave-and-reassign.promote-and-leave"
-msgstr "Terfi et ve ayrıl"
+msgstr "Terfi ettir ve ayrıl"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.leave-and-reassign.select-memeber-to-promote"
-msgstr "Terfi etmek için bir üye seçin"
+msgstr "Terfi ettirmek için bir üye seçin"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.leave-and-reassign.title"
-msgstr "Terfi etmek için bir üye seçin"
+msgstr "Terfi ettirmek için bir üye seçin"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.leave-confirm.accept"
@@ -1313,23 +1458,33 @@ msgstr "Bu takımdan ayrılmak istediğinden emin misin?"
#: src/app/main/ui/dashboard/sidebar.cljs
msgid "modals.leave-confirm.title"
-msgstr "Takımdan ayrılmak"
+msgstr "Takımdan ayrıl"
#: src/app/main/ui/dashboard/team.cljs
msgid "modals.promote-owner-confirm.accept"
-msgstr "Terfi et"
+msgstr "Terfi ettir"
#: src/app/main/ui/dashboard/team.cljs
msgid "modals.promote-owner-confirm.message"
-msgstr "Bu kullanıcıyı sahip olarak terfi etmek istediğinden emin misin?"
+msgstr "Bu kullanıcıyı sahip olarak terfi ettirmek istediğinden emin misin?"
#: src/app/main/ui/dashboard/team.cljs
msgid "modals.promote-owner-confirm.title"
-msgstr "Sahip olarak terfi et"
+msgstr "Sahip olarak terfi ettir"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.remove-shared-confirm.accept"
-msgstr "Paylaşılmış Kütüphane olarak kaldır"
+msgstr "Paylaşılan Kütüphane olarak kaldır"
+
+#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.remove-shared-confirm.hint"
+msgstr ""
+"Paylaşılan Kütüphane olarak kaldırıldıktan sonra, bu dosyanın Dosya "
+"Kütüphanesi, dosyalarınızın geri kalanında artık kullanılabilir olmayacak."
+
+#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
+msgid "modals.remove-shared-confirm.message"
+msgstr "“%s” Paylaşılan Kütüphanesini Kaldır"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs
msgid "modals.update-remote-component.accept"
@@ -1355,7 +1510,7 @@ msgstr "Davet başarıyla iletildi"
#: src/app/main/ui/settings/delete_account.cljs
msgid "notifications.profile-deletion-not-allowed"
-msgstr "Profilini silemezsin. Önce takımlarını birine atamalsın."
+msgstr "Profilini silemezsin. Önce takımlarını birine atamalısın."
#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs
msgid "notifications.profile-saved"
@@ -1363,7 +1518,115 @@ msgstr "Profil başarıyla kaydedildi!"
#: src/app/main/ui/settings/change_email.cljs
msgid "notifications.validation-email-sent"
-msgstr "%s adresine doğrulama e-postası gönderildi. E-postalarınızı kontrol edin!"
+msgstr "%s adresine doğrulama e-postası gönderildi. E-posta kutunuza bakın!"
+
+msgid "onboarding.contrib.alt"
+msgstr "Açık Kaynak"
+
+msgid "onboarding.contrib.desc1"
+msgstr ""
+"Penpot, topluluk tarafından ve topluluk için yapılan Açık Kaynaklı bir "
+"projedir. İş birliği yapmak istiyorsanız hoş geldiniz!"
+
+msgid "onboarding.contrib.desc2.1"
+msgstr "Projeye"
+
+msgid "onboarding.contrib.desc2.2"
+msgstr "ve katkıda bulunma talimatlarını izleyebilirsiniz :)"
+
+msgid "onboarding.contrib.link"
+msgstr "github'da erişebilir"
+
+msgid "onboarding.contrib.title"
+msgstr "Açık Kaynağa Katkıda Bulunan Birisi?"
+
+msgid "onboarding.slide.0.alt"
+msgstr "Tasarımlar oluşturun"
+
+msgid "onboarding.slide.0.desc1"
+msgstr "Tüm takım üyeleriyle iş birliği içinde güzel kullanıcı arayüzleri oluşturun."
+
+msgid "onboarding.slide.0.desc2"
+msgstr ""
+"Bileşenler, kütüphaneler ve tasarım sistemleriyle uygun ölçekte tutarlılığı "
+"sağlayın."
+
+msgid "onboarding.slide.0.title"
+msgstr "Kütüphaneler, biçimler ve bileşenler tasarlayın"
+
+msgid "onboarding.slide.1.alt"
+msgstr "Etkileşimli prototipler"
+
+msgid "onboarding.slide.1.desc1"
+msgstr "Ürün davranışını taklit etmek için zengin etkileşimler oluşturun."
+
+msgid "onboarding.slide.1.desc2"
+msgstr ""
+"Paydaşlarla paylaşın, takımınıza teklifler sunun ve tasarımlarınız için "
+"kullanıcı testlerini başlatın, hepsi tek bir yerde."
+
+msgid "onboarding.slide.1.title"
+msgstr "Etkileşimlerle tasarımlarınıza hayat verin"
+
+msgid "onboarding.slide.2.alt"
+msgstr "Geri bildirim alın"
+
+msgid "onboarding.slide.2.desc1"
+msgstr ""
+"Tüm takım üyeleri tasarımlar üzerinde gerçek zamanlı tasarım, çok oyunculu "
+"ve merkezi yorumlar, fikirler ve geri bildirimler ile aynı anda çalışır."
+
+msgid "onboarding.slide.2.title"
+msgstr "Geri bildirim alın, çalışmanızı sunun ve paylaşın"
+
+msgid "onboarding.slide.3.desc1"
+msgstr ""
+"Tüm bileşenlerinizin ve biçimlerinizin tasarımını ve kodunu eşzamanlayın ve "
+"kod parçacıkları alın."
+
+msgid "onboarding.slide.3.desc2"
+msgstr ""
+"İşaretleme (SVG, HTML) veya biçimler (CSS, Less, Stylus…) gibi kod "
+"özellikleri alın ve sağlayın."
+
+msgid "onboarding.team.create.button"
+msgstr "Takım oluştur"
+
+msgid "onboarding.team.create.desc1"
+msgstr ""
+"Biriyle mi çalışıyorsunuz? Projelerde birlikte çalışmak ve tasarım "
+"varlıklarını paylaşmak için bir takım oluşturun."
+
+msgid "onboarding.team.create.input-placeholder"
+msgstr "Yeni takım adı gir"
+
+msgid "onboarding.team.create.title"
+msgstr "Takım oluştur"
+
+msgid "onboarding.team.start.button"
+msgstr "Hemen başla"
+
+msgid "onboarding.team.start.desc1"
+msgstr ""
+"Hemen Penpot'a atlayın ve kendi başınıza tasarlamaya başlayın. Daha sonra "
+"takımlar oluşturma şansınız olacak."
+
+msgid "onboarding.team.start.title"
+msgstr "Tasarlamaya başla"
+
+msgid "onboarding.welcome.alt"
+msgstr "Penpot"
+
+msgid "onboarding.welcome.desc1"
+msgstr "Sizi ilk Alfa sürümüyle tanıştırmaktan mutluluk duyuyoruz."
+
+msgid "onboarding.welcome.desc2"
+msgstr ""
+"Penpot hala geliştirme aşamasındadır ve sürekli güncellemeler olacaktır. "
+"İlk kararlı sürümü beğeneceğinizi umuyoruz."
+
+msgid "onboarding.welcome.title"
+msgstr "Penpot'a hoş geldiniz!"
#: src/app/main/ui/auth/recovery.cljs
msgid "profile.recovery.go-to-login"
@@ -1379,11 +1642,11 @@ msgstr "%s - Penpot"
#: src/app/main/ui/dashboard/fonts.cljs
msgid "title.dashboard.font-providers"
-msgstr "Yazıtipi Sağlayıcıları - %s - Penpot"
+msgstr "Yazı Tipi Sağlayıcıları - %s - Penpot"
#: src/app/main/ui/dashboard/fonts.cljs
msgid "title.dashboard.fonts"
-msgstr "Fontlar - %s - Penpot"
+msgstr "Yazı Tipleri - %s - Penpot"
#: src/app/main/ui/dashboard/projects.cljs
msgid "title.dashboard.projects"
@@ -1423,7 +1686,7 @@ msgstr "Üyeler - %s - Penpot"
#: src/app/main/ui/dashboard/team.cljs
msgid "title.team-settings"
-msgstr "Ayarlar * %s - Penpot"
+msgstr "Ayarlar - %s - Penpot"
#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
msgid "title.viewer"
@@ -1435,11 +1698,14 @@ msgstr "%s - Penpot"
#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
msgid "viewer.empty-state"
-msgstr "Sayfada çerçeve bulunmuyor."
+msgstr "Sayfada çalışma yüzeyi bulunamadı."
#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
msgid "viewer.frame-not-found"
-msgstr "Çerçeve bulunmadı."
+msgstr "Çalışma yüzeyi bulunamadı."
+
+msgid "viewer.header.comments-section"
+msgstr "Yorumlar"
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.dont-show-interactions"
@@ -1461,6 +1727,9 @@ msgstr "Tam Ekran"
msgid "viewer.header.interactions"
msgstr "Etkileşimler"
+msgid "viewer.header.interactions-section"
+msgstr "Etkileşimler"
+
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.share.copy-link"
msgstr "Bağlantıyı kopyala"
@@ -1548,6 +1817,10 @@ msgstr "Bileşenler"
msgid "workspace.assets.create-group"
msgstr "Grup oluştur"
+#: src/app/main/ui/workspace/sidebar/assets.cljs
+msgid "workspace.assets.create-group-hint"
+msgstr "Ögeleriniz otomatik olarak \"grup adı / öge adı\" olarak adlandırılacak"
+
#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.delete"
msgstr "Sil"
@@ -1590,7 +1863,7 @@ msgstr "Yeniden adlandır"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.rename-group"
-msgstr "Grubu yeniden isimlendir"
+msgstr "Grubu yeniden adlandır"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.search"
@@ -1599,7 +1872,7 @@ msgstr "Varlık ara"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.selected-count"
msgid_plural "workspace.assets.selected-count"
-msgstr[0] "Tek öge seçildi"
+msgstr[0] "%s öge seçildi"
msgstr[1] "%s öge seçildi"
#: src/app/main/ui/workspace/sidebar/assets.cljs
@@ -1634,6 +1907,10 @@ msgstr "Harf Boşluğu"
msgid "workspace.assets.typography.line-height"
msgstr "Satır Yüksekliği"
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs
+msgid "workspace.assets.typography.sample"
+msgstr "Ag"
+
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
msgid "workspace.assets.typography.text-transform"
msgstr "Metin Dönüşümü"
@@ -1720,7 +1997,7 @@ msgstr "Cetveli göster"
#: src/app/main/ui/workspace/header.cljs
msgid "workspace.header.save-error"
-msgstr "Kaydetmede hata"
+msgstr "Kaydetme hatası"
#: src/app/main/ui/workspace/header.cljs
msgid "workspace.header.saved"
@@ -1754,10 +2031,22 @@ msgstr "Büyük önizlemeler"
msgid "workspace.libraries.colors.file-library"
msgstr "Dosya kütüphanesi"
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.hsv"
+msgstr "HSV"
+
#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs
msgid "workspace.libraries.colors.recent-colors"
msgstr "Son renkler"
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.rgb-complementary"
+msgstr "RGB Tamamlayıcı"
+
+#: src/app/main/ui/workspace/colorpicker.cljs
+msgid "workspace.libraries.colors.rgba"
+msgstr "RGBA"
+
#: src/app/main/ui/workspace/colorpicker.cljs
msgid "workspace.libraries.colors.save-color"
msgstr "Renk biçimini kaydet"
@@ -1839,8 +2128,11 @@ msgstr "Kütüphaneler"
msgid "workspace.library.own"
msgstr "Kütüphanelerim"
+msgid "workspace.library.store"
+msgstr "Mağaza kütüphaneleri"
+
msgid "workspace.options.blur-options.background-blur"
-msgstr "Arkaplan"
+msgstr "Arka plan"
msgid "workspace.options.blur-options.layer-blur"
msgstr "Katman"
@@ -1849,6 +2141,18 @@ msgstr "Katman"
msgid "workspace.options.blur-options.title"
msgstr "Bulanıklık"
+#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
+msgid "workspace.options.blur-options.title.group"
+msgstr "Grup bulanıklığı"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
+msgid "workspace.options.blur-options.title.multiple"
+msgstr "Seçim bulanıklığı"
+
+#: src/app/main/ui/workspace/sidebar/options/page.cljs
+msgid "workspace.options.canvas-background"
+msgstr "Tuval arka planı"
+
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs
msgid "workspace.options.component"
msgstr "Bileşen"
@@ -1929,6 +2233,10 @@ msgstr "Sütunlar"
msgid "workspace.options.grid.params.columns"
msgstr "Sütunlar"
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.params.gutter"
+msgstr "Aralık"
+
#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
msgid "workspace.options.grid.params.height"
msgstr "Yükseklik"
@@ -1993,6 +2301,10 @@ msgstr "Satırlar"
msgid "workspace.options.grid.square"
msgstr "Kare"
+#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs
+msgid "workspace.options.grid.title"
+msgstr "Izgara ve Yerleşim Düzenleri"
+
#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
msgid "workspace.options.layer-options.blend-mode.color"
msgstr "Renk"
@@ -2001,6 +2313,34 @@ msgstr "Renk"
msgid "workspace.options.layer-options.blend-mode.color-burn"
msgstr "Renk yanması"
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.darken"
+msgstr "Karart"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.difference"
+msgstr "Fark"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.hard-light"
+msgstr "Sert ışık"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.hue"
+msgstr "Ton"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.lighten"
+msgstr "Aydınlat"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.luminosity"
+msgstr "Parlaklık"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.multiply"
+msgstr "Çoğalt"
+
#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
msgid "workspace.options.layer-options.blend-mode.normal"
msgstr "Normal"
@@ -2017,6 +2357,10 @@ msgstr "Doygunluk"
msgid "workspace.options.layer-options.blend-mode.screen"
msgstr "Ekran"
+#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+msgid "workspace.options.layer-options.blend-mode.soft-light"
+msgstr "Yumuşak ışık"
+
#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
msgid "workspace.options.layer-options.title"
msgstr "Katman"
@@ -2035,7 +2379,7 @@ msgstr "Git"
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.none"
-msgstr "Hiç biri"
+msgstr "Hiçbiri"
#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.position"
@@ -2046,7 +2390,7 @@ msgid "workspace.options.prototype"
msgstr "Prototip"
msgid "workspace.options.radius"
-msgstr "Yarı çap"
+msgstr "Yarıçap"
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.radius.all-corners"
@@ -2070,6 +2414,10 @@ msgstr ""
msgid "workspace.options.select-artboard"
msgstr "Çalışma yüzeyi seç"
+#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
+msgid "workspace.options.shadow-options.blur"
+msgstr "Bulanıklık"
+
#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
msgid "workspace.options.shadow-options.drop-shadow"
msgstr "Kabartı gölgesi"
@@ -2106,10 +2454,38 @@ msgstr "Gölge seçimi"
msgid "workspace.options.size"
msgstr "Boyut"
+#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs
+msgid "workspace.options.size-presets"
+msgstr "Boyut ön ayarları"
+
#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
msgid "workspace.options.stroke"
msgstr "Kontur"
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.circle-marker"
+msgstr "Daire işaretleyici"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.diamond-marker"
+msgstr "Elmas işaretleyici"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.none"
+msgstr "Hiçbiri"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.round"
+msgstr "Yuvarlak"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.square"
+msgstr "Kare"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+msgid "workspace.options.stroke-cap.square-marker"
+msgstr "Kare işaretleyici"
+
#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
msgid "workspace.options.stroke.center"
msgstr "Merkez"
@@ -2209,10 +2585,17 @@ msgstr "Küçük harf"
msgid "workspace.options.text-options.none"
msgstr "Hiçbiri"
+#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+msgid "workspace.options.text-options.preset"
+msgstr "Ön ayar"
+
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.strikethrough"
msgstr "Üstü çizili"
+msgid "workspace.options.text-options.text-case"
+msgstr "Durum"
+
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.title"
msgstr "Metin"
@@ -2238,7 +2621,7 @@ msgid "workspace.options.text-options.uppercase"
msgstr "Büyük Harf"
msgid "workspace.options.text-options.vertical-align"
-msgstr "Düşey hizalama"
+msgstr "Dikey hizalama"
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.use-play-button"
@@ -2276,11 +2659,11 @@ msgstr "Düğümleri tuttur (%s)"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.back"
-msgstr "Arkaya gönder"
+msgstr "En arkaya gönder"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.backward"
-msgstr "En arkaya gönder"
+msgstr "Arkaya gönder"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.copy"
@@ -2298,6 +2681,10 @@ msgstr "Kes"
msgid "workspace.shape.menu.delete"
msgstr "Sil"
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.detach-instance"
+msgstr "Örneği ayır"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.duplicate"
msgstr "Çoğalt"
@@ -2346,6 +2733,10 @@ msgstr "Maskele"
msgid "workspace.shape.menu.paste"
msgstr "Yapıştır"
+#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.reset-overrides"
+msgstr "Geçersiz kılmaları sıfırla"
+
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.show"
msgstr "Göster"
@@ -2360,7 +2751,11 @@ msgstr "Grubu dağıt"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.unlock"
-msgstr "Çöz"
+msgstr "Kilidi aç"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.unmask"
+msgstr "Maskelemeyi kaldır"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.update-main"
@@ -2376,7 +2771,7 @@ msgstr "Katmanlar (%s)"
#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs
msgid "workspace.sidebar.options.svg-attrs.title"
-msgstr "SVG Öznitelikleri İçeri Aktarıldı"
+msgstr "İçe Aktarılan SVG Öznitelikleri"
#: src/app/main/ui/workspace/sidebar/sitemap.cljs
msgid "workspace.sidebar.sitemap"
@@ -2412,12 +2807,16 @@ msgstr "Çalışma Yüzeyi (%s)"
#: src/app/main/ui/workspace/left_toolbar.cljs
msgid "workspace.toolbar.image"
-msgstr "Resim (%s)"
+msgstr "Görsel (%s)"
#: src/app/main/ui/workspace/left_toolbar.cljs
msgid "workspace.toolbar.move"
msgstr "Taşı"
+#: src/app/main/ui/workspace/left_toolbar.cljs
+msgid "workspace.toolbar.path"
+msgstr "Yol (%s)"
+
#: src/app/main/ui/workspace/left_toolbar.cljs
msgid "workspace.toolbar.rect"
msgstr "Dikdörtgen (%s)"
@@ -2461,7 +2860,7 @@ msgid "workspace.undo.entry.multiple.group"
msgstr "gruplar"
msgid "workspace.undo.entry.multiple.media"
-msgstr "grafik varlığı"
+msgstr "grafik varlıkları"
msgid "workspace.undo.entry.multiple.multiple"
msgstr "nesneler"
@@ -2469,6 +2868,9 @@ msgstr "nesneler"
msgid "workspace.undo.entry.multiple.page"
msgstr "sayfalar"
+msgid "workspace.undo.entry.multiple.path"
+msgstr "yollar"
+
msgid "workspace.undo.entry.multiple.rect"
msgstr "dikdörtgenler"
@@ -2478,6 +2880,9 @@ msgstr "şekiller"
msgid "workspace.undo.entry.multiple.text"
msgstr "metinler"
+msgid "workspace.undo.entry.multiple.typography"
+msgstr "tipografi varlıkları"
+
#: src/app/main/ui/workspace/sidebar/history.cljs
msgid "workspace.undo.entry.new"
msgstr "Yeni %s"
@@ -2501,7 +2906,7 @@ msgid "workspace.undo.entry.single.group"
msgstr "grup"
msgid "workspace.undo.entry.single.image"
-msgstr "resim"
+msgstr "görsel"
msgid "workspace.undo.entry.single.media"
msgstr "grafik varlığı"
@@ -2512,6 +2917,9 @@ msgstr "nesne"
msgid "workspace.undo.entry.single.page"
msgstr "sayfa"
+msgid "workspace.undo.entry.single.path"
+msgstr "yol"
+
msgid "workspace.undo.entry.single.rect"
msgstr "dikdörtgen"
@@ -2521,6 +2929,9 @@ msgstr "şekil"
msgid "workspace.undo.entry.single.text"
msgstr "metin"
+msgid "workspace.undo.entry.single.typography"
+msgstr "tipografi varlığı"
+
#: src/app/main/ui/workspace/sidebar/history.cljs
msgid "workspace.undo.entry.unknown"
msgstr "%s üstündeki işlem"
@@ -2531,12 +2942,15 @@ msgstr "Geçmiş"
#: src/app/main/data/workspace/libraries.cljs
msgid "workspace.updates.dismiss"
-msgstr "Gözardı et"
+msgstr "Yoksay"
#: src/app/main/data/workspace/libraries.cljs
msgid "workspace.updates.there-are-updates"
-msgstr "Paylaşılmış kütüphanelerde güncellemeler mevcut"
+msgstr "Paylaşılan kütüphaneler için güncellemeler var"
#: src/app/main/data/workspace/libraries.cljs
msgid "workspace.updates.update"
-msgstr "Güncelle"
\ No newline at end of file
+msgstr "Güncelle"
+
+msgid "workspace.viewport.click-to-close-path"
+msgstr "Yolu kapatmak için tıklayın"
\ No newline at end of file
diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po
index 71d16f01c..2aa70e16e 100644
--- a/frontend/translations/zh_CN.po
+++ b/frontend/translations/zh_CN.po
@@ -2101,14 +2101,6 @@ msgstr "图层成组"
msgid "workspace.options.layer-options.title.multiple"
msgstr "已选中的图层"
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.navigate-to"
-msgstr "导航到"
-
-#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
-msgid "workspace.options.none"
-msgstr "无"
-
#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.position"
msgstr "位置"
diff --git a/frontend/translations/zh_Hant.po b/frontend/translations/zh_Hant.po
new file mode 100644
index 000000000..b1fc23594
--- /dev/null
+++ b/frontend/translations/zh_Hant.po
@@ -0,0 +1,6 @@
+msgid ""
+msgstr ""
+"X-Generator: Weblate\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
\ No newline at end of file
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index ca1890d13..e525c2b5a 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -2,12 +2,12 @@
# yarn lockfile v1
-"@babel/runtime-corejs3@^7.12.1":
- version "7.14.0"
- resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.14.0.tgz#6bf5fbc0b961f8e3202888cb2cd0fb7a0a9a3f66"
- integrity sha512-0R0HTZWHLk6G8jIk0FtoX+AatCtKnswS98VhXwGImFc759PJRp4Tru0PQYZofyijTFUr+gT8Mu7sgXVJLQ0ceg==
+"@babel/runtime-corejs3@^7.14.9":
+ version "7.15.4"
+ resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.15.4.tgz#403139af262b9a6e8f9ba04a6fdcebf8de692bf1"
+ integrity sha512-lWcAqKeB624/twtTc3w6w/2o9RqJPaNBhPGK6DKLSiwuVWC7WFkypWyNg+CpZoyJH0jVzv1uMtXZ/5/lQOLtCg==
dependencies:
- core-js-pure "^3.0.0"
+ core-js-pure "^3.16.0"
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7":
@@ -45,6 +45,81 @@
normalize-path "^2.0.1"
through2 "^2.0.3"
+"@sentry/browser@^6.12.0":
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.12.0.tgz#970cd68fa117a1e1336fdb373e3b1fa76cd63e2d"
+ integrity sha512-wsJi1NLOmfwtPNYxEC50dpDcVY7sdYckzwfqz1/zHrede1mtxpqSw+7iP4bHADOJXuF+ObYYTHND0v38GSXznQ==
+ dependencies:
+ "@sentry/core" "6.12.0"
+ "@sentry/types" "6.12.0"
+ "@sentry/utils" "6.12.0"
+ tslib "^1.9.3"
+
+"@sentry/core@6.12.0":
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.12.0.tgz#bc7c5f0785b6a392d9ad47bd9b1fae3f5389996c"
+ integrity sha512-mU/zdjlzFHzdXDZCPZm8OeCw7c9xsbL49Mq0TrY0KJjLt4CJBkiq5SDTGfRsenBLgTedYhe5Z/J8Z+xVVq+MfQ==
+ dependencies:
+ "@sentry/hub" "6.12.0"
+ "@sentry/minimal" "6.12.0"
+ "@sentry/types" "6.12.0"
+ "@sentry/utils" "6.12.0"
+ tslib "^1.9.3"
+
+"@sentry/hub@6.12.0":
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.12.0.tgz#29e323ab6a95e178fb14fffb684aa0e09707197f"
+ integrity sha512-yR/UQVU+ukr42bSYpeqvb989SowIXlKBanU0cqLFDmv5LPCnaQB8PGeXwJAwWhQgx44PARhmB82S6Xor8gYNxg==
+ dependencies:
+ "@sentry/types" "6.12.0"
+ "@sentry/utils" "6.12.0"
+ tslib "^1.9.3"
+
+"@sentry/minimal@6.12.0":
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.12.0.tgz#cbe20e95056cedb9709d7d5b2119ef95206a9f8c"
+ integrity sha512-r3C54Q1KN+xIqUvcgX9DlcoWE7ezWvFk2pSu1Ojx9De81hVqR9u5T3sdSAP2Xma+um0zr6coOtDJG4WtYlOtsw==
+ dependencies:
+ "@sentry/hub" "6.12.0"
+ "@sentry/types" "6.12.0"
+ tslib "^1.9.3"
+
+"@sentry/tracing@^6.12.0":
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.12.0.tgz#a05c8985ee7fed7310b029b147d8f9f14f2a2e67"
+ integrity sha512-u10QHNknPBzbWSUUNMkvuH53sQd5NaBo6YdNPj4p5b7sE7445Sh0PwBpRbY3ZiUUiwyxV59fx9UQ4yVnPGxZQA==
+ dependencies:
+ "@sentry/hub" "6.12.0"
+ "@sentry/minimal" "6.12.0"
+ "@sentry/types" "6.12.0"
+ "@sentry/utils" "6.12.0"
+ tslib "^1.9.3"
+
+"@sentry/types@6.12.0":
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.12.0.tgz#b7395688a79403c6df8d8bb8d81deb8222519853"
+ integrity sha512-urtgLzE4EDMAYQHYdkgC0Ei9QvLajodK1ntg71bGn0Pm84QUpaqpPDfHRU+i6jLeteyC7kWwa5O5W1m/jrjGXA==
+
+"@sentry/utils@6.12.0":
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.12.0.tgz#3de261e8d11bdfdc7add64a3065d43517802e975"
+ integrity sha512-oRHQ7TH5TSsJqoP9Gqq25Jvn9LKexXfAh/OoKwjMhYCGKGhqpDNUIZVgl9DWsGw5A5N5xnQyLOxDfyRV5RshdA==
+ dependencies:
+ "@sentry/types" "6.12.0"
+ tslib "^1.9.3"
+
+"@sindresorhus/is@^0.14.0":
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
+ integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
+
+"@szmarczak/http-timer@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
+ integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==
+ dependencies:
+ defer-to-connect "^1.0.1"
+
"@types/q@^1.5.1":
version "1.5.4"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
@@ -70,6 +145,13 @@ ajv@^6.12.3:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
+ansi-align@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
+ integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==
+ dependencies:
+ string-width "^4.1.0"
+
ansi-colors@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9"
@@ -99,6 +181,11 @@ ansi-regex@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -106,7 +193,7 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
-ansi-styles@^4.1.0:
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
@@ -311,13 +398,13 @@ atob@^2.1.2:
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
autoprefixer@^10.2.4:
- version "10.3.1"
- resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.3.1.tgz#954214821d3aa06692406c6a0a9e9d401eafbed2"
- integrity sha512-L8AmtKzdiRyYg7BUXJTzigmhbQRCXFKz6SA1Lqo0+AR2FBbQ4aTAPFSDlOutnFkjhiz8my4agGXog1xlMjPJ6A==
+ version "10.3.4"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.3.4.tgz#29efe5d19f51c281953178ddb5b84c5f1ca24c86"
+ integrity sha512-EKjKDXOq7ug+jagLzmnoTRpTT0q1KVzEJqrJd0hCBa7FiG0WbFOBCcJCy2QkW1OckpO3qgttA1aWjVbeIPAecw==
dependencies:
- browserslist "^4.16.6"
- caniuse-lite "^1.0.30001243"
- colorette "^1.2.2"
+ browserslist "^4.16.8"
+ caniuse-lite "^1.0.30001252"
+ colorette "^1.3.0"
fraction.js "^4.1.1"
normalize-range "^0.1.2"
postcss-value-parser "^4.1.0"
@@ -414,6 +501,20 @@ boolbase@^1.0.0, boolbase@~1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
+boxen@^5.0.0:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50"
+ integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==
+ dependencies:
+ ansi-align "^3.0.0"
+ camelcase "^6.2.0"
+ chalk "^4.1.0"
+ cli-boxes "^2.2.1"
+ string-width "^4.2.2"
+ type-fest "^0.20.2"
+ widest-line "^3.1.0"
+ wrap-ansi "^7.0.0"
+
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -516,16 +617,16 @@ browserify-zlib@^0.2.0:
dependencies:
pako "~1.0.5"
-browserslist@^4.16.6:
- version "4.16.6"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2"
- integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==
+browserslist@^4.16.8:
+ version "4.17.0"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.0.tgz#1fcd81ec75b41d6d4994fb0831b92ac18c01649c"
+ integrity sha512-g2BJ2a0nEYvEFQC208q8mVAhfNwpZ5Mu8BwgtCdZKO3qx98HChmeg448fPdUzld8aFmfLgVh7yymqV+q1lJZ5g==
dependencies:
- caniuse-lite "^1.0.30001219"
- colorette "^1.2.2"
- electron-to-chromium "^1.3.723"
+ caniuse-lite "^1.0.30001254"
+ colorette "^1.3.0"
+ electron-to-chromium "^1.3.830"
escalade "^3.1.1"
- node-releases "^1.1.71"
+ node-releases "^1.1.75"
buffer-crc32@~0.2.3:
version "0.2.13"
@@ -538,9 +639,9 @@ buffer-equal@^1.0.0:
integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74=
buffer-from@^1.0.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
- integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
+ integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer-xor@^1.0.3:
version "1.0.3"
@@ -581,6 +682,19 @@ cache-base@^1.0.1:
union-value "^1.0.0"
unset-value "^1.0.0"
+cacheable-request@^6.0.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
+ integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==
+ dependencies:
+ clone-response "^1.0.2"
+ get-stream "^5.1.0"
+ http-cache-semantics "^4.0.0"
+ keyv "^3.0.0"
+ lowercase-keys "^2.0.0"
+ normalize-url "^4.1.0"
+ responselike "^1.0.2"
+
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
@@ -589,25 +703,6 @@ call-bind@^1.0.0, call-bind@^1.0.2:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
-caller-callsite@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
- integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=
- dependencies:
- callsites "^2.0.0"
-
-caller-path@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
- integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=
- dependencies:
- caller-callsite "^2.0.0"
-
-callsites@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
- integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=
-
camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
@@ -618,10 +713,15 @@ camelcase@^5.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001243:
- version "1.0.30001244"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001244.tgz#a6dc49ad5fa02d81d04373ec3f5ceabc3da06abf"
- integrity sha512-Wb4UFZPkPoJoKKVfELPWytRzpemjP/s0pe22NriANru1NoI+5bGNxzKtk7edYL8rmCWTfQO8eRiF0pn1Dqzx7Q==
+camelcase@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
+ integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
+
+caniuse-lite@^1.0.30001252, caniuse-lite@^1.0.30001254:
+ version "1.0.30001257"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz#150aaf649a48bee531104cfeda57f92ce587f6e5"
+ integrity sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA==
caseless@~0.12.0:
version "0.12.0"
@@ -637,6 +737,14 @@ chalk@^2.4.1, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
+chalk@^4.1.0:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
chalk@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
@@ -645,7 +753,7 @@ chalk@^4.1.1:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-"chokidar@>=3.0.0 <4.0.0":
+"chokidar@>=3.0.0 <4.0.0", chokidar@^3.2.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
@@ -679,6 +787,11 @@ chokidar@^2.0.0:
optionalDependencies:
fsevents "^1.2.7"
+ci-info@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
+ integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
+
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -704,6 +817,11 @@ clean-css@^4.x:
dependencies:
source-map "~0.6.0"
+cli-boxes@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
+ integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
+
cliui@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
@@ -727,6 +845,13 @@ clone-buffer@^1.0.0:
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
+clone-response@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
+ integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
+ dependencies:
+ mimic-response "^1.0.0"
+
clone-stats@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680"
@@ -827,10 +952,10 @@ color@3.0.x:
color-convert "^1.9.1"
color-string "^1.5.2"
-colorette@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
- integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
+colorette@^1.2.2, colorette@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40"
+ integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
colors@^1.2.1:
version "1.4.0"
@@ -897,6 +1022,18 @@ config-chain@^1.1.12:
ini "^1.3.4"
proto-list "~1.2.1"
+configstore@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
+ integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==
+ dependencies:
+ dot-prop "^5.2.0"
+ graceful-fs "^4.1.2"
+ make-dir "^3.0.0"
+ unique-string "^2.0.0"
+ write-file-atomic "^3.0.0"
+ xdg-basedir "^4.0.0"
+
console-browserify@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
@@ -932,30 +1069,25 @@ copy-props@^2.0.1:
each-props "^1.3.2"
is-plain-object "^5.0.0"
-core-js-pure@^3.0.0:
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.13.0.tgz#9d267fb47d1d7046cfbc05e7b67bb235b6735355"
- integrity sha512-7VTvXbsMxROvzPAVczLgfizR8CyYnvWPrb1eGrtlZAJfjQWEHLofVfCKljLHdpazTfpaziRORwUH/kfGDKvpdA==
+core-js-pure@^3.16.0:
+ version "3.17.3"
+ resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.17.3.tgz#98ea3587188ab7ef4695db6518eeb71aec42604a"
+ integrity sha512-YusrqwiOTTn8058JDa0cv9unbXdIiIgcgI9gXso0ey4WgkFLd3lYlV9rp9n7nDCsYxXsMDTjA4m1h3T348mdlQ==
core-js@^3.6.4:
version "3.13.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.13.0.tgz#58ca436bf01d6903aee3d364089868d0d89fe58d"
integrity sha512-iWDbiyha1M5vFwPFmQnvRv+tJzGbFAm6XimJUT0NgHYW3xZEs1SkCAcasWSVFxpI2Xb/V1DDJckq3v90+bQnog==
-core-util-is@1.0.2, core-util-is@~1.0.0:
+core-util-is@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
-cosmiconfig@^5.0.0:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
- integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
- dependencies:
- import-fresh "^2.0.0"
- is-directory "^0.3.1"
- js-yaml "^3.13.1"
- parse-json "^4.0.0"
+core-util-is@~1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
+ integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
create-ecdh@^4.0.0:
version "4.0.4"
@@ -995,7 +1127,7 @@ cross-fetch@^3.0.4:
dependencies:
node-fetch "2.6.1"
-cross-spawn@^6.0.0:
+cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
@@ -1023,6 +1155,11 @@ crypto-browserify@^3.11.0:
randombytes "^2.0.0"
randomfill "^1.0.3"
+crypto-random-string@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
+ integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
+
css-select-base-adapter@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
@@ -1111,9 +1248,9 @@ dashdash@^1.12.0:
assert-plus "^1.0.0"
date-fns@^2.22.1:
- version "2.22.1"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.1.tgz#1e5af959831ebb1d82992bf67b765052d8f0efc4"
- integrity sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==
+ version "2.23.0"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.23.0.tgz#4e886c941659af0cf7b30fafdd1eaa37e88788a9"
+ integrity sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA==
dateformat@^3.0.3:
version "3.0.3"
@@ -1136,7 +1273,7 @@ debug@3.1.0:
dependencies:
ms "2.0.0"
-debug@3.X:
+debug@3.X, debug@^3.2.6:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
@@ -1160,6 +1297,18 @@ decode-uri-component@^0.2.0:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
+decompress-response@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
+ integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
+ dependencies:
+ mimic-response "^1.0.0"
+
+deep-extend@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+ integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
default-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f"
@@ -1172,6 +1321,11 @@ default-resolution@^2.0.0:
resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684"
integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=
+defer-to-connect@^1.0.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
+ integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
+
define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@@ -1277,6 +1431,13 @@ domutils@^1.7.0:
dom-serializer "0"
domelementtype "1"
+dot-prop@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
+ integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==
+ dependencies:
+ is-obj "^2.0.0"
+
draft-js@^0.11.7:
version "0.11.7"
resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.11.7.tgz#be293aaa255c46d8a6647f3860aa4c178484a206"
@@ -1286,6 +1447,11 @@ draft-js@^0.11.7:
immutable "~3.7.4"
object-assign "^4.1.1"
+duplexer3@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
+ integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
+
duplexify@^3.6.0:
version "3.7.1"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
@@ -1322,10 +1488,10 @@ editorconfig@^0.15.3:
semver "^5.6.0"
sigmund "^1.0.1"
-electron-to-chromium@^1.3.723:
- version "1.3.774"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.774.tgz#4d6661a23119e35151646c9543b346bb3beca423"
- integrity sha512-Fggh17Q1yyv1uMzq8Qn1Ci58P50qcRXMXd2MBcB9sxo6rJxjUutWcNw8uCm3gFWMdcblBO6mDT5HzX/RVRRECA==
+electron-to-chromium@^1.3.830:
+ version "1.3.840"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.840.tgz#3f2a1df97015d9b1db5d86a4c6bd4cdb920adcbb"
+ integrity sha512-yRoUmTLDJnkIJx23xLY7GbSvnmDCq++NSuxHDQ0jiyDJ9YZBUGJcrdUqm+ZwZFzMbCciVzfem2N2AWiHJcWlbw==
elliptic@^6.5.3:
version "6.5.4"
@@ -1340,6 +1506,11 @@ elliptic@^6.5.3:
minimalistic-assert "^1.0.1"
minimalistic-crypto-utils "^1.0.1"
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
enabled@2.0.x:
version "2.0.0"
resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
@@ -1393,6 +1564,32 @@ es-abstract@^1.17.2, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2:
string.prototype.trimstart "^1.0.4"
unbox-primitive "^1.0.1"
+es-abstract@^1.19.1:
+ version "1.19.1"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3"
+ integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==
+ dependencies:
+ call-bind "^1.0.2"
+ es-to-primitive "^1.2.1"
+ function-bind "^1.1.1"
+ get-intrinsic "^1.1.1"
+ get-symbol-description "^1.0.0"
+ has "^1.0.3"
+ has-symbols "^1.0.2"
+ internal-slot "^1.0.3"
+ is-callable "^1.2.4"
+ is-negative-zero "^2.0.1"
+ is-regex "^1.1.4"
+ is-shared-array-buffer "^1.0.1"
+ is-string "^1.0.7"
+ is-weakref "^1.0.1"
+ object-inspect "^1.11.0"
+ object-keys "^1.1.1"
+ object.assign "^4.1.2"
+ string.prototype.trimend "^1.0.4"
+ string.prototype.trimstart "^1.0.4"
+ unbox-primitive "^1.0.1"
+
es-to-primitive@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
@@ -1448,6 +1645,11 @@ escalade@^3.1.1:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+escape-goat@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
+ integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
+
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -1808,7 +2010,7 @@ get-caller-file@^1.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
-get-intrinsic@^1.0.2, get-intrinsic@^1.1.1:
+get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
@@ -1817,13 +2019,28 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
-get-stream@^4.0.0:
+get-stream@^4.0.0, get-stream@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
dependencies:
pump "^3.0.0"
+get-stream@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
+ integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
+ dependencies:
+ pump "^3.0.0"
+
+get-symbol-description@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
+ integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==
+ dependencies:
+ call-bind "^1.0.2"
+ get-intrinsic "^1.1.1"
+
get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
@@ -1914,6 +2131,13 @@ glob@^7.1.1, glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
+global-dirs@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686"
+ integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==
+ dependencies:
+ ini "2.0.0"
+
global-modules@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
@@ -1941,6 +2165,23 @@ glogg@^1.0.0:
dependencies:
sparkles "^1.0.0"
+got@^9.6.0:
+ version "9.6.0"
+ resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
+ integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==
+ dependencies:
+ "@sindresorhus/is" "^0.14.0"
+ "@szmarczak/http-timer" "^1.1.2"
+ cacheable-request "^6.0.0"
+ decompress-response "^3.3.0"
+ duplexer3 "^0.1.4"
+ get-stream "^4.1.0"
+ lowercase-keys "^1.0.1"
+ mimic-response "^1.0.1"
+ p-cancelable "^1.0.0"
+ to-readable-stream "^1.0.0"
+ url-parse-lax "^3.0.0"
+
graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
@@ -2008,13 +2249,13 @@ gulp-mustache@^5.0.0:
through2 "^3.0.1"
gulp-postcss@^9.0.0:
- version "9.0.0"
- resolved "https://registry.yarnpkg.com/gulp-postcss/-/gulp-postcss-9.0.0.tgz#2ade18809ab475dae743a88bd6501af0b04ee54e"
- integrity sha512-5mSQ9CK8salSagrXgrVyILfEMy6I5rUGPRiR9rVjgJV9m/rwdZYUhekMr+XxDlApfc5ZdEJ8gXNZrU/TsgT5dQ==
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/gulp-postcss/-/gulp-postcss-9.0.1.tgz#d43caa2f2ce1018f889f7c1296faf82e9928b66f"
+ integrity sha512-9QUHam5JyXwGUxaaMvoFQVT44tohpEFpM8xBdPfdwTYGM0AItS1iTQz0MpsF8Jroh7GF5Jt2GVPaYgvy8qD2Fw==
dependencies:
fancy-log "^1.3.3"
plugin-error "^1.0.1"
- postcss-load-config "^2.1.1"
+ postcss-load-config "^3.0.0"
vinyl-sourcemaps-apply "^0.2.1"
gulp-rename@^2.0.0:
@@ -2111,6 +2352,13 @@ has-symbols@^1.0.1, has-symbols@^1.0.2:
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
+has-tostringtag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
+ integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
+ dependencies:
+ has-symbols "^1.0.2"
+
has-value@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@@ -2142,6 +2390,11 @@ has-values@^1.0.0:
is-number "^3.0.0"
kind-of "^4.0.0"
+has-yarn@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77"
+ integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==
+
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
@@ -2180,9 +2433,9 @@ he@1.1.1:
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
highlight.js@^11.0.1:
- version "11.1.0"
- resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.1.0.tgz#0198f7326e64ddfbea5f76b00e84ab542cf24ae8"
- integrity sha512-X9VVhYKHQPPuwffO8jk4bP/FVj+ibNCy3HxZZNDXFtJrq4O5FdcdCDRIkDis5MiMnjh7UwEdHgRZJcHFYdzDdA==
+ version "11.2.0"
+ resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.2.0.tgz#a7e3b8c1fdc4f0538b93b2dc2ddd53a40c6ab0f0"
+ integrity sha512-JOySjtOEcyG8s4MLR2MNbLUyaXqUunmSnL2kdV/KuGJOmHZuAR5xC54Ko7goAXBWNhf09Vy3B+U7vR62UZ/0iw==
hmac-drbg@^1.0.1:
version "1.0.1"
@@ -2205,6 +2458,11 @@ hosted-git-info@^2.1.4:
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
+http-cache-semantics@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
+ integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
+
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@@ -2231,6 +2489,11 @@ ieee754@^1.1.4:
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+ignore-by-default@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
+ integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk=
+
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
@@ -2241,27 +2504,29 @@ immutable@~3.7.4:
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"
integrity sha1-E7TTyxK++hVIKib+Gy665kAHHks=
-import-cwd@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
- integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=
+import-cwd@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-3.0.0.tgz#20845547718015126ea9b3676b7592fb8bd4cf92"
+ integrity sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==
dependencies:
- import-from "^2.1.0"
+ import-from "^3.0.0"
-import-fresh@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
- integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY=
+import-from@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966"
+ integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==
dependencies:
- caller-path "^2.0.0"
- resolve-from "^3.0.0"
+ resolve-from "^5.0.0"
-import-from@^2.1.0:
+import-lazy@^2.1.0:
version "2.1.0"
- resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1"
- integrity sha1-M1238qev/VOqpHHUuAId7ja387E=
- dependencies:
- resolve-from "^3.0.0"
+ resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
+ integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+ integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
inflight@^1.0.4:
version "1.0.6"
@@ -2286,11 +2551,25 @@ inherits@2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-ini@^1.3.4:
+ini@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
+ integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
+
+ini@^1.3.4, ini@~1.3.0:
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+internal-slot@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
+ integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==
+ dependencies:
+ get-intrinsic "^1.1.0"
+ has "^1.0.3"
+ side-channel "^1.0.4"
+
interpret@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
@@ -2374,6 +2653,18 @@ is-callable@^1.1.4, is-callable@^1.2.3:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
+is-callable@^1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
+ integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
+
+is-ci@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
+ integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
+ dependencies:
+ ci-info "^2.0.0"
+
is-core-module@^2.2.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491"
@@ -2418,11 +2709,6 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
is-data-descriptor "^1.0.0"
kind-of "^6.0.2"
-is-directory@^0.3.1:
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
- integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
-
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -2452,6 +2738,11 @@ is-fullwidth-code-point@^2.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
is-glob@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
@@ -2466,6 +2757,14 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
+is-installed-globally@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
+ integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==
+ dependencies:
+ global-dirs "^3.0.0"
+ is-path-inside "^3.0.2"
+
is-negated-glob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
@@ -2476,6 +2775,11 @@ is-negative-zero@^2.0.1:
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
+is-npm@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8"
+ integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==
+
is-number-object@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb"
@@ -2498,6 +2802,16 @@ is-number@^7.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+is-obj@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
+ integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
+
+is-path-inside@^3.0.2:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
@@ -2523,6 +2837,14 @@ is-regex@^1.1.3:
call-bind "^1.0.2"
has-symbols "^1.0.2"
+is-regex@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
+ integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
+ dependencies:
+ call-bind "^1.0.2"
+ has-tostringtag "^1.0.0"
+
is-relative@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"
@@ -2530,6 +2852,11 @@ is-relative@^1.0.0:
dependencies:
is-unc-path "^1.0.0"
+is-shared-array-buffer@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6"
+ integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==
+
is-stream@^1.0.1, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -2545,6 +2872,13 @@ is-string@^1.0.5, is-string@^1.0.6:
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f"
integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==
+is-string@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
+ integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
+ dependencies:
+ has-tostringtag "^1.0.0"
+
is-symbol@^1.0.2, is-symbol@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
@@ -2552,7 +2886,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3:
dependencies:
has-symbols "^1.0.2"
-is-typedarray@~1.0.0:
+is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
@@ -2574,11 +2908,23 @@ is-valid-glob@^1.0.0:
resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa"
integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=
+is-weakref@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.1.tgz#842dba4ec17fa9ac9850df2d6efbc1737274f2a2"
+ integrity sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==
+ dependencies:
+ call-bind "^1.0.0"
+
is-windows@^1.0.1, is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
+is-yarn-global@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
+ integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
+
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -2634,6 +2980,11 @@ jsbn@~0.1.0:
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
+json-buffer@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
+ integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
+
json-parse-better-errors@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@@ -2677,9 +3028,9 @@ jsprim@^1.2.2:
verror "1.10.0"
jszip@^3.6.0:
- version "3.6.0"
- resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.6.0.tgz#839b72812e3f97819cc13ac4134ffced95dd6af9"
- integrity sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9"
+ integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==
dependencies:
lie "~3.3.0"
pako "~1.0.2"
@@ -2696,6 +3047,13 @@ kew@^0.7.0:
resolved "https://registry.yarnpkg.com/kew/-/kew-0.7.0.tgz#79d93d2d33363d6fdd2970b335d9141ad591d79b"
integrity sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=
+keyv@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
+ integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==
+ dependencies:
+ json-buffer "3.0.0"
+
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@@ -2740,6 +3098,13 @@ last-run@^1.1.0:
default-resolution "^2.0.0"
es6-weak-map "^2.0.1"
+latest-version@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
+ integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==
+ dependencies:
+ package-json "^6.3.0"
+
lazystream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4"
@@ -2789,6 +3154,11 @@ liftoff@^3.1.0:
rechoir "^0.6.2"
resolve "^1.1.7"
+lilconfig@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd"
+ integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==
+
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -2800,6 +3170,16 @@ load-json-file@^1.0.0:
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
+load-json-file@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
+ integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs=
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^4.0.0"
+ pify "^3.0.0"
+ strip-bom "^3.0.0"
+
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -2936,6 +3316,16 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
+lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
+ integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
+
+lowercase-keys@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
+ integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
+
lru-cache@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
@@ -2944,6 +3334,13 @@ lru-cache@^4.1.5:
pseudomap "^1.0.2"
yallist "^2.1.2"
+lru-cache@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+ integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+ dependencies:
+ yallist "^4.0.0"
+
lru-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3"
@@ -2951,10 +3348,17 @@ lru-queue@^0.1.0:
dependencies:
es5-ext "~0.10.2"
-luxon@^1.26.0:
- version "1.28.0"
- resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf"
- integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==
+luxon@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/luxon/-/luxon-2.0.2.tgz#11f2cd4a11655fdf92e076b5782d7ede5bcdd133"
+ integrity sha512-ZRioYLCgRHrtTORaZX1mx+jtxKtKuI5ZDvHNAmqpUzGqSrR+tL4FVLn/CUGMA3h0+AKD1MAxGI5GnCqR5txNqg==
+
+make-dir@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
+ integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
+ dependencies:
+ semver "^6.0.0"
make-iterator@^1.0.0:
version "1.0.1"
@@ -2987,10 +3391,10 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
-marked@^2.1.1:
- version "2.1.3"
- resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.3.tgz#bd017cef6431724fd4b27e0657f5ceb14bff3753"
- integrity sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==
+marked@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-3.0.4.tgz#b8a1539e5e05c6ea9e93f15c0bad1d54ce890406"
+ integrity sha512-jBo8AOayNaEcvBhNobg6/BLhdsK3NvnKWJg33MAAPbvTWiG4QBn9gpW1+7RssrKu4K1dKlN+0goVQwV41xEfOA==
matchdep@^2.0.0:
version "2.0.0"
@@ -3044,6 +3448,11 @@ memoizee@0.4.X:
next-tick "^1.1.0"
timers-ext "^0.1.7"
+memorystream@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
+ integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI=
+
micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@@ -3088,6 +3497,11 @@ mimic-fn@^2.0.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+mimic-response@^1.0.0, mimic-response@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
+ integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
+
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
@@ -3110,7 +3524,7 @@ minimist@0.0.8:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
-minimist@^1.2.5:
+minimist@^1.2.0, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
@@ -3195,9 +3609,9 @@ nan@^2.12.1:
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
nanoid@^3.1.23:
- version "3.1.23"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
- integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
+ version "3.1.25"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152"
+ integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==
nanomatch@^1.2.9:
version "1.2.13"
@@ -3265,10 +3679,26 @@ node-libs-browser@^2.2.1:
util "^0.11.0"
vm-browserify "^1.0.1"
-node-releases@^1.1.71:
- version "1.1.73"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20"
- integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==
+node-releases@^1.1.75:
+ version "1.1.75"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe"
+ integrity sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==
+
+nodemon@^2.0.13:
+ version "2.0.13"
+ resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.13.tgz#67d40d3a4d5bd840aa785c56587269cfcf5d24aa"
+ integrity sha512-UMXMpsZsv1UXUttCn6gv8eQPhn6DR4BW+txnL3IN5IHqrCwcrT/yWHfL35UsClGXknTH79r5xbu+6J1zNHuSyA==
+ dependencies:
+ chokidar "^3.2.2"
+ debug "^3.2.6"
+ ignore-by-default "^1.0.1"
+ minimatch "^3.0.4"
+ pstree.remy "^1.1.7"
+ semver "^5.7.1"
+ supports-color "^5.5.0"
+ touch "^3.1.0"
+ undefsafe "^2.0.3"
+ update-notifier "^5.1.0"
nopt@^5.0.0:
version "5.0.0"
@@ -3277,6 +3707,13 @@ nopt@^5.0.0:
dependencies:
abbrev "1"
+nopt@~1.0.10:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
+ integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=
+ dependencies:
+ abbrev "1"
+
normalize-package-data@^2.3.2:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -3304,6 +3741,11 @@ normalize-range@^0.1.2:
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
+normalize-url@^4.1.0:
+ version "4.5.1"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
+ integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
+
now-and-later@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c"
@@ -3311,6 +3753,21 @@ now-and-later@^2.0.0:
dependencies:
once "^1.3.2"
+npm-run-all@^4.1.5:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
+ integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ chalk "^2.4.1"
+ cross-spawn "^6.0.5"
+ memorystream "^0.3.1"
+ minimatch "^3.0.4"
+ pidtree "^0.3.0"
+ read-pkg "^3.0.0"
+ shell-quote "^1.6.1"
+ string.prototype.padend "^3.0.0"
+
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -3354,6 +3811,11 @@ object-inspect@^1.10.3:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369"
integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==
+object-inspect@^1.11.0, object-inspect@^1.9.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
+ integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
+
object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@@ -3477,6 +3939,11 @@ os-locale@^3.0.0:
lcid "^2.0.0"
mem "^4.0.0"
+p-cancelable@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
+ integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
+
p-defer@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
@@ -3511,6 +3978,16 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+package-json@^6.3.0:
+ version "6.5.0"
+ resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0"
+ integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==
+ dependencies:
+ got "^9.6.0"
+ registry-auth-token "^4.0.0"
+ registry-url "^5.0.0"
+ semver "^6.2.0"
+
pako@~1.0.2, pako@~1.0.5:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
@@ -3624,6 +4101,13 @@ path-type@^1.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+path-type@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
+ integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
+ dependencies:
+ pify "^3.0.0"
+
pbkdf2@^3.0.3:
version "3.1.2"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075"
@@ -3665,11 +4149,21 @@ picomatch@^2.0.4, picomatch@^2.2.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
+pidtree@^0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a"
+ integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==
+
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
+pify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+ integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
+
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
@@ -3705,13 +4199,14 @@ postcss-clean@^1.2.2:
clean-css "^4.x"
postcss "^6.x"
-postcss-load-config@^2.1.1:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a"
- integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==
+postcss-load-config@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.0.tgz#d39c47091c4aec37f50272373a6a648ef5e97829"
+ integrity sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==
dependencies:
- cosmiconfig "^5.0.0"
- import-cwd "^2.0.0"
+ import-cwd "^3.0.0"
+ lilconfig "^2.0.3"
+ yaml "^1.10.2"
postcss-value-parser@^4.1.0:
version "4.1.0"
@@ -3737,14 +4232,19 @@ postcss@^7.0.16:
supports-color "^6.1.0"
postcss@^8.3.5:
- version "8.3.5"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709"
- integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==
+ version "8.3.6"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea"
+ integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.23"
source-map-js "^0.6.2"
+prepend-http@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
+ integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
+
pretty-hrtime@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
@@ -3801,6 +4301,11 @@ psl@^1.1.28:
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
+pstree.remy@^1.1.7:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
+ integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
+
public-encrypt@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
@@ -3853,6 +4358,13 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+pupa@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
+ integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==
+ dependencies:
+ escape-goat "^2.0.0"
+
q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@@ -3893,6 +4405,16 @@ randomfill@^1.0.3:
randombytes "^2.0.5"
safe-buffer "^5.1.0"
+rc@^1.2.8:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+ integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+ dependencies:
+ deep-extend "^0.6.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
react-dom@~17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
@@ -3949,6 +4471,15 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
+read-pkg@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
+ integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=
+ dependencies:
+ load-json-file "^4.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^3.0.0"
+
"readable-stream@2 || 3", readable-stream@^3.4.0, readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
@@ -4000,9 +4531,9 @@ rechoir@^0.6.2:
resolve "^1.1.6"
regenerator-runtime@^0.13.4:
- version "0.13.7"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
- integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
+ version "0.13.9"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
+ integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
@@ -4012,6 +4543,20 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
+registry-auth-token@^4.0.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250"
+ integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==
+ dependencies:
+ rc "^1.2.8"
+
+registry-url@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009"
+ integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==
+ dependencies:
+ rc "^1.2.8"
+
remove-bom-buffer@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53"
@@ -4114,10 +4659,10 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1:
expand-tilde "^2.0.0"
global-modules "^1.0.0"
-resolve-from@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
- integrity sha1-six699nWiBvItuZTM17rywoYh0g=
+resolve-from@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
+ integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
resolve-options@^1.1.0:
version "1.1.0"
@@ -4139,6 +4684,13 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.4.0:
is-core-module "^2.2.0"
path-parse "^1.0.6"
+responselike@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
+ integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
+ dependencies:
+ lowercase-keys "^1.0.0"
+
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@@ -4189,9 +4741,9 @@ safe-regex@^1.1.0:
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass@^1.35.1:
- version "1.35.2"
- resolved "https://registry.yarnpkg.com/sass/-/sass-1.35.2.tgz#b732314fcdaf7ef8d0f1698698adc378043cb821"
- integrity sha512-jhO5KAR+AMxCEwIH3v+4zbB2WB0z67V1X0jbapfVwQQdjHZUGUyukpnoM6+iCMfsIUC016w9OPKQ5jrNOS9uXw==
+ version "1.41.0"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.41.0.tgz#f7b41dc00336a4c03429c37b9680b86758af61d4"
+ integrity sha512-wb8nT60cjo9ZZMcHzG7TzdbFtCAmHEKWrH+zAdScPb4ZxL64WQBnGdbp5nwlenW5wJPcHva1JWmVa0h6iqA5eg==
dependencies:
chokidar ">=3.0.0 <4.0.0"
@@ -4208,6 +4760,13 @@ scheduler@^0.20.2:
loose-envify "^1.1.0"
object-assign "^4.1.1"
+semver-diff@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
+ integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==
+ dependencies:
+ semver "^6.3.0"
+
semver-greatest-satisfied-range@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b"
@@ -4215,11 +4774,23 @@ semver-greatest-satisfied-range@^1.1.0:
dependencies:
sver-compat "^1.5.0"
-"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0:
+"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+semver@^6.0.0, semver@^6.2.0, semver@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+ integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
+semver@^7.3.4:
+ version "7.3.5"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
+ integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+ dependencies:
+ lru-cache "^6.0.0"
+
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -4258,10 +4829,10 @@ shadow-cljs-jar@1.3.2:
resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b"
integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg==
-shadow-cljs@2.15.2:
- version "2.15.2"
- resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.15.2.tgz#195ad2cc45d3334920e629721f06c6d63802b1ac"
- integrity sha512-WPlSMkGgbU5b2nrt+Y1A1TsPs5Rip/JvCxGG2t2Pvzo+pLJ+RcpkZgAxjNQNNA7VYWEh5Pqwyvq5KzQ+0LMsxw==
+shadow-cljs@2.15.9:
+ version "2.15.9"
+ resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.15.9.tgz#cb256a9af12c3df1f0b2bbef344e365dab74b519"
+ integrity sha512-t2KrrMvJZtUFf2xIAL+OL76ahYHPT8VAKxhDic3kloTgOYfZpHRm/S/3C0iW982U3ZJdLUZxmBeluH4Q7564dg==
dependencies:
node-libs-browser "^2.2.1"
readline-sync "^1.4.7"
@@ -4282,6 +4853,11 @@ shebang-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
+shell-quote@^1.6.1:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
+ integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
+
should-equal@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3"
@@ -4326,6 +4902,15 @@ should@^13.2.3:
should-type-adaptors "^1.0.1"
should-util "^1.0.0"
+side-channel@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+ integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+ dependencies:
+ call-bind "^1.0.0"
+ get-intrinsic "^1.0.2"
+ object-inspect "^1.9.0"
+
sigmund@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
@@ -4336,6 +4921,11 @@ signal-exit@^3.0.0:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
+signal-exit@^3.0.2:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
+ integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
+
simple-swizzle@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
@@ -4405,9 +4995,9 @@ source-map-support@^0.4.15:
source-map "^0.5.6"
source-map-support@^0.5.16:
- version "0.5.19"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
- integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
+ version "0.5.20"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"
+ integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
@@ -4556,11 +5146,29 @@ string-width@^2.0.0, string-width@^2.1.1:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
+string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
string.prototype.codepointat@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc"
integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==
+string.prototype.padend@^3.0.0:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz#997a6de12c92c7cb34dc8a201a6c53d9bd88a5f1"
+ integrity sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.3"
+ es-abstract "^1.19.1"
+
string.prototype.trimend@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
@@ -4612,6 +5220,13 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
+strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
strip-bom-string@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92"
@@ -4624,11 +5239,21 @@ strip-bom@^2.0.0:
dependencies:
is-utf8 "^0.2.0"
+strip-bom@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+ integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
+
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+ integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
+
supports-color@5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
@@ -4636,7 +5261,7 @@ supports-color@5.4.0:
dependencies:
has-flag "^3.0.0"
-supports-color@^5.3.0, supports-color@^5.4.0:
+supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
@@ -4797,6 +5422,11 @@ to-object-path@^0.3.0:
dependencies:
kind-of "^3.0.2"
+to-readable-stream@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
+ integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==
+
to-regex-range@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38"
@@ -4829,6 +5459,13 @@ to-through@^2.0.0:
dependencies:
through2 "^2.0.3"
+touch@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
+ integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==
+ dependencies:
+ nopt "~1.0.10"
+
tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
@@ -4847,6 +5484,11 @@ triple-beam@^1.2.0, triple-beam@^1.3.0:
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
+tslib@^1.9.3:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
+ integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+
tslib@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
@@ -4869,6 +5511,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
+type-fest@^0.20.2:
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
+ integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+
type@^1.0.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
@@ -4879,6 +5526,13 @@ type@^2.0.0:
resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d"
integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==
+typedarray-to-buffer@^3.1.5:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
+ integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
+ dependencies:
+ is-typedarray "^1.0.0"
+
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@@ -4904,6 +5558,11 @@ unc-path-regex@^0.1.2:
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo=
+undefsafe@^2.0.3:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
+ integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
+
undertaker-registry@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/undertaker-registry/-/undertaker-registry-1.0.1.tgz#5e4bda308e4a8a2ae584f9b9a4359a499825cc50"
@@ -4943,6 +5602,13 @@ unique-stream@^2.0.2:
json-stable-stringify-without-jsonify "^1.0.1"
through2-filter "^3.0.0"
+unique-string@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
+ integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==
+ dependencies:
+ crypto-random-string "^2.0.0"
+
unquote@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"
@@ -4961,6 +5627,26 @@ upath@^1.1.1:
resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
+update-notifier@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9"
+ integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==
+ dependencies:
+ boxen "^5.0.0"
+ chalk "^4.1.0"
+ configstore "^5.0.1"
+ has-yarn "^2.1.0"
+ import-lazy "^2.1.0"
+ is-ci "^2.0.0"
+ is-installed-globally "^0.4.0"
+ is-npm "^5.0.0"
+ is-yarn-global "^0.3.0"
+ latest-version "^5.1.0"
+ pupa "^2.1.1"
+ semver "^7.3.4"
+ semver-diff "^3.1.1"
+ xdg-basedir "^4.0.0"
+
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
@@ -4973,6 +5659,13 @@ urix@^0.1.0:
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
+url-parse-lax@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
+ integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
+ dependencies:
+ prepend-http "^2.0.0"
+
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@@ -5137,6 +5830,13 @@ which@^1.2.10, which@^1.2.14, which@^1.2.9, which@^1.3.1:
dependencies:
isexe "^2.0.0"
+widest-line@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
+ integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==
+ dependencies:
+ string-width "^4.0.0"
+
winston-transport@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59"
@@ -5168,16 +5868,40 @@ wrap-ansi@^2.0.0:
string-width "^1.0.1"
strip-ansi "^3.0.1"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+write-file-atomic@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
+ integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
+ dependencies:
+ imurmurhash "^0.1.4"
+ is-typedarray "^1.0.0"
+ signal-exit "^3.0.2"
+ typedarray-to-buffer "^3.1.5"
+
ws@^7.4.6:
version "7.5.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691"
integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==
+xdg-basedir@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
+ integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
+
xmldom@0.1.27:
version "0.1.27"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
@@ -5189,11 +5913,11 @@ xpath@^0.0.27:
integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==
xregexp@^5.0.1:
- version "5.0.2"
- resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-5.0.2.tgz#798aa7757836f39cdbdeeba3daf94d75f7a9dcc1"
- integrity sha512-JPNfN40YMNSDxZrahMrmtNH1QqPJp0/qNeEJM2nnOlhcBdfCCjekPYFV2OnwKxwvpEYglH1RBotbpRRaEuCG8Q==
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-5.1.0.tgz#c87e7ae5ffa5fdc520f898a467dcba02b0d391e9"
+ integrity sha512-PynwUWtXnSZr8tpQlDPMZfPTyv78EYuA4oI959ukxcQ0a9O/lvndLVKy5wpImzzA26eMxpZmnAXJYiQA13AtWA==
dependencies:
- "@babel/runtime-corejs3" "^7.12.1"
+ "@babel/runtime-corejs3" "^7.14.9"
xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.2"
@@ -5215,6 +5939,16 @@ yallist@^2.1.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yaml@^1.10.2:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
+ integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
+
yargs-parser@^11.1.1:
version "11.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
diff --git a/manage.sh b/manage.sh
index 80d9c6a85..2298eb089 100755
--- a/manage.sh
+++ b/manage.sh
@@ -44,22 +44,39 @@ function pull-devenv-if-not-exists {
function start-devenv {
pull-devenv-if-not-exists $@;
- docker-compose -p $DEVENV_PNAME -f docker/devenv/docker-compose.yaml up -d;
+
+ # Check if the "backend-only" container is running. If it is, we need tot stop it first
+ if [[ ! $(docker ps -f "name=penpot-backend" -q) ]]; then
+ docker compose -p $DEVENV_PNAME --profile backend -f docker/devenv/docker-compose.yaml stop -t 2 backend;
+ fi
+
+ docker compose -p $DEVENV_PNAME --profile full -f docker/devenv/docker-compose.yaml up -d;
+}
+
+function start-backend {
+ pull-devenv-if-not-exists $@;
+
+ # Check if the "devenv" container is running. If it is, we need tot stop it first because conflicts with the backend
+ if [[ ! $(docker ps -f "name=penpot-devenv-main" -q) ]]; then
+ docker compose -p $DEVENV_PNAME --profile full -f docker/devenv/docker-compose.yaml stop -t 2 main;
+ fi
+
+ docker compose -p $DEVENV_PNAME --profile backend -f docker/devenv/docker-compose.yaml up -d;
}
function stop-devenv {
- docker-compose -p $DEVENV_PNAME -f docker/devenv/docker-compose.yaml stop -t 2;
+ docker compose -p $DEVENV_PNAME --profile full --profile backend -f docker/devenv/docker-compose.yaml stop -t 2;
}
function drop-devenv {
- docker-compose -p $DEVENV_PNAME -f docker/devenv/docker-compose.yaml down -t 2 -v;
+ docker compose -p $DEVENV_PNAME --profile full --profile backend -f docker/devenv/docker-compose.yaml down -t 2 -v;
echo "Clean old development image $DEVENV_IMGNAME..."
docker images $DEVENV_IMGNAME -q | awk '{print $3}' | xargs --no-run-if-empty docker rmi
}
function log-devenv {
- docker-compose -p $DEVENV_PNAME -f docker/devenv/docker-compose.yaml logs -f --tail=50
+ docker compose -p $DEVENV_PNAME -f docker/devenv/docker-compose.yaml logs -f --tail=50
}
function run-devenv {
@@ -70,6 +87,14 @@ function run-devenv {
docker exec -ti penpot-devenv-main sudo -EH -u penpot /home/start-tmux.sh
}
+function run-backend {
+ if [[ ! $(docker ps -f "name=penpot-backend" -q) ]]; then
+ start-backend
+ fi
+
+ docker exec -ti penpot-backend sudo -EH -u penpot /home/start-tmux-back.sh
+}
+
function build {
echo ">> build start: $1"
local version=$(print-current-version);
@@ -171,10 +196,12 @@ function usage {
echo "Options:"
echo "- pull-devenv Pulls docker development oriented image"
echo "- build-devenv Build docker development oriented image"
- echo "- start-devenv Start the development oriented docker-compose service."
- echo "- stop-devenv Stops the development oriented docker-compose service."
- echo "- drop-devenv Remove the development oriented docker-compose containers, volumes and clean images."
+ echo "- start-devenv Start the development oriented docker compose service."
+ echo "- stop-devenv Stops the development oriented docker compose service."
+ echo "- drop-devenv Remove the development oriented docker compose containers, volumes and clean images."
echo "- run-devenv Attaches to the running devenv container and starts development environment"
+ echo "- start-backend Start the backend only service."
+ echo "- run-backend Starts a backend-only instance and attach tmux to it"
echo " based on tmux (frontend at localhost:3449, backend at localhost:6060)."
echo ""
}
@@ -196,9 +223,15 @@ case $1 in
start-devenv)
start-devenv ${@:2}
;;
+ start-backend)
+ start-backend ${@:2}
+ ;;
run-devenv)
run-devenv ${@:2}
;;
+ run-backend)
+ run-backend ${@:2}
+ ;;
stop-devenv)
stop-devenv ${@:2}
;;
diff --git a/version.txt b/version.txt
index 75997d5e7..27167ba29 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-1.8.2-alpha
+1.9.0-alpha