From 66f88576e1b434b0510da5f93e95969b95d70d0a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 26 Mar 2021 10:37:19 +0100 Subject: [PATCH 001/155] :bug: Fix text selection in comments --- CHANGES.md | 1 + frontend/resources/styles/main/partials/comments.scss | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 20f76d437..c44241f99 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -53,6 +53,7 @@ - Updates worksans font [#744](https://github.com/penpot/penpot/issues/744) - Fix problem with team management in dashboard [Taiga #1475](https://tree.taiga.io/project/penpot/issue/1475) - Fix problem with blending modes in masks [Taiga #1476](https://tree.taiga.io/project/penpot/issue/1476) +- Fix text selection in comments [#745](https://github.com/penpot/penpot/issues/745) ### :arrow_up: Deps updates diff --git a/frontend/resources/styles/main/partials/comments.scss b/frontend/resources/styles/main/partials/comments.scss index 216de4c2a..cc6248efa 100644 --- a/frontend/resources/styles/main/partials/comments.scss +++ b/frontend/resources/styles/main/partials/comments.scss @@ -42,6 +42,7 @@ border-radius: 2px; min-width: 280px; max-width: 280px; + user-select: text; .comments { max-height: 420px; From 4f8d82dae7829dd3df0f5a8c405c6df6bba6af15 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 26 Mar 2021 10:41:27 +0100 Subject: [PATCH 002/155] :bug: Fixed some issues when shift-selecting on sidebars --- common/app/common/pages/helpers.cljc | 22 ++++++++++--------- frontend/src/app/main/data/workspace.cljs | 3 ++- .../app/main/ui/workspace/shapes/outline.cljs | 4 ++-- .../src/app/main/ui/workspace/viewport.cljs | 3 ++- .../main/ui/workspace/viewport/selection.cljs | 8 ++++++- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/common/app/common/pages/helpers.cljc b/common/app/common/pages/helpers.cljc index d47a38383..2a5ba99aa 100644 --- a/common/app/common/pages/helpers.cljc +++ b/common/app/common/pages/helpers.cljc @@ -187,16 +187,18 @@ (defn clean-loops "Clean a list of ids from circular references." [objects ids] - (loop [ids ids - id (first ids) - others (rest ids)] - (if-not id - ids - (recur (cond-> ids - (some #(contains? ids %) (get-parents id objects)) - (disj id)) - (first others) - (rest others))))) + (let [parent-selected? + (fn [id] + (let [parents (get-parents id objects)] + (some ids parents))) + + add-element + (fn [result id] + (cond-> result + (not (parent-selected? id)) + (conj id)))] + + (reduce add-element (d/ordered-set) ids))) (defn calculate-invalid-targets [shape-id objects] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 61b51eebb..b29c43bd0 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1352,7 +1352,8 @@ ptk/WatchEvent (watch [_ state stream] (let [objects (dwc/lookup-page-objects state) - selected (get-in state [:workspace-local :selected]) + selected (->> (get-in state [:workspace-local :selected]) + (cp/clean-loops objects)) pdata (reduce (partial collect-object-ids objects) {} selected) initial {:type :copied-shapes :file-id (:current-file-id state) diff --git a/frontend/src/app/main/ui/workspace/shapes/outline.cljs b/frontend/src/app/main/ui/workspace/shapes/outline.cljs index 13ad2d5a6..58b8b7d56 100644 --- a/frontend/src/app/main/ui/workspace/shapes/outline.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/outline.cljs @@ -20,8 +20,8 @@ (mf/defc outline {::mf/wrap-props false} [props] - (let [zoom (mf/deref refs/selected-zoom) - shape (unchecked-get props "shape") + (let [shape (unchecked-get props "shape") + zoom (unchecked-get props "zoom") color (unchecked-get props "color") transform (gsh/transform-matrix shape) path? (= :path (:type shape)) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 04849970d..3962ae83a 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -202,7 +202,8 @@ :selected selected :hover (when (not= :frame (:type @hover)) #{(or @frame-hover (:id @hover))}) - :edition edition}]) + :edition edition + :zoom zoom}]) (when show-selection-handlers? [:& selection/selection-handlers diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index 16b5edfbb..d9a5a2522 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -296,7 +296,13 @@ (mf/defc multiple-selection-handlers [{:keys [shapes selected zoom color show-distances disable-handlers on-move-selected] :as props}] - (let [shape (geom/setup {:type :rect} (geom/selection-rect (->> shapes (map geom/transform-shape)))) + (let [shape (mf/use-memo + (mf/deps shapes) + #(->> shapes + (map geom/transform-shape) + (geom/selection-rect) + (geom/setup {:type :rect}))) + shape-center (geom/center-shape shape) hover-id (-> (mf/deref refs/current-hover) first) From 66f9e984991be9bde940d4fde7ee352819daef42 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 26 Mar 2021 10:50:52 +0100 Subject: [PATCH 003/155] :recycle: Moved outlines to viewport namespace --- .../src/app/main/ui/workspace/viewport.cljs | 3 +- .../ui/workspace/viewport/interactions.cljs | 5 +- .../{shapes => viewport}/outline.cljs | 53 ++++++++++++++++--- .../main/ui/workspace/viewport/selection.cljs | 2 +- .../main/ui/workspace/viewport/widgets.cljs | 30 +---------- 5 files changed, 54 insertions(+), 39 deletions(-) rename frontend/src/app/main/ui/workspace/{shapes => viewport}/outline.cljs (51%) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 3962ae83a..7e7ac7332 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -22,6 +22,7 @@ [app.main.ui.workspace.viewport.gradients :as gradients] [app.main.ui.workspace.viewport.hooks :as hooks] [app.main.ui.workspace.viewport.interactions :as interactions] + [app.main.ui.workspace.viewport.outline :as outline] [app.main.ui.workspace.viewport.pixel-overlay :as pixel-overlay] [app.main.ui.workspace.viewport.presence :as presence] [app.main.ui.workspace.viewport.selection :as selection] @@ -197,7 +198,7 @@ [:g {:style {:pointer-events (if disable-events? "none" "auto")}} (when show-outlines? - [:& widgets/shape-outlines + [:& outline/shape-outlines {:objects objects :selected selected :hover (when (not= :frame (:type @hover)) diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs index 50df4ea07..c77f315da 100644 --- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs @@ -15,12 +15,13 @@ [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.workspace.shapes.outline :refer [outline]] + [app.main.ui.workspace.viewport.outline :refer [outline]] [app.util.data :as dt] [app.util.dom :as dom] [app.util.keyboard :as kbd] [cuerdas.core :as str] - [rumext.alpha :as mf])) + [rumext.alpha :as mf] + )) (defn- get-click-interaction [shape] diff --git a/frontend/src/app/main/ui/workspace/shapes/outline.cljs b/frontend/src/app/main/ui/workspace/viewport/outline.cljs similarity index 51% rename from frontend/src/app/main/ui/workspace/shapes/outline.cljs rename to frontend/src/app/main/ui/workspace/viewport/outline.cljs index 58b8b7d56..dcffdc669 100644 --- a/frontend/src/app/main/ui/workspace/shapes/outline.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/outline.cljs @@ -7,15 +7,16 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL -(ns app.main.ui.workspace.shapes.outline +(ns app.main.ui.workspace.viewport.outline (:require - [rumext.alpha :as mf] [app.common.geom.shapes :as gsh] - [app.util.object :as obj] - [rumext.util :refer [map->obj]] + [app.common.pages :as cp] [app.main.refs :as refs] - [app.util.geom.path :as ugp])) - + [app.util.geom.path :as ugp] + [app.util.object :as obj] + [clojure.set :as set] + [rumext.alpha :as mf] + [rumext.util :refer [map->obj]])) (mf/defc outline {::mf/wrap-props false} @@ -60,3 +61,43 @@ :height height})] [:> outline-type (map->obj (merge common props))])) + +(mf/defc shape-outlines-render + {::mf/wrap-props false + ::mf/wrap [#(mf/memo' % (mf/check-props ["shapes" "zoom"]))]} + [props] + (let [shapes (obj/get props "shapes") + zoom (obj/get props "zoom") + color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes)))) + "#31EFB8" "#00E0FF")] + (for [shape shapes] + [:& outline {:key (str "outline-" (:id shape)) + :shape (gsh/transform-shape shape) + :zoom zoom + :color color}]))) + +(mf/defc shape-outlines + {::mf/wrap-props false} + [props] + (let [selected (or (obj/get props "selected") #{}) + hover (or (obj/get props "hover") #{}) + objects (obj/get props "objects") + edition (obj/get props "edition") + zoom (obj/get props "zoom") + + transform (mf/deref refs/current-transform) + + outlines-ids (->> (set/union selected hover) + (cp/clean-loops objects)) + + show-outline? (fn [shape] (and (not (:hidden shape)) + (not (:blocked shape)))) + + shapes (->> outlines-ids + (filter #(not= edition %)) + (map #(get objects %)) + (filterv show-outline?))] + + [:g.outlines {:display (when (some? transform) "none")} + [:& shape-outlines-render {:shapes shapes + :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 d9a5a2522..0e2fb2033 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -23,7 +23,7 @@ [app.main.ui.cursors :as cur] [app.main.ui.hooks :as hooks] [app.main.ui.measurements :as msr] - [app.main.ui.workspace.shapes.outline :refer [outline]] + [app.main.ui.workspace.viewport.outline :refer [outline]] [app.main.ui.workspace.shapes.path.editor :refer [path-editor]] [app.util.data :as d] [app.util.debug :refer [debug?]] diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 679c824f8..c60d02293 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -17,39 +17,11 @@ [app.main.store :as st] [app.main.streams :as ms] [app.main.ui.hooks :as hooks] - [app.main.ui.workspace.shapes.outline :refer [outline]] [app.main.ui.workspace.shapes.path.actions :refer [path-actions]] [app.util.dom :as dom] - [clojure.set :as set] + [app.util.object :as obj] [rumext.alpha :as mf])) -(mf/defc shape-outlines - {::mf/wrap-props false} - [props] - (let [objects (unchecked-get props "objects") - selected (or (unchecked-get props "selected") #{}) - hover (or (unchecked-get props "hover") #{}) - edition (unchecked-get props "edition") - outline? (set/union selected hover) - show-outline? (fn [shape] (and (not (:hidden shape)) - (not (:blocked shape)) - (not= edition (:id shape)) - (outline? (:id shape)))) - - shapes (cond->> (vals objects) - show-outline? (filter show-outline?)) - - transform (mf/deref refs/current-transform) - color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes)))) - "#31EFB8" "#00E0FF")] - (when (nil? transform) - [:g.outlines - (for [shape shapes] - [:& outline {:key (str "outline-" (:id shape)) - :shape (gsh/transform-shape shape) - :color color}])]))) - - (mf/defc pixel-grid [{:keys [vbox zoom]}] [:g.pixel-grid From e6a2cc16a444b22a5680a6490c87bb9b06c8927c Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 26 Mar 2021 10:52:29 +0100 Subject: [PATCH 004/155] :bug: Fix problem with blocked shapes --- CHANGES.md | 1 + frontend/src/app/worker/selection.cljs | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c44241f99..c5b86f334 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -54,6 +54,7 @@ - Fix problem with team management in dashboard [Taiga #1475](https://tree.taiga.io/project/penpot/issue/1475) - Fix problem with blending modes in masks [Taiga #1476](https://tree.taiga.io/project/penpot/issue/1476) - Fix text selection in comments [#745](https://github.com/penpot/penpot/issues/745) +- Fix problem with blocked shapes [Taiga #1480](https://tree.taiga.io/project/penpot/issue/1480) ### :arrow_up: Deps updates diff --git a/frontend/src/app/worker/selection.cljs b/frontend/src/app/worker/selection.cljs index 425ae2bd0..cba546625 100644 --- a/frontend/src/app/worker/selection.cljs +++ b/frontend/src/app/worker/selection.cljs @@ -54,6 +54,7 @@ match-criteria? (fn [shape] (and (not (:hidden shape)) + (not (:blocked shape)) (or (not frame-id) (= frame-id (:frame-id shape))) (case (:type shape) :frame include-frames? From 9cd15fd362ba339855a835904a336b84e988bd3f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 29 Mar 2021 15:38:56 +0200 Subject: [PATCH 005/155] :paperclip: Set next version number. --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 06c33719e..1433486b4 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.4.0-alpha +1.5.0-alpha From 5111551c89191b0301c93fe1b3f6994135aebf23 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 30 Mar 2021 14:47:37 +0200 Subject: [PATCH 006/155] :paperclip: Update .gitignore file. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fdad56268..8bec9960d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ node_modules /backend/dist/ /backend/logs/ /backend/- +/telemetry/ /frontend/npm-debug.log /frontend/target/ /frontend/dist/ From edb88027a4a62a25feb868f8b4efebb721c8c345 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 30 Mar 2021 14:47:52 +0200 Subject: [PATCH 007/155] :paperclip: Minor fix on readme file. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6d842a8d8..b508bac45 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,9 @@ Please refer to the [help center](https://help.penpot.app). 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/. + +This Source Code Form is "Incompatible With Secondary Licenses", as +defined by the Mozilla Public License, v. 2.0. + +Copyright (c) UXBOX Labs SL ``` From 6f1508acc1cc9393588df3a304e8698f91e9c6f3 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 30 Mar 2021 14:50:42 +0200 Subject: [PATCH 008/155] :arrow_up: Update slf4j dependency. --- backend/deps.edn | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/deps.edn b/backend/deps.edn index 196bb9920..46b1b426b 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -15,8 +15,8 @@ org.apache.logging.log4j/log4j-core {:mvn/version "2.14.1"} org.apache.logging.log4j/log4j-web {:mvn/version "2.14.1"} org.apache.logging.log4j/log4j-jul {:mvn/version "2.14.1"} - org.apache.logging.log4j/log4j-slf4j-impl {:mvn/version "2.14.1"} - org.slf4j/slf4j-api {:mvn/version "1.7.30"} + org.apache.logging.log4j/log4j-slf4j18-impl {:mvn/version "2.14.1"} + org.slf4j/slf4j-api {:mvn/version "2.0.0-alpha1"} org.zeromq/jeromq {:mvn/version "0.5.2"} com.taoensso/nippy {:mvn/version "3.1.1"} From cf2998eeeca51e75c28b427f6c74db6455887a25 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 30 Mar 2021 14:51:50 +0200 Subject: [PATCH 009/155] :fire: Remove unused files. --- backend/resources/basedir | 0 backend/resources/builtin.edn | 11 ------- backend/resources/config/default.edn | 44 ---------------------------- backend/resources/config/test.edn | 18 ------------ 4 files changed, 73 deletions(-) delete mode 100644 backend/resources/basedir delete mode 100644 backend/resources/builtin.edn delete mode 100644 backend/resources/config/default.edn delete mode 100644 backend/resources/config/test.edn diff --git a/backend/resources/basedir b/backend/resources/basedir deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/resources/builtin.edn b/backend/resources/builtin.edn deleted file mode 100644 index 7e3216732..000000000 --- a/backend/resources/builtin.edn +++ /dev/null @@ -1,11 +0,0 @@ -{:icons - [{:name "Material Design (Action)" - :path "./material/action/svg/production" - :regex #"^.*_48px\.svg$"}] - - :images - [{:name "Generic Collection 1" - :path "./my-images/collection1/" - :regex #"^.*\.(png|jpg|webp)$"}]} - - diff --git a/backend/resources/config/default.edn b/backend/resources/config/default.edn deleted file mode 100644 index 452d7503b..000000000 --- a/backend/resources/config/default.edn +++ /dev/null @@ -1,44 +0,0 @@ -{;; A secret key used for create tokens - ;; WARNING: this is a default secret key and - ;; it should be overwritten in production env. - :secret "5qjiAn-QUpawUNqGP10UZKklSqbLKcdGY3sJpq0UUACpVXGg2HOFJCBejDWVHskhRyp7iHb4rjOLXX2ZjF-5cw" - - :registration - { - :enabled true} - - :smtp - {:host "localhost" ;; Hostname of the desired SMTP server. - :port 25 ;; Port of SMTP server. - :user nil ;; Username to authenticate with (if authenticating). - :pass nil ;; Password to authenticate with (if authenticating). - :ssl false ;; Enables SSL encryption if value is truthy. - :tls false ;; Enables TLS encryption if value is truthy. - :enabled false ;; Enables SMTP if value is truthy. - :noop true} - - :auth-options {:alg :a256kw :enc :a128cbc-hs256} - - :email {:reply-to "no-reply@uxbox.io" - :from "no-reply@uxbox.io" - :support "support@uxbox.io"} - - :http {:port 6060 - :max-body-size 52428800 - :debug true} - - :media - {:directory "resources/public/media" - :uri "http://localhost:6060/media/"} - - :static - {:directory "resources/public/static" - :uri "http://localhost:6060/static/"} - - :database - {:adapter "postgresql" - :username nil - :password nil - :database-name "uxbox" - :server-name "localhost" - :port-number 5432}} diff --git a/backend/resources/config/test.edn b/backend/resources/config/test.edn deleted file mode 100644 index 31bc83242..000000000 --- a/backend/resources/config/test.edn +++ /dev/null @@ -1,18 +0,0 @@ -{:migrations - {:verbose false} - - :media - {:directory "/tmp/uxbox/media" - :uri "http://localhost:6060/media/"} - - :static - {:directory "/tmp/uxbox/static" - :uri "http://localhost:6060/static/"} - - :database - {:adapter "postgresql" - :username nil - :password nil - :database-name "test" - :server-name "localhost" - :port-number 5432}} From 59a45530a8e4a504f6142f0765fe193fc2db7aab Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 30 Mar 2021 14:52:42 +0200 Subject: [PATCH 010/155] :whale: Add babashka to devenv. Among other changes. --- docker/devenv/Dockerfile | 24 +++++++++++++++++------- docker/devenv/docker-compose.yaml | 4 ---- docker/devenv/files/nginx.conf | 4 ++++ docker/devenv/files/postgresql_init.sql | 1 + 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index db151af6a..f888c480c 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -5,7 +5,8 @@ ARG DEBIAN_FRONTEND=noninteractive ENV NODE_VERSION=v14.16.0 \ CLOJURE_VERSION=1.10.3.814 \ - CLJKONDO_VERSION=2021.03.03 \ + CLJKONDO_VERSION=2021.03.22 \ + BABASHKA_VERSION=0.3.0 \ LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 @@ -26,6 +27,7 @@ RUN set -ex; \ git \ rlwrap \ unzip \ + fakeroot \ ; \ echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \ locale-gen; \ @@ -115,8 +117,6 @@ RUN set -ex; \ apt-get -qqy install postgresql-client-13; \ rm -rf /var/lib/apt/lists/*; -WORKDIR /home - RUN set -ex; \ curl -LfsSo /tmp/nodejs.tar.xz https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-linux-x64.tar.xz; \ mkdir -p /usr/local/nodejs; \ @@ -128,12 +128,22 @@ RUN set -ex; \ /usr/local/nodejs/bin/npm install -g svgo; \ rm -rf /tmp/nodejs.tar.xz; +# Install clj-kondo +RUN set -ex; \ + curl -LfsSo /tmp/clj-kondo.zip https://github.com/borkdude/clj-kondo/releases/download/v$CLJKONDO_VERSION/clj-kondo-$CLJKONDO_VERSION-linux-amd64.zip; \ + cd /usr/local/bin; \ + unzip /tmp/clj-kondo.zip; \ + rm /tmp/clj-kondo.zip; + +# Install babashka RUN set -ex; \ cd /tmp; \ - wget https://github.com/borkdude/clj-kondo/releases/download/v$CLJKONDO_VERSION/clj-kondo-$CLJKONDO_VERSION-linux-amd64.zip; \ - unzip clj-kondo-$CLJKONDO_VERSION-linux-amd64.zip; \ - mv clj-kondo /usr/local/bin/; \ - rm clj-kondo-$CLJKONDO_VERSION-linux-amd64.zip; + curl -LfsSo /tmp/babashka.tar.gz https://github.com/babashka/babashka/releases/download/v$BABASHKA_VERSION/babashka-$BABASHKA_VERSION-linux-amd64.tar.gz; \ + cd /usr/local/bin; \ + tar -xf /tmp/babashka.tar.gz; \ + rm -rf /tmp/babashka.tar.gz; + +WORKDIR /home EXPOSE 3447 EXPOSE 3448 diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml index 22c506b2c..536b019c5 100644 --- a/docker/devenv/docker-compose.yaml +++ b/docker/devenv/docker-compose.yaml @@ -38,10 +38,6 @@ services: - 9090:9090 environment: - - PENPOT_DATABASE_URI=postgresql://postgres/penpot - - PENPOT_DATABASE_USERNAME=penpot - - PENPOT_DATABASE_PASSWORD=penpot - - PENPOT_REDIS_URI=redis://redis/0 - EXTERNAL_UID=${CURRENT_USER_ID} # STMP setup - PENPOT_SMTP_ENABLED=true diff --git a/docker/devenv/files/nginx.conf b/docker/devenv/files/nginx.conf index a4b13cb73..2ce3f8316 100644 --- a/docker/devenv/files/nginx.conf +++ b/docker/devenv/files/nginx.conf @@ -111,6 +111,10 @@ http { proxy_pass http://127.0.0.1:6061; } + location /telemetry { + proxy_pass http://127.0.0.1:6070/inbox; + } + location /playground { alias /home/penpot/penpot/experiments/; add_header Cache-Control "no-cache, max-age=0"; diff --git a/docker/devenv/files/postgresql_init.sql b/docker/devenv/files/postgresql_init.sql index 3f174e897..c36960e25 100644 --- a/docker/devenv/files/postgresql_init.sql +++ b/docker/devenv/files/postgresql_init.sql @@ -1 +1,2 @@ CREATE DATABASE penpot_test; +CREATE DATABASE penpot_telemetry; From 0926fbcbc6b0cda3e0d5e468558e71b8006e716c Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 30 Mar 2021 14:55:19 +0200 Subject: [PATCH 011/155] :recycle: Minor code reorganization. Improves modularity and reusability and allows usage of backend code as a library. --- backend/dev/user.clj | 2 +- backend/src/app/cli/fixtures.clj | 3 +- backend/src/app/cli/manage.clj | 5 +- backend/src/app/cli/migrate_media.clj | 10 +- backend/src/app/config.clj | 56 +- backend/src/app/db.clj | 11 +- backend/src/app/emails.clj | 96 +++- backend/src/app/http.clj | 85 +-- backend/src/app/http/feedback.clj | 17 +- backend/src/app/http/oauth/github.clj | 5 +- backend/src/app/loggers/mattermost.clj | 2 +- backend/src/app/main.clj | 568 ++++++++++----------- backend/src/app/rpc/mutations/demo.clj | 13 +- backend/src/app/rpc/mutations/files.clj | 12 +- backend/src/app/rpc/mutations/profile.clj | 62 ++- backend/src/app/rpc/mutations/projects.clj | 10 +- backend/src/app/rpc/mutations/teams.clj | 34 +- backend/src/app/tasks.clj | 110 ---- backend/src/app/tasks/sendmail.clj | 2 +- backend/src/app/tasks/telemetry.clj | 25 +- backend/src/app/telemetry.clj | 121 ----- backend/src/app/util/blob.clj | 24 +- backend/src/app/util/time.clj | 1 - backend/src/app/worker.clj | 196 +++++-- backend/tests/app/tests/helpers.clj | 20 +- common/app/common/data.cljc | 3 + common/app/common/exceptions.cljc | 2 +- 27 files changed, 704 insertions(+), 791 deletions(-) delete mode 100644 backend/src/app/tasks.clj delete mode 100644 backend/src/app/telemetry.clj diff --git a/backend/dev/user.clj b/backend/dev/user.clj index d7ee62abf..b6816c3ad 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -70,7 +70,7 @@ [] (alter-var-root #'system (fn [sys] (when sys (ig/halt! sys)) - (-> (main/build-system-config cfg/config) + (-> main/system-config (ig/prep) (ig/init)))) :started) diff --git a/backend/src/app/cli/fixtures.clj b/backend/src/app/cli/fixtures.clj index 414ea90ae..710d3f1fe 100644 --- a/backend/src/app/cli/fixtures.clj +++ b/backend/src/app/cli/fixtures.clj @@ -12,7 +12,6 @@ (:require [app.common.pages :as cp] [app.common.uuid :as uuid] - [app.config :as cfg] [app.db :as db] [app.main :as main] [app.rpc.mutations.profile :as profile] @@ -233,7 +232,7 @@ (defn run [{:keys [preset] :or {preset :small}}] - (let [config (select-keys (main/build-system-config cfg/config) + (let [config (select-keys main/system-config [:app.db/pool :app.telemetry/migrations :app.migrations/migrations diff --git a/backend/src/app/cli/manage.clj b/backend/src/app/cli/manage.clj index 36093375e..04216fc22 100644 --- a/backend/src/app/cli/manage.clj +++ b/backend/src/app/cli/manage.clj @@ -5,12 +5,11 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.cli.manage "A manage cli api." (:require - [app.config :as cfg] [app.db :as db] [app.main :as main] [app.rpc.mutations.profile :as profile] @@ -26,7 +25,7 @@ (defn init-system [] - (let [data (-> (main/build-system-config cfg/config) + (let [data (-> main/system-config (select-keys [:app.db/pool :app.metrics/metrics]) (assoc :app.migrations/all {}))] (-> data ig/prep ig/init))) diff --git a/backend/src/app/cli/migrate_media.clj b/backend/src/app/cli/migrate_media.clj index 4afaf79b2..6a0531126 100644 --- a/backend/src/app/cli/migrate_media.clj +++ b/backend/src/app/cli/migrate_media.clj @@ -10,7 +10,7 @@ (ns app.cli.migrate-media (:require [app.common.media :as cm] - [app.config :as cfg] + [app.config :as cf] [app.db :as db] [app.main :as main] [app.storage :as sto] @@ -34,7 +34,7 @@ (defn run [] - (let [config (select-keys (main/build-system-config cfg/config) + (let [config (select-keys main/system-config [:app.db/pool :app.migrations/migrations :app.metrics/metrics @@ -60,7 +60,7 @@ (->> (db/exec! conn ["select * from profile"]) (filter #(not (str/empty? (:photo %)))) (seq)))] - (let [base (fs/path (:storage-fs-old-directory cfg/config)) + (let [base (fs/path (cf/get :storage-fs-old-directory)) storage (-> (:app.storage/storage system) (assoc :conn conn))] (doseq [profile (retrieve-profiles conn)] @@ -81,7 +81,7 @@ (->> (db/exec! conn ["select * from team"]) (filter #(not (str/empty? (:photo %)))) (seq)))] - (let [base (fs/path (:storage-fs-old-directory cfg/config)) + (let [base (fs/path (cf/get :storage-fs-old-directory)) storage (-> (:app.storage/storage system) (assoc :conn conn))] (doseq [team (retrieve-teams conn)] @@ -105,7 +105,7 @@ from file_media_object as fmo join file_media_thumbnail as fth on (fth.media_object_id = fmo.id)"]) (seq)))] - (let [base (fs/path (:storage-fs-old-directory cfg/config)) + (let [base (fs/path (cf/get :storage-fs-old-directory)) storage (-> (:app.storage/storage system) (assoc :conn conn))] (doseq [mobj (retrieve-media-objects conn)] diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index aff4989a0..a48878a81 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -5,7 +5,7 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.config "A configuration management." @@ -15,10 +15,19 @@ [app.common.version :as v] [app.util.time :as dt] [clojure.core :as c] + [clojure.pprint :as pprint] [clojure.spec.alpha :as s] [cuerdas.core :as str] [environ.core :refer [env]])) +(prefer-method print-method + clojure.lang.IRecord + clojure.lang.IDeref) + +(prefer-method pprint/simple-dispatch + clojure.lang.IPersistentMap + clojure.lang.IDeref) + (def defaults {:http-server-port 6060 :host "devenv" @@ -221,39 +230,31 @@ ::telemetry-server-enabled ::telemetry-server-port ::telemetry-uri + ::telemetry-referer ::telemetry-with-taiga ::tenant])) -(defn- env->config - [env] - (reduce-kv - (fn [acc k v] - (cond-> acc - (str/starts-with? (name k) "penpot-") - (assoc (keyword (subs (name k) 7)) v) +(defn read-env + [prefix] + (let [prefix (str prefix "-") + len (count prefix)] + (reduce-kv + (fn [acc k v] + (cond-> acc + (str/starts-with? (name k) prefix) + (assoc (keyword (subs (name k) len)) v))) + {} + env))) - (str/starts-with? (name k) "app-") - (assoc (keyword (subs (name k) 4)) v))) - {} - env)) (defn- read-config - [env] - (->> (env->config env) + [] + (->> (read-env "penpot") (merge defaults) (us/conform ::config))) -(defn- read-test-config - [env] - (merge {:redis-uri "redis://redis/1" - :database-uri "postgresql://postgres/penpot_test" - :storage-fs-directory "/tmp/app/storage" - :migrations-verbose false} - (read-config env))) - (def version (v/parse "%version%")) -(def config (read-config env)) -(def test-config (read-test-config env)) +(def config (atom (read-config))) (def deletion-delay (dt/duration {:days 7})) @@ -261,6 +262,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))) diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index 7791190c2..29a5627f8 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -9,6 +9,7 @@ (ns app.db (:require + [app.common.data :as d] [app.common.exceptions :as ex] [app.common.geom.point :as gpt] [app.common.spec :as us] @@ -48,8 +49,8 @@ (declare instrument-jdbc!) +(s/def ::name keyword?) (s/def ::uri ::us/not-empty-string) -(s/def ::name ::us/not-empty-string) (s/def ::min-pool-size ::us/integer) (s/def ::max-pool-size ::us/integer) (s/def ::migrations map?) @@ -59,14 +60,14 @@ (defmethod ig/init-key ::pool [_ {:keys [migrations metrics] :as cfg}] - (log/infof "initialize connection pool '%s' with uri '%s'" (:name cfg) (:uri cfg)) + (log/infof "initialize connection pool '%s' with uri '%s'" (name (:name cfg)) (:uri cfg)) (instrument-jdbc! (:registry metrics)) (let [pool (create-pool cfg)] (when (seq migrations) (with-open [conn ^AutoCloseable (open pool)] (mg/setup! conn) - (doseq [[mname steps] migrations] - (mg/migrate! conn {:name (name mname) :steps steps})))) + (doseq [[name steps] migrations] + (mg/migrate! conn {:name (d/name name) :steps steps})))) pool)) (defmethod ig/halt-key! ::pool @@ -100,7 +101,7 @@ mtf (PrometheusMetricsTrackerFactory. (:registry metrics))] (doto config (.setJdbcUrl (str "jdbc:" dburi)) - (.setPoolName (:name cfg "default")) + (.setPoolName (d/name (:name cfg))) (.setAutoCommit true) (.setReadOnly false) (.setConnectionTimeout 8000) ;; 8seg diff --git a/backend/src/app/emails.clj b/backend/src/app/emails.clj index 74fbaf84b..dd0fcf4e1 100644 --- a/backend/src/app/emails.clj +++ b/backend/src/app/emails.clj @@ -5,7 +5,7 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.emails "Main api for send emails." @@ -14,36 +14,34 @@ [app.config :as cfg] [app.db :as db] [app.db.sql :as sql] - [app.tasks :as tasks] [app.util.emails :as emails] - [clojure.spec.alpha :as s])) + [app.worker :as wrk] + [clojure.spec.alpha :as s] + [clojure.tools.logging :as log] + [integrant.core :as ig])) -;; --- Defaults - -(defn default-context - [] - {:assets-uri (:assets-uri cfg/config) - :public-uri (:public-uri cfg/config)}) - -;; --- Public API +;; --- PUBLIC API (defn render [email-factory context] (email-factory context)) (defn send! "Schedule the email for sending." - [conn email-factory context] - (us/verify fn? email-factory) - (us/verify map? context) - (let [email (email-factory context)] - (tasks/submit! conn {:name "sendmail" - :delay 0 - :max-retries 1 - :priority 200 - :props email}))) + [{:keys [::conn ::factory] :as context}] + (us/verify fn? factory) + (us/verify some? conn) + (let [email (factory context)] + (wrk/submit! (assoc email + ::wrk/task :sendmail + ::wrk/delay 0 + ::wrk/max-retries 1 + ::wrk/priority 200 + ::wrk/conn conn)))) +;; --- BOUNCE/COMPLAINS HANDLING + (def sql:profile-complaint-report "select (select count(*) from profile_complaint_report @@ -91,7 +89,7 @@ (>= (count reports) threshold)))) -;; --- Emails +;; --- EMAIL FACTORIES (s/def ::subject ::us/string) (s/def ::content ::us/string) @@ -101,7 +99,7 @@ (def feedback "A profile feedback email." - (emails/template-factory ::feedback default-context)) + (emails/template-factory ::feedback)) (s/def ::name ::us/string) (s/def ::register @@ -109,7 +107,7 @@ (def register "A new profile registration welcome email." - (emails/template-factory ::register default-context)) + (emails/template-factory ::register)) (s/def ::token ::us/string) (s/def ::password-recovery @@ -117,7 +115,7 @@ (def password-recovery "A password recovery notification email." - (emails/template-factory ::password-recovery default-context)) + (emails/template-factory ::password-recovery)) (s/def ::pending-email ::us/email) (s/def ::change-email @@ -125,7 +123,7 @@ (def change-email "Password change confirmation email" - (emails/template-factory ::change-email default-context)) + (emails/template-factory ::change-email)) (s/def :internal.emails.invite-to-team/invited-by ::us/string) (s/def :internal.emails.invite-to-team/team ::us/string) @@ -138,4 +136,50 @@ (def invite-to-team "Teams member invitation email." - (emails/template-factory ::invite-to-team default-context)) + (emails/template-factory ::invite-to-team)) + + +;; --- SENDMAIL TASK + +(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) + +(defmethod ig/pre-init-spec ::sendmail-handler [_] + (s/keys :req-un [::enabled] + :opt-un [::username + ::password + ::tls + ::ssl + ::host + ::port + ::default-from + ::default-reply-to])) + +(defmethod ig/init-key ::sendmail-handler + [_ cfg] + (fn [{:keys [props] :as task}] + (if (:enabled cfg) + (emails/send! cfg props) + (send-console! cfg props)))) + +(defn- send-console! + [cfg email] + (let [baos (java.io.ByteArrayOutputStream.) + mesg (emails/smtp-message cfg email)] + (.writeTo mesg baos) + (let [out (with-out-str + (println "email console dump:") + (println "******** start email" (:id email) "**********") + (println (.toString baos)) + (println "******** end email "(:id email) "**********"))] + (log/info out)))) + diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 65b641ea6..ae7a43df7 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -5,13 +5,13 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http (:require [app.common.data :as d] + [app.common.exceptions :as ex] [app.common.spec :as us] - [app.config :as cfg] [app.http.errors :as errors] [app.http.middleware :as middleware] [app.metrics :as mtx] @@ -26,22 +26,24 @@ org.eclipse.jetty.server.handler.ErrorHandler org.eclipse.jetty.server.handler.StatisticsHandler)) +(declare router-handler) + (s/def ::handler fn?) +(s/def ::router some?) (s/def ::ws (s/map-of ::us/string fn?)) -(s/def ::port ::cfg/http-server-port) +(s/def ::port ::us/integer) (s/def ::name ::us/string) (defmethod ig/pre-init-spec ::server [_] - (s/keys :req-un [::handler ::port] - :opt-un [::ws ::name ::mtx/metrics])) + (s/keys :req-un [::port] + :opt-un [::ws ::name ::mtx/metrics ::router ::handler])) (defmethod ig/prep-key ::server [_ cfg] - (merge {:name "http"} - (d/without-nils cfg))) + (merge {:name "http"} (d/without-nils cfg))) (defmethod ig/init-key ::server - [_ {:keys [handler ws port name metrics] :as opts}] + [_ {:keys [handler router ws port name metrics] :as opts}] (log/infof "starting '%s' server on port %s." name port) (let [pre-start (fn [^Server server] (let [handler (doto (ErrorHandler.) @@ -49,7 +51,7 @@ (.setServer server))] (.setErrorHandler server ^ErrorHandler handler) (when metrics - (let [stats (new StatisticsHandler)] + (let [stats (StatisticsHandler.)] (.setHandler ^StatisticsHandler stats (.getHandler server)) (.setHandler server stats) (mtx/instrument-jetty! (:registry metrics) stats))))) @@ -63,6 +65,13 @@ (when (seq ws) {:websockets ws})) + handler (cond + (fn? handler) handler + (some? router) (router-handler router) + :else (ex/raise :type :internal + :code :invalid-argument + :hint "Missing `handler` or `router` option.")) + server (jetty/run-jetty handler options)] (assoc opts :server server))) @@ -71,31 +80,13 @@ (log/infof "stoping '%s' server on port %s." name port) (jetty/stop-server server)) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Http Main Handler (Router) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(declare create-router) - -(s/def ::rpc map?) -(s/def ::session map?) -(s/def ::metrics map?) -(s/def ::oauth map?) -(s/def ::storage map?) -(s/def ::assets map?) -(s/def ::feedback fn?) - -(defmethod ig/pre-init-spec ::router [_] - (s/keys :req-un [::rpc ::session ::metrics ::oauth ::storage ::assets ::feedback])) - -(defmethod ig/init-key ::router - [_ cfg] - (let [handler (rr/ring-handler - (create-router cfg) - (rr/routes - (rr/create-resource-handler {:path "/"}) - (rr/create-default-handler)) - {:middleware [middleware/server-timing]})] +(defn- router-handler + [router] + (let [handler (rr/ring-handler router + (rr/routes + (rr/create-resource-handler {:path "/"}) + (rr/create-default-handler)) + {:middleware [middleware/server-timing]})] (fn [request] (try (handler request) @@ -104,18 +95,30 @@ (let [cdata (errors/get-error-context request e)] (update-thread-context! cdata) (log/errorf e "unhandled exception: %s (id: %s)" (ex-message e) (str (:id cdata))) - {:status 500 - :body "internal server error"}) + {:status 500 :body "internal server error"}) (catch Throwable e (log/errorf e "unhandled exception: %s" (ex-message e)) - {:status 500 - :body "internal server error"}))))))) + {:status 500 :body "internal server error"}))))))) -(defn- create-router - [{:keys [session rpc oauth metrics svgparse assets feedback] :as cfg}] + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Http Main Handler (Router) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(s/def ::rpc map?) +(s/def ::session map?) +(s/def ::oauth map?) +(s/def ::storage map?) +(s/def ::assets map?) +(s/def ::feedback fn?) + +(defmethod ig/pre-init-spec ::router [_] + (s/keys :req-un [::rpc ::session ::mtx/metrics ::oauth ::storage ::assets ::feedback])) + +(defmethod ig/init-key ::router + [_ {:keys [session rpc oauth metrics svgparse assets feedback] :as cfg}] (rr/router [["/metrics" {:get (:handler metrics)}] - ["/assets" {:middleware [[middleware/format-response-body] [middleware/errors errors/handle]]} ["/by-id/:id" {:get (:objects-handler assets)}] diff --git a/backend/src/app/http/feedback.clj b/backend/src/app/http/feedback.clj index 0d3fa4959..1ab206435 100644 --- a/backend/src/app/http/feedback.clj +++ b/backend/src/app/http/feedback.clj @@ -15,7 +15,7 @@ [app.common.spec :as us] [app.config :as cfg] [app.db :as db] - [app.emails :as emails] + [app.emails :as eml] [app.rpc.queries.profile :as profile] [clojure.spec.alpha :as s] [integrant.core :as ig])) @@ -62,11 +62,12 @@ [pool profile params] (let [params (us/conform ::feedback params) destination (cfg/get :feedback-destination)] - (emails/send! pool emails/feedback - {:to destination - :profile profile - :reply-to (:from params) - :email (:from params) - :subject (:subject params) - :content (:content params)}) + (eml/send! {::eml/conn pool + ::eml/factory eml/feedback + :to destination + :profile profile + :reply-to (:from params) + :email (:from params) + :subject (:subject params) + :content (:content params)}) nil)) diff --git a/backend/src/app/http/oauth/github.clj b/backend/src/app/http/oauth/github.clj index bfa4c3c14..379b9143a 100644 --- a/backend/src/app/http/oauth/github.clj +++ b/backend/src/app/http/oauth/github.clj @@ -5,13 +5,12 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.oauth.github (:require [app.common.exceptions :as ex] [app.common.spec :as us] - [app.config :as cfg] [app.http.oauth.google :as gg] [app.util.http :as http] [app.util.time :as dt] @@ -105,7 +104,7 @@ state (tokens :generate {:iss :github-oauth :invitation-token invitation :exp (dt/in-future "15m")}) - params {:client_id (:client-id cfg/config) + params {:client_id (:client-id cfg) :redirect_uri (build-redirect-url cfg) :state state :scope scope} diff --git a/backend/src/app/loggers/mattermost.clj b/backend/src/app/loggers/mattermost.clj index c2247e71e..f7eef7a1c 100644 --- a/backend/src/app/loggers/mattermost.clj +++ b/backend/src/app/loggers/mattermost.clj @@ -65,7 +65,7 @@ (try (let [uri (:uri cfg) text (str "Unhandled exception (@channel):\n" - "- detail: " (:public-uri cfg/config) "/dbg/error-by-id/" id "\n" + "- detail: " (cfg/get :public-uri) "/dbg/error-by-id/" id "\n" "- host: `" host "`\n" "- version: `" version "`\n" (when error diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 9d72865f1..e1a98a56a 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -5,353 +5,332 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main (:require [app.common.data :as d] - [app.config :as cfg] + [app.config :as cf] [app.util.time :as dt] - [clojure.pprint :as pprint] [clojure.tools.logging :as log] [integrant.core :as ig])) -;; Set value for all new threads bindings. -(alter-var-root #'*assert* (constantly (:asserts-enabled cfg/config))) +(def system-config + {:app.db/pool + {:uri (cf/get :database-uri) + :username (cf/get :database-username) + :password (cf/get :database-password) + :metrics (ig/ref :app.metrics/metrics) + :migrations (ig/ref :app.migrations/all) + :name :main + :min-pool-size 0 + :max-pool-size 20} -(derive :app.telemetry/server :app.http/server) + :app.metrics/metrics + {:definitions + {:profile-register + {:name "actions_profile_register_count" + :help "A global counter of user registrations." + :type :counter} + :profile-activation + {:name "actions_profile_activation_count" + :help "A global counter of profile activations" + :type :counter}}} -;; --- Entry point + :app.migrations/all + {:main (ig/ref :app.migrations/migrations)} -(defn build-system-config - [config] - (d/deep-merge - {:app.db/pool - {:uri (:database-uri config) - :username (:database-username config) - :password (:database-password config) - :metrics (ig/ref :app.metrics/metrics) - :migrations (ig/ref :app.migrations/all) - :name "main" - :min-pool-size 0 - :max-pool-size 20} + :app.migrations/migrations + {} - :app.metrics/metrics - {:definitions - {:profile-register - {:name "actions_profile_register_count" - :help "A global counter of user registrations." - :type :counter} - :profile-activation - {:name "actions_profile_activation_count" - :help "A global counter of profile activations" - :type :counter}}} + :app.msgbus/msgbus + {:backend (cf/get :msgbus-backend :redis) + :redis-uri (cf/get :redis-uri)} - :app.migrations/all - {:main (ig/ref :app.migrations/migrations) - :telemetry (ig/ref :app.telemetry/migrations)} + :app.tokens/tokens + {:sprops (ig/ref :app.setup/props)} - :app.migrations/migrations - {} + :app.storage/gc-deleted-task + {:pool (ig/ref :app.db/pool) + :storage (ig/ref :app.storage/storage) + :min-age (dt/duration {:hours 2})} - :app.telemetry/migrations - {} + :app.storage/gc-touched-task + {:pool (ig/ref :app.db/pool)} - :app.msgbus/msgbus - {:backend (:msgbus-backend config :redis) - :redis-uri (:redis-uri config)} + :app.storage/recheck-task + {:pool (ig/ref :app.db/pool) + :storage (ig/ref :app.storage/storage)} - :app.tokens/tokens - {:sprops (ig/ref :app.setup/props)} + :app.http.session/session + {:pool (ig/ref :app.db/pool) + :cookie-name (cf/get :http-session-cookie-name)} - :app.storage/gc-deleted-task - {:pool (ig/ref :app.db/pool) - :storage (ig/ref :app.storage/storage) - :min-age (dt/duration {:hours 2})} + :app.http.session/gc-task + {:pool (ig/ref :app.db/pool) + :max-age (cf/get :http-session-idle-max-age)} - :app.storage/gc-touched-task - {:pool (ig/ref :app.db/pool)} + :app.http.session/updater + {:pool (ig/ref :app.db/pool) + :metrics (ig/ref :app.metrics/metrics) + :executor (ig/ref :app.worker/executor) + :session (ig/ref :app.http.session/session) + :max-batch-age (cf/get :http-session-updater-batch-max-age) + :max-batch-size (cf/get :http-session-updater-batch-max-size)} - :app.storage/recheck-task - {:pool (ig/ref :app.db/pool) - :storage (ig/ref :app.storage/storage)} + :app.http.awsns/handler + {:tokens (ig/ref :app.tokens/tokens) + :pool (ig/ref :app.db/pool)} - :app.http.session/session - {:pool (ig/ref :app.db/pool) - :cookie-name (:http-session-cookie-name config)} + :app.http/server + {:port (cf/get :http-server-port) + :router (ig/ref :app.http/router) + :metrics (ig/ref :app.metrics/metrics) + :ws {"/ws/notifications" (ig/ref :app.notifications/handler)}} - :app.http.session/gc-task - {:pool (ig/ref :app.db/pool) - :max-age (:http-session-idle-max-age config)} + :app.http/router + { + :rpc (ig/ref :app.rpc/rpc) + :session (ig/ref :app.http.session/session) + :tokens (ig/ref :app.tokens/tokens) + :public-uri (cf/get :public-uri) + :metrics (ig/ref :app.metrics/metrics) + :oauth (ig/ref :app.http.oauth/all) + :assets (ig/ref :app.http.assets/handlers) + :svgparse (ig/ref :app.svgparse/handler) + :storage (ig/ref :app.storage/storage) + :sns-webhook (ig/ref :app.http.awsns/handler) + :feedback (ig/ref :app.http.feedback/handler) + :error-report-handler (ig/ref :app.loggers.mattermost/handler)} - :app.http.session/updater - {:pool (ig/ref :app.db/pool) - :metrics (ig/ref :app.metrics/metrics) - :executor (ig/ref :app.worker/executor) - :session (ig/ref :app.http.session/session) - :max-batch-age (:http-session-updater-batch-max-age config) - :max-batch-size (:http-session-updater-batch-max-size config)} + :app.http.assets/handlers + {:metrics (ig/ref :app.metrics/metrics) + :assets-path (cf/get :assets-path) + :storage (ig/ref :app.storage/storage) + :cache-max-age (dt/duration {:hours 24}) + :signature-max-age (dt/duration {:hours 24 :minutes 5})} - :app.http.awsns/handler - {:tokens (ig/ref :app.tokens/tokens) - :pool (ig/ref :app.db/pool)} + :app.http.feedback/handler + {:pool (ig/ref :app.db/pool)} - :app.http/server - {:port (:http-server-port config) - :handler (ig/ref :app.http/router) - :metrics (ig/ref :app.metrics/metrics) - :ws {"/ws/notifications" (ig/ref :app.notifications/handler)}} + :app.http.oauth/all + {:google (ig/ref :app.http.oauth/google) + :gitlab (ig/ref :app.http.oauth/gitlab) + :github (ig/ref :app.http.oauth/github)} - :app.http/router - {:rpc (ig/ref :app.rpc/rpc) - :session (ig/ref :app.http.session/session) - :tokens (ig/ref :app.tokens/tokens) - :public-uri (:public-uri config) - :metrics (ig/ref :app.metrics/metrics) - :oauth (ig/ref :app.http.oauth/all) - :assets (ig/ref :app.http.assets/handlers) - :svgparse (ig/ref :app.svgparse/handler) - :storage (ig/ref :app.storage/storage) - :sns-webhook (ig/ref :app.http.awsns/handler) - :feedback (ig/ref :app.http.feedback/handler) - :error-report-handler (ig/ref :app.loggers.mattermost/handler)} + :app.http.oauth/google + {:rpc (ig/ref :app.rpc/rpc) + :session (ig/ref :app.http.session/session) + :tokens (ig/ref :app.tokens/tokens) + :public-uri (cf/get :public-uri) + :client-id (cf/get :google-client-id) + :client-secret (cf/get :google-client-secret)} - :app.http.assets/handlers - {:metrics (ig/ref :app.metrics/metrics) - :assets-path (:assets-path config) - :storage (ig/ref :app.storage/storage) - :cache-max-age (dt/duration {:hours 24}) - :signature-max-age (dt/duration {:hours 24 :minutes 5})} + :app.http.oauth/github + {:rpc (ig/ref :app.rpc/rpc) + :session (ig/ref :app.http.session/session) + :tokens (ig/ref :app.tokens/tokens) + :public-uri (cf/get :public-uri) + :client-id (cf/get :github-client-id) + :client-secret (cf/get :github-client-secret)} - :app.http.feedback/handler - {:pool (ig/ref :app.db/pool)} + :app.http.oauth/gitlab + {:rpc (ig/ref :app.rpc/rpc) + :session (ig/ref :app.http.session/session) + :tokens (ig/ref :app.tokens/tokens) + :public-uri (cf/get :public-uri) + :base-uri (cf/get :gitlab-base-uri) + :client-id (cf/get :gitlab-client-id) + :client-secret (cf/get :gitlab-client-secret)} - :app.http.oauth/all - {:google (ig/ref :app.http.oauth/google) - :gitlab (ig/ref :app.http.oauth/gitlab) - :github (ig/ref :app.http.oauth/github)} + :app.svgparse/svgc + {:metrics (ig/ref :app.metrics/metrics)} - :app.http.oauth/google - {:rpc (ig/ref :app.rpc/rpc) - :session (ig/ref :app.http.session/session) - :tokens (ig/ref :app.tokens/tokens) - :public-uri (:public-uri config) - :client-id (:google-client-id config) - :client-secret (:google-client-secret config)} + ;; HTTP Handler for SVG parsing + :app.svgparse/handler + {:metrics (ig/ref :app.metrics/metrics) + :svgc (ig/ref :app.svgparse/svgc)} - :app.http.oauth/github - {:rpc (ig/ref :app.rpc/rpc) - :session (ig/ref :app.http.session/session) - :tokens (ig/ref :app.tokens/tokens) - :public-uri (:public-uri config) - :client-id (:github-client-id config) - :client-secret (:github-client-secret config)} + ;; RLimit definition for password hashing + :app.rlimits/password + (cf/get :rlimits-password) - :app.http.oauth/gitlab - {:rpc (ig/ref :app.rpc/rpc) - :session (ig/ref :app.http.session/session) - :tokens (ig/ref :app.tokens/tokens) - :public-uri (:public-uri config) - :base-uri (:gitlab-base-uri config) - :client-id (:gitlab-client-id config) - :client-secret (:gitlab-client-secret config)} + ;; RLimit definition for image processing + :app.rlimits/image + (cf/get :rlimits-image) - ;; HTTP Handler for SVG parsing - :app.svgparse/handler - {:metrics (ig/ref :app.metrics/metrics)} + ;; A collection of rlimits as hash-map. + :app.rlimits/all + {:password (ig/ref :app.rlimits/password) + :image (ig/ref :app.rlimits/image)} - ;; RLimit definition for password hashing - :app.rlimits/password - (:rlimits-password config) + :app.rpc/rpc + {:pool (ig/ref :app.db/pool) + :session (ig/ref :app.http.session/session) + :tokens (ig/ref :app.tokens/tokens) + :metrics (ig/ref :app.metrics/metrics) + :storage (ig/ref :app.storage/storage) + :msgbus (ig/ref :app.msgbus/msgbus) + :rlimits (ig/ref :app.rlimits/all) + :svgc (ig/ref :app.svgparse/svgc) + :public-uri (cf/get :public-uri)} - ;; RLimit definition for image processing - :app.rlimits/image - (:rlimits-image config) + :app.notifications/handler + {:msgbus (ig/ref :app.msgbus/msgbus) + :pool (ig/ref :app.db/pool) + :session (ig/ref :app.http.session/session) + :metrics (ig/ref :app.metrics/metrics) + :executor (ig/ref :app.worker/executor)} - ;; A collection of rlimits as hash-map. - :app.rlimits/all - {:password (ig/ref :app.rlimits/password) - :image (ig/ref :app.rlimits/image)} + :app.worker/executor + {:min-threads 0 + :max-threads 256 + :idle-timeout 60000 + :name :worker} - :app.rpc/rpc - {:pool (ig/ref :app.db/pool) - :session (ig/ref :app.http.session/session) - :tokens (ig/ref :app.tokens/tokens) - :metrics (ig/ref :app.metrics/metrics) - :storage (ig/ref :app.storage/storage) - :msgbus (ig/ref :app.msgbus/msgbus) - :rlimits (ig/ref :app.rlimits/all)} + :app.worker/worker + {:executor (ig/ref :app.worker/executor) + :tasks (ig/ref :app.worker/registry) + :metrics (ig/ref :app.metrics/metrics) + :pool (ig/ref :app.db/pool)} - :app.notifications/handler - {:msgbus (ig/ref :app.msgbus/msgbus) - :pool (ig/ref :app.db/pool) - :session (ig/ref :app.http.session/session) - :metrics (ig/ref :app.metrics/metrics) - :executor (ig/ref :app.worker/executor)} + :app.worker/scheduler + {:executor (ig/ref :app.worker/executor) + :tasks (ig/ref :app.worker/registry) + :pool (ig/ref :app.db/pool) + :schedule + [{:cron #app/cron "0 0 0 */1 * ? *" ;; daily + :task :file-media-gc} - :app.worker/executor - {:name "worker"} + {:cron #app/cron "0 0 */1 * * ?" ;; hourly + :task :file-xlog-gc} - :app.worker/worker - {:executor (ig/ref :app.worker/executor) - :pool (ig/ref :app.db/pool) - :tasks (ig/ref :app.tasks/registry)} + {:cron #app/cron "0 0 1 */1 * ?" ;; daily (1 hour shift) + :task :storage-deleted-gc} - :app.worker/scheduler - {:executor (ig/ref :app.worker/executor) - :pool (ig/ref :app.db/pool) - :tasks (ig/ref :app.tasks/registry) - :schedule - [{:id "file-media-gc" - :cron #app/cron "0 0 0 */1 * ? *" ;; daily - :task :file-media-gc} + {:cron #app/cron "0 0 2 */1 * ?" ;; daily (2 hour shift) + :task :storage-touched-gc} - {:id "file-xlog-gc" - :cron #app/cron "0 0 */1 * * ?" ;; hourly - :task :file-xlog-gc} + {:cron #app/cron "0 0 3 */1 * ?" ;; daily (3 hour shift) + :task :session-gc} - {:id "storage-deleted-gc" - :cron #app/cron "0 0 1 */1 * ?" ;; daily (1 hour shift) - :task :storage-deleted-gc} + {:cron #app/cron "0 0 */1 * * ?" ;; hourly + :task :storage-recheck} - {:id "storage-touched-gc" - :cron #app/cron "0 0 2 */1 * ?" ;; daily (2 hour shift) - :task :storage-touched-gc} + {:cron #app/cron "0 0 0 */1 * ?" ;; daily + :task :tasks-gc} - {:id "session-gc" - :cron #app/cron "0 0 3 */1 * ?" ;; daily (3 hour shift) - :task :session-gc} + (when (cf/get :telemetry-enabled) + {:cron #app/cron "0 0 */6 * * ?" ;; every 6h + :uri (cf/get :telemetry-uri) + :task :telemetry})]} - {:id "storage-recheck" - :cron #app/cron "0 0 */1 * * ?" ;; hourly - :task :storage-recheck} + :app.worker/registry + {:metrics (ig/ref :app.metrics/metrics) + :tasks + {:sendmail (ig/ref :app.emails/sendmail-handler) + :delete-object (ig/ref :app.tasks.delete-object/handler) + :delete-profile (ig/ref :app.tasks.delete-profile/handler) + :file-media-gc (ig/ref :app.tasks.file-media-gc/handler) + :file-xlog-gc (ig/ref :app.tasks.file-xlog-gc/handler) + :storage-deleted-gc (ig/ref :app.storage/gc-deleted-task) + :storage-touched-gc (ig/ref :app.storage/gc-touched-task) + :storage-recheck (ig/ref :app.storage/recheck-task) + :tasks-gc (ig/ref :app.tasks.tasks-gc/handler) + :telemetry (ig/ref :app.tasks.telemetry/handler) + :session-gc (ig/ref :app.http.session/gc-task)}} - {:id "tasks-gc" - :cron #app/cron "0 0 0 */1 * ?" ;; daily - :task :tasks-gc} + :app.emails/sendmail-handler + {:host (cf/get :smtp-host) + :port (cf/get :smtp-port) + :ssl (cf/get :smtp-ssl) + :tls (cf/get :smtp-tls) + :enabled (cf/get :smtp-enabled) + :username (cf/get :smtp-username) + :password (cf/get :smtp-password) + :metrics (ig/ref :app.metrics/metrics) + :default-reply-to (cf/get :smtp-default-reply-to) + :default-from (cf/get :smtp-default-from)} - (when (:telemetry-enabled config) - {:id "telemetry" - :cron #app/cron "0 0 */6 * * ?" ;; every 6h - :uri (:telemetry-uri config) - :task :telemetry})]} + :app.tasks.tasks-gc/handler + {:pool (ig/ref :app.db/pool) + :max-age (dt/duration {:hours 24}) + :metrics (ig/ref :app.metrics/metrics)} - :app.tasks/registry - {:metrics (ig/ref :app.metrics/metrics) - :tasks - {:sendmail (ig/ref :app.tasks.sendmail/handler) - :delete-object (ig/ref :app.tasks.delete-object/handler) - :delete-profile (ig/ref :app.tasks.delete-profile/handler) - :file-media-gc (ig/ref :app.tasks.file-media-gc/handler) - :file-xlog-gc (ig/ref :app.tasks.file-xlog-gc/handler) - :storage-deleted-gc (ig/ref :app.storage/gc-deleted-task) - :storage-touched-gc (ig/ref :app.storage/gc-touched-task) - :storage-recheck (ig/ref :app.storage/recheck-task) - :tasks-gc (ig/ref :app.tasks.tasks-gc/handler) - :telemetry (ig/ref :app.tasks.telemetry/handler) - :session-gc (ig/ref :app.http.session/gc-task)}} + :app.tasks.delete-object/handler + {:pool (ig/ref :app.db/pool) + :metrics (ig/ref :app.metrics/metrics)} - :app.tasks.sendmail/handler - {:host (:smtp-host config) - :port (:smtp-port config) - :ssl (:smtp-ssl config) - :tls (:smtp-tls config) - :enabled (:smtp-enabled config) - :username (:smtp-username config) - :password (:smtp-password config) - :metrics (ig/ref :app.metrics/metrics) - :default-reply-to (:smtp-default-reply-to config) - :default-from (:smtp-default-from config)} + :app.tasks.delete-storage-object/handler + {:pool (ig/ref :app.db/pool) + :storage (ig/ref :app.storage/storage) + :metrics (ig/ref :app.metrics/metrics)} - :app.tasks.tasks-gc/handler - {:pool (ig/ref :app.db/pool) - :max-age (dt/duration {:hours 24}) - :metrics (ig/ref :app.metrics/metrics)} + :app.tasks.delete-profile/handler + {:pool (ig/ref :app.db/pool) + :metrics (ig/ref :app.metrics/metrics)} - :app.tasks.delete-object/handler - {:pool (ig/ref :app.db/pool) - :metrics (ig/ref :app.metrics/metrics)} + :app.tasks.file-media-gc/handler + {:pool (ig/ref :app.db/pool) + :metrics (ig/ref :app.metrics/metrics) + :max-age (dt/duration {:hours 48})} - :app.tasks.delete-storage-object/handler - {:pool (ig/ref :app.db/pool) - :storage (ig/ref :app.storage/storage) - :metrics (ig/ref :app.metrics/metrics)} + :app.tasks.file-xlog-gc/handler + {:pool (ig/ref :app.db/pool) + :metrics (ig/ref :app.metrics/metrics) + :max-age (dt/duration {:hours 48})} - :app.tasks.delete-profile/handler - {:pool (ig/ref :app.db/pool) - :metrics (ig/ref :app.metrics/metrics)} + :app.tasks.telemetry/handler + {:pool (ig/ref :app.db/pool) + :version (:full cf/version) + :uri (cf/get :telemetry-uri) + :sprops (ig/ref :app.setup/props)} - :app.tasks.file-media-gc/handler - {:pool (ig/ref :app.db/pool) - :metrics (ig/ref :app.metrics/metrics) - :max-age (dt/duration {:hours 48})} + :app.srepl/server + {:port (cf/get :srepl-port) + :host (cf/get :srepl-host)} - :app.tasks.file-xlog-gc/handler - {:pool (ig/ref :app.db/pool) - :metrics (ig/ref :app.metrics/metrics) - :max-age (dt/duration {:hours 48})} + :app.setup/props + {:pool (ig/ref :app.db/pool)} - :app.tasks.telemetry/handler - {:pool (ig/ref :app.db/pool) - :version (:full cfg/version) - :uri (:telemetry-uri config) - :sprops (ig/ref :app.setup/props)} + :app.loggers.zmq/receiver + {:endpoint (cf/get :loggers-zmq-uri)} - :app.srepl/server - {:port (:srepl-port config) - :host (:srepl-host config)} + :app.loggers.loki/reporter + {:uri (cf/get :loggers-loki-uri) + :receiver (ig/ref :app.loggers.zmq/receiver) + :executor (ig/ref :app.worker/executor)} - :app.setup/props - {:pool (ig/ref :app.db/pool)} + :app.loggers.mattermost/reporter + {:uri (cf/get :error-report-webhook) + :receiver (ig/ref :app.loggers.zmq/receiver) + :pool (ig/ref :app.db/pool) + :executor (ig/ref :app.worker/executor)} - :app.loggers.zmq/receiver - {:endpoint (:loggers-zmq-uri config)} + :app.loggers.mattermost/handler + {:pool (ig/ref :app.db/pool)} - :app.loggers.loki/reporter - {:uri (:loggers-loki-uri config) - :receiver (ig/ref :app.loggers.zmq/receiver) - :executor (ig/ref :app.worker/executor)} + :app.storage/storage + {:pool (ig/ref :app.db/pool) + :executor (ig/ref :app.worker/executor) + :backend (cf/get :storage-backend :fs) + :backends {:s3 (ig/ref [::main :app.storage.s3/backend]) + :db (ig/ref [::main :app.storage.db/backend]) + :fs (ig/ref [::main :app.storage.fs/backend]) + :tmp (ig/ref [::tmp :app.storage.fs/backend])}} - :app.loggers.mattermost/reporter - {:uri (:error-report-webhook config) - :receiver (ig/ref :app.loggers.zmq/receiver) - :pool (ig/ref :app.db/pool) - :executor (ig/ref :app.worker/executor)} + [::main :app.storage.s3/backend] + {:region (cf/get :storage-s3-region) + :bucket (cf/get :storage-s3-bucket)} - :app.loggers.mattermost/handler - {:pool (ig/ref :app.db/pool)} + [::main :app.storage.fs/backend] + {:directory (cf/get :storage-fs-directory)} - :app.storage/storage - {:pool (ig/ref :app.db/pool) - :executor (ig/ref :app.worker/executor) - :backend (:storage-backend config :fs) - :backends {:s3 (ig/ref [::main :app.storage.s3/backend]) - :db (ig/ref [::main :app.storage.db/backend]) - :fs (ig/ref [::main :app.storage.fs/backend]) - :tmp (ig/ref [::tmp :app.storage.fs/backend])}} + [::tmp :app.storage.fs/backend] + {:directory "/tmp/penpot"} - [::main :app.storage.s3/backend] - {:region (:storage-s3-region config) - :bucket (:storage-s3-bucket config)} - - [::main :app.storage.fs/backend] - {:directory (:storage-fs-directory config)} - - [::tmp :app.storage.fs/backend] - {:directory "/tmp/penpot"} - - [::main :app.storage.db/backend] - {:pool (ig/ref :app.db/pool)}} - - (when (:telemetry-server-enabled config) - {:app.telemetry/handler - {:pool (ig/ref :app.db/pool) - :executor (ig/ref :app.worker/executor)} - - :app.telemetry/server - {:port (:telemetry-server-port config 6063) - :handler (ig/ref :app.telemetry/handler) - :name "telemetry"}}))) + [::main :app.storage.db/backend] + {:pool (ig/ref :app.db/pool)}}) (defmethod ig/init-key :default [_ data] data) (defmethod ig/prep-key :default @@ -364,15 +343,14 @@ (defn start [] - (let [system-config (build-system-config cfg/config)] - (ig/load-namespaces system-config) - (alter-var-root #'system (fn [sys] - (when sys (ig/halt! sys)) - (-> system-config - (ig/prep) - (ig/init)))) - (log/infof "welcome to penpot (version: '%s')" - (:full cfg/version)))) + (ig/load-namespaces system-config) + (alter-var-root #'system (fn [sys] + (when sys (ig/halt! sys)) + (-> system-config + (ig/prep) + (ig/init)))) + (log/infof "welcome to penpot (version: '%s')" + (:full cf/version))) (defn stop [] @@ -380,14 +358,6 @@ (when sys (ig/halt! sys)) nil))) -(prefer-method print-method - clojure.lang.IRecord - clojure.lang.IDeref) - -(prefer-method pprint/simple-dispatch - clojure.lang.IPersistentMap - clojure.lang.IDeref) - (defn -main [& _args] (start)) diff --git a/backend/src/app/rpc/mutations/demo.clj b/backend/src/app/rpc/mutations/demo.clj index 80658a5da..4f78184bf 100644 --- a/backend/src/app/rpc/mutations/demo.clj +++ b/backend/src/app/rpc/mutations/demo.clj @@ -5,7 +5,7 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.demo "A demo specific mutations." @@ -16,8 +16,8 @@ [app.db :as db] [app.rpc.mutations.profile :as profile] [app.setup.initial-data :as sid] - [app.tasks :as tasks] [app.util.services :as sv] + [app.worker :as wrk] [buddy.core.codecs :as bc] [buddy.core.nonce :as bn] [clojure.spec.alpha :as s])) @@ -40,7 +40,7 @@ :password password :props {:onboarding-viewed true}}] - (when-not (:allow-demo-users cfg/config) + (when-not (cfg/get :allow-demo-users) (ex/raise :type :validation :code :demo-users-not-allowed :hint "Demo users are disabled by config.")) @@ -51,9 +51,10 @@ (sid/load-initial-project! conn)) ;; Schedule deletion of the demo profile - (tasks/submit! conn {:name "delete-profile" - :delay cfg/deletion-delay - :props {:profile-id id}}) + (wrk/submit! {::wrk/task :delete-profile + ::wrk/delay cfg/deletion-delay + ::wrk/conn conn + :profile-id id}) {:email email :password password}))) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 0631429d5..fb547bfa9 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -5,7 +5,7 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.files (:require @@ -19,10 +19,10 @@ [app.rpc.permissions :as perms] [app.rpc.queries.files :as files] [app.rpc.queries.projects :as proj] - [app.tasks :as tasks] [app.util.blob :as blob] [app.util.services :as sv] [app.util.time :as dt] + [app.worker :as wrk] [clojure.spec.alpha :as s])) ;; --- Helpers & Specs @@ -126,9 +126,11 @@ (files/check-edition-permissions! conn profile-id id) ;; Schedule object deletion - (tasks/submit! conn {:name "delete-object" - :delay cfg/deletion-delay - :props {:id id :type :file}}) + (wrk/submit! {::wrk/task :delete-object + ::wrk/delay cfg/deletion-delay + ::wrk/conn conn + :id id + :type :file}) (mark-file-deleted conn params))) diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index dc0ec95fe..57e79e402 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -14,16 +14,16 @@ [app.common.uuid :as uuid] [app.config :as cfg] [app.db :as db] - [app.emails :as emails] + [app.emails :as eml] [app.media :as media] [app.rpc.mutations.projects :as projects] [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] [app.setup.initial-data :as sid] [app.storage :as sto] - [app.tasks :as tasks] [app.util.services :as sv] [app.util.time :as dt] + [app.worker :as wrk] [buddy.hashers :as hashers] [clojure.spec.alpha :as s] [cuerdas.core :as str])) @@ -117,16 +117,19 @@ ;; Don't allow proceed in register page if the email is ;; already reported as permanent bounced - (when (emails/has-bounce-reports? conn (:email profile)) + (when (eml/has-bounce-reports? conn (:email profile)) (ex/raise :type :validation :code :email-has-permanent-bounces :hint "looks like the email has one or many bounces reported")) - (emails/send! conn emails/register - {:to (:email profile) - :name (:fullname profile) - :token vtoken - :extra-data ptoken}) + (eml/send! {::eml/conn conn + ::eml/factory eml/register + :public-uri (:public-uri cfg) + :to (:email profile) + :name (:fullname profile) + :token vtoken + :extra-data ptoken}) + (with-meta profile {:before-complete (annotate-profile-register metrics profile)}))))) @@ -439,7 +442,7 @@ {:changed true}) (defn- request-email-change - [{:keys [conn tokens]} {:keys [profile email] :as params}] + [{:keys [conn tokens] :as cfg} {:keys [profile email] :as params}] (let [token (tokens :generate {:iss :change-email :exp (dt/in-future "15m") @@ -452,22 +455,24 @@ (when (not= email (:email profile)) (check-profile-existence! conn params)) - (when-not (emails/allow-send-emails? conn profile) + (when-not (eml/allow-send-emails? conn profile) (ex/raise :type :validation :code :profile-is-muted :hint "looks like the profile has reported repeatedly as spam or has permanent bounces.")) - (when (emails/has-bounce-reports? conn email) + (when (eml/has-bounce-reports? conn email) (ex/raise :type :validation :code :email-has-permanent-bounces :hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce")) - (emails/send! conn emails/change-email - {:to (:email profile) - :name (:fullname profile) - :pending-email email - :token token - :extra-data ptoken}) + (eml/send! {::eml/conn conn + ::eml/factory eml/change-email + :public-uri (:public-uri cfg) + :to (:email profile) + :name (:fullname profile) + :pending-email email + :token token + :extra-data ptoken}) nil)) @@ -493,16 +498,18 @@ (let [ptoken (tokens :generate-predefined {:iss :profile-identity :profile-id (:id profile)})] - (emails/send! conn emails/password-recovery - {:to (:email profile) - :token (:token profile) - :name (:fullname profile) - :extra-data ptoken}) + (eml/send! {::eml/conn conn + ::eml/factory eml/password-recovery + :public-uri (:public-uri cfg) + :to (:email profile) + :token (:token profile) + :name (:fullname profile) + :extra-data ptoken}) nil))] (db/with-atomic [conn pool] (when-let [profile (profile/retrieve-profile-data-by-email conn email)] - (when-not (emails/allow-send-emails? conn profile) + (when-not (eml/allow-send-emails? conn profile) (ex/raise :type :validation :code :profile-is-muted :hint "looks like the profile has reported repeatedly as spam or has permanent bounces.")) @@ -512,7 +519,7 @@ :code :profile-not-verified :hint "the user need to validate profile before recover password")) - (when (emails/has-bounce-reports? conn (:email profile)) + (when (eml/has-bounce-reports? conn (:email profile)) (ex/raise :type :validation :code :email-has-permanent-bounces :hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce")) @@ -579,9 +586,10 @@ (check-can-delete-profile! conn profile-id) ;; Schedule a complete deletion of profile - (tasks/submit! conn {:name "delete-profile" - :delay cfg/deletion-delay - :props {:profile-id profile-id}}) + (wrk/submit! {::wrk/task :delete-profile + ::wrk/dalay cfg/deletion-delay + ::wrk/conn conn + :profile-id profile-id}) (db/update! conn :profile {:deleted-at (dt/now)} diff --git a/backend/src/app/rpc/mutations/projects.clj b/backend/src/app/rpc/mutations/projects.clj index 3dbdef8f0..1a03fd34d 100644 --- a/backend/src/app/rpc/mutations/projects.clj +++ b/backend/src/app/rpc/mutations/projects.clj @@ -16,9 +16,9 @@ [app.rpc.permissions :as perms] [app.rpc.queries.projects :as proj] [app.rpc.queries.teams :as teams] - [app.tasks :as tasks] [app.util.services :as sv] [app.util.time :as dt] + [app.worker :as wrk] [clojure.spec.alpha :as s])) ;; --- Helpers & Specs @@ -128,9 +128,11 @@ (proj/check-edition-permissions! conn profile-id id) ;; Schedule object deletion - (tasks/submit! conn {:name "delete-object" - :delay cfg/deletion-delay - :props {:id id :type :project}}) + (wrk/submit! {::wrk/task :delete-object + ::wrk/delay cfg/deletion-delay + ::wrk/conn conn + :id id + :type :project}) (db/update! conn :project {:deleted-at (dt/now)} diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index c1934de9d..46de58083 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -5,7 +5,7 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.teams (:require @@ -15,16 +15,16 @@ [app.common.uuid :as uuid] [app.config :as cfg] [app.db :as db] - [app.emails :as emails] + [app.emails :as eml] [app.media :as media] [app.rpc.mutations.projects :as projects] [app.rpc.permissions :as perms] [app.rpc.queries.profile :as profile] [app.rpc.queries.teams :as teams] [app.storage :as sto] - [app.tasks :as tasks] [app.util.services :as sv] [app.util.time :as dt] + [app.worker :as wrk] [clojure.spec.alpha :as s] [datoteka.core :as fs])) @@ -139,9 +139,11 @@ :code :only-owner-can-delete-team)) ;; Schedule object deletion - (tasks/submit! conn {:name "delete-object" - :delay cfg/deletion-delay - :props {:id id :type :team}}) + (wrk/submit! {::wrk/task :delete-object + ::wrk/delay cfg/deletion-delay + ::wrk/conn conn + :id id + :type :team}) (db/update! conn :team {:deleted-at (dt/now)} @@ -323,27 +325,29 @@ :code :insufficient-permissions)) ;; First check if the current profile is allowed to send emails. - (when-not (emails/allow-send-emails? conn profile) + (when-not (eml/allow-send-emails? conn profile) (ex/raise :type :validation :code :profile-is-muted :hint "looks like the profile has reported repeatedly as spam or has permanent bounces")) - (when (and member (not (emails/allow-send-emails? conn member))) + (when (and member (not (eml/allow-send-emails? conn member))) (ex/raise :type :validation :code :member-is-muted :hint "looks like the profile has reported repeatedly as spam or has permanent bounces")) ;; Secondly check if the invited member email is part of the ;; global spam/bounce report. - (when (emails/has-bounce-reports? conn email) + (when (eml/has-bounce-reports? conn email) (ex/raise :type :validation :code :email-has-permanent-bounces :hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce")) - (emails/send! conn emails/invite-to-team - {:to email - :invited-by (:fullname profile) - :team (:name team) - :token itoken - :extra-data ptoken}) + (eml/send! {::eml/conn conn + ::eml/factory eml/invite-to-team + :public-uri (:public-uri cfg) + :to email + :invited-by (:fullname profile) + :team (:name team) + :token itoken + :extra-data ptoken}) nil))) diff --git a/backend/src/app/tasks.clj b/backend/src/app/tasks.clj deleted file mode 100644 index 5ef8f0d4f..000000000 --- a/backend/src/app/tasks.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/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL - -(ns app.tasks - (:require - [app.common.spec :as us] - [app.common.uuid :as uuid] - [app.db :as db] - [app.metrics :as mtx] - [app.util.time :as dt] - [app.worker] - [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] - [integrant.core :as ig])) - -(s/def ::name ::us/string) -(s/def ::delay - (s/or :int ::us/integer - :duration dt/duration?)) -(s/def ::queue ::us/string) - -(s/def ::task-options - (s/keys :req-un [::name] - :opt-un [::delay ::props ::queue])) - -(def ^:private sql:insert-new-task - "insert into task (id, name, props, queue, priority, max_retries, scheduled_at) - values (?, ?, ?, ?, ?, ?, clock_timestamp() + ?) - returning id") - -(defn submit! - [conn {:keys [name delay props queue priority max-retries] - :or {delay 0 props {} queue "default" priority 100 max-retries 3} - :as options}] - (us/verify ::task-options options) - (let [duration (dt/duration delay) - interval (db/interval duration) - props (db/tjson props) - id (uuid/next)] - (log/debugf "submit task '%s' to be executed in '%s'" name (str duration)) - (db/exec-one! conn [sql:insert-new-task id name props queue priority max-retries interval]) - id)) - -(defn- instrument! - [registry] - (mtx/instrument-vars! - [#'submit!] - {:registry registry - :type :counter - :labels ["name"] - :name "tasks_submit_total" - :help "A counter of task submissions." - :wrap (fn [rootf mobj] - (let [mdata (meta rootf) - origf (::original mdata rootf)] - (with-meta - (fn [conn params] - (let [tname (:name params)] - (mobj :inc [tname]) - (origf conn params))) - {::original origf})))}) - - (mtx/instrument-vars! - [#'app.worker/run-task] - {:registry registry - :type :summary - :quantiles [] - :name "tasks_checkout_timing" - :help "Latency measured between scheduld_at and execution time." - :wrap (fn [rootf mobj] - (let [mdata (meta rootf) - origf (::original mdata rootf)] - (with-meta - (fn [tasks item] - (let [now (inst-ms (dt/now)) - sat (inst-ms (:scheduled-at item))] - (mobj :observe (- now sat)) - (origf tasks item))) - {::original origf})))})) - -;; --- STATE INIT: REGISTRY - -(s/def ::tasks - (s/map-of keyword? fn?)) - -(defmethod ig/pre-init-spec ::registry [_] - (s/keys :req-un [::mtx/metrics ::tasks])) - -(defmethod ig/init-key ::registry - [_ {:keys [metrics tasks]}] - (instrument! (:registry metrics)) - (let [mobj (mtx/create - {:registry (:registry metrics) - :type :summary - :labels ["name"] - :quantiles [] - :name "tasks_timing" - :help "Background task execution timing."})] - (reduce-kv (fn [res k v] - (let [tname (name k)] - (log/debugf "registring task '%s'" tname) - (assoc res tname (mtx/wrap-summary v mobj [tname])))) - {} - tasks))) diff --git a/backend/src/app/tasks/sendmail.clj b/backend/src/app/tasks/sendmail.clj index 0619b75a2..333f7efd1 100644 --- a/backend/src/app/tasks/sendmail.clj +++ b/backend/src/app/tasks/sendmail.clj @@ -5,7 +5,7 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tasks.sendmail (:require diff --git a/backend/src/app/tasks/telemetry.clj b/backend/src/app/tasks/telemetry.clj index 64ca03c8e..4de76ce0f 100644 --- a/backend/src/app/tasks/telemetry.clj +++ b/backend/src/app/tasks/telemetry.clj @@ -5,13 +5,14 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tasks.telemetry "A task that is reponsible to collect anonymous statistical information about the current instance and send it to the telemetry server." (:require + [app.common.data :as d] [app.common.exceptions :as ex] [app.common.spec :as us] [app.config :as cfg] @@ -32,7 +33,6 @@ (s/def ::sprops (s/keys :req-un [::instance-id])) - (defmethod ig/pre-init-spec ::handler [_] (s/keys :req-un [::db/pool ::version ::uri ::sprops])) @@ -128,11 +128,16 @@ (defn- retrieve-stats [{:keys [conn version]}] - (merge - {:version version - :with-taiga (:telemetry-with-taiga cfg/config false) - :total-teams (retrieve-num-teams conn) - :total-projects (retrieve-num-projects conn) - :total-files (retrieve-num-files conn)} - (retrieve-team-averages conn) - (retrieve-jvm-stats))) + (let [referer (if (cfg/get :telemetry-with-taiga) + "taiga" + (cfg/get :telemetry-referer))] + (-> {:version version + :referer referer + :total-teams (retrieve-num-teams conn) + :total-projects (retrieve-num-projects conn) + :total-files (retrieve-num-files conn)} + (d/merge + (retrieve-team-averages conn) + (retrieve-jvm-stats)) + (d/without-nils)))) + diff --git a/backend/src/app/telemetry.clj b/backend/src/app/telemetry.clj deleted file mode 100644 index a5268ef23..000000000 --- a/backend/src/app/telemetry.clj +++ /dev/null @@ -1,121 +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/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL - -(ns app.telemetry - (:require - [app.common.spec :as us] - [app.db :as db] - [app.http.middleware :refer [wrap-parse-request-body]] - [clojure.pprint :refer [pprint]] - [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] - [integrant.core :as ig] - [promesa.exec :as px] - [ring.middleware.keyword-params :refer [wrap-keyword-params]] - [ring.middleware.params :refer [wrap-params]])) - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Migrations -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(def sql:create-instance-table - "CREATE TABLE IF NOT EXISTS telemetry.instance ( - id uuid PRIMARY KEY, - created_at timestamptz NOT NULL DEFAULT now() - );") - -(def sql:create-info-table - "CREATE TABLE telemetry.info ( - instance_id uuid, - created_at timestamptz NOT NULL DEFAULT clock_timestamp(), - data jsonb NOT NULL, - - PRIMARY KEY (instance_id, created_at) - ) PARTITION BY RANGE(created_at); - - CREATE TABLE telemetry.info_default (LIKE telemetry.info INCLUDING ALL); - - ALTER TABLE telemetry.info - ATTACH PARTITION telemetry.info_default DEFAULT;") - -(def migrations - [{:name "0001-add-telemetry-schema" - :fn #(db/exec! % ["CREATE SCHEMA IF NOT EXISTS telemetry;"])} - - {:name "0002-add-instance-table" - :fn #(db/exec! % [sql:create-instance-table])} - - {:name "0003-add-info-table" - :fn #(db/exec! % [sql:create-info-table])} - - {:name "0004-del-instance-table" - :fn #(db/exec! % ["DROP TABLE telemetry.instance;"])}]) - -(defmethod ig/init-key ::migrations [_ _] migrations) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Router Handler -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(declare handler) -(declare process-request) - -(defmethod ig/init-key ::handler - [_ cfg] - (-> (partial handler cfg) - (wrap-keyword-params) - (wrap-params) - (wrap-parse-request-body))) - -(s/def ::instance-id ::us/uuid) -(s/def ::params (s/keys :req-un [::instance-id])) - -(defn handler - [{:keys [executor] :as cfg} {:keys [params] :as request}] - (try - (let [params (us/conform ::params params) - cfg (assoc cfg - :instance-id (:instance-id params) - :data (dissoc params :instance-id))] - (px/run! executor (partial process-request cfg))) - (catch Exception e - ;; We don't want notify user of a error, just log it for posible - ;; future investigation. - (log/warn e (str "unexpected error on telemetry:\n" - (when-let [edata (ex-data e)] - (str "ex-data: \n" - (with-out-str (pprint edata)))) - (str "params: \n" - (with-out-str (pprint params))))))) - {:status 200 - :body "OK\n"}) - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Request Processing -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(def sql:insert-instance-info - "insert into telemetry.info (instance_id, data, created_at) - values (?, ?, date_trunc('day', now())) - on conflict (instance_id, created_at) - do update set data = ?") - -(defn- process-request - [{:keys [pool instance-id data]}] - (try - (db/with-atomic [conn pool] - (let [data (db/json data)] - (db/exec! conn [sql:insert-instance-info - instance-id - data - data]))) - (catch Exception e - (log/errorf e "error on procesing request")))) diff --git a/backend/src/app/util/blob.clj b/backend/src/app/util/blob.clj index b478f9de2..b3ecf3249 100644 --- a/backend/src/app/util/blob.clj +++ b/backend/src/app/util/blob.clj @@ -5,13 +5,13 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2016-2020 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.util.blob - "A generic blob storage encoding. Mainly used for - page data, page options and txlog payload storage." + "A generic blob storage encoding. Mainly used for page data, page + options and txlog payload storage." (:require - [app.config :as cfg] + [app.config :as cf] [app.util.transit :as t] [taoensso.nippy :as n]) (:import @@ -33,17 +33,15 @@ (declare encode-v2) (declare encode-v3) -(def default-version - (:default-blob-version cfg/config 1)) - (defn encode ([data] (encode data nil)) - ([data {:keys [version] :or {version default-version}}] - (case (long version) - 1 (encode-v1 data) - 2 (encode-v2 data) - 3 (encode-v3 data) - (throw (ex-info "unsupported version" {:version version}))))) + ([data {:keys [version]}] + (let [version (or version (cf/get :default-blob-version 1))] + (case (long version) + 1 (encode-v1 data) + 2 (encode-v2 data) + 3 (encode-v3 data) + (throw (ex-info "unsupported version" {:version version})))))) (defn decode "A function used for decode persisted blobs in the database." diff --git a/backend/src/app/util/time.clj b/backend/src/app/util/time.clj index 51d0f89dd..febc0f8de 100644 --- a/backend/src/app/util/time.clj +++ b/backend/src/app/util/time.clj @@ -60,7 +60,6 @@ [t1 t2] (Duration/between t1 t2)) - (letfn [(conformer [v] (cond (duration? v) v diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index fe078ba02..76a2ab8c3 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -5,15 +5,17 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.worker "Async tasks abstraction (impl)." (:require + [app.common.data :as d] [app.common.exceptions :as ex] [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.log4j :refer [update-thread-context!]] [app.util.time :as dt] @@ -35,21 +37,13 @@ ;; Executor ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(s/def ::name ::us/string) +(s/def ::name keyword?) (s/def ::min-threads ::us/integer) (s/def ::max-threads ::us/integer) (s/def ::idle-timeout ::us/integer) (defmethod ig/pre-init-spec ::executor [_] - (s/keys :opt-un [::min-threads ::max-threads ::idle-timeout ::name])) - -(defmethod ig/prep-key ::executor - [_ cfg] - (merge {:min-threads 0 - :max-threads 256 - :idle-timeout 60000 - :name "worker"} - cfg)) + (s/keys :req-un [::min-threads ::max-threads ::idle-timeout ::name])) (defmethod ig/init-key ::executor [_ {:keys [min-threads max-threads idle-timeout name]}] @@ -57,28 +51,29 @@ (int min-threads) (int idle-timeout)) (.setStopTimeout 500) - (.setName name) + (.setName (d/name name)) (.start))) (defmethod ig/halt-key! ::executor [_ instance] (.stop ^QueuedThreadPool instance)) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Worker ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (declare event-loop-fn) +(declare instrument-tasks) -(s/def ::queue ::us/string) +(s/def ::queue keyword?) (s/def ::parallelism ::us/integer) (s/def ::batch-size ::us/integer) -(s/def ::tasks (s/map-of string? fn?)) +(s/def ::tasks (s/map-of keyword? fn?)) (s/def ::poll-interval ::dt/duration) (defmethod ig/pre-init-spec ::worker [_] (s/keys :req-un [::executor + ::mtx/metrics ::db/pool ::batch-size ::name @@ -88,29 +83,29 @@ (defmethod ig/prep-key ::worker [_ cfg] - (merge {:batch-size 2 - :name "worker" - :poll-interval (dt/duration {:seconds 5}) - :queue "default"} - cfg)) + (d/merge {:batch-size 2 + :name :worker + :poll-interval (dt/duration {:seconds 5}) + :queue :default} + (d/without-nils cfg))) (defmethod ig/init-key ::worker [_ {:keys [pool poll-interval name queue] :as cfg}] - (log/infof "starting worker '%s' on queue '%s'" name queue) - (let [cch (a/chan 1) - poll-ms (inst-ms poll-interval)] + (log/infof "starting worker '%s' on queue '%s'" (d/name name) (d/name queue)) + (let [close-ch (a/chan 1) + poll-ms (inst-ms poll-interval)] (a/go-loop [] - (let [[val port] (a/alts! [cch (event-loop-fn cfg)] :priority true)] + (let [[val port] (a/alts! [close-ch (event-loop-fn cfg)] :priority true)] (cond ;; Terminate the loop if close channel is closed or ;; event-loop-fn returns nil. - (or (= port cch) (nil? val)) - (log/infof "stop condition found; shutdown worker: '%s'" name) + (or (= port close-ch) (nil? val)) + (log/infof "stop condition found; shutdown worker: '%s'" (d/name name)) (db/pool-closed? pool) (do (log/info "worker eventloop is aborted because pool is closed") - (a/close! cch)) + (a/close! close-ch)) (and (instance? java.sql.SQLException val) (contains? #{"08003" "08006" "08001" "08004"} (.getSQLState ^java.sql.SQLException val))) @@ -143,13 +138,55 @@ (reify java.lang.AutoCloseable (close [_] - (a/close! cch))))) + (a/close! close-ch))))) (defmethod ig/halt-key! ::worker [_ instance] (.close ^java.lang.AutoCloseable instance)) +;; --- SUBMIT + +(s/def ::task keyword?) +(s/def ::delay (s/or :int ::us/integer :duration dt/duration?)) +(s/def ::conn some?) +(s/def ::priority ::us/integer) +(s/def ::max-retries ::us/integer) + +(s/def ::submit-options + (s/keys :req [::task ::conn] + :opt [::delay ::queue ::priority ::max-retries])) + +(def ^:private sql:insert-new-task + "insert into task (id, name, props, queue, priority, max_retries, scheduled_at) + values (?, ?, ?, ?, ?, ?, clock_timestamp() + ?) + returning id") + +(defn- extract-props + [options] + (persistent! + (reduce-kv (fn [res k v] + (cond-> res + (not (qualified-keyword? k)) + (assoc! k v))) + (transient {}) + options))) + +(defn submit! + [{:keys [::task ::delay ::queue ::priority ::max-retries ::conn] + :or {delay 0 queue :default priority 100 max-retries 3} + :as options}] + (us/verify ::submit-options options) + (let [duration (dt/duration delay) + interval (db/interval duration) + props (-> options extract-props db/tjson) + id (uuid/next)] + (log/debugf "submit task '%s' to be executed in '%s'" (d/name task) (str duration)) + (db/exec-one! conn [sql:insert-new-task id (d/name task) props (d/name queue) priority max-retries interval]) + id)) + +;; --- RUNNER + (def ^:private sql:mark-as-retry @@ -194,17 +231,18 @@ nil)) (defn- decode-task-row - [{:keys [props] :as row}] + [{:keys [props name] :as row}] (when row (cond-> row - (db/pgobject? props) (assoc :props (db/decode-transit-pgobject props))))) + (db/pgobject? props) (assoc :props (db/decode-transit-pgobject props)) + (string? name) (assoc :name (keyword name))))) (defn- handle-task [tasks {:keys [name] :as item}] (let [task-fn (get tasks name)] (if task-fn (task-fn item) - (log/warnf "no task handler found for '%s'" (pr-str name))) + (log/warnf "no task handler found for '%s'" (d/name name))) {:status :completed :task item})) (defn get-error-context @@ -236,13 +274,14 @@ (defn- run-task [{:keys [tasks]} item] - (try - (log/debugf "started task '%s/%s/%s'" (:name item) (:id item) (:retry-num item)) - (handle-task tasks item) - (catch Exception e - (handle-exception e item)) - (finally - (log/debugf "finished task '%s/%s/%s'" (:name item) (:id item) (:retry-num item))))) + (let [name (d/name (:name item))] + (try + (log/debugf "started task '%s/%s/%s'" name (:id item) (:retry-num item)) + (handle-task tasks item) + (catch Exception e + (handle-exception e item)) + (finally + (log/debugf "finished task '%s/%s/%s'" name (:id item) (:retry-num item)))))) (def sql:select-next-tasks "select * from task as t @@ -256,7 +295,7 @@ (defn- event-loop-fn* [{:keys [pool executor batch-size] :as cfg}] (db/with-atomic [conn pool] - (let [queue (:queue cfg) + (let [queue (name (:queue cfg)) items (->> (db/exec! conn [sql:select-next-tasks queue batch-size]) (map decode-task-row) (seq)) @@ -288,16 +327,16 @@ (declare synchronize-schedule) (s/def ::fn (s/or :var var? :fn fn?)) -(s/def ::id ::us/string) +(s/def ::id keyword?) (s/def ::cron dt/cron?) (s/def ::props (s/nilable map?)) (s/def ::task keyword?) -(s/def ::scheduled-task-spec - (s/keys :req-un [::id ::cron ::task] - :opt-un [::props])) +(s/def ::scheduled-task + (s/keys :req-un [::cron ::task] + :opt-un [::props ::id])) -(s/def ::schedule (s/coll-of (s/nilable ::scheduled-task-spec))) +(s/def ::schedule (s/coll-of (s/nilable ::scheduled-task))) (defmethod ig/pre-init-spec ::scheduler [_] (s/keys :req-un [::executor ::db/pool ::schedule ::tasks])) @@ -307,8 +346,13 @@ (let [scheduler (Executors/newScheduledThreadPool (int 1)) schedule (->> schedule (filter some?) + ;; If id is not defined, use the task as id. + (map (fn [{:keys [id task] :as item}] + (if (some? id) + item + (assoc item :id task)))) (map (fn [{:keys [task] :as item}] - (let [f (get tasks (name task))] + (let [f (get tasks task)] (when-not f (ex/raise :type :internal :code :task-not-found @@ -341,7 +385,8 @@ (defn- synchronize-schedule-item [conn {:keys [id cron]}] - (let [cron (str cron)] + (let [cron (str cron) + id (name id)] (log/infof "initialize scheduled task '%s' (cron: '%s')" id cron) (db/exec-one! conn [sql:upsert-scheduled-task id cron cron]))) @@ -390,3 +435,62 @@ [{:keys [scheduler] :as cfg} {:keys [cron] :as task}] (let [ms (ms-until-valid cron)] (px/schedule! scheduler ms (partial execute-scheduled-task cfg task)))) + +;; --- INSTRUMENTATION + +(defn instrument! + [registry] + (mtx/instrument-vars! + [#'submit!] + {:registry registry + :type :counter + :labels ["name"] + :name "tasks_submit_total" + :help "A counter of task submissions." + :wrap (fn [rootf mobj] + (let [mdata (meta rootf) + origf (::original mdata rootf)] + (with-meta + (fn [conn params] + (let [tname (:name params)] + (mobj :inc [tname]) + (origf conn params))) + {::original origf})))}) + + (mtx/instrument-vars! + [#'app.worker/run-task] + {:registry registry + :type :summary + :quantiles [] + :name "tasks_checkout_timing" + :help "Latency measured between scheduld_at and execution time." + :wrap (fn [rootf mobj] + (let [mdata (meta rootf) + origf (::original mdata rootf)] + (with-meta + (fn [tasks item] + (let [now (inst-ms (dt/now)) + sat (inst-ms (:scheduled-at item))] + (mobj :observe (- now sat)) + (origf tasks item))) + {::original origf})))})) + + +(defmethod ig/pre-init-spec ::registry [_] + (s/keys :req-un [::mtx/metrics ::tasks])) + +(defmethod ig/init-key ::registry + [_ {:keys [metrics tasks]}] + (let [mobj (mtx/create + {:registry (:registry metrics) + :type :summary + :labels ["name"] + :quantiles [] + :name "tasks_timing" + :help "Background task execution timing."})] + (reduce-kv (fn [res k v] + (let [tname (name k)] + (log/debugf "registring task '%s'" tname) + (assoc res k (mtx/wrap-summary v mobj [tname])))) + {} + tasks))) diff --git a/backend/tests/app/tests/helpers.clj b/backend/tests/app/tests/helpers.clj index 2c4584573..7c86307e5 100644 --- a/backend/tests/app/tests/helpers.clj +++ b/backend/tests/app/tests/helpers.clj @@ -13,7 +13,7 @@ [app.common.pages :as cp] [app.common.spec :as us] [app.common.uuid :as uuid] - [app.config :as cfg] + [app.config :as cf] [app.db :as db] [app.main :as main] [app.media] @@ -38,16 +38,12 @@ (def ^:dynamic *system* nil) (def ^:dynamic *pool* nil) -(def config - (merge {:redis-uri "redis://redis/1" - :database-uri "postgresql://postgres/penpot_test" - :storage-fs-directory "/tmp/app/storage" - :migrations-verbose false} - cfg/config)) - (defn state-init [next] - (let [config (-> (main/build-system-config config) + (let [config (-> main/system-config + (assoc-in [:app.msgbus/msgbus :redis-uri] "redis://redis/1") + (assoc-in [:app.db/pool :uri] "postgresql://postgres/penpot_test") + (assoc-in [[:app.main/main :app.storage.fs/backend] :directory] "/tmp/app/storage") (dissoc :app.srepl/server :app.http/server :app.http/router @@ -328,8 +324,10 @@ "Helper for mock app.config/get" [data] (fn - ([key] (get (merge config data) key)) - ([key default] (get (merge config data) key default)))) + ([key] + (get data key (cf/get key))) + ([key default] + (get data key (cf/get key default))))) (defn reset-mock! [m] diff --git a/common/app/common/data.cljc b/common/app/common/data.cljc index f65fd4161..e3e23dfb8 100644 --- a/common/app/common/data.cljc +++ b/common/app/common/data.cljc @@ -401,6 +401,9 @@ (keyword? maybe-keyword) (core/name maybe-keyword) + (string? maybe-keyword) + maybe-keyword + (nil? maybe-keyword) default-value :else diff --git a/common/app/common/exceptions.cljc b/common/app/common/exceptions.cljc index 96782de95..240194489 100644 --- a/common/app/common/exceptions.cljc +++ b/common/app/common/exceptions.cljc @@ -2,7 +2,7 @@ ;; 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) 2016 Andrey Antukh +;; Copyright (c) Andrey Antukh (ns app.common.exceptions "A helpers for work with exceptions." From 351daacca07963a1f496c3a88ee1533b04119b95 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 30 Mar 2021 15:35:18 +0200 Subject: [PATCH 012/155] :wrench: Change CI configuration. --- .circleci/config.yml | 10 +++++----- backend/src/app/config.clj | 2 +- backend/tests/app/tests/helpers.clj | 19 +++++++++++++++---- .../tests/app/tests/test_bounces_handling.clj | 4 ---- .../tests/app/tests/test_services_profile.clj | 12 ++++++------ 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ef402408f..f54e162e6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ jobs: environment: POSTGRES_USER: penpot_test POSTGRES_PASSWORD: penpot_test - POSTGRES_DB: penpot + POSTGRES_DB: penpot_test - image: circleci/redis:6.0.8 @@ -45,10 +45,10 @@ jobs: name: backend test command: "clojure -M:dev:tests" environment: - PENPOT_DATABASE_URI: "postgresql://localhost/penpot" - PENPOT_DATABASE_USERNAME: penpot_test - PENPOT_DATABASE_PASSWORD: penpot_test - PENPOT_REDIS_URI: "redis://localhost/1" + PENPOT_TEST_DATABASE_URI: "postgresql://localhost/penpot_test" + PENPOT_TEST_DATABASE_USERNAME: penpot_test + PENPOT_TEST_DATABASE_PASSWORD: penpot_test + PENPOT_TEST_REDIS_URI: "redis://localhost/1" - run: working_directory: "./frontend" diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index a48878a81..dfc3be70c 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -32,7 +32,7 @@ {:http-server-port 6060 :host "devenv" :tenant "dev" - :database-uri "postgresql://127.0.0.1/penpot" + :database-uri "postgresql://postgres/penpot" :database-username "penpot" :database-password "penpot" diff --git a/backend/tests/app/tests/helpers.clj b/backend/tests/app/tests/helpers.clj index 7c86307e5..ed0e1b31c 100644 --- a/backend/tests/app/tests/helpers.clj +++ b/backend/tests/app/tests/helpers.clj @@ -38,11 +38,22 @@ (def ^:dynamic *system* nil) (def ^:dynamic *pool* nil) +(def defaults + {:database-uri "postgresql://postgres/penpot_test" + :redis-uri "redis://redis/1"}) + +(def config + (->> (cf/read-env "penpot-test") + (merge cf/defaults defaults) + (us/conform ::cf/config))) + (defn state-init [next] (let [config (-> main/system-config - (assoc-in [:app.msgbus/msgbus :redis-uri] "redis://redis/1") - (assoc-in [:app.db/pool :uri] "postgresql://postgres/penpot_test") + (assoc-in [:app.msgbus/msgbus :redis-uri] (:redis-uri config)) + (assoc-in [:app.db/pool :uri] (:database-uri config)) + (assoc-in [:app.db/pool :username] (:database-username config)) + (assoc-in [:app.db/pool :password] (:database-password config)) (assoc-in [[:app.main/main :app.storage.fs/backend] :directory] "/tmp/app/storage") (dissoc :app.srepl/server :app.http/server @@ -325,9 +336,9 @@ [data] (fn ([key] - (get data key (cf/get key))) + (get data key (get @cf/config key))) ([key default] - (get data key (cf/get key default))))) + (get data key (get @cf/config key default))))) (defn reset-mock! [m] diff --git a/backend/tests/app/tests/test_bounces_handling.clj b/backend/tests/app/tests/test_bounces_handling.clj index 065ada03f..3750565bb 100644 --- a/backend/tests/app/tests/test_bounces_handling.clj +++ b/backend/tests/app/tests/test_bounces_handling.clj @@ -21,10 +21,6 @@ (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) -;; (with-mocks [mock {:target 'app.tasks/submit! :return nil}] -;; Right now we have many different scenarios what can cause a -;; bounce/complain report. - (defn- decode-row [{:keys [content] :as row}] (cond-> row diff --git a/backend/tests/app/tests/test_services_profile.clj b/backend/tests/app/tests/test_services_profile.clj index f527a3d31..4b164d64f 100644 --- a/backend/tests/app/tests/test_services_profile.clj +++ b/backend/tests/app/tests/test_services_profile.clj @@ -132,7 +132,7 @@ (t/is (nil? result))) ;; Request profile to be deleted - (with-mocks [mock {:target 'app.tasks/submit! :return nil}] + (with-mocks [mock {:target 'app.worker/submit! :return nil}] (let [params {::th/type :delete-profile :profile-id (:id prof)} out (th/mutation! params)] @@ -140,11 +140,11 @@ ;; check the mock (let [mock (deref mock) - mock-params (second (:call-args mock))] + mock-params (first (:call-args mock))] (t/is (:called? mock)) (t/is (= 1 (:call-count mock))) - (t/is (= "delete-profile" (:name mock-params))) - (t/is (= (:id prof) (get-in mock-params [:props :profile-id])))))) + (t/is (= :delete-profile (:app.worker/task mock-params))) + (t/is (= (:id prof) (:profile-id mock-params)))))) ;; query files after profile soft deletion (let [params {::th/type :files @@ -257,8 +257,8 @@ :terms-privacy true} out (th/mutation! data)] ;; (th/print-result! out) - (let [mock (deref mock) - [_ _ params] (:call-args mock)] + (let [mock (deref mock) + [params] (:call-args mock)] ;; (clojure.pprint/pprint params) (t/is (:called? mock)) (t/is (= (:email data) (:to params))) From 43465f7c4b1b12e4e21cc15259e16bcbe9cf159d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 31 Mar 2021 08:31:22 +0200 Subject: [PATCH 013/155] :fire: Remove unused prop. --- backend/src/app/main.clj | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index e1a98a56a..fb18c6833 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -222,7 +222,6 @@ (when (cf/get :telemetry-enabled) {:cron #app/cron "0 0 */6 * * ?" ;; every 6h - :uri (cf/get :telemetry-uri) :task :telemetry})]} :app.worker/registry From 43e75401d72dbe2f4efc6c7177397afabdbca1d6 Mon Sep 17 00:00:00 2001 From: madmath03 Date: Mon, 1 Mar 2021 00:09:42 +0000 Subject: [PATCH 014/155] :tada: Fully automate dev setup with Gitpod. This commit implements a fully-automated development setup using Gitpod.io, an online IDE for GitLab, GitHub, and Bitbucket that enables Dev-Environments-As-Code. This makes it easy for anyone to get a ready-to-code workspace for any branch, issue or pull request almost instantly with a single click. :whale: Gitpod docker image with Clojure Signed-off-by: madmath03 :whale: Fix path to GitPod docker image Signed-off-by: madmath03 :whale: Use sudo for setup Signed-off-by: madmath03 :whale: More sudo commands Signed-off-by: madmath03 :whale: Remove penpot user in gitpod Signed-off-by: madmath03 :whale: Brew install redis Signed-off-by: madmath03 :wrench: Init DB and penpot user Signed-off-by: madmath03 :whale: Switch user for installs Signed-off-by: madmath03 :wrench: Improve startup and DB init Signed-off-by: madmath03 :wrench: Configure gitpod tasks Signed-off-by: madmath03 :wrench: Configure gitpod ports Signed-off-by: madmath03 :wrench: Setup for mailhog Signed-off-by: madmath03 :bug: Use perms to install mailhog :bug: Install mailhog before workspace creation Signed-off-by: mathieu.brunot :wrench: Manage signed commits Signed-off-by: madmath03 :wrench: Configure tasks to wait on ports :wrench: Improve Gitpod config Signed-off-by: madmath03 :arrow_up: Upgrade deps in gitpod Signed-off-by: madmath03 :art: Use absolute path for cd Signed-off-by: madmath03 :wrench: Add nginx conf Signed-off-by: madmath03 :wrench: Fix nginx config for gitpod Signed-off-by: madmath03 :wrench: Ensure nginx listens all incoming :art: Change layers order Signed-off-by: madmath03 :art: Change layers order Signed-off-by: madmath03 :wrench: Set Nginx logs permissions Signed-off-by: madmath03 :bug: Use sudo to create nginx logs Signed-off-by: madmath03 --- .gitpod.yml | 84 +++++++++++++++ README.md | 1 + docker/gitpod/Dockerfile | 103 ++++++++++++++++++ docker/gitpod/files/nginx.conf | 136 ++++++++++++++++++++++++ docker/gitpod/files/postgresql_init.sql | 3 + 5 files changed, 327 insertions(+) create mode 100644 .gitpod.yml create mode 100644 docker/gitpod/Dockerfile create mode 100644 docker/gitpod/files/nginx.conf create mode 100644 docker/gitpod/files/postgresql_init.sql diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 000000000..c513062fa --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,84 @@ +image: + file: docker/gitpod/Dockerfile + +ports: + # nginx + - port: 3449 + onOpen: open-preview + # frontend nREPL + - port: 3447 + onOpen: ignore + visibility: private + # frontend shadow server + - port: 3448 + onOpen: notify + visibility: private + - port: 6060 + - port: 9090 + # exporter shadow server + - port: 9630 + onOpen: notify + visibility: private + # exporter http server + - port: 6061 + onOpen: ignore + # mailhog web interface + - port: 8025 + onOpen: notify + # mailhog postfix + - port: 1025 + onOpen: ignore + # postgres + - port: 5432 + onOpen: ignore + # redis + - port: 6379 + onOpen: ignore + # openldap + - port: 389 + onOpen: ignore + +tasks: + # https://github.com/gitpod-io/gitpod/issues/666#issuecomment-534347856 + - before: > + [[ ! -z ${GNUGPG} ]] && + cd ~ && + rm -rf .gnupg && + echo ${GNUGPG} | base64 -d | tar --no-same-owner -xzvf - + name: signed terminal + init: > + [[ ! -z ${GNUGPG_KEY} ]] && + git config --global commit.gpgsign true && + git config --global user.signingkey ${GNUGPG_KEY} + command: cd $GITPOD_REPO_ROOT + + - init: yarn --cwd $GITPOD_REPO_ROOT/frontend/ install + name: frontend shadow watch + command: cd $GITPOD_REPO_ROOT/frontend/ && npx -y shadow-cljs watch main + + - init: yarn --cwd $GITPOD_REPO_ROOT/exporter/ install + name: exporter shadow watch + command: gp await-port 3448 && cd $GITPOD_REPO_ROOT/exporter/ && npx -y shadow-cljs watch main + + - name: exporter web server + openMode: split-right + command: gp await-port 9630 && cd $GITPOD_REPO_ROOT/exporter/ && ./scripts/wait-and-start.sh + + - init: gp await-port 5432 && psql -f $GITPOD_REPO_ROOT/docker/gitpod/files/postgresql_init.sql + name: backend + command: cd $GITPOD_REPO_ROOT/backend/ && ./scripts/start-dev + + - name: gulp + command: gp await-port 3448 && cd $GITPOD_REPO_ROOT/frontend/ && npx -y gulp --theme=${PENPOT_THEME} watch + + - name: redis + command: redis-server + + - before: go get github.com/mailhog/MailHog + name: mailhog + command: MailHog + + - name: Nginx + command: > + nginx && + multitail /var/log/nginx/access.log -I /var/log/nginx/error.log diff --git a/README.md b/README.md index b508bac45..5bc569307 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![License: MPL-2.0][uri_license_image]][uri_license] [![Gitter](https://badges.gitter.im/sereno-xyz/community.svg)](https://gitter.im/penpot/community) [![Managed with Taiga.io](https://img.shields.io/badge/managed%20with-TAIGA.io-709f14.svg)](https://tree.taiga.io/project/penpot/ "Managed with Taiga.io") +[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/penpot/penpot) # PENPOT # diff --git a/docker/gitpod/Dockerfile b/docker/gitpod/Dockerfile new file mode 100644 index 000000000..ea3a0b972 --- /dev/null +++ b/docker/gitpod/Dockerfile @@ -0,0 +1,103 @@ +FROM gitpod/workspace-postgres + +# Install custom tools, runtimes, etc. +# For example "bastet", a command-line tetris clone: +# RUN brew install bastet +# +# More information: https://www.gitpod.io/docs/config-docker/ + +RUN set -ex; \ + brew install redis; \ + brew install imagemagick; \ + brew install mailhog; \ + brew install openldap; \ + sudo mkdir -p /var/log/nginx; \ + sudo chown gitpod:gitpod /var/log/nginx + +COPY docker/gitpod/files/nginx.conf /etc/nginx/nginx.conf + +USER root + +ENV CLOJURE_VERSION=1.10.3.814 \ + CLJKONDO_VERSION=2021.03.03 \ + LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 + +RUN set -ex; \ + useradd -m -g users -s /bin/bash penpot; \ + passwd penpot -d; \ + echo "penpot ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +RUN set -ex; \ + apt-get -qq update; \ + apt-get -qqy install \ + gconf-service \ + libasound2 \ + libatk1.0-0 \ + libatk-bridge2.0-0 \ + libcairo2 \ + libcups2 \ + libdbus-1-3 \ + libexpat1 \ + libfontconfig1 \ + libgcc1 \ + libgconf-2-4 \ + libgdk-pixbuf2.0-0 \ + libglib2.0-0 \ + libgtk-3-0 \ + libnspr4 \ + libpango-1.0-0 \ + libpangocairo-1.0-0 \ + libx11-6 \ + libx11-xcb1 \ + libxcb1 \ + libxcomposite1 \ + libxcursor1 \ + libxdamage1 \ + libxext6 \ + libxfixes3 \ + libxi6 \ + libxrandr2 \ + libxrender1 \ + libxss1 \ + libxtst6 \ + fonts-liberation \ + libappindicator1 \ + libnss3 \ + libgbm1 \ + ; \ + rm -rf /var/lib/apt/lists/*; + +RUN set -ex; \ + wget "https://download.clojure.org/install/linux-install-$CLOJURE_VERSION.sh"; \ + chmod +x "linux-install-$CLOJURE_VERSION.sh"; \ + "./linux-install-$CLOJURE_VERSION.sh"; \ + rm -rf "linux-install-$CLOJURE_VERSION.sh" + +RUN set -ex; \ + cd /tmp; \ + wget "https://github.com/borkdude/clj-kondo/releases/download/v${CLJKONDO_VERSION}/clj-kondo-${CLJKONDO_VERSION}-linux-amd64.zip"; \ + unzip "clj-kondo-${CLJKONDO_VERSION}-linux-amd64.zip"; \ + sudo mv clj-kondo /usr/local/bin/; \ + rm "clj-kondo-${CLJKONDO_VERSION}-linux-amd64.zip"; + +USER gitpod + +ENV PENPOT_SMTP_ENABLED=true \ + PENPOT_SMTP_HOST=localhost \ + PENPOT_SMTP_PORT=1025 \ + PENPOT_SMTP_USER= \ + PENPOT_SMTP_PASSWORD= \ + PENPOT_SMTP_SSL=false \ + PENPOT_SMTP_TLS=false \ + PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@example.com \ + PENPOT_SMTP_DEFAULT_FROM=no-reply@example.com \ + PENPOT_SMTP_ENABLED=true \ + PENPOT_SMTP_HOST=localhost \ + PENPOT_SMTP_PORT=1025 \ + PENPOT_SMTP_USER= \ + PENPOT_SMTP_PASSWORD= \ + PENPOT_SMTP_SSL=false \ + PENPOT_SMTP_TLS=false + +# TODO Retrieve OpenLDAP from rroemhild/docker-test-openldap diff --git a/docker/gitpod/files/nginx.conf b/docker/gitpod/files/nginx.conf new file mode 100644 index 000000000..80c5d5b43 --- /dev/null +++ b/docker/gitpod/files/nginx.conf @@ -0,0 +1,136 @@ +# This Nginx config file is optional. +# If you don't have this file, the Nginx config from the workspace-full docker image will be taken. + +worker_processes auto; +pid /var/run/nginx/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +env GITPOD_REPO_ROOT; + +events { + worker_connections 768; + # multi_accept on; +} + +http { + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + # server_tokens off; + + # server_names_hash_bucket_size 64; + # server_name_in_redirect off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + gzip on; + + gzip_vary on; + gzip_proxied any; + gzip_comp_level 3; + gzip_buffers 16 8k; + gzip_http_version 1.1; + + gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + # include /etc/nginx/sites-enabled/*; + + server { + set_by_lua $gitpod_repo_root 'return os.getenv("GITPOD_REPO_ROOT")'; + + listen 0.0.0.0:3449 default_server; + server_name _; + + client_max_body_size 5M; + charset utf-8; + + proxy_http_version 1.1; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + resolver 8.8.8.8; + + etag off; + + root $gitpod_repo_root/frontend/resources/public; + + location @handle_redirect { + set $redirect_uri "$upstream_http_location"; + set $redirect_host "$upstream_http_x_host"; + set $redirect_cache_control "$upstream_http_cache_control"; + + proxy_buffering off; + + proxy_set_header Host "$redirect_host"; + proxy_hide_header etag; + proxy_hide_header x-amz-id-2; + proxy_hide_header x-amz-request-id; + proxy_hide_header x-amz-meta-server-side-encryption; + proxy_hide_header x-amz-server-side-encryption; + proxy_pass $redirect_uri; + + add_header x-internal-redirect "$redirect_uri"; + add_header x-cache-control "$redirect_cache_control"; + add_header cache-control "$redirect_cache_control"; + } + + location /assets { + proxy_pass http://127.0.0.1:6060/assets; + recursive_error_pages on; + proxy_intercept_errors on; + error_page 301 302 307 = @handle_redirect; + } + + location /internal/assets { + internal; + alias $gitpod_repo_root/backend/resources/public/assets; + add_header x-internal-redirect "$upstream_http_x_accel_redirect"; + } + + location /api { + proxy_pass http://127.0.0.1:6060/api; + } + + location /webhooks { + proxy_pass http://127.0.0.1:6060/webhooks; + } + + location /dbg { + proxy_pass http://127.0.0.1:6060/dbg; + } + + location /export { + proxy_pass http://127.0.0.1:6061; + } + + location /playground { + alias $gitpod_repo_root/experiments/; + add_header Cache-Control "no-cache, max-age=0"; + autoindex on; + } + + location /ws/notifications { + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_pass http://127.0.0.1:6060/ws/notifications; + } + + location / { + add_header Cache-Control "no-cache, max-age=0"; + } + } +} diff --git a/docker/gitpod/files/postgresql_init.sql b/docker/gitpod/files/postgresql_init.sql new file mode 100644 index 000000000..1766a643d --- /dev/null +++ b/docker/gitpod/files/postgresql_init.sql @@ -0,0 +1,3 @@ +CREATE DATABASE penpot; +CREATE USER penpot PASSWORD 'penpot'; +ALTER ROLE penpot SUPERUSER; From f4f51dbf6bbbfc24c2a988598f667e2bd4f98a2a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 31 Mar 2021 14:34:46 +0200 Subject: [PATCH 015/155] :sparkles: Add minor adaptations for gitpod config and docker files. --- .gitpod.yml | 71 +++++++++++++++++++----------- docker/gitpod/Dockerfile | 48 +++++++++++++------- docker/gitpod/files/nginx.conf | 2 +- exporter/scripts/wait-and-start.sh | 1 + 4 files changed, 79 insertions(+), 43 deletions(-) diff --git a/.gitpod.yml b/.gitpod.yml index c513062fa..0735ba2dd 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -5,72 +5,93 @@ ports: # nginx - port: 3449 onOpen: open-preview + # frontend nREPL - port: 3447 onOpen: ignore visibility: private + # frontend shadow server - port: 3448 - onOpen: notify + onOpen: ignore visibility: private + + # backend - port: 6060 - - port: 9090 + onOpen: ignore + # exporter shadow server - port: 9630 - onOpen: notify + onOpen: ignore visibility: private + # exporter http server - port: 6061 onOpen: ignore + # mailhog web interface - port: 8025 - onOpen: notify + onOpen: ignore + # mailhog postfix - port: 1025 onOpen: ignore + # postgres - port: 5432 onOpen: ignore + # redis - port: 6379 onOpen: ignore + # openldap - port: 389 onOpen: ignore tasks: # https://github.com/gitpod-io/gitpod/issues/666#issuecomment-534347856 - - before: > + - name: gulp + command: > + cd $GITPOD_REPO_ROOT/frontend/; + yarn && gp sync-done 'frontend-yarn'; + npx gulp --theme=${PENPOT_THEME} watch + + - name: frontend shadow watch + command: > + cd $GITPOD_REPO_ROOT/frontend/; + gp sync-await 'frontend-yarn'; + npx shadow-cljs watch main + + - init: gp await-port 5432 && psql -f $GITPOD_REPO_ROOT/docker/gitpod/files/postgresql_init.sql + name: backend + command: > + cd $GITPOD_REPO_ROOT/backend/; + ./scripts/start-dev + + - name: exporter shadow watch + command: + cd $GITPOD_REPO_ROOT/exporter/; + gp sync-await 'frontend-yarn'; + yarn && npx shadow-cljs watch main + + - name: exporter web server + command: > + cd $GITPOD_REPO_ROOT/exporter/; + ./scripts/wait-and-start.sh + + - name: signed terminal + before: > [[ ! -z ${GNUGPG} ]] && cd ~ && rm -rf .gnupg && echo ${GNUGPG} | base64 -d | tar --no-same-owner -xzvf - - name: signed terminal init: > [[ ! -z ${GNUGPG_KEY} ]] && git config --global commit.gpgsign true && git config --global user.signingkey ${GNUGPG_KEY} command: cd $GITPOD_REPO_ROOT - - init: yarn --cwd $GITPOD_REPO_ROOT/frontend/ install - name: frontend shadow watch - command: cd $GITPOD_REPO_ROOT/frontend/ && npx -y shadow-cljs watch main - - - init: yarn --cwd $GITPOD_REPO_ROOT/exporter/ install - name: exporter shadow watch - command: gp await-port 3448 && cd $GITPOD_REPO_ROOT/exporter/ && npx -y shadow-cljs watch main - - - name: exporter web server - openMode: split-right - command: gp await-port 9630 && cd $GITPOD_REPO_ROOT/exporter/ && ./scripts/wait-and-start.sh - - - init: gp await-port 5432 && psql -f $GITPOD_REPO_ROOT/docker/gitpod/files/postgresql_init.sql - name: backend - command: cd $GITPOD_REPO_ROOT/backend/ && ./scripts/start-dev - - - name: gulp - command: gp await-port 3448 && cd $GITPOD_REPO_ROOT/frontend/ && npx -y gulp --theme=${PENPOT_THEME} watch - - name: redis command: redis-server diff --git a/docker/gitpod/Dockerfile b/docker/gitpod/Dockerfile index ea3a0b972..0c2fe3a14 100644 --- a/docker/gitpod/Dockerfile +++ b/docker/gitpod/Dockerfile @@ -19,7 +19,8 @@ COPY docker/gitpod/files/nginx.conf /etc/nginx/nginx.conf USER root ENV CLOJURE_VERSION=1.10.3.814 \ - CLJKONDO_VERSION=2021.03.03 \ + CLJKONDO_VERSION=2021.03.22 \ + BABASHKA_VERSION=0.3.1 \ LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 @@ -59,6 +60,7 @@ RUN set -ex; \ libxi6 \ libxrandr2 \ libxrender1 \ + libxshmfence1 \ libxss1 \ libxtst6 \ fonts-liberation \ @@ -69,21 +71,39 @@ RUN set -ex; \ rm -rf /var/lib/apt/lists/*; RUN set -ex; \ - wget "https://download.clojure.org/install/linux-install-$CLOJURE_VERSION.sh"; \ - chmod +x "linux-install-$CLOJURE_VERSION.sh"; \ - "./linux-install-$CLOJURE_VERSION.sh"; \ - rm -rf "linux-install-$CLOJURE_VERSION.sh" + curl -LfsSo /tmp/openjdk.tar.gz https://github.com/AdoptOpenJDK/openjdk16-binaries/releases/download/jdk-16%2B36/OpenJDK16-jdk_x64_linux_hotspot_16_36.tar.gz; \ + mkdir -p /usr/lib/jvm/openjdk16; \ + cd /usr/lib/jvm/openjdk16; \ + tar -xf /tmp/openjdk.tar.gz --strip-components=1; \ + rm -rf /tmp/openjdk.tar.gz; +# Install clojure cli +RUN set -ex; \ + curl -LfsSo /tmp/clojure.sh https://download.clojure.org/install/linux-install-$CLOJURE_VERSION.sh; \ + chmod +x /tmp/clojure.sh; \ + /tmp/clojure.sh; \ + rm -rf /tmp/clojure.sh; + +# Install clj-kondo +RUN set -ex; \ + curl -LfsSo /tmp/clj-kondo.zip https://github.com/borkdude/clj-kondo/releases/download/v$CLJKONDO_VERSION/clj-kondo-$CLJKONDO_VERSION-linux-amd64.zip; \ + cd /usr/local/bin; \ + unzip /tmp/clj-kondo.zip; \ + rm /tmp/clj-kondo.zip; + +# Install babashka RUN set -ex; \ cd /tmp; \ - wget "https://github.com/borkdude/clj-kondo/releases/download/v${CLJKONDO_VERSION}/clj-kondo-${CLJKONDO_VERSION}-linux-amd64.zip"; \ - unzip "clj-kondo-${CLJKONDO_VERSION}-linux-amd64.zip"; \ - sudo mv clj-kondo /usr/local/bin/; \ - rm "clj-kondo-${CLJKONDO_VERSION}-linux-amd64.zip"; + curl -LfsSo /tmp/babashka.tar.gz https://github.com/babashka/babashka/releases/download/v$BABASHKA_VERSION/babashka-$BABASHKA_VERSION-linux-amd64.tar.gz; \ + cd /usr/local/bin; \ + tar -xf /tmp/babashka.tar.gz; \ + rm -rf /tmp/babashka.tar.gz; USER gitpod -ENV PENPOT_SMTP_ENABLED=true \ +ENV PATH="/usr/lib/jvm/openjdk16/bin:/usr/local/nodejs/bin:$PATH" \ + JAVA_HOME=/usr/lib/jvm/openjdk16 \ + PENPOT_SMTP_ENABLED=true \ PENPOT_SMTP_HOST=localhost \ PENPOT_SMTP_PORT=1025 \ PENPOT_SMTP_USER= \ @@ -92,12 +112,6 @@ ENV PENPOT_SMTP_ENABLED=true \ PENPOT_SMTP_TLS=false \ PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@example.com \ PENPOT_SMTP_DEFAULT_FROM=no-reply@example.com \ - PENPOT_SMTP_ENABLED=true \ - PENPOT_SMTP_HOST=localhost \ - PENPOT_SMTP_PORT=1025 \ - PENPOT_SMTP_USER= \ - PENPOT_SMTP_PASSWORD= \ - PENPOT_SMTP_SSL=false \ - PENPOT_SMTP_TLS=false + PENPOT_DATABASE_URI="postgresql://localhost/penpot" # TODO Retrieve OpenLDAP from rroemhild/docker-test-openldap diff --git a/docker/gitpod/files/nginx.conf b/docker/gitpod/files/nginx.conf index 80c5d5b43..319b4bcbb 100644 --- a/docker/gitpod/files/nginx.conf +++ b/docker/gitpod/files/nginx.conf @@ -97,7 +97,7 @@ http { location /internal/assets { internal; - alias $gitpod_repo_root/backend/resources/public/assets; + alias $gitpod_repo_root/backend/assets; add_header x-internal-redirect "$upstream_http_x_accel_redirect"; } diff --git a/exporter/scripts/wait-and-start.sh b/exporter/scripts/wait-and-start.sh index 730ed16bc..c04935367 100755 --- a/exporter/scripts/wait-and-start.sh +++ b/exporter/scripts/wait-and-start.sh @@ -2,4 +2,5 @@ bb -i '(babashka.wait/wait-for-port "localhost" 9630)'; bb -i '(babashka.wait/wait-for-path "target/app.js")'; +sleep 2; node target/app.js From 018b47ab6be39651179ea8047c6ab2cadfb28639 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 31 Mar 2021 14:35:24 +0200 Subject: [PATCH 016/155] :arrow_up: Update frontend dependencies (yarn.lock). --- frontend/yarn.lock | 183 +++++++++++++++++++++++---------------------- 1 file changed, 94 insertions(+), 89 deletions(-) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 17778502f..8b338f6d4 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3,9 +3,9 @@ "@babel/runtime-corejs3@^7.12.1": - version "7.13.9" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.13.9.tgz#b2fa9a6e5690ef8d4c4f2d30cac3ec1a8bb633ce" - integrity sha512-p6WSr71+5u/VBf1KDS/Y4dK3ZwbV+DD6wQO3X2EbUVluEOiyXUk09DzcwSaUH4WomYXrEPC+i2rqzuthhZhOJw== + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.13.10.tgz#14c3f4c85de22ba88e8e86685d13e8861a82fe86" + integrity sha512-x/XYVQ1h684pp1mJwOV4CyvqZXqbc8CMsMGUnAbuc82ZCdv1U63w5RSUzgDSXQHG5Rps/kiksH6g2D5BuaKyXg== dependencies: core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" @@ -350,13 +350,13 @@ atob@^2.1.2: integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== autoprefixer@^10.2.4: - version "10.2.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.4.tgz#c0e7cf24fcc6a1ae5d6250c623f0cb8beef2f7e1" - integrity sha512-DCCdUQiMD+P/as8m3XkeTUkUKuuRqLGcwD0nll7wevhqoJfMRpJlkFd1+MQh1pvupjiQuip42lc/VFvfUTMSKw== + version "10.2.5" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.5.tgz#096a0337dbc96c0873526d7fef5de4428d05382d" + integrity sha512-7H4AJZXvSsn62SqZyJCP+1AWwOuoYpUfK6ot9vm0e87XD6mT8lDywc9D9OTJPMULyGcvmIxzTAMeG2Cc+YX+fA== dependencies: - browserslist "^4.16.1" - caniuse-lite "^1.0.30001181" - colorette "^1.2.1" + browserslist "^4.16.3" + caniuse-lite "^1.0.30001196" + colorette "^1.2.2" fraction.js "^4.0.13" normalize-range "^0.1.2" postcss-value-parser "^4.1.0" @@ -562,7 +562,7 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.16.1: +browserslist@^4.16.3: version "4.16.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== @@ -677,10 +677,10 @@ 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.30001181: - version "1.0.30001196" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001196.tgz#00518a2044b1abf3e0df31fadbe5ed90b63f4e64" - integrity sha512-CPvObjD3ovWrNBaXlAIGWmg2gQQuJ5YhuciUOjPRox6hIQttu8O+b51dx6VIpIY9ESd2d0Vac1RKpICdG4rGUg== +caniuse-lite@^1.0.30001181, caniuse-lite@^1.0.30001196: + version "1.0.30001205" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001205.tgz#d79bf6a6fb13196b4bb46e5143a22ca0242e0ef8" + integrity sha512-TL1GrS5V6LElbitPazidkBMD9sa448bQDDLrumDqaggmKFcuU2JW1wTOHJPukAcOMtEmLcmDJEzfRrf+GjM0Og== caseless@~0.12.0: version "0.12.0" @@ -866,9 +866,9 @@ color-name@^1.0.0: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + version "1.5.5" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" + integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" @@ -926,7 +926,7 @@ component-emitter@^1.2.1: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== -compute-scroll-into-view@^1.0.16: +compute-scroll-into-view@^1.0.17: version "1.0.17" resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab" integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg== @@ -989,22 +989,22 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= copy-props@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe" - integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A== + version "2.0.5" + resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.5.tgz#03cf9ae328d4ebb36f8f1d804448a6af9ee3f2d2" + integrity sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw== dependencies: - each-props "^1.3.0" - is-plain-object "^2.0.1" + each-props "^1.3.2" + is-plain-object "^5.0.0" core-js-pure@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.9.1.tgz#677b322267172bd490e4464696f790cbc355bec5" - integrity sha512-laz3Zx0avrw9a4QEIdmIblnVuJz8W51leY9iLThatCsFawWxC3sE4guASC78JbCin+DkwMpCdp1AVAuzL/GN7A== + version "3.10.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.10.0.tgz#dab9d6b141779b622b40567e7a536d2276646c15" + integrity sha512-CC582enhrFZStO4F8lGI7QL3SYx7/AIRc+IdSi3btrQGrVsTawo5K/crmKbRrQ+MOMhNX4v+PATn0k2NN6wI7A== core-js@^3.6.4: - version "3.9.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.9.1.tgz#cec8de593db8eb2a85ffb0dbdeb312cb6e5460ae" - integrity sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg== + version "3.10.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.10.0.tgz#9a020547c8b6879f929306949e31496bbe2ae9b3" + integrity sha512-MQx/7TLgmmDVamSyfE+O+5BHvG1aUGj/gHhLn1wVtm2B5u1eVIPvh7vkfjwWKNCjrTJB8+He99IntSQ1qP+vYQ== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -1053,9 +1053,9 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: sha.js "^2.4.8" cross-fetch@^3.0.4: - version "3.0.6" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" - integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== + version "3.1.3" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.3.tgz#b8e7d5f19161c4a0ca916f707978848786043afb" + integrity sha512-2i6v88DTqVBNODyjD9U6Ycn/uSZNvyHe25cIbo2fFnAACAsaLTJsd23miRWiR5NuiGXR9wpJ9d40/9WAhjDIrw== dependencies: node-fetch "2.6.1" @@ -1372,7 +1372,7 @@ duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" -each-props@^1.3.0: +each-props@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333" integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA== @@ -1399,9 +1399,9 @@ editorconfig@^0.15.3: sigmund "^1.0.1" electron-to-chromium@^1.3.649: - version "1.3.681" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.681.tgz#facd915ae46a020e8be566a2ecdc0b9444439be9" - integrity sha512-W6uYvSUTHuyX2DZklIESAqx57jfmGjUkd7Z3RWqLdj9Mmt39ylhBuvFXlskQnvBHj0MYXIeQI+mjiwVddZLSvA== + version "1.3.703" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.703.tgz#6d9b9a75c42a40775f5930329e642b22b227317f" + integrity sha512-SVBVhNB+4zPL+rvtWLw7PZQkw/Eqj1HQZs22xtcqW36+xoifzEOEEDEpkxSMfB6RFeSIOcG00w6z5mSqLr1Y6w== elliptic@^6.5.3: version "6.5.4" @@ -1965,9 +1965,9 @@ glob-parent@^3.1.0: path-dirname "^1.0.0" glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" @@ -2218,7 +2218,7 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" -has-bigints@^1.0.0: +has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== @@ -2228,7 +2228,7 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: +has-symbols@^1.0.1, 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== @@ -2307,9 +2307,9 @@ he@1.1.1: integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= highlight.js@^10.6.0: - version "10.6.0" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.6.0.tgz#0073aa71d566906965ba6e1b7be7b2682f5e18b6" - integrity sha512-8mlRcn5vk/r4+QcqerapwBYTe+iPL5ih6xrNylxrnBdHQiijDETfXX7VIxC3UiCRiINBJfANBAsPzAvRQj8RpQ== + version "10.7.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.1.tgz#a8ec4152db24ea630c90927d6cae2a45f8ecb955" + integrity sha512-S6G97tHGqJ/U8DsXcEdnACbirtbx58Bx9CzIVeYli8OuswCfYI/LsXH2EiGcoGio1KAC3x4mmUwulOllJ2ZyRA== hmac-drbg@^1.0.1: version "1.0.1" @@ -2657,6 +2657,11 @@ is-plain-object@^3.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-promise@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -3335,9 +3340,9 @@ mustache@^3.0.0: integrity sha512-RERvMFdLpaFfSRIEe632yDm5nsd0SDKn8hGmcUwswnyiE5mtdZLDybtHAz6hjJhawokF0hXvGLtx9mrQfm6FkA== mustache@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.1.0.tgz#8c1b042238a982d2eb2d30efc6c14296ae3f699d" - integrity sha512-0FsgP/WVq4mKyjolIyX+Z9Bd+3WS8GOwoUTyKXT5cTYMGeauNTi2HPCwERqseC1IHAy0Z7MDZnJBfjabd4O8GQ== + version "4.2.0" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" + integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== mute-stdout@^1.0.0: version "1.0.1" @@ -3349,10 +3354,10 @@ nan@^2.12.1, nan@^2.13.2: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nanoid@^3.1.20: - version "3.1.20" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" - integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== +nanoid@^3.1.22: + version "3.1.22" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" + integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== nanomatch@^1.2.9: version "1.2.13" @@ -3961,12 +3966,12 @@ postcss@^7.0.16: supports-color "^6.1.0" postcss@^8.2.7: - version "8.2.7" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.7.tgz#48ed8d88b4de10afa0dfd1c3f840aa57b55c4d47" - integrity sha512-DsVLH3xJzut+VT+rYr0mtvOtpTjSyqDwPf5EZWXcb0uAKfitGpTY9Ec+afi2+TgdN8rWS9Cs88UDYehKo/RvOw== + version "8.2.9" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.9.tgz#fd95ff37b5cee55c409b3fdd237296ab4096fba3" + integrity sha512-b+TmuIL4jGtCHtoLi+G/PisuIl9avxs8IZMSmlABRwNz5RLUUACrC+ws81dcomz1nRezm5YPdXiMEzBEKgYn+Q== dependencies: colorette "^1.2.2" - nanoid "^3.1.20" + nanoid "^3.1.22" source-map "^0.6.1" pretty-hrtime@^1.0.0: @@ -4109,18 +4114,18 @@ randomfill@^1.0.3: safe-buffer "^5.1.0" react-dom@~17.0.1: - version "17.0.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6" - integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug== + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - scheduler "^0.20.1" + scheduler "^0.20.2" react@~17.0.1: - version "17.0.1" - resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127" - integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w== + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -4375,9 +4380,9 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: inherits "^2.0.1" rxjs@~7.0.0-beta.12: - version "7.0.0-beta.12" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.0.0-beta.12.tgz#5ad9f482030cf53ed3c613447b68b126650c934b" - integrity sha512-1AzVwquI9XTcI9Tmq3yq3KSzf+R8PX5lOPw1LcO6HJAbJGteScegAKgMaN3+O1kQ18ZWvDPQ23Fx8lvYZwCkYQ== + version "7.0.0-beta.14" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.0.0-beta.14.tgz#03f9d10d0b923715fc1a75c1e1d119b5a3882157" + integrity sha512-mXvWwjgZiZwka0r5dIbZnsYjx2WqeSpvFejNs8aJkxAP6lzy8xsjKpcsxasX5AnVXCKeTt/8rGrIG8N+8HR3qA== dependencies: tslib "~2.1.0" @@ -4425,20 +4430,20 @@ sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -scheduler@^0.20.1: - version "0.20.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.1.tgz#da0b907e24026b01181ecbc75efdc7f27b5a000c" - integrity sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw== +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" scroll-into-view-if-needed@^2.2.20: - version "2.2.27" - resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.27.tgz#c696e439bb50128abc558317b39c929907bd0620" - integrity sha512-BKiRstRm4u1bZvw+Wu9TxXhyMZ9fskb/9fbuSGuRzwHhlbKlDetL4dBdYaPfQbEFTttQmpkNtFH7sQpk4rZf9w== + version "2.2.28" + resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz#5a15b2f58a52642c88c8eca584644e01703d645a" + integrity sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w== dependencies: - compute-scroll-into-view "^1.0.16" + compute-scroll-into-view "^1.0.17" scss-tokenizer@^0.2.3: version "0.2.3" @@ -4499,9 +4504,9 @@ shadow-cljs-jar@1.3.2: integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg== shadow-cljs@^2.11.20: - version "2.11.20" - resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.11.20.tgz#fa814b74a7fa7909ff056994a80f8f7435881046" - integrity sha512-TmZp1Hjp49oziqaTdBYwO0qzvoVMYa+O5c7rwdfO334bdYhTPMmJJeG/EbeJpRvLAKErjbxGmY4P28rqfPmZ3w== + version "2.12.0" + resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.12.0.tgz#468903b2eaff9e3c9c52c3db2c756e55cb7f0e0a" + integrity sha512-w/YaFdB3IFYMe6av+1JwLQg1PPfDfnkeF537X5t1seYB3nRw9NWXgIfGtyZ7d0Iavw+0HQi7iWWiVMt6+yrWyA== dependencies: node-libs-browser "^2.2.1" readline-sync "^1.4.7" @@ -5173,9 +5178,9 @@ type@^1.0.1: integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== type@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.3.0.tgz#ada7c045f07ead08abf9e2edd29be1a0c0661132" - integrity sha512-rgPIqOdfK/4J9FhiVrZ3cveAjRRo5rsQBAIhnylX874y1DX/kEKSVdLsnuHB6l1KTjHyU01VjiMBHgU2adejyg== + version "2.5.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" + integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== typedarray@^0.0.6: version "0.0.6" @@ -5183,9 +5188,9 @@ typedarray@^0.0.6: integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= ua-parser-js@^0.7.18: - version "0.7.24" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.24.tgz#8d3ecea46ed4f1f1d63ec25f17d8568105dc027c" - integrity sha512-yo+miGzQx5gakzVK3QFfN0/L9uVhosXBBO7qmnk7c2iw1IhL212wfA3zbnI54B0obGwC/5NWub/iT9sReMx+Fw== + version "0.7.26" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.26.tgz#b3731860e241419abd5b542b1a0881070d92e0ce" + integrity sha512-VwIvGlFNmpKbjzRt51jpbbFTrKIEgGHxIwA8Y69K1Bqc6bTIV7TaGGABOkghSFQWsLmcRB4drGvpfv9z2szqoQ== ultron@~1.1.0: version "1.1.1" @@ -5193,14 +5198,14 @@ ultron@~1.1.0: integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== unbox-primitive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.0.tgz#eeacbc4affa28e9b3d36b5eaeccc50b3251b1d3f" - integrity sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA== + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== dependencies: function-bind "^1.1.1" - has-bigints "^1.0.0" - has-symbols "^1.0.0" - which-boxed-primitive "^1.0.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" unc-path-regex@^0.1.2: version "0.1.2" @@ -5412,7 +5417,7 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -which-boxed-primitive@^1.0.1: +which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== From a5994140e2cfa87069a0feb81bb6d0f30fa81455 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 31 Mar 2021 14:25:07 +0200 Subject: [PATCH 017/155] :paperclip: Fix linter issues. --- backend/src/app/config.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index a7ff21a3c..ec5cb77d6 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -15,8 +15,8 @@ [app.common.version :as v] [app.util.time :as dt] [clojure.core :as c] - [clojure.pprint :as pprint] [clojure.java.io :as io] + [clojure.pprint :as pprint] [clojure.spec.alpha :as s] [cuerdas.core :as str] [environ.core :refer [env]])) From 1e642bba8f18155b950babc5abe068bbdf5e5f83 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 31 Mar 2021 14:48:28 +0200 Subject: [PATCH 018/155] :paperclip: Update changelog. --- CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index efe24c301..0dc19a480 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,12 +4,17 @@ ### :sparkles: New features +- Add integration with gitpod.io (an online IDE) [#807](https://github.com/penpot/penpot/pull/807) + + ### :bug: Bugs fixed ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) +- madmath03 (by [Monogramm](https://github.com/Monogramm)) [#807](https://github.com/penpot/penpot/pull/807) + ## 1.4.0-alpha From 78fe0ab7e3bfcb59dd4d3dd47fdf90af217730ab Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 5 Apr 2021 08:39:31 +0200 Subject: [PATCH 019/155] :bug: Fixes problem with docker-compose environment --- docker/devenv/docker-compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml index 9046c6dd8..fb76391f1 100644 --- a/docker/devenv/docker-compose.yaml +++ b/docker/devenv/docker-compose.yaml @@ -37,6 +37,7 @@ services: - 9090:9090 environment: + - PENPOT_REDIS_URI=redis://redis/0 - EXTERNAL_UID=${CURRENT_USER_ID} # STMP setup - PENPOT_SMTP_ENABLED=true From c7fcb00b81a609fce9418a388bc2900104d453bf Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 6 Apr 2021 09:01:07 +0200 Subject: [PATCH 020/155] :arrow_up: Update devenv image and compose file. --- backend/src/app/config.clj | 2 +- docker/devenv/Dockerfile | 6 +++--- docker/devenv/docker-compose.yaml | 1 - docker/gitpod/Dockerfile | 1 + 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index ec5cb77d6..a65bbb7e8 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -44,7 +44,7 @@ :asserts-enabled false :public-uri "http://localhost:3449" - :redis-uri "redis://localhost/0" + :redis-uri "redis://redis/0" :srepl-host "127.0.0.1" :srepl-port 6062 diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index a8c6e002a..34285df04 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -4,9 +4,9 @@ LABEL maintainer="Andrey Antukh " ARG DEBIAN_FRONTEND=noninteractive ENV NODE_VERSION=v14.16.0 \ - CLOJURE_VERSION=1.10.3.814 \ - CLJKONDO_VERSION=2021.03.22 \ - BABASHKA_VERSION=0.3.1 \ + CLOJURE_VERSION=1.10.3.822 \ + CLJKONDO_VERSION=2021.03.31 \ + BABASHKA_VERSION=0.3.2 \ LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml index fb76391f1..9046c6dd8 100644 --- a/docker/devenv/docker-compose.yaml +++ b/docker/devenv/docker-compose.yaml @@ -37,7 +37,6 @@ services: - 9090:9090 environment: - - PENPOT_REDIS_URI=redis://redis/0 - EXTERNAL_UID=${CURRENT_USER_ID} # STMP setup - PENPOT_SMTP_ENABLED=true diff --git a/docker/gitpod/Dockerfile b/docker/gitpod/Dockerfile index 0c2fe3a14..02a90206c 100644 --- a/docker/gitpod/Dockerfile +++ b/docker/gitpod/Dockerfile @@ -113,5 +113,6 @@ ENV PATH="/usr/lib/jvm/openjdk16/bin:/usr/local/nodejs/bin:$PATH" \ PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@example.com \ PENPOT_SMTP_DEFAULT_FROM=no-reply@example.com \ PENPOT_DATABASE_URI="postgresql://localhost/penpot" + PENPOT_REDIS_URI="redis://localhost/0" # TODO Retrieve OpenLDAP from rroemhild/docker-test-openldap From 7b67e05e508a4f8f158a96ff129fe5c4cb029ad4 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 6 Apr 2021 09:15:06 +0200 Subject: [PATCH 021/155] :arrow_up: Update gitpod dockerfile. --- docker/gitpod/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/gitpod/Dockerfile b/docker/gitpod/Dockerfile index 02a90206c..7db973a46 100644 --- a/docker/gitpod/Dockerfile +++ b/docker/gitpod/Dockerfile @@ -18,9 +18,9 @@ COPY docker/gitpod/files/nginx.conf /etc/nginx/nginx.conf USER root -ENV CLOJURE_VERSION=1.10.3.814 \ - CLJKONDO_VERSION=2021.03.22 \ - BABASHKA_VERSION=0.3.1 \ +ENV CLOJURE_VERSION=1.10.3.822 \ + CLJKONDO_VERSION=2021.03.31 \ + BABASHKA_VERSION=0.3.2 \ LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 From 19ced21b20d934ff1da45d6b1de8bcefe7802c0c Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Wed, 7 Apr 2021 09:16:41 +0200 Subject: [PATCH 022/155] :sparkles: Minor design fixes --- frontend/resources/styles/main/layouts/login.scss | 1 - frontend/src/app/main/ui/settings/sidebar.cljs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/resources/styles/main/layouts/login.scss b/frontend/resources/styles/main/layouts/login.scss index e7c348c23..03de4ceaa 100644 --- a/frontend/resources/styles/main/layouts/login.scss +++ b/frontend/resources/styles/main/layouts/login.scss @@ -40,7 +40,6 @@ svg { fill: #2C233E; max-width: 11vw; - width: 100%; height: 80px; } } diff --git a/frontend/src/app/main/ui/settings/sidebar.cljs b/frontend/src/app/main/ui/settings/sidebar.cljs index b0850b59b..6bcba0e02 100644 --- a/frontend/src/app/main/ui/settings/sidebar.cljs +++ b/frontend/src/app/main/ui/settings/sidebar.cljs @@ -87,7 +87,7 @@ [:hr] [:li {:on-click show-release-notes} - i/msg-info + i/pencil [:span.element-title (tr "labels.release-notes")]] (when cf/feedback-enabled From 9544ee2140803b10ce92807bdc48668d9abf5cc4 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Apr 2021 14:14:50 +0200 Subject: [PATCH 023/155] :fire: Remove artifacts from changes file. --- CHANGES.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 0dc19a480..a81af5975 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -75,7 +75,6 @@ - Fix titles in viewer thumbnails too long [Taiga #1474](https://tree.taiga.io/project/penpot/issue/1474) - Fix when right click on a selected text shows artboard contextual menu [Taiga #1226](https://tree.taiga.io/project/penpot/issue/1226) ->>>>>>> origin/staging ### :arrow_up: Deps updates From 2c6b89698930932dd854cd91c9c388f6fdc958e4 Mon Sep 17 00:00:00 2001 From: nik gaffney <41225+zzkt@users.noreply.github.com> Date: Fri, 9 Apr 2021 12:16:40 +0200 Subject: [PATCH 024/155] Update en.subj spelling --- backend/resources/emails/invite-to-team/en.subj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/resources/emails/invite-to-team/en.subj b/backend/resources/emails/invite-to-team/en.subj index 1ff46884a..d4d00f731 100644 --- a/backend/resources/emails/invite-to-team/en.subj +++ b/backend/resources/emails/invite-to-team/en.subj @@ -1 +1 @@ -Inviation to join {{team}} \ No newline at end of file +Invitation to join {{team}} From 6f2306439c4afffbeea0a53ab4f3c972c5b1e687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 9 Apr 2021 14:03:56 +0200 Subject: [PATCH 025/155] :books: Add contribution to change log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index a81af5975..10746d5e5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ ### :heart: Community contributions by (Thank you!) - madmath03 (by [Monogramm](https://github.com/Monogramm)) [#807](https://github.com/penpot/penpot/pull/807) +- zzkt [#814](https://github.com/penpot/penpot/pull/814) ## 1.4.0-alpha From e12a6e65a6e95b7d611d8b14f53671f58c18947e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 6 Apr 2021 23:25:34 +0200 Subject: [PATCH 026/155] :recycle: Refactor logging. --- backend/resources/log4j2-devenv.xml | 2 +- backend/scripts/repl | 2 +- backend/src/app/cli/fixtures.clj | 38 +++++--- backend/src/app/cli/manage.clj | 4 +- backend/src/app/cli/migrate_media.clj | 4 +- backend/src/app/db.clj | 6 +- backend/src/app/emails.clj | 6 +- backend/src/app/http.clj | 21 +++-- backend/src/app/http/awsns.clj | 25 ++--- backend/src/app/http/errors.clj | 29 +++--- backend/src/app/http/oauth/github.clj | 8 +- backend/src/app/http/oauth/gitlab.clj | 8 +- backend/src/app/http/oauth/google.clj | 8 +- backend/src/app/http/session.clj | 24 +++-- backend/src/app/loggers/loki.clj | 14 ++- backend/src/app/loggers/mattermost.clj | 15 +-- backend/src/app/loggers/zmq.clj | 4 +- backend/src/app/main.clj | 6 +- backend/src/app/metrics.clj | 6 +- backend/src/app/msgbus.clj | 27 ++++-- backend/src/app/notifications.clj | 25 +++-- backend/src/app/rpc.clj | 10 +- backend/src/app/storage.clj | 18 +++- backend/src/app/svgparse.clj | 5 +- backend/src/app/tasks/delete_object.clj | 8 +- backend/src/app/tasks/delete_profile.clj | 8 +- backend/src/app/tasks/file_media_gc.clj | 15 ++- backend/src/app/tasks/file_xlog_gc.clj | 6 +- backend/src/app/tasks/tasks_gc.clj | 6 +- backend/src/app/util/log4j.clj | 27 ------ backend/src/app/util/logging.clj | 111 +++++++++++++++++++++++ backend/src/app/util/migrations.clj | 6 +- backend/src/app/worker.clj | 74 ++++++++++----- 33 files changed, 383 insertions(+), 193 deletions(-) delete mode 100644 backend/src/app/util/log4j.clj create mode 100644 backend/src/app/util/logging.clj diff --git a/backend/resources/log4j2-devenv.xml b/backend/resources/log4j2-devenv.xml index 3c18f6bd3..7f7ba7b1f 100644 --- a/backend/resources/log4j2-devenv.xml +++ b/backend/resources/log4j2-devenv.xml @@ -6,7 +6,7 @@ - + diff --git a/backend/scripts/repl b/backend/scripts/repl index 6a21835b1..32fd12d42 100755 --- a/backend/scripts/repl +++ b/backend/scripts/repl @@ -2,7 +2,7 @@ export PENPOT_ASSERTS_ENABLED=true -export OPTIONS="-A:jmx-remote:dev -J-Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -J-Xms512m -J-Xmx512m -J-Dlog4j2.configurationFile=log4j2-devenv.xml" +export OPTIONS="-A:jmx-remote:dev -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -J-Xms512m -J-Xmx512m -J-Dlog4j2.configurationFile=log4j2-devenv.xml" export OPTIONS_EVAL="nil" # export OPTIONS_EVAL="(set! *warn-on-reflection* true)" diff --git a/backend/src/app/cli/fixtures.clj b/backend/src/app/cli/fixtures.clj index 710d3f1fe..0f2ebd6bd 100644 --- a/backend/src/app/cli/fixtures.clj +++ b/backend/src/app/cli/fixtures.clj @@ -16,8 +16,8 @@ [app.main :as main] [app.rpc.mutations.profile :as profile] [app.util.blob :as blob] + [app.util.logging :as l] [buddy.hashers :as hashers] - [clojure.tools.logging :as log] [integrant.core :as ig])) (defn- mk-uuid @@ -74,7 +74,9 @@ (let [rng (java.util.Random. 1)] (letfn [(create-profile [conn index] (let [id (mk-uuid "profile" index) - _ (log/info "create profile" index id) + _ (l/info :action "create profile" + :index index + :id id) prof (register-profile conn {:id id @@ -90,20 +92,22 @@ prof)) (create-profiles [conn] - (log/info "create profiles") + (l/info :action "create profiles") (collect (partial create-profile conn) (range (:num-profiles opts)))) (create-team [conn index] (let [id (mk-uuid "team" index) name (str "Team" index)] - (log/info "create team" index id) + (l/info :action "create team" + :index index + :id id) (db/insert! conn :team {:id id :name name}) id)) (create-teams [conn] - (log/info "create teams") + (l/info :action "create teams") (collect (partial create-team conn) (range (:num-teams opts)))) @@ -111,7 +115,9 @@ (let [id (mk-uuid "file" project-id index) name (str "file" index) data (cp/make-file-data id)] - (log/info "create file" index id) + (l/info :action "create file" + :index index + :id id) (db/insert! conn :file {:id id :data (blob/encode data) @@ -126,7 +132,7 @@ id)) (create-files [conn owner-id project-id] - (log/info "create files") + (l/info :action "create files") (run! (partial create-file conn owner-id project-id) (range (:num-files-per-project opts)))) @@ -138,7 +144,9 @@ (str "project " index) "Drafts") is-default (nil? index)] - (log/info "create project" index id) + (l/info :action "create project" + :index index + :id id) (db/insert! conn :project {:id id :team-id team-id @@ -153,7 +161,7 @@ id)) (create-projects [conn team-id profile-ids] - (log/info "create projects") + (l/info :action "create projects") (let [owner-id (rng-nth rng profile-ids) project-ids (conj (collect (partial create-project conn team-id owner-id) @@ -170,14 +178,16 @@ :can-edit true})) (setup-team [conn team-id profile-ids] - (log/info "setup team" team-id profile-ids) + (l/info :action "setup team" + :team-id team-id + :profile-ids (pr-str profile-ids)) (assign-profile-to-team conn team-id true (first profile-ids)) (run! (partial assign-profile-to-team conn team-id false) (rest profile-ids)) (create-projects conn team-id profile-ids)) (assign-teams-and-profiles [conn teams profiles] - (log/info "assign teams and profiles") + (l/info :action "assign teams and profiles") (loop [team-id (first teams) teams (rest teams)] (when-not (nil? team-id) @@ -194,7 +204,9 @@ project-id (:default-project-id owner) data (cp/make-file-data id)] - (log/info "create draft file" index id) + (l/info :action "create draft file" + :index index + :id id) (db/insert! conn :file {:id id :data (blob/encode data) @@ -244,6 +256,6 @@ (try (run-in-system system preset) (catch Exception e - (log/errorf e "unhandled exception")) + (l/error :hint "unhandled exception" :cause e)) (finally (ig/halt! system))))) diff --git a/backend/src/app/cli/manage.clj b/backend/src/app/cli/manage.clj index 04216fc22..3ee9ca76d 100644 --- a/backend/src/app/cli/manage.clj +++ b/backend/src/app/cli/manage.clj @@ -14,9 +14,9 @@ [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]] - [clojure.tools.logging :as log] [integrant.core :as ig]) (:import java.io.Console)) @@ -34,7 +34,7 @@ [{:keys [label type] :or {type :text}}] (let [^Console console (System/console)] (when-not console - (log/error "no console found, can proceed") + (l/error :hint "no console found, can proceed") (System/exit 1)) (binding [*out* (.writer console)] diff --git a/backend/src/app/cli/migrate_media.clj b/backend/src/app/cli/migrate_media.clj index 6a0531126..207feff99 100644 --- a/backend/src/app/cli/migrate_media.clj +++ b/backend/src/app/cli/migrate_media.clj @@ -14,7 +14,7 @@ [app.db :as db] [app.main :as main] [app.storage :as sto] - [clojure.tools.logging :as log] + [app.util.logging :as l] [cuerdas.core :as str] [datoteka.core :as fs] [integrant.core :as ig])) @@ -49,7 +49,7 @@ (run-in-system) (ig/halt!)) (catch Exception e - (log/errorf e "Unhandled exception."))))) + (l/error :hint "unhandled exception" :cause e))))) ;; --- IMPL diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index 29a5627f8..d2b6f7cc1 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -17,11 +17,11 @@ [app.metrics :as mtx] [app.util.json :as json] [app.util.migrations :as mg] + [app.util.logging :as l] [app.util.time :as dt] [app.util.transit :as t] [clojure.java.io :as io] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig] [next.jdbc :as jdbc] [next.jdbc.date-time :as jdbc-dt]) @@ -60,7 +60,9 @@ (defmethod ig/init-key ::pool [_ {:keys [migrations metrics] :as cfg}] - (log/infof "initialize connection pool '%s' with uri '%s'" (name (:name cfg)) (:uri cfg)) + (l/info :action "initialize connection pool" + :name (d/name (:name cfg)) + :uri (:uri cfg)) (instrument-jdbc! (:registry metrics)) (let [pool (create-pool cfg)] (when (seq migrations) diff --git a/backend/src/app/emails.clj b/backend/src/app/emails.clj index dd0fcf4e1..5d832bc47 100644 --- a/backend/src/app/emails.clj +++ b/backend/src/app/emails.clj @@ -15,13 +15,13 @@ [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] - [clojure.tools.logging :as log] [integrant.core :as ig])) - ;; --- PUBLIC API + (defn render [email-factory context] (email-factory context)) @@ -181,5 +181,5 @@ (println "******** start email" (:id email) "**********") (println (.toString baos)) (println "******** end email "(:id email) "**********"))] - (log/info out)))) + (l/info :email out)))) diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index f5cf2d47d..dacd4d050 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -15,7 +15,7 @@ [app.http.errors :as errors] [app.http.middleware :as middleware] [app.metrics :as mtx] - [app.util.log4j :refer [update-thread-context!]] + [app.util.logging :as l] [clojure.spec.alpha :as s] [clojure.tools.logging :as log] [integrant.core :as ig] @@ -44,7 +44,7 @@ (defmethod ig/init-key ::server [_ {:keys [handler router ws port name metrics] :as opts}] - (log/infof "starting '%s' server on port %s." name port) + (l/info :msg "starting http server" :port port :name name) (let [pre-start (fn [^Server server] (let [handler (doto (ErrorHandler.) (.setShowStacks true) @@ -77,7 +77,9 @@ (defmethod ig/halt-key! ::server [_ {:keys [server name port] :as opts}] - (log/infof "stoping '%s' server on port %s." name port) + (l/info :msg "stoping http server" + :name name + :port port) (jetty/stop-server server)) (defn- router-handler @@ -93,11 +95,16 @@ (catch Throwable e (try (let [cdata (errors/get-error-context request e)] - (update-thread-context! cdata) - (log/errorf e "unhandled exception: %s (id: %s)" (ex-message e) (str (:id cdata))) - {:status 500 :body "internal server error"}) + (l/update-thread-context! cdata) + (l/error :hint "unhandled exception" + :message (ex-message e) + :error-id (str (:id cdata)) + :cause e)) + {:status 500 :body "internal server error"} (catch Throwable e - (log/errorf e "unhandled exception: %s" (ex-message e)) + (l/error :hint "unhandled exception" + :message (ex-message e) + :cause e) {:status 500 :body "internal server error"}))))))) diff --git a/backend/src/app/http/awsns.clj b/backend/src/app/http/awsns.clj index ea47131ce..7dccbb08c 100644 --- a/backend/src/app/http/awsns.clj +++ b/backend/src/app/http/awsns.clj @@ -14,9 +14,9 @@ [app.db :as db] [app.db.sql :as sql] [app.util.http :as http] + [app.util.logging :as l] [clojure.pprint :refer [pprint]] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [cuerdas.core :as str] [integrant.core :as ig] [jsonista.core :as j])) @@ -25,11 +25,6 @@ (declare parse-notification) (declare process-report) -(defn- pprint-report - [message] - (binding [clojure.pprint/*print-right-margin* 120] - (with-out-str (pprint message)))) - (defmethod ig/pre-init-spec ::handler [_] (s/keys :req-un [::db/pool])) @@ -42,19 +37,17 @@ (= mtype "SubscriptionConfirmation") (let [surl (get body "SubscribeURL") stopic (get body "TopicArn")] - (log/infof "subscription received (topic=%s, url=%s)" stopic surl) + (l/info :action "subscription received" :topic stopic :url surl) (http/send! {:uri surl :method :post :timeout 10000})) (= mtype "Notification") (when-let [message (parse-json (get body "Message"))] - ;; (log/infof "Received: %s" (pr-str message)) (let [notification (parse-notification cfg message)] (process-report cfg notification))) :else - (log/warn (str "unexpected data received\n" - (pprint-report body)))) - + (l/warn :hint "unexpected data received" + :report (pr-str body))) {:status 200 :body ""}))) (defn- parse-bounce @@ -184,15 +177,15 @@ (defn- process-report [cfg {:keys [type profile-id] :as report}] - (log/trace (str "procesing report:\n" (pprint-report report))) + (l/trace :action "procesing report" :report (pr-str report)) (cond ;; In this case we receive a bounce/complaint notification without ;; confirmed identity, we just emit a warning but do nothing about ;; it because this is not a normal case. All notifications should ;; come with profile identity. (nil? profile-id) - (log/warn (str "a notification without identity recevied from AWS\n" - (pprint-report report))) + (l/warn :msg "a notification without identity recevied from AWS" + :report (pr-str report)) (= "bounce" type) (register-bounce-for-profile cfg report) @@ -201,7 +194,7 @@ (register-complaint-for-profile cfg report) :else - (log/warn (str "unrecognized report received from AWS\n" - (pprint-report report))))) + (l/warn :msg "unrecognized report received from AWS" + :report (pr-str report)))) diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index e5e7d6af2..76ca5e638 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -12,8 +12,7 @@ (:require [app.common.exceptions :as ex] [app.common.uuid :as uuid] - [app.util.log4j :refer [update-thread-context!]] - [clojure.tools.logging :as log] + [app.util.logging :as l] [cuerdas.core :as str] [expound.alpha :as expound])) @@ -73,8 +72,11 @@ [error request] (let [edata (ex-data error) cdata (get-error-context request error)] - (update-thread-context! cdata) - (log/errorf error "internal error: assertion (id: %s)" (str (:id cdata))) + (l/update-thread-context! cdata) + (l/error :hint "internal error: assertion" + :error-id (str (:id cdata)) + :cause error) + {:status 500 :body {:type :server-error :data (-> edata @@ -97,10 +99,11 @@ (ex/exception? (:handling edata))) (handle-exception (:handling edata) request) (let [cdata (get-error-context request error)] - (update-thread-context! cdata) - (log/errorf error "internal error: %s (id: %s)" - (ex-message error) - (str (:id cdata))) + (l/update-thread-context! cdata) + (l/error :hint "internal error" + :error-message (ex-message error) + :error-id (str (:id cdata)) + :cause error) {:status 500 :body {:type :server-error :hint (ex-message error) @@ -111,11 +114,11 @@ (let [cdata (get-error-context request error) state (.getSQLState ^java.sql.SQLException error)] - (update-thread-context! cdata) - (log/errorf error "PSQL Exception: %s (id: %s, state: %s)" - (ex-message error) - (str (:id cdata)) - state) + (l/update-thread-context! cdata) + (l/error :hint "psql exception" + :error-message (ex-message error) + :error-id (str (:id cdata)) + :sql-state state) (cond (= state "57014") diff --git a/backend/src/app/http/oauth/github.clj b/backend/src/app/http/oauth/github.clj index 379b9143a..635af416b 100644 --- a/backend/src/app/http/oauth/github.clj +++ b/backend/src/app/http/oauth/github.clj @@ -13,10 +13,10 @@ [app.common.spec :as us] [app.http.oauth.google :as gg] [app.util.http :as http] + [app.util.logging :as l] [app.util.time :as dt] [clojure.data.json :as json] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig] [lambdaisland.uri :as u])) @@ -63,7 +63,8 @@ (get "access_token")))) (catch Exception e - (log/error e "unexpected error on get-access-token") + (l/error :hint "unexpected error on get-access-token" + :cause e) nil))) (defn- get-user-info @@ -80,7 +81,8 @@ :backend "github" :fullname (get data "name")}))) (catch Exception e - (log/error e "unexpected exception on get-user-info") + (l/error :hint "unexpected exception on get-user-info" + :cause e) nil))) (defn- retrieve-info diff --git a/backend/src/app/http/oauth/gitlab.clj b/backend/src/app/http/oauth/gitlab.clj index 4a8b0a7c2..b3d04567e 100644 --- a/backend/src/app/http/oauth/gitlab.clj +++ b/backend/src/app/http/oauth/gitlab.clj @@ -14,10 +14,10 @@ [app.common.spec :as us] [app.http.oauth.google :as gg] [app.util.http :as http] + [app.util.logging :as l] [app.util.time :as dt] [clojure.data.json :as json] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig] [lambdaisland.uri :as u])) @@ -62,7 +62,8 @@ (get "access_token")))) (catch Exception e - (log/error e "unexpected error on get-access-token") + (l/error :hint "unexpected error on get-access-token" + :cause e) nil))) (defn- get-user-info @@ -81,7 +82,8 @@ :fullname (get data "name")}))) (catch Exception e - (log/error e "unexpected exception on get-user-info") + (l/error :hint "unexpected exception on get-user-info" + :cause e) nil))) diff --git a/backend/src/app/http/oauth/google.clj b/backend/src/app/http/oauth/google.clj index ce9211689..aea6e6639 100644 --- a/backend/src/app/http/oauth/google.clj +++ b/backend/src/app/http/oauth/google.clj @@ -12,10 +12,10 @@ [app.common.exceptions :as ex] [app.common.spec :as us] [app.util.http :as http] + [app.util.logging :as l] [app.util.time :as dt] [clojure.data.json :as json] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig] [lambdaisland.uri :as u])) @@ -51,7 +51,8 @@ (-> (json/read-str (:body res)) (get "access_token")))) (catch Exception e - (log/error e "unexpected error on get-access-token") + (l/error :hint "unexpected error on get-access-token" + :cause e) nil))) (defn- get-user-info @@ -69,7 +70,8 @@ :backend "google" :fullname (get data "name")}))) (catch Exception e - (log/error e "unexpected exception on get-user-info") + (l/error :hint "unexpected exception on get-user-info" + :cause e) nil))) (defn- retrieve-info diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index 316558257..8eb09eb9d 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -15,14 +15,13 @@ [app.db :as db] [app.metrics :as mtx] [app.util.async :as aa] - [app.util.log4j :refer [update-thread-context!]] + [app.util.logging :as l] [app.util.time :as dt] [app.worker :as wrk] [buddy.core.codecs :as bc] [buddy.core.nonce :as bn] [clojure.core.async :as a] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig])) ;; --- IMPL @@ -68,7 +67,7 @@ (if-let [{:keys [id profile-id] :as session} (retrieve-from-request cfg request)] (let [ech (::events-ch cfg)] (a/>!! ech id) - (update-thread-context! {:profile-id profile-id}) + (l/update-thread-context! {:profile-id profile-id}) (handler (assoc request :profile-id profile-id))) (handler request)))) @@ -132,9 +131,9 @@ (defmethod ig/init-key ::updater [_ {:keys [session metrics] :as cfg}] - (log/infof "initialize session updater (max-batch-age=%s, max-batch-size=%s)" - (str (:max-batch-age cfg)) - (str (:max-batch-size cfg))) + (l/info :action "initialize session updater" + :max-batch-age (str (:max-batch-age cfg)) + :max-batch-size (str (:max-batch-size cfg))) (let [input (batch-events cfg (::events-ch session)) mcnt (mtx/create {:name "http_session_update_total" @@ -146,8 +145,13 @@ (let [result (a/ system-config (ig/prep) (ig/init)))) - (log/infof "welcome to penpot (version: '%s')" - (:full cf/version))) + (l/info :msg "welcome to penpot" + :version (:full cf/version))) (defn stop [] diff --git a/backend/src/app/metrics.clj b/backend/src/app/metrics.clj index e71283ca2..bf1daef4b 100644 --- a/backend/src/app/metrics.clj +++ b/backend/src/app/metrics.clj @@ -5,13 +5,13 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.metrics (:require [app.common.exceptions :as ex] + [app.util.logging :as l] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig]) (:import io.prometheus.client.CollectorRegistry @@ -50,7 +50,7 @@ (defmethod ig/init-key ::metrics [_ {:keys [definitions] :as cfg}] - (log/infof "Initializing prometheus registry and instrumentation.") + (l/info :action "initialize metrics") (let [registry (create-registry) definitions (reduce-kv (fn [res k v] (->> (assoc v :registry registry) diff --git a/backend/src/app/msgbus.clj b/backend/src/app/msgbus.clj index c6d7c217b..e8da9a350 100644 --- a/backend/src/app/msgbus.clj +++ b/backend/src/app/msgbus.clj @@ -14,10 +14,10 @@ [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] - [clojure.tools.logging :as log] [integrant.core :as ig] [promesa.core :as p]) (:import @@ -60,7 +60,8 @@ (defmethod ig/init-key ::msgbus [_ {:keys [backend buffer-size] :as cfg}] - (log/debugf "initializing msgbus (backend=%s)" (name backend)) + (l/debug :action "initialize msgbus" + :backend (name backend)) (let [cfg (init-backend cfg) ;; Channel used for receive publications from the application. @@ -165,13 +166,14 @@ (when-let [val (a/ pending: %s" (pr-str pending)) (some->> (seq pending) (send-off chans unsubscribe-channels)) diff --git a/backend/src/app/notifications.clj b/backend/src/app/notifications.clj index 9897b7a32..75cb64f46 100644 --- a/backend/src/app/notifications.clj +++ b/backend/src/app/notifications.clj @@ -14,12 +14,12 @@ [app.db :as db] [app.metrics :as mtx] [app.util.async :as aa] + [app.util.logging :as l] [app.util.time :as dt] [app.util.transit :as t] [app.worker :as wrk] [clojure.core.async :as a] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig] [ring.adapter.jetty9 :as jetty] [ring.middleware.cookies :refer [wrap-cookies]] @@ -149,7 +149,9 @@ :out-ch out-ch :sub-ch sub-ch)] - (log/tracef "on-connect %s" (:session-id cfg)) + (l/log :level :trace + :action "connect" + :session (:session-id cfg)) ;; Forward all messages from out-ch to the websocket ;; connection @@ -172,19 +174,26 @@ (a/close! sub-ch)))) (on-error [_conn e] - (log/tracef "on-error %s (%s)" (:session-id cfg) (ex-message e)) + (l/log :level :trace + :action "error" + :session (:session-id cfg)) + (a/close! out-ch) (a/close! rcv-ch)) (on-close [_conn _status _reason] - (log/tracef "on-close %s" (:session-id cfg)) + (l/log :level :trace + :action "close" + :session (:session-id cfg)) + (a/close! out-ch) (a/close! rcv-ch)) (on-message [_ws message] (let [message (t/decode-str message)] (when-not (a/offer! rcv-ch message) - (log/warn "droping ws input message, channe full"))))] + (l/log :level :warn + :msg "drop messages"))))] {:on-connect on-connect :on-error on-error @@ -254,12 +263,10 @@ (defmethod handle-message :connect [cfg _] - ;; (log/debugf "profile '%s' is connected to file '%s'" profile-id file-id) (send-presence cfg :connect)) (defmethod handle-message :disconnect [cfg _] - ;; (log/debugf "profile '%s' is disconnected from '%s'" profile-id file-id) (send-presence cfg :disconnect)) (defmethod handle-message :keepalive @@ -277,5 +284,7 @@ (defmethod handle-message :default [_ws message] (a/go - (log/warnf "received unexpected message: %s" message))) + (l/log :level :warn + :msg "received unexpected message" + :message message))) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index de202964b..ab16c0c37 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -5,7 +5,7 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc (:require @@ -15,9 +15,9 @@ [app.db :as db] [app.metrics :as mtx] [app.rlimits :as rlm] + [app.util.logging :as l] [app.util.services :as sv] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [cuerdas.core :as str] [integrant.core :as ig])) @@ -76,7 +76,8 @@ (ex/raise :type :internal :code :rlimit-not-configured :hint (str/fmt "%s rlimit not configured" key))) - (log/tracef "adding rlimit to '%s' rpc handler" (::sv/name mdata)) + (l/trace :action "add rlimit" + :handler (::sv/name mdata)) (fn [cfg params] (rlm/execute rlinst (f cfg params)))) f)) @@ -86,7 +87,8 @@ (let [f (wrap-with-rlimits cfg f mdata) f (wrap-with-metrics cfg f mdata) spec (or (::sv/spec mdata) (s/spec any?))] - (log/tracef "registering '%s' command to rpc service" (::sv/name mdata)) + (l/trace :action "register" + :name (::sv/name mdata)) (fn [params] (when (and (:auth mdata true) (not (uuid? (:profile-id params)))) (ex/raise :type :authentication diff --git a/backend/src/app/storage.clj b/backend/src/app/storage.clj index 94e7e3b70..e8621ac8b 100644 --- a/backend/src/app/storage.clj +++ b/backend/src/app/storage.clj @@ -19,10 +19,10 @@ [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] - [clojure.tools.logging :as log] [cuerdas.core :as str] [datoteka.core :as fs] [integrant.core :as ig] @@ -310,7 +310,9 @@ (run! (partial delete-in-bulk conn) groups) (recur (+ n ^long total))) (do - (log/infof "gc-deleted: processed %s items" n) + (l/info :task "gc-deleted" + :action "permanently delete items" + :count n) {:deleted n}))))))) (def sql:retrieve-deleted-objects @@ -382,7 +384,12 @@ (recur (+ cntf (count to-freeze)) (+ cntd (count to-delete)))) (do - (log/infof "gc-touched: %s objects marked as freeze and %s marked to be deleted" cntf cntd) + (l/info :task "gc-touched" + :action "mark freeze" + :count cntf) + (l/info :task "gc-touched" + :action "mark for deletion" + :count cntd) {:freeze cntf :delete cntd}))))))) (def sql:retrieve-touched-objects @@ -459,7 +466,10 @@ (recur (+ n (count all)) (+ d (count to-delete)))) (do - (log/infof "recheck: processed %s items, %s deleted" n d) + (l/info :task "recheck" + :action "recheck items" + :processed n + :deleted n) {:processed n :deleted d}))))))) (def sql:retrieve-pending-to-recheck diff --git a/backend/src/app/svgparse.clj b/backend/src/app/svgparse.clj index 0dd3621f9..5a773d28c 100644 --- a/backend/src/app/svgparse.clj +++ b/backend/src/app/svgparse.clj @@ -11,8 +11,8 @@ (:require [app.common.exceptions :as ex] [app.metrics :as mtx] + [app.util.logging :as l] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [clojure.xml :as xml] [integrant.core :as ig]) (:import @@ -60,7 +60,8 @@ (with-open [istream (IOUtils/toInputStream data "UTF-8")] (xml/parse istream secure-factory)) (catch Exception e - (log/warnf "error on processing svg: %s" (ex-message e)) + (l/warn :hint "error on processing svg" + :message (ex-message e)) (ex/raise :type :validation :code :invalid-svg-file :cause e)))) diff --git a/backend/src/app/tasks/delete_object.clj b/backend/src/app/tasks/delete_object.clj index 78fd47007..91fa1f702 100644 --- a/backend/src/app/tasks/delete_object.clj +++ b/backend/src/app/tasks/delete_object.clj @@ -5,15 +5,16 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (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.util.logging :as l] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig])) (declare handle-deletion) @@ -37,7 +38,8 @@ (defmethod handle-deletion :default [_conn {:keys [type]}] - (log/warnf "no handler found for '%s'" type)) + (l/warn :hint "no handler found" + :type (d/name type))) (defmethod handle-deletion :file [conn {:keys [id] :as props}] diff --git a/backend/src/app/tasks/delete_profile.clj b/backend/src/app/tasks/delete_profile.clj index 923ccf814..5c05a1d8a 100644 --- a/backend/src/app/tasks/delete_profile.clj +++ b/backend/src/app/tasks/delete_profile.clj @@ -13,8 +13,8 @@ [app.common.spec :as us] [app.db :as db] [app.db.sql :as sql] + [app.util.logging :as l] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig])) (declare delete-profile-data) @@ -47,7 +47,8 @@ (if (or (:is-demo profile) (:deleted-at profile)) (delete-profile-data conn id) - (log/warnf "profile '%s' does not match constraints for deletion" id)))))) + (l/warn :hint "profile does not match constraints for deletion" + :profile-id id)))))) ;; --- IMPL @@ -70,7 +71,8 @@ (defn- delete-profile-data [conn profile-id] - (log/debugf "proceding to delete all data related to profile '%s'" 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 eebd434b5..cad9b1e02 100644 --- a/backend/src/app/tasks/file_media_gc.clj +++ b/backend/src/app/tasks/file_media_gc.clj @@ -15,9 +15,9 @@ [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] - [clojure.tools.logging :as log] [integrant.core :as ig])) (declare process-file) @@ -40,7 +40,7 @@ (run! (partial process-file cfg) files) (recur (+ n (count files)))) (do - (log/debugf "finalized with total of %s processed files" n) + (l/debug :msg "finished processing files" :processed n) {:processed n})))))))) (def ^:private @@ -88,7 +88,10 @@ unused (->> (db/query conn :file-media-object {:file-id id}) (remove #(contains? used (:id %))))] - (log/debugf "processing file: id='%s' age='%s' to-delete=%s" id age (count unused)) + (l/debug :action "processing file" + :id id + :age age + :to-delete (count unused)) ;; Mark file as trimmed (db/update! conn :file @@ -96,8 +99,10 @@ {:id id}) (doseq [mobj unused] - (log/debugf "deleting media object: id='%s' media-id='%s' thumb-id='%s'" - (:id mobj) (:media-id mobj) (:thumbnail-id mobj)) + (l/debug :action "deleting media object" + :id (:id mobj) + :media-id (:media-id mobj) + :thumbnail-id (:thumbnail-id mobj)) ;; NOTE: deleting the file-media-object in the database ;; automatically marks as toched the referenced storage objects. (db/delete! conn :file-media-object {:id (:id mobj)})) diff --git a/backend/src/app/tasks/file_xlog_gc.clj b/backend/src/app/tasks/file_xlog_gc.clj index d333f2ac5..adff21a55 100644 --- a/backend/src/app/tasks/file_xlog_gc.clj +++ b/backend/src/app/tasks/file_xlog_gc.clj @@ -5,16 +5,16 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tasks.file-xlog-gc "A maintenance task that performs a garbage collection of the file change (transaction) log." (:require [app.db :as db] + [app.util.logging :as l] [app.util.time :as dt] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig])) (declare sql:delete-files-xlog) @@ -31,7 +31,7 @@ (let [interval (db/interval max-age) result (db/exec-one! conn [sql:delete-files-xlog interval]) result (:next.jdbc/update-count result)] - (log/debugf "removed %s rows from file-change table" result) + (l/debug :action "trim file-change table" :removed result) result)))) (def ^:private diff --git a/backend/src/app/tasks/tasks_gc.clj b/backend/src/app/tasks/tasks_gc.clj index 3ff4e8db0..3dc19353a 100644 --- a/backend/src/app/tasks/tasks_gc.clj +++ b/backend/src/app/tasks/tasks_gc.clj @@ -5,16 +5,16 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tasks.tasks-gc "A maintenance task that performs a cleanup of already executed tasks from the database table." (:require [app.db :as db] + [app.util.logging :as l] [app.util.time :as dt] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig])) (declare sql:delete-completed-tasks) @@ -31,7 +31,7 @@ (let [interval (db/interval max-age) result (db/exec-one! conn [sql:delete-completed-tasks interval]) result (:next.jdbc/update-count result)] - (log/debugf "removed %s rows from tasks-completed table" result) + (l/debug :action "trim completed tasks table" :removed result) result)))) (def ^:private diff --git a/backend/src/app/util/log4j.clj b/backend/src/app/util/log4j.clj deleted file mode 100644 index cd41bae78..000000000 --- a/backend/src/app/util/log4j.clj +++ /dev/null @@ -1,27 +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/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2021 UXBOX Labs SL - -(ns app.util.log4j - (:require - [clojure.pprint :refer [pprint]]) - (:import - org.apache.logging.log4j.ThreadContext)) - -(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/logging.clj b/backend/src/app/util/logging.clj new file mode 100644 index 000000000..afe03bab7 --- /dev/null +++ b/backend/src/app/util/logging.clj @@ -0,0 +1,111 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.util.logging + (:require + [linked.core :as lk] + [clojure.pprint :refer [pprint]]) + (:import + java.util.Map + 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 msg ::logger ::async] :as props}] + (let [props (dissoc props :level :cause ::logger ::async) + 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 [_#] (write-log! ~logger-sym ~level-sym ~cause (build-map-message ~props)))) + `(write-log! ~logger-sym ~level-sym ~cause (build-map-message ~props))))))) + +(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 9255657ab..bc47f5967 100644 --- a/backend/src/app/util/migrations.clj +++ b/backend/src/app/util/migrations.clj @@ -5,13 +5,13 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.migrations (:require + [app.util.logging :as l] [clojure.java.io :as io] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [cuerdas.core :as str] [next.jdbc :as jdbc])) @@ -40,7 +40,7 @@ (defn- impl-migrate-single [pool modname {:keys [name] :as migration}] (when-not (registered? pool modname (:name migration)) - (log/info (str/format "applying migration %s/%s" modname name)) + (l/info :action "apply migration" :module modname :name name) (register! pool modname name) ((:fn migration) pool))) diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index 76a2ab8c3..229d4c7b6 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -17,11 +17,10 @@ [app.db :as db] [app.metrics :as mtx] [app.util.async :as aa] - [app.util.log4j :refer [update-thread-context!]] + [app.util.logging :as l] [app.util.time :as dt] [clojure.core.async :as a] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [cuerdas.core :as str] [integrant.core :as ig] [promesa.exec :as px]) @@ -91,7 +90,10 @@ (defmethod ig/init-key ::worker [_ {:keys [pool poll-interval name queue] :as cfg}] - (log/infof "starting worker '%s' on queue '%s'" (d/name name) (d/name queue)) + (l/log :level :info + :msg "start" + :name (d/name name) + :queue (d/name queue)) (let [close-ch (a/chan 1) poll-ms (inst-ms poll-interval)] (a/go-loop [] @@ -100,30 +102,32 @@ ;; Terminate the loop if close channel is closed or ;; event-loop-fn returns nil. (or (= port close-ch) (nil? val)) - (log/infof "stop condition found; shutdown worker: '%s'" (d/name name)) + (l/log :level :debug :msg "stop condition found") (db/pool-closed? pool) (do - (log/info "worker eventloop is aborted because pool is closed") + (l/log :level :debug :msg "eventloop aboirted because pool is closed") (a/close! close-ch)) (and (instance? java.sql.SQLException val) (contains? #{"08003" "08006" "08001" "08004"} (.getSQLState ^java.sql.SQLException val))) (do - (log/error "connection error, trying resume in some instants") + (l/log :level :error :hint "connection error, trying resume in some instants") (a/ options extract-props db/tjson) id (uuid/next)] - (log/debugf "submit task '%s' to be executed in '%s'" (d/name task) (str duration)) + (l/log :level :debug + :action "submit task" + :name (d/name task) + :in duration) (db/exec-one! conn [sql:insert-new-task id (d/name task) props (d/name queue) priority max-retries interval]) id)) @@ -242,7 +249,9 @@ (let [task-fn (get tasks name)] (if task-fn (task-fn item) - (log/warnf "no task handler found for '%s'" (d/name name))) + (l/log :level :warn + :msg "no task handler found" + :name (d/name name))) {:status :completed :task item})) (defn get-error-context @@ -266,8 +275,12 @@ (assoc :inc-by 0)) (let [cdata (get-error-context error item)] - (update-thread-context! cdata) - (log/errorf error "unhandled exception on task (id: '%s')" (:id cdata)) + (l/update-thread-context! cdata) + (l/log :level :error + :cause error + :hint "unhandled exception on task" + :id (:id cdata)) + (if (>= (:retry-num item) (:max-retries item)) {:status :failed :task item :error error} {:status :retry :task item :error error}))))) @@ -276,12 +289,21 @@ [{:keys [tasks]} item] (let [name (d/name (:name item))] (try - (log/debugf "started task '%s/%s/%s'" name (:id item) (:retry-num item)) + (l/log :level :debug + :action "start task" + :name name + :id (:id item) + :retry (:retry-num item)) + (handle-task tasks item) (catch Exception e (handle-exception e item)) (finally - (log/debugf "finished task '%s/%s/%s'" name (:id item) (:retry-num item)))))) + (l/log :level :debug + :action "start task" + :name name + :id (:id item) + :retry (:retry-num item)))))) (def sql:select-next-tasks "select * from task as t @@ -349,8 +371,8 @@ ;; If id is not defined, use the task as id. (map (fn [{:keys [id task] :as item}] (if (some? id) - item - (assoc item :id task)))) + (assoc item :id (d/name id)) + (assoc item :id (d/name task))))) (map (fn [{:keys [task] :as item}] (let [f (get tasks task)] (when-not f @@ -387,7 +409,10 @@ [conn {:keys [id cron]}] (let [cron (str cron) id (name id)] - (log/infof "initialize scheduled task '%s' (cron: '%s')" id cron) + (l/log :level :debug + :action "initialize scheduled task" + :id id + :cron cron) (db/exec-one! conn [sql:upsert-scheduled-task id cron cron]))) (defn- synchronize-schedule @@ -407,8 +432,10 @@ [{:keys [executor pool] :as cfg} {:keys [id] :as task}] (letfn [(run-task [conn] (try - (when (db/exec-one! conn [sql:lock-scheduled-task id]) - (log/debugf "executing scheduled task '%s'" id) + (when (db/exec-one! conn [sql:lock-scheduled-task (d/name id)]) + (l/log :level :debug + :action "execute scheduled task" + :id id) ((:fn task) task)) (catch Throwable e e))) @@ -417,7 +444,10 @@ (db/with-atomic [conn pool] (let [result (run-task conn)] (when (ex/exception? result) - (log/errorf result "unhandled exception on scheduled task '%s'" id)))))] + (l/log :level :error + :cause result + :hint "unhandled exception on scheduled task" + :id id)))))] (try (px/run! executor handle-task) @@ -490,7 +520,9 @@ :help "Background task execution timing."})] (reduce-kv (fn [res k v] (let [tname (name k)] - (log/debugf "registring task '%s'" tname) + (l/log :level :debug + :action "register task" + :name tname) (assoc res k (mtx/wrap-summary v mobj [tname])))) {} tasks))) From c40d9d9a7c8dec989277f5b8f42bf9587a3a6b4c Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 6 Apr 2021 23:25:50 +0200 Subject: [PATCH 027/155] :fire: Remove unused code. --- backend/src/app/tasks/sendmail.clj | 58 ------------------------------ 1 file changed, 58 deletions(-) delete mode 100644 backend/src/app/tasks/sendmail.clj diff --git a/backend/src/app/tasks/sendmail.clj b/backend/src/app/tasks/sendmail.clj deleted file mode 100644 index 333f7efd1..000000000 --- a/backend/src/app/tasks/sendmail.clj +++ /dev/null @@ -1,58 +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/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) UXBOX Labs SL - -(ns app.tasks.sendmail - (:require - [app.config :as cfg] - [app.util.emails :as emails] - [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] - [integrant.core :as ig])) - -(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) - -(defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [::enabled] - :opt-un [::username - ::password - ::tls - ::ssl - ::host - ::port - ::default-from - ::default-reply-to])) - -(defmethod ig/init-key ::handler - [_ cfg] - (fn [{:keys [props] :as task}] - (if (:enabled cfg) - (emails/send! cfg props) - (send-console! cfg props)))) - -(defn- send-console! - [cfg email] - (let [baos (java.io.ByteArrayOutputStream.) - mesg (emails/smtp-message cfg email)] - (.writeTo mesg baos) - (let [out (with-out-str - (println "email console dump:") - (println "******** start email" (:id email) "**********") - (println (.toString baos)) - (println "******** end email "(:id email) "**********"))] - (log/info out)))) From 8daf6e822eabacc07687f79f3f112b0bb8105e9d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Apr 2021 17:45:22 +0200 Subject: [PATCH 028/155] :tada: Add profile activity registry logger. --- backend/resources/log4j2-devenv.xml | 4 ++ backend/resources/log4j2.xml | 4 ++ backend/src/app/http.clj | 9 +++- backend/src/app/http/middleware.clj | 16 ++++++ backend/src/app/http/session.clj | 11 ++-- backend/src/app/worker.clj | 82 ++++++++++++----------------- 6 files changed, 70 insertions(+), 56 deletions(-) diff --git a/backend/resources/log4j2-devenv.xml b/backend/resources/log4j2-devenv.xml index 7f7ba7b1f..eb38cb183 100644 --- a/backend/resources/log4j2-devenv.xml +++ b/backend/resources/log4j2-devenv.xml @@ -32,6 +32,10 @@ + + + + diff --git a/backend/resources/log4j2.xml b/backend/resources/log4j2.xml index 660f8075a..96750c045 100644 --- a/backend/resources/log4j2.xml +++ b/backend/resources/log4j2.xml @@ -14,6 +14,10 @@ + + + + diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index dacd4d050..011cd391f 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -127,7 +127,10 @@ (rr/router [["/metrics" {:get (:handler metrics)}] ["/assets" {:middleware [[middleware/format-response-body] - [middleware/errors errors/handle]]} + [middleware/errors errors/handle] + [middleware/cookies] + (:middleware session) + middleware/activity-logger]} ["/by-id/:id" {:get (:objects-handler assets)}] ["/by-file-media-id/:id" {:get (:file-objects-handler assets)}] ["/by-file-media-id/:id/thumbnail" {:get (:file-thumbnails-handler assets)}]] @@ -161,6 +164,8 @@ ["/github" {:post (get-in oauth [:github :handler])}] ["/github/callback" {:get (get-in oauth [:github :callback-handler])}]] - ["/rpc" {:middleware [(:middleware session)]} + ["/rpc" {:middleware [(:middleware session) + middleware/activity-logger]} + ["/query/:type" {:get (:query-handler rpc)}] ["/mutation/:type" {:post (:mutation-handler rpc)}]]]])) diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index 1cfc7459d..c281a4bf0 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -11,6 +11,7 @@ (:require [app.metrics :as mtx] [app.util.json :as json] + [app.util.logging :as l] [app.util.transit :as t] [buddy.core.codecs :as bc] [buddy.core.hash :as bh] @@ -165,3 +166,18 @@ (def etag {:name ::etag :compile (constantly wrap-etag)}) + +(defn activity-logger + [handler] + (let [logger "penpot.profile-activity"] + (fn [{:keys [headers] :as request}] + (let [ip-addr (get headers "x-forwarded-for") + profile-id (:profile-id request) + qstring (:query-string request)] + (l/info ::l/async true + ::l/logger logger + :ip-addr ip-addr + :profile-id profile-id + :uri (str (:uri request) (if qstring (str "?" qstring))) + :method (name (:request-method request))) + (handler request))))) diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index 8eb09eb9d..1c0dca885 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -65,8 +65,8 @@ [cfg handler] (fn [request] (if-let [{:keys [id profile-id] :as session} (retrieve-from-request cfg request)] - (let [ech (::events-ch cfg)] - (a/>!! ech id) + (let [events-ch (::events-ch cfg)] + (a/>!! events-ch id) (l/update-thread-context! {:profile-id profile-id}) (handler (assoc request :profile-id profile-id))) (handler request)))) @@ -109,6 +109,7 @@ [_ data] (a/close! (::events-ch data))) + ;; --- STATE INIT: SESSION UPDATER (declare batch-events) @@ -145,10 +146,10 @@ (let [result (a/ options extract-props db/tjson) id (uuid/next)] - (l/log :level :debug - :action "submit task" - :name (d/name task) - :in duration) + (l/debug :action "submit task" + :name (d/name task) + :in duration) (db/exec-one! conn [sql:insert-new-task id (d/name task) props (d/name queue) priority max-retries interval]) id)) -;; --- RUNNER +;; --- RUNNER (def ^:private sql:mark-as-retry @@ -249,9 +246,8 @@ (let [task-fn (get tasks name)] (if task-fn (task-fn item) - (l/log :level :warn - :msg "no task handler found" - :name (d/name name))) + (l/warn :msg "no task handler found" + :name (d/name name))) {:status :completed :task item})) (defn get-error-context @@ -276,10 +272,9 @@ (let [cdata (get-error-context error item)] (l/update-thread-context! cdata) - (l/log :level :error - :cause error - :hint "unhandled exception on task" - :id (:id cdata)) + (l/error :cause error + :hint "unhandled exception on task" + :id (:id cdata)) (if (>= (:retry-num item) (:max-retries item)) {:status :failed :task item :error error} @@ -289,21 +284,19 @@ [{:keys [tasks]} item] (let [name (d/name (:name item))] (try - (l/log :level :debug - :action "start task" - :name name - :id (:id item) - :retry (:retry-num item)) + (l/debug :action "start task" + :name name + :id (:id item) + :retry (:retry-num item)) (handle-task tasks item) (catch Exception e (handle-exception e item)) (finally - (l/log :level :debug - :action "start task" - :name name - :id (:id item) - :retry (:retry-num item)))))) + (l/debug :action "end task" + :name name + :id (:id item) + :retry (:retry-num item)))))) (def sql:select-next-tasks "select * from task as t @@ -407,12 +400,8 @@ (defn- synchronize-schedule-item [conn {:keys [id cron]}] - (let [cron (str cron) - id (name id)] - (l/log :level :debug - :action "initialize scheduled task" - :id id - :cron cron) + (let [cron (str cron)] + (l/debug :action "initialize scheduled task" :id id :cron cron) (db/exec-one! conn [sql:upsert-scheduled-task id cron cron]))) (defn- synchronize-schedule @@ -433,9 +422,7 @@ (letfn [(run-task [conn] (try (when (db/exec-one! conn [sql:lock-scheduled-task (d/name id)]) - (l/log :level :debug - :action "execute scheduled task" - :id id) + (l/debug :action "execute scheduled task" :id id) ((:fn task) task)) (catch Throwable e e))) @@ -444,10 +431,9 @@ (db/with-atomic [conn pool] (let [result (run-task conn)] (when (ex/exception? result) - (l/log :level :error - :cause result - :hint "unhandled exception on scheduled task" - :id id)))))] + (l/error :cause result + :hint "unhandled exception on scheduled task" + :id id)))))] (try (px/run! executor handle-task) @@ -520,9 +506,7 @@ :help "Background task execution timing."})] (reduce-kv (fn [res k v] (let [tname (name k)] - (l/log :level :debug - :action "register task" - :name tname) + (l/debug :action "register task" :name tname) (assoc res k (mtx/wrap-summary v mobj [tname])))) {} tasks))) From d889d39151d113ef9ce41a7b5e9a77395fc631a6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Apr 2021 18:36:05 +0200 Subject: [PATCH 029/155] :paperclip: Fix linter issues. --- backend/src/app/db.clj | 4 ++-- backend/src/app/http.clj | 1 - backend/src/app/http/awsns.clj | 1 - backend/src/app/http/middleware.clj | 2 +- backend/src/app/notifications.clj | 17 +++++------------ backend/src/app/util/logging.clj | 4 +--- backend/src/app/util/migrations.clj | 1 - 7 files changed, 9 insertions(+), 21 deletions(-) diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index d2b6f7cc1..d1debe094 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -5,7 +5,7 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.db (:require @@ -16,8 +16,8 @@ [app.db.sql :as sql] [app.metrics :as mtx] [app.util.json :as json] - [app.util.migrations :as mg] [app.util.logging :as l] + [app.util.migrations :as mg] [app.util.time :as dt] [app.util.transit :as t] [clojure.java.io :as io] diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 011cd391f..ed86ccd12 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -17,7 +17,6 @@ [app.metrics :as mtx] [app.util.logging :as l] [clojure.spec.alpha :as s] - [clojure.tools.logging :as log] [integrant.core :as ig] [reitit.ring :as rr] [ring.adapter.jetty9 :as jetty]) diff --git a/backend/src/app/http/awsns.clj b/backend/src/app/http/awsns.clj index 7dccbb08c..77ebf4c6f 100644 --- a/backend/src/app/http/awsns.clj +++ b/backend/src/app/http/awsns.clj @@ -15,7 +15,6 @@ [app.db.sql :as sql] [app.util.http :as http] [app.util.logging :as l] - [clojure.pprint :refer [pprint]] [clojure.spec.alpha :as s] [cuerdas.core :as str] [integrant.core :as ig] diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index c281a4bf0..c117a9a8e 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -178,6 +178,6 @@ ::l/logger logger :ip-addr ip-addr :profile-id profile-id - :uri (str (:uri request) (if qstring (str "?" qstring))) + :uri (str (:uri request) (when qstring (str "?" qstring))) :method (name (:request-method request))) (handler request))))) diff --git a/backend/src/app/notifications.clj b/backend/src/app/notifications.clj index 75cb64f46..a045f153b 100644 --- a/backend/src/app/notifications.clj +++ b/backend/src/app/notifications.clj @@ -149,9 +149,7 @@ :out-ch out-ch :sub-ch sub-ch)] - (l/log :level :trace - :action "connect" - :session (:session-id cfg)) + (l/trace :event "connect" :session (:session-id cfg)) ;; Forward all messages from out-ch to the websocket ;; connection @@ -173,18 +171,14 @@ ;; close subscription (a/close! sub-ch)))) - (on-error [_conn e] - (l/log :level :trace - :action "error" - :session (:session-id cfg)) + (on-error [_conn _e] + (l/trace :event "error" :session (:session-id cfg)) (a/close! out-ch) (a/close! rcv-ch)) (on-close [_conn _status _reason] - (l/log :level :trace - :action "close" - :session (:session-id cfg)) + (l/trace :event "close" :session (:session-id cfg)) (a/close! out-ch) (a/close! rcv-ch)) @@ -192,8 +186,7 @@ (on-message [_ws message] (let [message (t/decode-str message)] (when-not (a/offer! rcv-ch message) - (l/log :level :warn - :msg "drop messages"))))] + (l/warn :msg "drop messages"))))] {:on-connect on-connect :on-error on-error diff --git a/backend/src/app/util/logging.clj b/backend/src/app/util/logging.clj index afe03bab7..0f5fc5bb5 100644 --- a/backend/src/app/util/logging.clj +++ b/backend/src/app/util/logging.clj @@ -9,10 +9,8 @@ (ns app.util.logging (:require - [linked.core :as lk] [clojure.pprint :refer [pprint]]) (:import - java.util.Map org.apache.logging.log4j.Level org.apache.logging.log4j.LogManager org.apache.logging.log4j.Logger @@ -65,7 +63,7 @@ ^Object msg))) (defmacro log - [& {:keys [level cause msg ::logger ::async] :as props}] + [& {:keys [level cause ::logger ::async] :as props}] (let [props (dissoc props :level :cause ::logger ::async) logger (or logger (str *ns*)) logger-sym (gensym "log") diff --git a/backend/src/app/util/migrations.clj b/backend/src/app/util/migrations.clj index bc47f5967..aa6bbf43c 100644 --- a/backend/src/app/util/migrations.clj +++ b/backend/src/app/util/migrations.clj @@ -12,7 +12,6 @@ [app.util.logging :as l] [clojure.java.io :as io] [clojure.spec.alpha :as s] - [cuerdas.core :as str] [next.jdbc :as jdbc])) (s/def ::name string?) From 0a44dbd92161a3cf76fa950adce2536c56436345 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 9 Apr 2021 15:24:25 +0200 Subject: [PATCH 030/155] :bug: Fixes problem with pan and space --- CHANGES.md | 2 ++ frontend/src/app/main/data/workspace.cljs | 26 +++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 10746d5e5..8135b7eb4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,8 @@ ### :bug: Bugs fixed +- Fixes problem with pan and space [#811](https://github.com/penpot/penpot/issues/811) + ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 5ff8e7a68..b31034db4 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -441,24 +441,22 @@ (defn start-panning [] (ptk/reify ::start-panning - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:workspace-local :panning] true))) - ptk/WatchEvent (watch [_ state stream] (let [stopper (->> stream (rx/filter (ptk/type? ::finish-panning))) zoom (-> (get-in state [:workspace-local :zoom]) gpt/point)] - (->> stream - (rx/filter ms/pointer-event?) - (rx/filter #(= :delta (:source %))) - (rx/map :pt) - (rx/take-until stopper) - (rx/map (fn [delta] - (let [delta (gpt/divide delta zoom)] - (update-viewport-position {:x #(- % (:x delta)) - :y #(- % (:y delta))}))))))))) + (when-not (get-in state [:workspace-local :panning]) + (rx/concat + (rx/of #(-> % (assoc-in [:workspace-local :panning] true))) + (->> stream + (rx/filter ms/pointer-event?) + (rx/filter #(= :delta (:source %))) + (rx/map :pt) + (rx/take-until stopper) + (rx/map (fn [delta] + (let [delta (gpt/divide delta zoom)] + (update-viewport-position {:x #(- % (:x delta)) + :y #(- % (:y delta))}))))))))))) (defn finish-panning [] (ptk/reify ::finish-panning From 7d14aef393dae9c45170da0647da77c17afb9381 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 9 Apr 2021 17:21:34 +0200 Subject: [PATCH 031/155] :recycle: Refactor http client. Start using Fetch API. --- backend/src/app/http.clj | 1 - frontend/deps.edn | 2 +- frontend/src/app/main/data/fetch.cljs | 35 --- frontend/src/app/main/data/workspace.cljs | 9 +- .../app/main/data/workspace/persistence.cljs | 16 +- frontend/src/app/main/repo.cljs | 109 +++++----- frontend/src/app/main/ui/shapes/image.cljs | 21 +- .../src/app/main/ui/shapes/text/embed.cljs | 36 +++- .../sidebar/options/menus/exports.cljs | 31 ++- .../ui/workspace/viewport/pixel_overlay.cljs | 3 +- frontend/src/app/util/http.cljs | 199 ++++++++++-------- frontend/src/app/util/http_api.cljs | 59 ------ frontend/src/app/util/router.cljs | 4 +- frontend/src/app/util/webapi.cljs | 34 +-- frontend/src/app/worker/thumbnails.cljs | 3 +- 15 files changed, 257 insertions(+), 305 deletions(-) delete mode 100644 frontend/src/app/main/data/fetch.cljs delete mode 100644 frontend/src/app/util/http_api.cljs diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index ed86ccd12..daf59fa2f 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -165,6 +165,5 @@ ["/rpc" {:middleware [(:middleware session) middleware/activity-logger]} - ["/query/:type" {:get (:query-handler rpc)}] ["/mutation/:type" {:post (:mutation-handler rpc)}]]]])) diff --git a/frontend/deps.edn b/frontend/deps.edn index df5753b1e..51d43a0f6 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -11,7 +11,7 @@ danlentz/clj-uuid {:mvn/version "0.1.9"} frankiesardo/linked {:mvn/version "1.3.0"} - funcool/beicon {:mvn/version "2021.01.29-1"} + funcool/beicon {:mvn/version "2021.04.09-1"} funcool/cuerdas {:mvn/version "2020.03.26-3"} funcool/okulary {:mvn/version "2020.04.14-0"} funcool/potok {:mvn/version "3.2.0"} diff --git a/frontend/src/app/main/data/fetch.cljs b/frontend/src/app/main/data/fetch.cljs deleted file mode 100644 index 947ad1391..000000000 --- a/frontend/src/app/main/data/fetch.cljs +++ /dev/null @@ -1,35 +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/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL - -(ns app.main.data.fetch - (:require - [promesa.core :as p] - [potok.core :as ptk] - [okulary.core :as l] - [app.util.object :as obj] - [app.main.store :as st])) - -(defn pending-ref [] - (l/derived ::to-fetch st/state)) - -(defn add [to-fetch id] - (let [to-fetch (or to-fetch (hash-set))] - (conj to-fetch id))) - -(defn fetch-as-data-uri [url] - (let [id (random-uuid)] - (st/emit! (fn [state] (update state ::to-fetch add id))) - (-> (js/fetch url) - (p/then (fn [res] (.blob res))) - (p/then (fn [blob] - (let [reader (js/FileReader.)] - (p/create (fn [resolve reject] - (obj/set! reader "onload" #(resolve [url (.-result reader)])) - (.readAsDataURL reader blob)))))) - (p/finally #(st/emit! (fn [state] (update state ::to-fetch disj id))))))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index b31034db4..1bf9a34c5 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1321,7 +1321,9 @@ (let [obj (maybe-translate obj objects selected)] (if (= type :image) (let [url (cfg/resolve-file-media (:metadata obj))] - (->> (http/fetch-as-data-url url) + (->> (http/send! {:method :get :uri url}) + (rx/map :body) + (rx/mapcat wapi/read-file-as-data-url) (rx/map #(assoc obj ::data %)) (rx/take 1))) (rx/of obj)))) @@ -1459,7 +1461,10 @@ (letfn [;; Given a file-id and img (part generated by the ;; copy-selected event), uploads the new media. (upload-media [file-id imgpart] - (->> (http/data-url->blob (:file-data imgpart)) + (->> (http/send! {:uri (:file-data imgpart) + :response-type :blob + :method :get}) + (rx/map :body) (rx/map (fn [blob] {:name (:name imgpart) diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 8fec68826..6d987437f 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -394,19 +394,13 @@ (or (contains? props :data) (contains? props :uris))))) -(defn parse-svg [[name text]] - (->> (http/send! {:method :post - :uri "/api/svg/parse" - :headers {"content-type" "image/svg+xml"} - :body text}) - (rx/map (fn [{:keys [status body]}] - (let [result (t/decode body)] - (if (= status 200) - (assoc result :name name) - (throw result))))))) +(defn parse-svg + [[name text]] + (->> (rp/query! :parse-svg {:data text}) + (rx/map #(assoc % :name name)))) (defn fetch-svg [name uri] - (->> (http/send! {:method :get :uri uri}) + (->> (http/send! {:method :get :uri uri :mode :no-cors}) (rx/map #(vector (or name (uu/uri-name uri)) (:body %))))) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 5d16f5979..7deeb8dad 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -10,9 +10,11 @@ (ns app.main.repo (:require [beicon.core :as rx] + [lambdaisland.uri :as u] [cuerdas.core :as str] [app.config :as cfg] - [app.util.http-api :as http])) + [app.util.transit :as t] + [app.util.http :as http])) (defn- handle-response [{:keys [status body] :as response}] @@ -30,8 +32,7 @@ (= 0 (:status response)) (rx/throw {:type :offline}) - (and (= 200 status) - (coll? body)) + (= 200 status) (rx/of body) (and (>= status 400) @@ -43,21 +44,29 @@ :status status :data body}))) -(defn send-query! - [id params] - (let [uri (str cfg/public-uri "/api/rpc/query/" (name id))] - (->> (http/send! {:method :get :uri uri :query params}) - (rx/mapcat handle-response)))) +(def ^:private base-uri (u/uri cfg/public-uri)) -(defn send-mutation! +(defn- send-query! + "A simple helper for send and receive transit data on the penpot + query api." [id params] - (let [uri (str cfg/public-uri "/api/rpc/mutation/" (name id))] - (->> (http/send! {:method :post :uri uri :body params}) - (rx/mapcat handle-response)))) + (->> (http/send! {:method :get + :uri (u/join base-uri "api/rpc/query/" (name id)) + :query params}) + (rx/map http/conditional-decode-transit) + (rx/mapcat handle-response))) -(defn- dispatch - [& args] - (first args)) +(defn- send-mutation! + "A simple helper for a common case of sending and receiving transit + data to the penpot mutation api." + [id params] + (->> (http/send! {:method :post + :uri (u/join base-uri "api/rpc/mutation/" (name id)) + :body (http/transit-data params)}) + (rx/map http/conditional-decode-transit) + (rx/mapcat handle-response))) + +(defn- dispatch [& args] (first args)) (defmulti query dispatch) (defmulti mutation dispatch) @@ -80,53 +89,59 @@ (defmethod mutation :login-with-google [id params] - (let [uri (str cfg/public-uri "/api/oauth/google")] + (let [uri (u/join base-uri "api/oauth/google")] (->> (http/send! {:method :post :uri uri :query params}) + (rx/map http/conditional-decode-transit) (rx/mapcat handle-response)))) (defmethod mutation :login-with-gitlab [id params] - (let [uri (str cfg/public-uri "/api/oauth/gitlab")] + (let [uri (u/join base-uri "api/oauth/gitlab")] (->> (http/send! {:method :post :uri uri :query params}) - (rx/mapcat handle-response)))) + (rx/map http/conditional-decode-transit) + (rx/mapcat handle-response)))) (defmethod mutation :login-with-github [id params] - (let [uri (str cfg/public-uri "/api/oauth/github")] + (let [uri (u/join base-uri "api/oauth/github")] (->> (http/send! {:method :post :uri uri :query params}) + (rx/map http/conditional-decode-transit) (rx/mapcat handle-response)))) -(defmethod mutation :upload-file-media-object - [id params] - (let [form (js/FormData.)] - (run! (fn [[key val]] - (if (list? val) - (.append form (name key) (first val) (second val)) - (.append form (name key) val))) - (seq params)) - (send-mutation! id form))) - (defmethod mutation :send-feedback [id params] - (let [uri (str cfg/public-uri "/api/feedback")] - (->> (http/send! {:method :post :uri uri :body params}) - (rx/mapcat handle-response)))) + (->> (http/send! {:method :post + :uri (u/join base-uri "api/feedback") + :body (http/transit-data params)}) + (rx/map http/conditional-decode-transit) + (rx/mapcat handle-response))) -(defmethod mutation :update-profile-photo +(defmethod query :export [id params] - (let [form (js/FormData.)] - (run! (fn [[key val]] - (.append form (name key) val)) - (seq params)) - (send-mutation! id form))) + (->> (http/send! {:method :post + :uri (u/join base-uri "export") + :body (http/transit-data params) + :response-type :blob}) + (rx/mapcat handle-response))) -(defmethod mutation :update-team-photo +(defmethod query :parse-svg + [id {:keys [data] :as params}] + (->> (http/send! {:method :post + :uri (u/join base-uri "api/svg/parse") + :headers {"content-type" "image/svg+xml"} + :body data + :response-type :text}) + (rx/map http/conditional-decode-transit) + (rx/mapcat handle-response))) + +(derive :upload-file-media-object ::multipart-upload) +(derive :update-profile-photo ::multipart-upload) +(derive :update-team-photo ::multipart-upload) + +(defmethod mutation ::multipart-upload [id params] - (let [form (js/FormData.)] - (run! (fn [[key val]] - (.append form (name key) val)) - (seq params)) - (send-mutation! id form))) - -(def client-error? http/client-error?) -(def server-error? http/server-error?) + (->> (http/send! {:method :post + :uri (u/join base-uri "/api/rpc/mutation/" (name id)) + :body (http/form-data params)}) + (rx/map http/conditional-decode-transit) + (rx/mapcat handle-response))) diff --git a/frontend/src/app/main/ui/shapes/image.cljs b/frontend/src/app/main/ui/shapes/image.cljs index 954b73efb..5eaa16ba2 100644 --- a/frontend/src/app/main/ui/shapes/image.cljs +++ b/frontend/src/app/main/ui/shapes/image.cljs @@ -5,19 +5,20 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.image (:require - [rumext.alpha :as mf] - [app.config :as cfg] [app.common.geom.shapes :as geom] + [app.config :as cfg] + [app.main.ui.context :as muc] [app.main.ui.shapes.attrs :as attrs] [app.util.dom :as dom] + [app.util.http :as http] [app.util.object :as obj] - [app.main.ui.context :as muc] - [app.main.data.fetch :as df] - [promesa.core :as p])) + [app.util.webapi :as wapi] + [beicon.core :as rx] + [rumext.alpha :as mf])) (mf/defc image-shape {::mf/wrap-props false} @@ -33,8 +34,12 @@ (mf/deps uri) (fn [] (if embed-resources? - (-> (df/fetch-as-data-uri uri) - (p/then #(reset! data-uri (second %))))))) + (->> (http/send! {:method :get + :uri uri + :response-type :blob}) + (rx/map :body) + (rx/mapcat wapi/read-file-as-data-url) + (rx/subs #(reset! data-uri %)))))) (let [transform (geom/transform-matrix shape) props (-> (attrs/extract-style-attrs shape) diff --git a/frontend/src/app/main/ui/shapes/text/embed.cljs b/frontend/src/app/main/ui/shapes/text/embed.cljs index 0ef584e5e..0ea0df6c1 100644 --- a/frontend/src/app/main/ui/shapes/text/embed.cljs +++ b/frontend/src/app/main/ui/shapes/text/embed.cljs @@ -11,12 +11,14 @@ (:require [app.common.data :as d] [app.common.text :as txt] - [app.main.data.fetch :as df] [app.main.fonts :as fonts] + [app.util.http :as http] + [app.util.webapi :as wapi] [app.util.object :as obj] [clojure.set :as set] [cuerdas.core :as str] [promesa.core :as p] + [beicon.core :as rx] [rumext.alpha :as mf])) (def font-face-template " @@ -44,10 +46,12 @@ "Given a font and the variant-id, retrieves the style CSS for it." [{:keys [id backend family variants] :as font} font-variant-id] (if (= :google backend) - (-> (fonts/gfont-url family [{:id font-variant-id}]) - (js/fetch) - (p/then (fn [res] (.text res)))) - + (->> (http/send! {:method :get + :mode :no-cors + :uri (fonts/gfont-url family [{:id font-variant-id}]) + :response-type :text}) + (rx/map :body) + (http/as-promise)) (let [{:keys [name weight style suffix] :as variant} (d/seek #(= (:id %) font-variant-id) variants) result (str/fmt font-face-template {:family family :style style @@ -55,14 +59,26 @@ :weight weight})] (p/resolved result)))) +(defn- to-promise + [observable] + (p/create (fn [resolve reject] + (->> (rx/take 1 observable) + (rx/subs resolve reject))))) + (defn get-font-data "Parses the CSS and retrieves the font data as DataURI." [^string css] - (->> css - (re-seq #"url\(([^)]+)\)") - (map second) - (map df/fetch-as-data-uri) - (p/all))) + (let [uris (->> (re-seq #"url\(([^)]+)\)" css) + (map second))] + (->> (rx/from (seq uris)) + (rx/mapcat (fn [uri] + (http/send! {:method :get + :uri uri + :response-type :blob}))) + (rx/map :body) + (rx/mapcat wapi/read-file-as-data-url) + (rx/reduce conj []) + (http/as-promise)))) (defn embed-font "Given a font-id and font-variant-id, retrieves the CSS for it and diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs index 0935c7038..4dc5ac0cb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs @@ -5,7 +5,7 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.exports (:require @@ -13,26 +13,24 @@ [beicon.core :as rx] [rumext.alpha :as mf] [app.common.data :as d] + [app.main.repo :as rp] [app.main.ui.icons :as i] [app.main.data.messages :as dm] [app.main.data.workspace :as udw] [app.main.store :as st] [app.util.object :as obj] [app.util.dom :as dom] - [app.util.http-api :as http] + [app.util.http :as http] [app.util.i18n :as i18n :refer [tr t]])) (defn- request-export [shape exports] - (http/send! {:method :post - :uri "/export" - :response-type :blob - :auth true - :body {:page-id (:page-id shape) - :file-id (:file-id shape) - :object-id (:id shape) - :name (:name shape) - :exports exports}})) + (rp/query! :export + {:page-id (:page-id shape) + :file-id (:file-id shape) + :object-id (:id shape) + :name (:name shape) + :exports exports})) (defn- trigger-download [filename blob] @@ -68,12 +66,11 @@ (swap! loading? not) (->> (request-export (assoc shape :page-id page-id :file-id file-id) exports) (rx/subs - (fn [{:keys [status body] :as response}] - (js/console.log status body) - (if (= status 200) - (trigger-download filename body) - (st/emit! (dm/error (tr "errors.unexpected-error"))))) - (constantly nil) + (fn [body] + (trigger-download filename body)) + (fn [error] + (swap! loading? not) + (st/emit! (dm/error (tr "errors.unexpected-error")))) (fn [] (swap! loading? not)))))) diff --git a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs index c85f1382d..b43775b8a 100644 --- a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs @@ -5,13 +5,12 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.pixel-overlay (:require [app.common.uuid :as uuid] [app.main.data.colors :as dwc] - [app.main.data.fetch :as mdf] [app.main.data.modal :as modal] [app.main.refs :as refs] [app.main.store :as st] diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs index de7571aa1..af255185e 100644 --- a/frontend/src/app/util/http.cljs +++ b/frontend/src/app/util/http.cljs @@ -5,25 +5,34 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.http "A http client with rx streams interface." - (:refer-clojure :exclude [get]) (:require + [app.common.data :as d] [app.config :as cfg] [app.util.object :as obj] [app.util.transit :as t] [beicon.core :as rx] - [cljs.core :as c] - [clojure.string :as str] - [goog.events :as events]) - (:import - [goog.net ErrorCode EventType] - [goog.net.XhrIo ResponseType] - [goog.net XhrIo] - [goog.Uri QueryData] - [goog Uri])) + [cuerdas.core :as str] + [lambdaisland.uri :as u] + [promesa.core :as p])) + +(defprotocol IBodyData + "A helper for define body data with the appropiate headers." + (-update-headers [_ headers]) + (-get-body-data [_])) + +(extend-protocol IBodyData + js/FormData + (-get-body-data [it] it) + (-update-headers [it headers] + (dissoc headers "content-type" "Content-Type")) + + default + (-get-body-data [it] it) + (-update-headers [it headers] headers)) (defn translate-method [method] @@ -37,70 +46,93 @@ :delete "DELETE" :trace "TRACE")) -(defn- normalize-headers +(defn parse-headers [headers] - (reduce-kv (fn [acc k v] - (assoc acc (str/lower-case k) v)) - {} (js->clj headers))) - -(defn- translate-error-code - [code] - (condp = code - ErrorCode.TIMEOUT :timeout - ErrorCode.EXCEPTION :exception - ErrorCode.HTTP_ERROR :http - ErrorCode.ABORT :abort - ErrorCode.OFFLINE :offline - nil)) - -(defn- translate-response-type - [type] - (case type - :text ResponseType.TEXT - :blob ResponseType.BLOB - ResponseType.DEFAULT)) - -(defn- create-uri - [uri qs qp] - (let [uri (Uri. uri)] - (when qs (.setQuery uri qs)) - (when qp - (let [dt (.createFromMap QueryData (clj->js qp))] - (.setQueryData uri dt))) - (.toString uri))) + (into {} (map vec) (seq (.entries ^js headers)))) (def default-headers {"x-frontend-version" (:full @cfg/version)}) -(defn- fetch - [{:keys [method uri query-string query headers body] :as request} - {:keys [timeout credentials? response-type] - :or {timeout 0 credentials? false response-type :text}}] - (let [uri (create-uri uri query-string query) - headers (merge default-headers headers) - headers (if headers (clj->js headers) #js {}) - method (translate-method method) - xhr (doto (XhrIo.) - (.setResponseType (translate-response-type response-type)) - (.setWithCredentials credentials?) - (.setTimeoutInterval timeout))] - (rx/create - (fn [sink] - (letfn [(on-complete [event] - (let [type (translate-error-code (.getLastErrorCode xhr)) - status (.getStatus xhr)] - (if (pos? status) - (sink (rx/end - {:status status - :body (.getResponse xhr) - :headers (normalize-headers (.getResponseHeaders xhr))})) - (sink (rx/end - {:status 0 - :error (if (= type :http) :abort type) - ::xhr xhr})))))] - (events/listen xhr EventType.COMPLETE on-complete) - (.send xhr uri method body headers) - #(.abort xhr)))))) +(defn fetch + [{:keys [method uri query headers body timeout mode] + :or {timeout 10000 mode :cors headers {}}}] + (rx/Observable.create + (fn [subscriber] + (let [controller (js/AbortController.) + signal (.-signal ^js controller) + unsubscribed? (volatile! false) + abortable? (volatile! true) + query (cond + (string? query) query + (map? query) (u/map->query-string query) + :else nil) + uri (cond-> uri + (string? uri) (u/uri) + (some? query) (assoc :query query)) + headers (->> (d/merge headers default-headers) + (-update-headers body)) + body (-get-body-data body) + params #js {:method (translate-method method) + :headers (clj->js headers) + :body body + :mode (d/name mode) + :redirect "follow" + :credentials "same-origin" + :referrerPolicy "no-referrer" + :signal signal}] + (-> (js/fetch (str uri) params) + (p/then (fn [response] + (vreset! abortable? false) + (.next ^js subscriber response) + (.complete ^js subscriber))) + (p/catch (fn [err] + (vreset! abortable? false) + (when-not @unsubscribed? + (.error ^js subscriber err))))) + (fn [] + (vreset! unsubscribed? true) + (when @abortable? + (.abort ^js controller))))))) + +(defn send! + [{:keys [response-type] :or {response-type :text} :as params}] + (letfn [(on-response [response] + (let [body (case response-type + :json (.json ^js response) + :text (.text ^js response) + :blob (.blob ^js response))] + (->> (rx/from body) + (rx/map (fn [body] + {::response response + :status (.-status ^js response) + :headers (parse-headers (.-headers ^js response)) + :body body})))))] + (->> (fetch params) + (rx/mapcat on-response)))) + +(defn form-data + [data] + (letfn [(append [form k v] + (if (list? v) + (.append form (name k) (first v) (second v)) + (.append form (name k) v)) + form)] + (reduce-kv append (js/FormData.) data))) + +(defn transit-data + [data] + (reify IBodyData + (-get-body-data [_] (t/encode data)) + (-update-headers [_ headers] + (assoc headers "content-type" "application/transit+json")))) + +(defn conditional-decode-transit + [{:keys [body headers status] :as response}] + (let [contentype (get headers "content-type")] + (if (and (str/starts-with? contentype "application/transit+json") + (pos? (count body))) + (assoc response :body (t/decode body)) + response))) (defn success? [{:keys [status]}] @@ -114,25 +146,8 @@ [{:keys [status]}] (<= 400 status 499)) -(defn send! - ([request] - (send! request nil)) - ([request options] - (fetch request options))) - -(defn fetch-as-data-url - [url] - (->> (send! {:method :get :uri url} {:response-type :blob}) - (rx/mapcat (fn [{:keys [body] :as rsp}] - (let [reader (js/FileReader.)] - (rx/create (fn [sink] - (obj/set! reader "onload" #(sink (reduced (.-result reader)))) - (.readAsDataURL reader body)))))))) - - - -(defn data-url->blob - [durl] - (->> (send! {:method :get :uri durl} {:response-type :blob}) - (rx/map :body) - (rx/take 1))) +(defn as-promise + [observable] + (p/create (fn [resolve reject] + (->> (rx/take 1 observable) + (rx/subs resolve reject))))) diff --git a/frontend/src/app/util/http_api.cljs b/frontend/src/app/util/http_api.cljs deleted file mode 100644 index 73cb69a50..000000000 --- a/frontend/src/app/util/http_api.cljs +++ /dev/null @@ -1,59 +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/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL - -(ns app.util.http-api - "A specific customizations of http client for api access." - (:require - [beicon.core :as rx] - [cuerdas.core :as str] - [app.util.http :as http] - [app.util.transit :as t])) - -(defn- conditional-decode - [{:keys [body headers status] :as response}] - (let [contentype (get headers "content-type")] - (if (and (str/starts-with? contentype "application/transit+json") - (pos? (count body))) - (assoc response :body (t/decode body)) - response))) - -(defn- handle-http-status - [{:keys [body status] :as response}] - (if (http/success? response) - (rx/of {:status status :payload body}) - (rx/throw {:status status :payload body}))) - -(def ^:private default-headers - {"content-type" "application/transit+json"}) - -(defn- impl-send - [{:keys [body headers auth method query uri response-type] - :or {auth true response-type :text}}] - (let [headers (merge {"Accept" "application/transit+json,*/*"} - (when (map? body) default-headers) - headers) - request {:method method - :uri uri - :headers headers - :query query - :body (if (map? body) - (t/encode body) - body)} - options {:response-type response-type - :credentials? auth}] - (http/send! request options))) - -(defn send! - [request] - (->> (impl-send request) - (rx/map conditional-decode))) - -(def success? http/success?) -(def client-error? http/client-error?) -(def server-error? http/server-error?) diff --git a/frontend/src/app/util/router.cljs b/frontend/src/app/util/router.cljs index 107f22e48..64089c0b5 100644 --- a/frontend/src/app/util/router.cljs +++ b/frontend/src/app/util/router.cljs @@ -147,8 +147,8 @@ history (:history state) router (:router state)] (ts/schedule #(on-change router (.getToken ^js history))) - (->> (rx/create (fn [sink] - (let [key (e/listen history "navigate" (fn [o] (sink (.-token ^js o))))] + (->> (rx/create (fn [subs] + (let [key (e/listen history "navigate" (fn [o] (rx/push! subs (.-token ^js o))))] (fn [] (bhistory/disable! history) (e/unlistenByKey key))))) diff --git a/frontend/src/app/util/webapi.cljs b/frontend/src/app/util/webapi.cljs index f3ce1da2f..bac663f21 100644 --- a/frontend/src/app/util/webapi.cljs +++ b/frontend/src/app/util/webapi.cljs @@ -5,36 +5,36 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.webapi "HTML5 web api helpers." (:require + [app.common.data :as d] [app.common.exceptions :as ex] [app.util.object :as obj] - [promesa.core :as p] + [app.util.transit :as t] [beicon.core :as rx] [cuerdas.core :as str] - [app.common.data :as d] - [app.util.transit :as t])) + [promesa.core :as p])) + +(defn- file-reader + [f] + (rx/create + (fn [subs] + (let [reader (js/FileReader.)] + (obj/set! reader "onload" #(do (rx/push! subs (.-result reader)) + (rx/end! subs))) + (f reader) + (constantly nil))))) (defn read-file-as-text [file] - (rx/create - (fn [sink] - (let [fr (js/FileReader.)] - (aset fr "onload" #(sink (rx/end (.-result fr)))) - (.readAsText fr file) - (constantly nil))))) + (file-reader #(.readAsText %1 file))) -(defn read-file-as-dataurl +(defn read-file-as-data-url [file] - (rx/create - (fn [sick] - (let [fr (js/FileReader.)] - (aset fr "onload" #(sick (rx/end (.-result fr)))) - (.readAsDataURL fr file)) - (constantly nil)))) + (file-reader #(.readAsDataURL ^js %1 file))) (defn ^boolean blob? [v] diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index d91d47f69..12620859d 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -15,7 +15,7 @@ [app.main.fonts :as fonts] [app.main.exports :as exports] [app.worker.impl :as impl] - [app.util.http-api :as http] + [app.util.http :as http] ["react-dom/server" :as rds])) (defn- handle-response @@ -39,6 +39,7 @@ (->> (http/send! {:uri uri :query {:file-id file-id :id page-id} :method :get}) + (rx/map http/conditional-decode-transit) (rx/mapcat handle-response) (rx/subs (fn [body] (resolve body)) From f545e41d10e9668271fae39c8dea229ff694cf98 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sat, 10 Apr 2021 09:43:04 +0200 Subject: [PATCH 032/155] :paperclip: Fix license header. --- README.md | 3 - backend/dev/user.clj | 5 +- backend/scripts/build | 3 - backend/src/app/cli/fixtures.clj | 5 +- backend/src/app/cli/manage.clj | 3 - backend/src/app/cli/migrate_media.clj | 5 +- backend/src/app/config.clj | 3 - backend/src/app/db.clj | 3 - backend/src/app/db/sql.clj | 5 +- backend/src/app/emails.clj | 3 - backend/src/app/http.clj | 3 - backend/src/app/http/assets.clj | 5 +- backend/src/app/http/awsns.clj | 5 +- backend/src/app/http/errors.clj | 5 +- backend/src/app/http/feedback.clj | 5 +- backend/src/app/http/middleware.clj | 5 +- backend/src/app/http/oauth/github.clj | 3 - backend/src/app/http/oauth/gitlab.clj | 5 +- backend/src/app/http/oauth/google.clj | 5 +- backend/src/app/http/session.clj | 5 +- backend/src/app/loggers/loki.clj | 5 +- backend/src/app/loggers/mattermost.clj | 5 +- backend/src/app/loggers/zmq.clj | 5 +- backend/src/app/main.clj | 3 - backend/src/app/media.clj | 5 +- backend/src/app/metrics.clj | 3 - backend/src/app/migrations.clj | 5 +- backend/src/app/migrations/migration_0023.clj | 5 +- backend/src/app/msgbus.clj | 5 +- backend/src/app/notifications.clj | 5 +- backend/src/app/rlimits.clj | 5 +- backend/src/app/rpc.clj | 3 - backend/src/app/rpc/mutations/comments.clj | 5 +- backend/src/app/rpc/mutations/demo.clj | 3 - backend/src/app/rpc/mutations/files.clj | 3 - backend/src/app/rpc/mutations/ldap.clj | 5 +- backend/src/app/rpc/mutations/management.clj | 5 +- backend/src/app/rpc/mutations/media.clj | 5 +- backend/src/app/rpc/mutations/profile.clj | 5 +- backend/src/app/rpc/mutations/projects.clj | 5 +- backend/src/app/rpc/mutations/teams.clj | 3 - .../src/app/rpc/mutations/verify_token.clj | 5 +- backend/src/app/rpc/mutations/viewer.clj | 5 +- backend/src/app/rpc/permissions.clj | 5 +- backend/src/app/rpc/queries/comments.clj | 5 +- backend/src/app/rpc/queries/files.clj | 5 +- backend/src/app/rpc/queries/profile.clj | 5 +- backend/src/app/rpc/queries/projects.clj | 5 +- backend/src/app/rpc/queries/recent_files.clj | 5 +- backend/src/app/rpc/queries/teams.clj | 5 +- backend/src/app/rpc/queries/viewer.clj | 5 +- backend/src/app/setup.clj | 5 +- backend/src/app/setup/initial_data.clj | 5 +- backend/src/app/srepl.clj | 5 +- backend/src/app/storage.clj | 5 +- backend/src/app/storage/db.clj | 5 +- backend/src/app/storage/fs.clj | 5 +- backend/src/app/storage/impl.clj | 5 +- backend/src/app/storage/s3.clj | 5 +- backend/src/app/svgparse.clj | 3 - backend/src/app/tasks/delete_object.clj | 3 - backend/src/app/tasks/delete_profile.clj | 5 +- backend/src/app/tasks/file_media_gc.clj | 5 +- backend/src/app/tasks/file_xlog_gc.clj | 3 - backend/src/app/tasks/tasks_gc.clj | 3 - backend/src/app/tasks/telemetry.clj | 3 - backend/src/app/tokens.clj | 5 +- backend/src/app/util/async.clj | 2 +- backend/src/app/util/blob.clj | 3 - backend/src/app/util/closeable.clj | 2 +- backend/src/app/util/data.clj | 54 ----- backend/src/app/util/dispatcher.clj | 95 --------- backend/src/app/util/emails.clj | 5 +- backend/src/app/util/http.clj | 5 +- backend/src/app/util/json.clj | 5 +- backend/src/app/util/logging.clj | 3 - backend/src/app/util/migrations.clj | 3 - backend/src/app/util/services.clj | 5 +- backend/src/app/util/sql.clj | 198 ------------------ backend/src/app/util/template.clj | 5 +- backend/src/app/util/time.clj | 5 +- backend/src/app/util/transit.clj | 5 +- backend/src/app/worker.clj | 3 - backend/tests/app/tests/helpers.clj | 5 +- .../tests/app/tests/test_bounces_handling.clj | 5 +- backend/tests/app/tests/test_common_geom.clj | 5 +- .../app/tests/test_common_geom_shapes.clj | 5 +- backend/tests/app/tests/test_common_pages.clj | 5 +- backend/tests/app/tests/test_emails.clj | 5 +- .../tests/app/tests/test_services_files.clj | 5 +- .../app/tests/test_services_management.clj | 5 +- .../tests/app/tests/test_services_media.clj | 5 +- .../tests/app/tests/test_services_profile.clj | 5 +- .../app/tests/test_services_projects.clj | 5 +- .../tests/app/tests/test_services_teams.clj | 5 +- .../tests/app/tests/test_services_viewer.clj | 5 +- backend/tests/app/tests/test_storage.clj | 5 +- common/app/common/attrs.cljc | 5 +- common/app/common/data.cljc | 2 +- common/app/common/exceptions.cljc | 2 +- common/app/common/geom/align.cljc | 5 +- common/app/common/geom/matrix.cljc | 5 +- common/app/common/geom/point.cljc | 5 +- common/app/common/geom/proportions.cljc | 5 +- common/app/common/geom/shapes.cljc | 5 +- common/app/common/geom/shapes/common.cljc | 5 +- common/app/common/geom/shapes/intersect.cljc | 5 +- common/app/common/geom/shapes/path.cljc | 5 +- common/app/common/geom/shapes/rect.cljc | 5 +- common/app/common/geom/shapes/transforms.cljc | 5 +- common/app/common/math.cljc | 5 +- common/app/common/media.cljc | 5 +- common/app/common/pages.cljc | 5 +- common/app/common/pages/changes.cljc | 5 +- common/app/common/pages/common.cljc | 5 +- common/app/common/pages/helpers.cljc | 5 +- common/app/common/pages/init.cljc | 5 +- common/app/common/pages/migrations.cljc | 5 +- common/app/common/pages/spec.cljc | 5 +- common/app/common/spec.cljc | 5 +- common/app/common/text.cljc | 5 +- common/app/common/uuid.cljc | 5 +- common/app/common/uuid_impl.js | 5 +- common/app/common/version.cljc | 5 +- exporter/src/app/browser.cljs | 5 +- exporter/src/app/config.cljs | 5 +- exporter/src/app/core.cljs | 5 +- exporter/src/app/http.cljs | 5 +- exporter/src/app/http/export.cljs | 5 +- exporter/src/app/http/export_bitmap.cljs | 5 +- exporter/src/app/http/export_svg.cljs | 3 - exporter/src/app/http/impl.cljs | 5 +- exporter/src/app/http/thumbnail.cljs | 5 +- exporter/src/app/util/shell.cljs | 3 - exporter/src/app/util/transit.cljs | 5 +- exporter/src/app/zipfile.cljs | 5 +- frontend/locales.clj | 6 + frontend/resources/styles/main-default.scss | 5 +- .../styles/main/layouts/main-layout.scss | 5 +- .../styles/main/partials/activity-bar.scss | 5 +- .../styles/main/partials/color-bullet.scss | 5 +- .../styles/main/partials/color-palette.scss | 5 +- .../styles/main/partials/context-menu.scss | 5 +- .../styles/main/partials/dashboard-grid.scss | 5 +- .../main/partials/dashboard-header.scss | 5 +- .../main/partials/dashboard-settings.scss | 7 +- .../main/partials/dashboard-sidebar.scss | 5 +- .../styles/main/partials/dashboard.scss | 5 +- .../styles/main/partials/handoff.scss | 5 +- .../partials/sidebar-document-history.scss | 5 +- .../main/partials/workspace-header.scss | 5 +- frontend/src/app/config.cljs | 5 +- frontend/src/app/main.cljs | 5 +- frontend/src/app/main/constants.cljs | 5 +- frontend/src/app/main/data/auth.cljs | 3 - frontend/src/app/main/data/colors.cljs | 2 +- frontend/src/app/main/data/comments.cljs | 5 +- frontend/src/app/main/data/dashboard.cljs | 2 +- frontend/src/app/main/data/history.cljs | 2 +- frontend/src/app/main/data/media.cljs | 5 +- frontend/src/app/main/data/messages.cljs | 5 +- frontend/src/app/main/data/modal.cljs | 5 +- frontend/src/app/main/data/shortcuts.cljs | 5 +- frontend/src/app/main/data/users.cljs | 5 +- frontend/src/app/main/data/viewer.cljs | 5 +- .../src/app/main/data/viewer/shortcuts.cljs | 5 +- frontend/src/app/main/data/workspace.cljs | 5 +- .../src/app/main/data/workspace/comments.cljs | 5 +- .../src/app/main/data/workspace/common.cljs | 5 +- .../src/app/main/data/workspace/drawing.cljs | 5 +- .../app/main/data/workspace/drawing/box.cljs | 5 +- .../main/data/workspace/drawing/common.cljs | 5 +- .../main/data/workspace/drawing/curve.cljs | 5 +- .../app/main/data/workspace/drawing/path.cljs | 5 +- .../src/app/main/data/workspace/grid.cljs | 5 +- .../app/main/data/workspace/libraries.cljs | 5 +- .../data/workspace/libraries_helpers.cljs | 5 +- .../main/data/workspace/notifications.cljs | 5 +- .../app/main/data/workspace/persistence.cljs | 5 +- .../app/main/data/workspace/selection.cljs | 5 +- .../app/main/data/workspace/shortcuts.cljs | 5 +- .../app/main/data/workspace/svg_upload.cljs | 5 +- .../src/app/main/data/workspace/texts.cljs | 5 +- .../app/main/data/workspace/transforms.cljs | 5 +- frontend/src/app/main/exports.cljs | 5 +- frontend/src/app/main/fonts.clj | 5 +- frontend/src/app/main/fonts.cljs | 3 - frontend/src/app/main/refs.cljs | 5 +- frontend/src/app/main/repo.cljs | 3 - frontend/src/app/main/snap.cljs | 5 +- frontend/src/app/main/store.clj | 2 +- frontend/src/app/main/store.cljs | 2 +- frontend/src/app/main/streams.cljs | 2 +- frontend/src/app/main/ui.cljs | 3 - frontend/src/app/main/ui/auth.cljs | 5 +- frontend/src/app/main/ui/auth/login.cljs | 5 +- frontend/src/app/main/ui/auth/recovery.cljs | 5 +- .../app/main/ui/auth/recovery_request.cljs | 5 +- frontend/src/app/main/ui/auth/register.cljs | 5 +- .../src/app/main/ui/auth/verify_token.cljs | 5 +- frontend/src/app/main/ui/comments.cljs | 5 +- .../app/main/ui/components/code_block.cljs | 5 +- .../app/main/ui/components/color_bullet.cljs | 5 +- .../app/main/ui/components/context_menu.cljs | 5 +- .../app/main/ui/components/copy_button.cljs | 5 +- .../main/ui/components/editable_label.cljs | 5 +- .../main/ui/components/editable_select.cljs | 5 +- .../app/main/ui/components/file_uploader.cljs | 5 +- .../src/app/main/ui/components/forms.cljs | 5 +- .../app/main/ui/components/fullscreen.cljs | 5 +- .../app/main/ui/components/numeric_input.cljs | 5 +- .../src/app/main/ui/components/select.cljs | 5 +- .../app/main/ui/components/tab_container.cljs | 5 +- frontend/src/app/main/ui/confirm.cljs | 5 +- frontend/src/app/main/ui/context.cljs | 5 +- frontend/src/app/main/ui/cursors.clj | 5 +- frontend/src/app/main/ui/cursors.cljs | 5 +- frontend/src/app/main/ui/dashboard.cljs | 3 - .../src/app/main/ui/dashboard/comments.cljs | 5 +- .../src/app/main/ui/dashboard/file_menu.cljs | 7 +- frontend/src/app/main/ui/dashboard/files.cljs | 5 +- frontend/src/app/main/ui/dashboard/grid.cljs | 5 +- .../app/main/ui/dashboard/inline_edition.cljs | 5 +- .../src/app/main/ui/dashboard/libraries.cljs | 5 +- .../app/main/ui/dashboard/project_menu.cljs | 5 +- .../src/app/main/ui/dashboard/projects.cljs | 5 +- .../src/app/main/ui/dashboard/search.cljs | 5 +- .../src/app/main/ui/dashboard/sidebar.cljs | 5 +- frontend/src/app/main/ui/dashboard/team.cljs | 5 +- .../src/app/main/ui/dashboard/team_form.cljs | 3 - frontend/src/app/main/ui/handoff.cljs | 5 +- .../src/app/main/ui/handoff/attributes.cljs | 5 +- .../app/main/ui/handoff/attributes/blur.cljs | 5 +- .../main/ui/handoff/attributes/common.cljs | 5 +- .../app/main/ui/handoff/attributes/fill.cljs | 5 +- .../app/main/ui/handoff/attributes/image.cljs | 5 +- .../main/ui/handoff/attributes/layout.cljs | 5 +- .../main/ui/handoff/attributes/shadow.cljs | 5 +- .../main/ui/handoff/attributes/stroke.cljs | 5 +- .../app/main/ui/handoff/attributes/svg.cljs | 5 +- .../app/main/ui/handoff/attributes/text.cljs | 5 +- frontend/src/app/main/ui/handoff/code.cljs | 5 +- frontend/src/app/main/ui/handoff/exports.cljs | 5 +- .../src/app/main/ui/handoff/left_sidebar.cljs | 5 +- frontend/src/app/main/ui/handoff/render.cljs | 5 +- .../app/main/ui/handoff/right_sidebar.cljs | 5 +- .../main/ui/handoff/selection_feedback.cljs | 5 +- frontend/src/app/main/ui/hooks.cljs | 5 +- frontend/src/app/main/ui/icons.clj | 5 +- frontend/src/app/main/ui/icons.cljs | 5 +- frontend/src/app/main/ui/loader.cljs | 2 +- frontend/src/app/main/ui/measurements.cljs | 5 +- frontend/src/app/main/ui/messages.cljs | 5 +- frontend/src/app/main/ui/modal.cljs | 5 +- frontend/src/app/main/ui/onboarding.cljs | 5 +- frontend/src/app/main/ui/render.cljs | 5 +- frontend/src/app/main/ui/settings.cljs | 5 +- .../app/main/ui/settings/change_email.cljs | 5 +- .../app/main/ui/settings/delete_account.cljs | 5 +- .../src/app/main/ui/settings/feedback.cljs | 5 +- .../src/app/main/ui/settings/options.cljs | 5 +- .../src/app/main/ui/settings/password.cljs | 5 +- .../src/app/main/ui/settings/profile.cljs | 5 +- .../src/app/main/ui/settings/sidebar.cljs | 3 - frontend/src/app/main/ui/shapes/attrs.cljs | 5 +- frontend/src/app/main/ui/shapes/circle.cljs | 5 +- .../src/app/main/ui/shapes/custom_stroke.cljs | 4 +- frontend/src/app/main/ui/shapes/filters.cljs | 5 +- frontend/src/app/main/ui/shapes/frame.cljs | 5 +- .../src/app/main/ui/shapes/gradients.cljs | 5 +- frontend/src/app/main/ui/shapes/group.cljs | 5 +- frontend/src/app/main/ui/shapes/image.cljs | 3 - frontend/src/app/main/ui/shapes/mask.cljs | 5 +- frontend/src/app/main/ui/shapes/path.cljs | 5 +- frontend/src/app/main/ui/shapes/rect.cljs | 5 +- frontend/src/app/main/ui/shapes/shape.cljs | 5 +- frontend/src/app/main/ui/shapes/svg_defs.cljs | 5 +- frontend/src/app/main/ui/shapes/svg_raw.cljs | 5 +- frontend/src/app/main/ui/shapes/text.cljs | 5 +- .../src/app/main/ui/shapes/text/embed.cljs | 3 - .../src/app/main/ui/shapes/text/styles.cljs | 5 +- frontend/src/app/main/ui/static.cljs | 5 +- frontend/src/app/main/ui/viewer.cljs | 5 +- frontend/src/app/main/ui/viewer/header.cljs | 5 +- frontend/src/app/main/ui/viewer/shapes.cljs | 5 +- .../src/app/main/ui/viewer/thumbnails.cljs | 5 +- frontend/src/app/main/ui/workspace.cljs | 5 +- .../app/main/ui/workspace/colorpalette.cljs | 5 +- .../app/main/ui/workspace/colorpicker.cljs | 5 +- .../workspace/colorpicker/color_inputs.cljs | 5 +- .../ui/workspace/colorpicker/gradients.cljs | 5 +- .../ui/workspace/colorpicker/harmony.cljs | 5 +- .../main/ui/workspace/colorpicker/hsva.cljs | 5 +- .../ui/workspace/colorpicker/libraries.cljs | 5 +- .../main/ui/workspace/colorpicker/ramp.cljs | 5 +- .../colorpicker/slider_selector.cljs | 5 +- .../src/app/main/ui/workspace/comments.cljs | 5 +- .../app/main/ui/workspace/context_menu.cljs | 5 +- .../app/main/ui/workspace/coordinates.cljs | 5 +- .../src/app/main/ui/workspace/effects.cljs | 5 +- .../src/app/main/ui/workspace/header.cljs | 5 +- .../app/main/ui/workspace/left_toolbar.cljs | 5 +- .../src/app/main/ui/workspace/libraries.cljs | 3 +- .../src/app/main/ui/workspace/presence.cljs | 3 - frontend/src/app/main/ui/workspace/rules.cljs | 5 +- .../src/app/main/ui/workspace/shapes.cljs | 5 +- .../ui/workspace/shapes/bounding_box.cljs | 2 +- .../app/main/ui/workspace/shapes/common.cljs | 5 +- .../app/main/ui/workspace/shapes/frame.cljs | 5 +- .../app/main/ui/workspace/shapes/group.cljs | 5 +- .../app/main/ui/workspace/shapes/path.cljs | 5 +- .../ui/workspace/shapes/path/actions.cljs | 5 +- .../main/ui/workspace/shapes/path/common.cljs | 5 +- .../main/ui/workspace/shapes/path/editor.cljs | 5 +- .../app/main/ui/workspace/shapes/svg_raw.cljs | 5 +- .../app/main/ui/workspace/shapes/text.cljs | 5 +- .../main/ui/workspace/shapes/text/editor.cljs | 5 +- .../src/app/main/ui/workspace/sidebar.cljs | 5 +- .../app/main/ui/workspace/sidebar/align.cljs | 3 +- .../app/main/ui/workspace/sidebar/assets.cljs | 5 +- .../main/ui/workspace/sidebar/history.cljs | 5 +- .../app/main/ui/workspace/sidebar/layers.cljs | 5 +- .../main/ui/workspace/sidebar/options.cljs | 5 +- .../ui/workspace/sidebar/options/common.cljs | 5 +- .../workspace/sidebar/options/menus/blur.cljs | 5 +- .../sidebar/options/menus/component.cljs | 5 +- .../sidebar/options/menus/exports.cljs | 3 - .../workspace/sidebar/options/menus/fill.cljs | 5 +- .../sidebar/options/menus/frame_grid.cljs | 5 +- .../sidebar/options/menus/interactions.cljs | 5 +- .../sidebar/options/menus/layer.cljs | 5 +- .../sidebar/options/menus/measures.cljs | 5 +- .../sidebar/options/menus/shadow.cljs | 5 +- .../sidebar/options/menus/stroke.cljs | 5 +- .../sidebar/options/menus/svg_attrs.cljs | 5 +- .../workspace/sidebar/options/menus/text.cljs | 5 +- .../sidebar/options/menus/typography.cljs | 5 +- .../ui/workspace/sidebar/options/page.cljs | 5 +- .../sidebar/options/rows/color_row.cljs | 5 +- .../sidebar/options/rows/input_row.cljs | 5 +- .../sidebar/options/shapes/circle.cljs | 5 +- .../sidebar/options/shapes/frame.cljs | 6 +- .../sidebar/options/shapes/group.cljs | 6 +- .../sidebar/options/shapes/image.cljs | 5 +- .../sidebar/options/shapes/multiple.cljs | 5 +- .../sidebar/options/shapes/path.cljs | 5 +- .../sidebar/options/shapes/rect.cljs | 5 +- .../sidebar/options/shapes/svg_raw.cljs | 5 +- .../sidebar/options/shapes/text.cljs | 5 +- .../main/ui/workspace/sidebar/sitemap.cljs | 5 +- .../src/app/main/ui/workspace/viewport.cljs | 5 +- .../main/ui/workspace/viewport/actions.cljs | 5 +- .../main/ui/workspace/viewport/comments.cljs | 5 +- .../main/ui/workspace/viewport/drawarea.cljs | 2 +- .../ui/workspace/viewport/frame_grid.cljs | 5 +- .../main/ui/workspace/viewport/gradients.cljs | 5 +- .../app/main/ui/workspace/viewport/hooks.cljs | 5 +- .../ui/workspace/viewport/interactions.cljs | 5 +- .../main/ui/workspace/viewport/outline.cljs | 5 +- .../ui/workspace/viewport/pixel_overlay.cljs | 3 - .../main/ui/workspace/viewport/presence.cljs | 5 +- .../main/ui/workspace/viewport/selection.cljs | 5 +- .../ui/workspace/viewport/snap_distances.cljs | 5 +- .../ui/workspace/viewport/snap_points.cljs | 5 +- .../app/main/ui/workspace/viewport/utils.cljs | 5 +- .../main/ui/workspace/viewport/widgets.cljs | 5 +- frontend/src/app/main/worker.cljs | 5 +- frontend/src/app/util/array.cljs | 5 +- frontend/src/app/util/avatars.cljs | 5 +- frontend/src/app/util/browser_history.js | 5 +- frontend/src/app/util/code_gen.cljs | 5 +- frontend/src/app/util/color.cljs | 5 +- frontend/src/app/util/data.cljs | 2 +- frontend/src/app/util/dom.cljs | 5 +- frontend/src/app/util/dom/dnd.cljs | 3 +- frontend/src/app/util/forms.cljs | 5 +- frontend/src/app/util/geom/grid.cljs | 5 +- frontend/src/app/util/geom/path.cljs | 21 +- frontend/src/app/util/geom/snap_points.cljs | 5 +- frontend/src/app/util/globals.js | 5 +- frontend/src/app/util/http.cljs | 3 - frontend/src/app/util/i18n.cljs | 5 +- frontend/src/app/util/kdtree.cljs | 2 +- frontend/src/app/util/keyboard.cljs | 5 +- frontend/src/app/util/logging.cljs | 2 +- frontend/src/app/util/object.cljs | 5 +- frontend/src/app/util/perf.clj | 2 +- frontend/src/app/util/perf.cljs | 2 +- frontend/src/app/util/quadtree.js | 4 +- frontend/src/app/util/range_tree.js | 5 +- frontend/src/app/util/router.cljs | 5 +- frontend/src/app/util/storage.cljs | 2 +- frontend/src/app/util/svg.cljs | 5 +- frontend/src/app/util/text_editor.cljs | 5 +- frontend/src/app/util/text_editor_impl.js | 3 - frontend/src/app/util/theme.cljs | 5 +- frontend/src/app/util/time.cljs | 5 +- frontend/src/app/util/timers.cljs | 5 +- frontend/src/app/util/transit.cljs | 5 +- frontend/src/app/util/uri.cljs | 5 +- frontend/src/app/util/webapi.cljs | 3 - frontend/src/app/util/websockets.cljs | 5 +- frontend/src/app/util/worker.cljs | 5 +- frontend/src/app/util/zip.cljs | 2 +- frontend/src/app/worker.cljs | 5 +- frontend/src/app/worker/impl.cljs | 2 +- frontend/src/app/worker/selection.cljs | 5 +- frontend/src/app/worker/snaps.cljs | 5 +- frontend/src/app/worker/thumbnails.cljs | 5 +- manage.sh | 3 - 410 files changed, 385 insertions(+), 1867 deletions(-) delete mode 100644 backend/src/app/util/data.clj delete mode 100644 backend/src/app/util/dispatcher.clj delete mode 100644 backend/src/app/util/sql.clj diff --git a/README.md b/README.md index 5bc569307..29d90c1dd 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,5 @@ 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/. -This Source Code Form is "Incompatible With Secondary Licenses", as -defined by the Mozilla Public License, v. 2.0. - Copyright (c) UXBOX Labs SL ``` diff --git a/backend/dev/user.clj b/backend/dev/user.clj index b6816c3ad..fc3fe94bd 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2016-2020 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns user (:require diff --git a/backend/scripts/build b/backend/scripts/build index ec65f4ea3..f1d9e03a4 100755 --- a/backend/scripts/build +++ b/backend/scripts/build @@ -4,9 +4,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns build diff --git a/backend/src/app/cli/fixtures.clj b/backend/src/app/cli/fixtures.clj index 0f2ebd6bd..cd09769cb 100644 --- a/backend/src/app/cli/fixtures.clj +++ b/backend/src/app/cli/fixtures.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.cli.fixtures "A initial fixtures." diff --git a/backend/src/app/cli/manage.clj b/backend/src/app/cli/manage.clj index 3ee9ca76d..29975d0a9 100644 --- a/backend/src/app/cli/manage.clj +++ b/backend/src/app/cli/manage.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.cli.manage diff --git a/backend/src/app/cli/migrate_media.clj b/backend/src/app/cli/migrate_media.clj index 207feff99..0d627afdc 100644 --- a/backend/src/app/cli/migrate_media.clj +++ b/backend/src/app/cli/migrate_media.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.cli.migrate-media (:require diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index a65bbb7e8..3d73599f0 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.config diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index d1debe094..32a91e8d6 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.db diff --git a/backend/src/app/db/sql.clj b/backend/src/app/db/sql.clj index e82d8e077..d2c92db38 100644 --- a/backend/src/app/db/sql.clj +++ b/backend/src/app/db/sql.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.db.sql (:refer-clojure :exclude [update]) diff --git a/backend/src/app/emails.clj b/backend/src/app/emails.clj index 5d832bc47..9033ca714 100644 --- a/backend/src/app/emails.clj +++ b/backend/src/app/emails.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.emails diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index daf59fa2f..63263c4f0 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.http diff --git a/backend/src/app/http/assets.clj b/backend/src/app/http/assets.clj index d5eb45994..e6e815c21 100644 --- a/backend/src/app/http/assets.clj +++ b/backend/src/app/http/assets.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.assets "Assets related handlers." diff --git a/backend/src/app/http/awsns.clj b/backend/src/app/http/awsns.clj index 77ebf4c6f..d5c29a979 100644 --- a/backend/src/app/http/awsns.clj +++ b/backend/src/app/http/awsns.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.http.awsns "AWS SNS webhook handler for bounces." diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index 76ca5e638..d2e45ed01 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.errors "A errors handling for the http server." diff --git a/backend/src/app/http/feedback.clj b/backend/src/app/http/feedback.clj index 1ab206435..dd78cfd57 100644 --- a/backend/src/app/http/feedback.clj +++ b/backend/src/app/http/feedback.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.feedback "A general purpose feedback module." diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index c117a9a8e..16d7429ed 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.middleware (:require diff --git a/backend/src/app/http/oauth/github.clj b/backend/src/app/http/oauth/github.clj index 635af416b..c48d9b401 100644 --- a/backend/src/app/http/oauth/github.clj +++ b/backend/src/app/http/oauth/github.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.http.oauth.github diff --git a/backend/src/app/http/oauth/gitlab.clj b/backend/src/app/http/oauth/gitlab.clj index b3d04567e..58c81396f 100644 --- a/backend/src/app/http/oauth/gitlab.clj +++ b/backend/src/app/http/oauth/gitlab.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.oauth.gitlab (:require diff --git a/backend/src/app/http/oauth/google.clj b/backend/src/app/http/oauth/google.clj index aea6e6639..b671079a6 100644 --- a/backend/src/app/http/oauth/google.clj +++ b/backend/src/app/http/oauth/google.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.oauth.google (:require diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index 1c0dca885..2f071089d 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.session (:require diff --git a/backend/src/app/loggers/loki.clj b/backend/src/app/loggers/loki.clj index bc97b1ad6..607f06e3b 100644 --- a/backend/src/app/loggers/loki.clj +++ b/backend/src/app/loggers/loki.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.loggers.loki "A Loki integration." diff --git a/backend/src/app/loggers/mattermost.clj b/backend/src/app/loggers/mattermost.clj index 143d8d77c..6b2c7dc8c 100644 --- a/backend/src/app/loggers/mattermost.clj +++ b/backend/src/app/loggers/mattermost.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.loggers.mattermost "A mattermost integration for error reporting." diff --git a/backend/src/app/loggers/zmq.clj b/backend/src/app/loggers/zmq.clj index b03c3c77c..1d069f7dd 100644 --- a/backend/src/app/loggers/zmq.clj +++ b/backend/src/app/loggers/zmq.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.loggers.zmq "A generic ZMQ listener." diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 7f0639d29..f39d85083 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main diff --git a/backend/src/app/media.clj b/backend/src/app/media.clj index 1886ac626..c811aed66 100644 --- a/backend/src/app/media.clj +++ b/backend/src/app/media.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.media "Media postprocessing." diff --git a/backend/src/app/metrics.clj b/backend/src/app/metrics.clj index bf1daef4b..52567eb82 100644 --- a/backend/src/app/metrics.clj +++ b/backend/src/app/metrics.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.metrics diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index 3f9f032e4..b3c579196 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.migrations (:require diff --git a/backend/src/app/migrations/migration_0023.clj b/backend/src/app/migrations/migration_0023.clj index b5ce1d3b8..6f66a7998 100644 --- a/backend/src/app/migrations/migration_0023.clj +++ b/backend/src/app/migrations/migration_0023.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.migrations.migration-0023 (:require diff --git a/backend/src/app/msgbus.clj b/backend/src/app/msgbus.clj index e8da9a350..8058c6577 100644 --- a/backend/src/app/msgbus.clj +++ b/backend/src/app/msgbus.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.msgbus "The msgbus abstraction implemented using redis as underlying backend." diff --git a/backend/src/app/notifications.clj b/backend/src/app/notifications.clj index a045f153b..75b7de0c6 100644 --- a/backend/src/app/notifications.clj +++ b/backend/src/app/notifications.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.notifications "A websocket based notifications mechanism." diff --git a/backend/src/app/rlimits.clj b/backend/src/app/rlimits.clj index 703fae507..86a3903db 100644 --- a/backend/src/app/rlimits.clj +++ b/backend/src/app/rlimits.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rlimits "Resource usage limits (in other words: semaphores)." diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index ab16c0c37..2a29749b8 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.rpc diff --git a/backend/src/app/rpc/mutations/comments.clj b/backend/src/app/rpc/mutations/comments.clj index 3008b8733..033c31ce2 100644 --- a/backend/src/app/rpc/mutations/comments.clj +++ b/backend/src/app/rpc/mutations/comments.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.comments (:require diff --git a/backend/src/app/rpc/mutations/demo.clj b/backend/src/app/rpc/mutations/demo.clj index 4f78184bf..2f0c583c2 100644 --- a/backend/src/app/rpc/mutations/demo.clj +++ b/backend/src/app/rpc/mutations/demo.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.demo diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index fb547bfa9..493be817b 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.files diff --git a/backend/src/app/rpc/mutations/ldap.clj b/backend/src/app/rpc/mutations/ldap.clj index 2799f9d66..8b5f93ff0 100644 --- a/backend/src/app/rpc/mutations/ldap.clj +++ b/backend/src/app/rpc/mutations/ldap.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.ldap (:require diff --git a/backend/src/app/rpc/mutations/management.clj b/backend/src/app/rpc/mutations/management.clj index ee885d0aa..177d02964 100644 --- a/backend/src/app/rpc/mutations/management.clj +++ b/backend/src/app/rpc/mutations/management.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.management "Move & Duplicate RPC methods for files and projects." diff --git a/backend/src/app/rpc/mutations/media.clj b/backend/src/app/rpc/mutations/media.clj index 2ae6ef4dc..26dbac324 100644 --- a/backend/src/app/rpc/mutations/media.clj +++ b/backend/src/app/rpc/mutations/media.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.media (:require diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index 57e79e402..e056ef840 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.profile (:require diff --git a/backend/src/app/rpc/mutations/projects.clj b/backend/src/app/rpc/mutations/projects.clj index 1a03fd34d..bc25ef8ff 100644 --- a/backend/src/app/rpc/mutations/projects.clj +++ b/backend/src/app/rpc/mutations/projects.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.projects (:require diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index 46de58083..701833162 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.teams diff --git a/backend/src/app/rpc/mutations/verify_token.clj b/backend/src/app/rpc/mutations/verify_token.clj index 4c7938e85..e294cfb73 100644 --- a/backend/src/app/rpc/mutations/verify_token.clj +++ b/backend/src/app/rpc/mutations/verify_token.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.verify-token (:require diff --git a/backend/src/app/rpc/mutations/viewer.clj b/backend/src/app/rpc/mutations/viewer.clj index 85d3aa245..85beafa96 100644 --- a/backend/src/app/rpc/mutations/viewer.clj +++ b/backend/src/app/rpc/mutations/viewer.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.mutations.viewer (:require diff --git a/backend/src/app/rpc/permissions.clj b/backend/src/app/rpc/permissions.clj index 9448f71ff..221b88363 100644 --- a/backend/src/app/rpc/permissions.clj +++ b/backend/src/app/rpc/permissions.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.permissions "A permission checking helper factories." diff --git a/backend/src/app/rpc/queries/comments.clj b/backend/src/app/rpc/queries/comments.clj index 56a3a8f19..2a5191043 100644 --- a/backend/src/app/rpc/queries/comments.clj +++ b/backend/src/app/rpc/queries/comments.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.queries.comments (:require diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index b96903ddd..9f0a0b3ff 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.queries.files (:require diff --git a/backend/src/app/rpc/queries/profile.clj b/backend/src/app/rpc/queries/profile.clj index 29fb1124b..8016b3d94 100644 --- a/backend/src/app/rpc/queries/profile.clj +++ b/backend/src/app/rpc/queries/profile.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.queries.profile (:require diff --git a/backend/src/app/rpc/queries/projects.clj b/backend/src/app/rpc/queries/projects.clj index be4697d44..29195ceb0 100644 --- a/backend/src/app/rpc/queries/projects.clj +++ b/backend/src/app/rpc/queries/projects.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.queries.projects (:require diff --git a/backend/src/app/rpc/queries/recent_files.clj b/backend/src/app/rpc/queries/recent_files.clj index e4f9b6b1f..51c1bbe3f 100644 --- a/backend/src/app/rpc/queries/recent_files.clj +++ b/backend/src/app/rpc/queries/recent_files.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.queries.recent-files (:require diff --git a/backend/src/app/rpc/queries/teams.clj b/backend/src/app/rpc/queries/teams.clj index a414f8d7a..7bad8ba59 100644 --- a/backend/src/app/rpc/queries/teams.clj +++ b/backend/src/app/rpc/queries/teams.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.queries.teams (:require diff --git a/backend/src/app/rpc/queries/viewer.clj b/backend/src/app/rpc/queries/viewer.clj index 4cb957f20..3ed2bbbc5 100644 --- a/backend/src/app/rpc/queries/viewer.clj +++ b/backend/src/app/rpc/queries/viewer.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.rpc.queries.viewer (:require diff --git a/backend/src/app/setup.clj b/backend/src/app/setup.clj index 21853d79b..ec2c10a96 100644 --- a/backend/src/app/setup.clj +++ b/backend/src/app/setup.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.setup "Initial data setup of instance." diff --git a/backend/src/app/setup/initial_data.clj b/backend/src/app/setup/initial_data.clj index 6c8499697..5514e7103 100644 --- a/backend/src/app/setup/initial_data.clj +++ b/backend/src/app/setup/initial_data.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.setup.initial-data (:refer-clojure :exclude [load]) diff --git a/backend/src/app/srepl.clj b/backend/src/app/srepl.clj index 5fa1314d3..71ef20a6d 100644 --- a/backend/src/app/srepl.clj +++ b/backend/src/app/srepl.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.srepl "Server Repl." diff --git a/backend/src/app/storage.clj b/backend/src/app/storage.clj index e8621ac8b..b67c2b215 100644 --- a/backend/src/app/storage.clj +++ b/backend/src/app/storage.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.storage "File Storage abstraction layer." diff --git a/backend/src/app/storage/db.clj b/backend/src/app/storage/db.clj index 9dee0b59c..a7ed7adbc 100644 --- a/backend/src/app/storage/db.clj +++ b/backend/src/app/storage/db.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.storage.db (:require diff --git a/backend/src/app/storage/fs.clj b/backend/src/app/storage/fs.clj index 9a0e52b4c..88ae8510b 100644 --- a/backend/src/app/storage/fs.clj +++ b/backend/src/app/storage/fs.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.storage.fs (:require diff --git a/backend/src/app/storage/impl.clj b/backend/src/app/storage/impl.clj index 00f356f56..27a44cbed 100644 --- a/backend/src/app/storage/impl.clj +++ b/backend/src/app/storage/impl.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.storage.impl "Storage backends abstraction layer." diff --git a/backend/src/app/storage/s3.clj b/backend/src/app/storage/s3.clj index 7f0499138..d3adcff0f 100644 --- a/backend/src/app/storage/s3.clj +++ b/backend/src/app/storage/s3.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.storage.s3 "Storage backends abstraction layer." diff --git a/backend/src/app/svgparse.clj b/backend/src/app/svgparse.clj index 5a773d28c..ad7847f59 100644 --- a/backend/src/app/svgparse.clj +++ b/backend/src/app/svgparse.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.svgparse diff --git a/backend/src/app/tasks/delete_object.clj b/backend/src/app/tasks/delete_object.clj index 91fa1f702..bcd6e4a48 100644 --- a/backend/src/app/tasks/delete_object.clj +++ b/backend/src/app/tasks/delete_object.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.tasks.delete-object diff --git a/backend/src/app/tasks/delete_profile.clj b/backend/src/app/tasks/delete_profile.clj index 5c05a1d8a..17e2facb4 100644 --- a/backend/src/app/tasks/delete_profile.clj +++ b/backend/src/app/tasks/delete_profile.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tasks.delete-profile "Task for permanent deletion of profiles." diff --git a/backend/src/app/tasks/file_media_gc.clj b/backend/src/app/tasks/file_media_gc.clj index cad9b1e02..d1bdc6751 100644 --- a/backend/src/app/tasks/file_media_gc.clj +++ b/backend/src/app/tasks/file_media_gc.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tasks.file-media-gc "A maintenance task that is responsible to purge the unused media diff --git a/backend/src/app/tasks/file_xlog_gc.clj b/backend/src/app/tasks/file_xlog_gc.clj index adff21a55..36fb71cf3 100644 --- a/backend/src/app/tasks/file_xlog_gc.clj +++ b/backend/src/app/tasks/file_xlog_gc.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.tasks.file-xlog-gc diff --git a/backend/src/app/tasks/tasks_gc.clj b/backend/src/app/tasks/tasks_gc.clj index 3dc19353a..a3560f0e1 100644 --- a/backend/src/app/tasks/tasks_gc.clj +++ b/backend/src/app/tasks/tasks_gc.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.tasks.tasks-gc diff --git a/backend/src/app/tasks/telemetry.clj b/backend/src/app/tasks/telemetry.clj index 4de76ce0f..564021c3e 100644 --- a/backend/src/app/tasks/telemetry.clj +++ b/backend/src/app/tasks/telemetry.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.tasks.telemetry diff --git a/backend/src/app/tokens.clj b/backend/src/app/tokens.clj index 4abbca855..ecf7c84a4 100644 --- a/backend/src/app/tokens.clj +++ b/backend/src/app/tokens.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tokens "Tokens generation service." diff --git a/backend/src/app/util/async.clj b/backend/src/app/util/async.clj index c78ab70e2..fb17e6a7e 100644 --- a/backend/src/app/util/async.clj +++ b/backend/src/app/util/async.clj @@ -2,7 +2,7 @@ ;; 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) 2020 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.util.async (:require diff --git a/backend/src/app/util/blob.clj b/backend/src/app/util/blob.clj index b3ecf3249..d70d12ab0 100644 --- a/backend/src/app/util/blob.clj +++ b/backend/src/app/util/blob.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.util.blob diff --git a/backend/src/app/util/closeable.clj b/backend/src/app/util/closeable.clj index 78da0565f..2cad6d172 100644 --- a/backend/src/app/util/closeable.clj +++ b/backend/src/app/util/closeable.clj @@ -2,7 +2,7 @@ ;; 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) 2016 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.util.closeable "A closeable abstraction. A drop in replacement for diff --git a/backend/src/app/util/data.clj b/backend/src/app/util/data.clj deleted file mode 100644 index 32bd107ee..000000000 --- a/backend/src/app/util/data.clj +++ /dev/null @@ -1,54 +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) 2016 Andrey Antukh - -(ns app.util.data - "Data transformations utils." - (:require [clojure.walk :as walk] - [cuerdas.core :as str])) - -;; TODO: move to app.common.helpers - -(defn dissoc-in - [m [k & ks]] - (if ks - (if-let [nextmap (get m k)] - (let [newmap (dissoc-in nextmap ks)] - (if (seq newmap) - (assoc m k newmap) - (dissoc m k))) - m) - (dissoc m k))) - -(defn normalize-attrs - "Recursively transforms all map keys from strings to keywords." - [m] - (letfn [(tf [[k v]] - (let [ks (-> (name k) - (str/replace "_" "-"))] - [(keyword ks) v])) - (walker [x] - (if (map? x) - (into {} (map tf) x) - x))] - (walk/postwalk walker m))) - -(defn strip-delete-attrs - [m] - (dissoc m :deleted-at)) - -(defn normalize - "Perform a common normalization transformation - for a entity (database retrieved) data structure." - [m] - (-> m normalize-attrs strip-delete-attrs)) - -(defn deep-merge - [& maps] - (letfn [(merge' [& maps] - (if (every? map? maps) - (apply merge-with merge' maps) - (last maps)))] - (apply merge' (remove nil? maps)))) diff --git a/backend/src/app/util/dispatcher.clj b/backend/src/app/util/dispatcher.clj deleted file mode 100644 index e86ae408c..000000000 --- a/backend/src/app/util/dispatcher.clj +++ /dev/null @@ -1,95 +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) 2019 Andrey Antukh - -(ns app.util.dispatcher - "A generic service dispatcher implementation." - (:refer-clojure :exclude [defmethod]) - (:require - [app.common.exceptions :as ex] - [clojure.spec.alpha :as s]) - (:import - java.util.HashMap - java.util.Map)) - -(definterface IDispatcher - (^void add [key f])) - -(deftype Dispatcher [reg attr wrap] - IDispatcher - (add [this key f] - (.put ^Map reg key (wrap f)) - this) - - - clojure.lang.IDeref - (deref [_] - {:registry reg - :attr attr - :wrap wrap}) - - clojure.lang.IFn - (invoke [_ params] - (let [key (get params attr) - f (.get ^Map reg key)] - (when (nil? f) - (ex/raise :type :method-not-found - :hint "No method found for the current request." - :context {:key key})) - (f params)))) - -(defn dispatcher? - [v] - (instance? IDispatcher v)) - -(defmacro defservice - [sname & {:keys [dispatch-by wrap]}] - `(def ~sname (Dispatcher. (HashMap.) ~dispatch-by ~wrap))) - -(defn parse-defmethod - [args] - (loop [r {} - s 0 - v (first args) - n (rest args)] - (case s - 0 (if (symbol? v) - (recur (assoc r :sym v) 1 (first n) (rest n)) - (throw (ex-info "first arg to `defmethod` should be a symbol" {}))) - 1 (if (qualified-keyword? v) - (recur (-> r - (assoc :key (keyword (name v))) - (assoc :meta {:spec v :doc nil})) - 3 (first n) (rest n)) - (recur r (inc s) v n)) - 2 (if (simple-keyword? v) - (recur (-> r - (assoc :key v) - (assoc :meta {:doc nil})) - 3 (first n) (rest n)) - (throw (ex-info "second arg to `defmethod` should be a keyword" {}))) - 3 (if (string? v) - (recur (update r :meta assoc :doc v) (inc s) (first n) (rest n)) - (recur r 4 v n)) - 4 (if (map? v) - (recur (update r :meta merge v) (inc s) (first n) (rest n)) - (recur r 5 v n)) - 5 (if (vector? v) - (assoc r :args v :body n) - (throw (ex-info "missing arguments vector" {})))))) - -(defn add-method - [^Dispatcher dsp key f meta] - (let [f (with-meta f meta)] - (.add dsp key f) - dsp)) - -(defmacro defmethod - [& args] - (let [{:keys [key meta sym args body]} (parse-defmethod args) - f `(fn ~args ~@body)] - `(do - (s/assert dispatcher? ~sym) - (add-method ~sym ~key ~f ~meta)))) diff --git a/backend/src/app/util/emails.clj b/backend/src/app/util/emails.clj index a2111d6f8..948ebe437 100644 --- a/backend/src/app/util/emails.clj +++ b/backend/src/app/util/emails.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.emails (:require diff --git a/backend/src/app/util/http.clj b/backend/src/app/util/http.clj index 068a03bb2..9fa6b9086 100644 --- a/backend/src/app/util/http.clj +++ b/backend/src/app/util/http.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.http "Http client abstraction layer." diff --git a/backend/src/app/util/json.clj b/backend/src/app/util/json.clj index 042517c62..0ffd859d1 100644 --- a/backend/src/app/util/json.clj +++ b/backend/src/app/util/json.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.json (:refer-clojure :exclude [read]) diff --git a/backend/src/app/util/logging.clj b/backend/src/app/util/logging.clj index 0f5fc5bb5..5aaa409a8 100644 --- a/backend/src/app/util/logging.clj +++ b/backend/src/app/util/logging.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.util.logging diff --git a/backend/src/app/util/migrations.clj b/backend/src/app/util/migrations.clj index aa6bbf43c..f79c50b2b 100644 --- a/backend/src/app/util/migrations.clj +++ b/backend/src/app/util/migrations.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.util.migrations diff --git a/backend/src/app/util/services.clj b/backend/src/app/util/services.clj index edc8c1074..1621ad760 100644 --- a/backend/src/app/util/services.clj +++ b/backend/src/app/util/services.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.util.services "A helpers and macros for define rpc like registry based services." diff --git a/backend/src/app/util/sql.clj b/backend/src/app/util/sql.clj deleted file mode 100644 index 61274db15..000000000 --- a/backend/src/app/util/sql.clj +++ /dev/null @@ -1,198 +0,0 @@ -;; Copyright (c) 2019 Andrey Antukh -;; All rights reserved. -;; -;; Redistribution and use in source and binary forms, with or without -;; modification, are permitted provided that the following conditions are met: -;; -;; * Redistributions of source code must retain the above copyright notice, this -;; list of conditions and the following disclaimer. -;; -;; * Redistributions in binary form must reproduce the above copyright notice, -;; this list of conditions and the following disclaimer in the documentation -;; and/or other materials provided with the distribution. -;; -;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -(ns app.util.sql - "A composable sql helpers." - (:refer-clojure :exclude [test update set format]) - (:require [clojure.core :as c] - [cuerdas.core :as str])) - -;; --- Low Level Helpers - -(defn raw-expr - [m] - (cond - (string? m) - {::type :raw-expr - :sql m - :params []} - - (vector? m) - {::type :raw-expr - :sql (first m) - :params (vec (rest m))} - - (and (map? m) - (= :raw-expr (::type m))) - m - - :else - (throw (ex-info "unexpected input" {:m m})))) - -(defn alias-expr - [m] - (cond - (string? m) - {::type :alias-expr - :sql m - :alias nil - :params []} - - (vector? m) - {::type :alias-expr - :sql (first m) - :alias (second m) - :params (vec (drop 2 m))} - - :else - (throw (ex-info "unexpected input" {:m m})))) - -;; --- SQL API (Select only) - -(defn from - [name] - {::type :query - ::from [(alias-expr name)] - ::order [] - ::select [] - ::join [] - ::where []}) - -(defn select - [m & fields] - (c/update m ::select into (map alias-expr fields))) - -(defn limit - [m n] - (assoc m ::limit [(raw-expr ["LIMIT ?" n])])) - -(defn offset - [m n] - (assoc m ::offset [(raw-expr ["OFFSET ?" n])])) - -(defn order - [m e] - (c/update m ::order conj (raw-expr e))) - -(defn- join* - [m type table condition] - (c/update m ::join conj - {::type :join-expr - :type type - :table (alias-expr table) - :condition (raw-expr condition)})) - -(defn join - [m table condition] - (join* m :inner table condition)) - -(defn ljoin - [m table condition] - (join* m :left table condition)) - -(defn rjoin - [m table condition] - (join* m :right table condition)) - -(defn where - [m & conditions] - (->> (filter identity conditions) - (reduce #(c/update %1 ::where conj (raw-expr %2)) m))) - -;; --- Formating - -(defmulti format-expr ::type) - -(defmethod format-expr :raw-expr - [{:keys [sql params]}] - [sql params]) - -(defmethod format-expr :alias-expr - [{:keys [sql alias params]}] - (if alias - [(str sql " AS " alias) params] - [sql params])) - -(defmethod format-expr :join-expr - [{:keys [table type condition]}] - (let [[csql cparams] (format-expr condition) - [tsql tparams] (format-expr table) - prefix (str/upper (name type))] - [(str prefix " JOIN " tsql " ON (" csql ")") (into cparams tparams)])) - -(defn- format-exprs - ([items] (format-exprs items {})) - ([items {:keys [prefix suffix join-with] - :or {prefix "" - suffix "" - join-with ","}}] - (loop [rs [] - rp [] - v (first items) - n (rest items)] - (if v - (let [[s p] (format-expr v)] - (recur (conj rs s) - (into rp p) - (first n) - (rest n))) - (if (empty? rs) - ["" []] - [(str prefix (str/join join-with rs) suffix) rp]))))) - -(defn- process-param-tokens - [sql] - (let [cnt (java.util.concurrent.atomic.AtomicInteger. 1)] - (str/replace sql #"\?" (fn [& _args] - (str "$" (.getAndIncrement cnt)))))) - -(def ^:private select-formatters - [#(format-exprs (::select %) {:prefix "SELECT "}) - #(format-exprs (::from %) {:prefix "FROM "}) - #(format-exprs (::join %) {:join-with " "}) - #(format-exprs (::where %) {:prefix "WHERE (" - :join-with ") AND (" - :suffix ")"}) - #(format-exprs (::order %) {:prefix "ORDER BY "} ) - #(format-exprs (::limit %)) - #(format-exprs (::offset %))]) - -(defn- collect - [formatters qdata] - (loop [sqls [] - params [] - f (first formatters) - r (rest formatters)] - (if (fn? f) - (let [[s p] (f qdata)] - (recur (conj sqls s) - (into params p) - (first r) - (rest r))) - [(str/join " " sqls) params]))) - -(defn fmt - [qdata] - (let [[sql params] (collect select-formatters qdata)] - (into [(process-param-tokens sql)] params))) diff --git a/backend/src/app/util/template.clj b/backend/src/app/util/template.clj index cd5e00f0a..2aa8c324d 100644 --- a/backend/src/app/util/template.clj +++ b/backend/src/app/util/template.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.template (:require diff --git a/backend/src/app/util/time.clj b/backend/src/app/util/time.clj index febc0f8de..c2839f483 100644 --- a/backend/src/app/util/time.clj +++ b/backend/src/app/util/time.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.time (:require diff --git a/backend/src/app/util/transit.clj b/backend/src/app/util/transit.clj index 7347eddb1..e200f5e69 100644 --- a/backend/src/app/util/transit.clj +++ b/backend/src/app/util/transit.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.transit (:require diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index f2b0a30ef..e33a11464 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.worker diff --git a/backend/tests/app/tests/helpers.clj b/backend/tests/app/tests/helpers.clj index ed0e1b31c..bf6de1ff1 100644 --- a/backend/tests/app/tests/helpers.clj +++ b/backend/tests/app/tests/helpers.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.helpers (:require diff --git a/backend/tests/app/tests/test_bounces_handling.clj b/backend/tests/app/tests/test_bounces_handling.clj index 3750565bb..57e838af5 100644 --- a/backend/tests/app/tests/test_bounces_handling.clj +++ b/backend/tests/app/tests/test_bounces_handling.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-bounces-handling (:require diff --git a/backend/tests/app/tests/test_common_geom.clj b/backend/tests/app/tests/test_common_geom.clj index 4c7acd75c..4656f716c 100644 --- a/backend/tests/app/tests/test_common_geom.clj +++ b/backend/tests/app/tests/test_common_geom.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-common-geom (:require diff --git a/backend/tests/app/tests/test_common_geom_shapes.clj b/backend/tests/app/tests/test_common_geom_shapes.clj index 860f05ea4..b53d3ebc5 100644 --- a/backend/tests/app/tests/test_common_geom_shapes.clj +++ b/backend/tests/app/tests/test_common_geom_shapes.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-common-geom-shapes (:require diff --git a/backend/tests/app/tests/test_common_pages.clj b/backend/tests/app/tests/test_common_pages.clj index f6b84cc67..80e3ea7e2 100644 --- a/backend/tests/app/tests/test_common_pages.clj +++ b/backend/tests/app/tests/test_common_pages.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-common-pages (:require diff --git a/backend/tests/app/tests/test_emails.clj b/backend/tests/app/tests/test_emails.clj index 7381f510a..7cc62966e 100644 --- a/backend/tests/app/tests/test_emails.clj +++ b/backend/tests/app/tests/test_emails.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-emails (:require diff --git a/backend/tests/app/tests/test_services_files.clj b/backend/tests/app/tests/test_services_files.clj index 4de3d8c5f..68f34eacb 100644 --- a/backend/tests/app/tests/test_services_files.clj +++ b/backend/tests/app/tests/test_services_files.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-services-files (:require diff --git a/backend/tests/app/tests/test_services_management.clj b/backend/tests/app/tests/test_services_management.clj index fd0d58233..f662b4381 100644 --- a/backend/tests/app/tests/test_services_management.clj +++ b/backend/tests/app/tests/test_services_management.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-services-management (:require diff --git a/backend/tests/app/tests/test_services_media.clj b/backend/tests/app/tests/test_services_media.clj index 0385e4719..112eed779 100644 --- a/backend/tests/app/tests/test_services_media.clj +++ b/backend/tests/app/tests/test_services_media.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-services-media (:require diff --git a/backend/tests/app/tests/test_services_profile.clj b/backend/tests/app/tests/test_services_profile.clj index 4b164d64f..7ac20c849 100644 --- a/backend/tests/app/tests/test_services_profile.clj +++ b/backend/tests/app/tests/test_services_profile.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-services-profile (:require diff --git a/backend/tests/app/tests/test_services_projects.clj b/backend/tests/app/tests/test_services_projects.clj index 0d4fabf8d..5c791fe74 100644 --- a/backend/tests/app/tests/test_services_projects.clj +++ b/backend/tests/app/tests/test_services_projects.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-services-projects (:require diff --git a/backend/tests/app/tests/test_services_teams.clj b/backend/tests/app/tests/test_services_teams.clj index da6ddb688..c64f7922d 100644 --- a/backend/tests/app/tests/test_services_teams.clj +++ b/backend/tests/app/tests/test_services_teams.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-services-teams (:require diff --git a/backend/tests/app/tests/test_services_viewer.clj b/backend/tests/app/tests/test_services_viewer.clj index 54d2ce93c..ce638a19e 100644 --- a/backend/tests/app/tests/test_services_viewer.clj +++ b/backend/tests/app/tests/test_services_viewer.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-services-viewer (:require diff --git a/backend/tests/app/tests/test_storage.clj b/backend/tests/app/tests/test_storage.clj index f967c491d..7c0f49e8f 100644 --- a/backend/tests/app/tests/test_storage.clj +++ b/backend/tests/app/tests/test_storage.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.tests.test-storage (:require diff --git a/common/app/common/attrs.cljc b/common/app/common/attrs.cljc index f341a9adb..ce6af9131 100644 --- a/common/app/common/attrs.cljc +++ b/common/app/common/attrs.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.attrs (:refer-clojure :exclude [merge])) diff --git a/common/app/common/data.cljc b/common/app/common/data.cljc index 8b399f2c3..a46b306f2 100644 --- a/common/app/common/data.cljc +++ b/common/app/common/data.cljc @@ -2,7 +2,7 @@ ;; 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) 2016-2019 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.common.data "Data manipulation and query helper functions." diff --git a/common/app/common/exceptions.cljc b/common/app/common/exceptions.cljc index 240194489..4fe202efd 100644 --- a/common/app/common/exceptions.cljc +++ b/common/app/common/exceptions.cljc @@ -2,7 +2,7 @@ ;; 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) Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.common.exceptions "A helpers for work with exceptions." diff --git a/common/app/common/geom/align.cljc b/common/app/common/geom/align.cljc index f06bcb4bd..4a14e4b1e 100644 --- a/common/app/common/geom/align.cljc +++ b/common/app/common/geom/align.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.geom.align (:require diff --git a/common/app/common/geom/matrix.cljc b/common/app/common/geom/matrix.cljc index 8b9c9a59d..04aa8651d 100644 --- a/common/app/common/geom/matrix.cljc +++ b/common/app/common/geom/matrix.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.geom.matrix (:require diff --git a/common/app/common/geom/point.cljc b/common/app/common/geom/point.cljc index 11fff0f4b..0124b142d 100644 --- a/common/app/common/geom/point.cljc +++ b/common/app/common/geom/point.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.geom.point (:refer-clojure :exclude [divide min max]) diff --git a/common/app/common/geom/proportions.cljc b/common/app/common/geom/proportions.cljc index 15e6bfffd..0fd8dccc3 100644 --- a/common/app/common/geom/proportions.cljc +++ b/common/app/common/geom/proportions.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.geom.proportions) diff --git a/common/app/common/geom/shapes.cljc b/common/app/common/geom/shapes.cljc index a71e0792a..de6826249 100644 --- a/common/app/common/geom/shapes.cljc +++ b/common/app/common/geom/shapes.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.geom.shapes (:require diff --git a/common/app/common/geom/shapes/common.cljc b/common/app/common/geom/shapes/common.cljc index 07b17ad2b..9b5c6d1b3 100644 --- a/common/app/common/geom/shapes/common.cljc +++ b/common/app/common/geom/shapes/common.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.geom.shapes.common (:require diff --git a/common/app/common/geom/shapes/intersect.cljc b/common/app/common/geom/shapes/intersect.cljc index 72ed180fb..6b9e3f5be 100644 --- a/common/app/common/geom/shapes/intersect.cljc +++ b/common/app/common/geom/shapes/intersect.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.geom.shapes.intersect (:require diff --git a/common/app/common/geom/shapes/path.cljc b/common/app/common/geom/shapes/path.cljc index f38467b33..2072ade31 100644 --- a/common/app/common/geom/shapes/path.cljc +++ b/common/app/common/geom/shapes/path.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.geom.shapes.path (:require diff --git a/common/app/common/geom/shapes/rect.cljc b/common/app/common/geom/shapes/rect.cljc index 80e06ef11..be221b1d9 100644 --- a/common/app/common/geom/shapes/rect.cljc +++ b/common/app/common/geom/shapes/rect.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.geom.shapes.rect (:require diff --git a/common/app/common/geom/shapes/transforms.cljc b/common/app/common/geom/shapes/transforms.cljc index 3162d233d..2f80ffb95 100644 --- a/common/app/common/geom/shapes/transforms.cljc +++ b/common/app/common/geom/shapes/transforms.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.geom.shapes.transforms (:require diff --git a/common/app/common/math.cljc b/common/app/common/math.cljc index e676c3f34..c81db2a39 100644 --- a/common/app/common/math.cljc +++ b/common/app/common/math.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.math "A collection of math utils." diff --git a/common/app/common/media.cljc b/common/app/common/media.cljc index ca3356751..df3a556ca 100644 --- a/common/app/common/media.cljc +++ b/common/app/common/media.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.media (:require diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 3ed7a4485..185a4fcd0 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.pages "A common (clj/cljs) functions and specs for pages." diff --git a/common/app/common/pages/changes.cljc b/common/app/common/pages/changes.cljc index 7f1da9a45..458b02eaa 100644 --- a/common/app/common/pages/changes.cljc +++ b/common/app/common/pages/changes.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.pages.changes (:require diff --git a/common/app/common/pages/common.cljc b/common/app/common/pages/common.cljc index 784896e3b..db1e9341c 100644 --- a/common/app/common/pages/common.cljc +++ b/common/app/common/pages/common.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.pages.common (:require diff --git a/common/app/common/pages/helpers.cljc b/common/app/common/pages/helpers.cljc index 2a5ba99aa..abaae925e 100644 --- a/common/app/common/pages/helpers.cljc +++ b/common/app/common/pages/helpers.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.pages.helpers (:require diff --git a/common/app/common/pages/init.cljc b/common/app/common/pages/init.cljc index 38267aaf5..79e0b50de 100644 --- a/common/app/common/pages/init.cljc +++ b/common/app/common/pages/init.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.pages.init (:require diff --git a/common/app/common/pages/migrations.cljc b/common/app/common/pages/migrations.cljc index 28aa8682e..588da5d95 100644 --- a/common/app/common/pages/migrations.cljc +++ b/common/app/common/pages/migrations.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.pages.migrations (:require diff --git a/common/app/common/pages/spec.cljc b/common/app/common/pages/spec.cljc index d99f44ef4..274d6f32e 100644 --- a/common/app/common/pages/spec.cljc +++ b/common/app/common/pages/spec.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.pages.spec (:require diff --git a/common/app/common/spec.cljc b/common/app/common/spec.cljc index 907395b40..56ede4ed5 100644 --- a/common/app/common/spec.cljc +++ b/common/app/common/spec.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.spec "Data manipulation and query helper functions." diff --git a/common/app/common/text.cljc b/common/app/common/text.cljc index d8123c039..a2864a79e 100644 --- a/common/app/common/text.cljc +++ b/common/app/common/text.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.text (:require diff --git a/common/app/common/uuid.cljc b/common/app/common/uuid.cljc index 3dad2eb0e..0cc0106df 100644 --- a/common/app/common/uuid.cljc +++ b/common/app/common/uuid.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.common.uuid (:refer-clojure :exclude [next uuid zero?]) diff --git a/common/app/common/uuid_impl.js b/common/app/common/uuid_impl.js index 40c944f10..d276ce516 100644 --- a/common/app/common/uuid_impl.js +++ b/common/app/common/uuid_impl.js @@ -3,10 +3,7 @@ * 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/. * - * This Source Code Form is "Incompatible With Secondary Licenses", as - * defined by the Mozilla Public License, v. 2.0. - * - * Copyright (c) 2020 UXBOX Labs SL + * Copyright (c) UXBOX Labs SL */ "use strict"; diff --git a/common/app/common/version.cljc b/common/app/common/version.cljc index b3dd591a0..aafad2d38 100644 --- a/common/app/common/version.cljc +++ b/common/app/common/version.cljc @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.common.version "A version parsing helper." diff --git a/exporter/src/app/browser.cljs b/exporter/src/app/browser.cljs index 5fb1bdefe..374464420 100644 --- a/exporter/src/app/browser.cljs +++ b/exporter/src/app/browser.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.browser (:require diff --git a/exporter/src/app/config.cljs b/exporter/src/app/config.cljs index 993c9555c..e9b83c23b 100644 --- a/exporter/src/app/config.cljs +++ b/exporter/src/app/config.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.config (:require diff --git a/exporter/src/app/core.cljs b/exporter/src/app/core.cljs index 2a20b0f67..6bc699476 100644 --- a/exporter/src/app/core.cljs +++ b/exporter/src/app/core.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.core (:require diff --git a/exporter/src/app/http.cljs b/exporter/src/app/http.cljs index cc63a7d53..d0d32ebf8 100644 --- a/exporter/src/app/http.cljs +++ b/exporter/src/app/http.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http (:require diff --git a/exporter/src/app/http/export.cljs b/exporter/src/app/http/export.cljs index a3ff36dbd..15ac3e8ba 100644 --- a/exporter/src/app/http/export.cljs +++ b/exporter/src/app/http/export.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.export (:require diff --git a/exporter/src/app/http/export_bitmap.cljs b/exporter/src/app/http/export_bitmap.cljs index 09eb81e16..cf17a5e6d 100644 --- a/exporter/src/app/http/export_bitmap.cljs +++ b/exporter/src/app/http/export_bitmap.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.export-bitmap (:require diff --git a/exporter/src/app/http/export_svg.cljs b/exporter/src/app/http/export_svg.cljs index db9795bb2..13b6bf946 100644 --- a/exporter/src/app/http/export_svg.cljs +++ b/exporter/src/app/http/export_svg.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.http.export-svg diff --git a/exporter/src/app/http/impl.cljs b/exporter/src/app/http/impl.cljs index f51c70370..15f00ba5b 100644 --- a/exporter/src/app/http/impl.cljs +++ b/exporter/src/app/http/impl.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.impl (:require diff --git a/exporter/src/app/http/thumbnail.cljs b/exporter/src/app/http/thumbnail.cljs index 3a6d1c8a3..f351d67a4 100644 --- a/exporter/src/app/http/thumbnail.cljs +++ b/exporter/src/app/http/thumbnail.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.http.thumbnail (:require diff --git a/exporter/src/app/util/shell.cljs b/exporter/src/app/util/shell.cljs index e4d727a00..f269fe131 100644 --- a/exporter/src/app/util/shell.cljs +++ b/exporter/src/app/util/shell.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.util.shell diff --git a/exporter/src/app/util/transit.cljs b/exporter/src/app/util/transit.cljs index 80ccfea15..38fbe3d4b 100644 --- a/exporter/src/app/util/transit.cljs +++ b/exporter/src/app/util/transit.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.transit (:require diff --git a/exporter/src/app/zipfile.cljs b/exporter/src/app/zipfile.cljs index 8c9cbeff0..bc54b327b 100644 --- a/exporter/src/app/zipfile.cljs +++ b/exporter/src/app/zipfile.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.zipfile (:require diff --git a/frontend/locales.clj b/frontend/locales.clj index a80a0a38a..a64761248 100644 --- a/frontend/locales.clj +++ b/frontend/locales.clj @@ -1,3 +1,9 @@ +;; 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 + (require '[clojure.pprint :as pp :refer [pprint]]) (require '[clojure.edn :as edn] diff --git a/frontend/resources/styles/main-default.scss b/frontend/resources/styles/main-default.scss index 4ad1f8eac..0c9ee57b1 100644 --- a/frontend/resources/styles/main-default.scss +++ b/frontend/resources/styles/main-default.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL //################################################# diff --git a/frontend/resources/styles/main/layouts/main-layout.scss b/frontend/resources/styles/main/layouts/main-layout.scss index b7045cd39..cb4b1c7b2 100644 --- a/frontend/resources/styles/main/layouts/main-layout.scss +++ b/frontend/resources/styles/main/layouts/main-layout.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .main-content { display: flex; diff --git a/frontend/resources/styles/main/partials/activity-bar.scss b/frontend/resources/styles/main/partials/activity-bar.scss index 5948b738c..b1fb0dc94 100644 --- a/frontend/resources/styles/main/partials/activity-bar.scss +++ b/frontend/resources/styles/main/partials/activity-bar.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .activity-bar { background-color: $color-gray-50; diff --git a/frontend/resources/styles/main/partials/color-bullet.scss b/frontend/resources/styles/main/partials/color-bullet.scss index a8041e58b..4c300a4e7 100644 --- a/frontend/resources/styles/main/partials/color-bullet.scss +++ b/frontend/resources/styles/main/partials/color-bullet.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .color-cell { .color-bullet { diff --git a/frontend/resources/styles/main/partials/color-palette.scss b/frontend/resources/styles/main/partials/color-palette.scss index 6bff34acb..5cb587d05 100644 --- a/frontend/resources/styles/main/partials/color-palette.scss +++ b/frontend/resources/styles/main/partials/color-palette.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .color-palette { @include animation(0,.5s,fadeInUp); diff --git a/frontend/resources/styles/main/partials/context-menu.scss b/frontend/resources/styles/main/partials/context-menu.scss index 3696e8dc1..d052cd349 100644 --- a/frontend/resources/styles/main/partials/context-menu.scss +++ b/frontend/resources/styles/main/partials/context-menu.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .context-menu { position: relative; diff --git a/frontend/resources/styles/main/partials/dashboard-grid.scss b/frontend/resources/styles/main/partials/dashboard-grid.scss index d76171832..659656ad2 100644 --- a/frontend/resources/styles/main/partials/dashboard-grid.scss +++ b/frontend/resources/styles/main/partials/dashboard-grid.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .dashboard-grid { font-size: $fs14; diff --git a/frontend/resources/styles/main/partials/dashboard-header.scss b/frontend/resources/styles/main/partials/dashboard-header.scss index cb95e1d8f..823e8806d 100644 --- a/frontend/resources/styles/main/partials/dashboard-header.scss +++ b/frontend/resources/styles/main/partials/dashboard-header.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .dashboard-header { align-items: center; diff --git a/frontend/resources/styles/main/partials/dashboard-settings.scss b/frontend/resources/styles/main/partials/dashboard-settings.scss index c273e3b71..7a81add24 100644 --- a/frontend/resources/styles/main/partials/dashboard-settings.scss +++ b/frontend/resources/styles/main/partials/dashboard-settings.scss @@ -1,11 +1,10 @@ +// Copyright (c) 2020 UXBOX Labs SL // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL + .dashboard-sidebar { &.settings { diff --git a/frontend/resources/styles/main/partials/dashboard-sidebar.scss b/frontend/resources/styles/main/partials/dashboard-sidebar.scss index 089f8c08b..cc2250e3f 100644 --- a/frontend/resources/styles/main/partials/dashboard-sidebar.scss +++ b/frontend/resources/styles/main/partials/dashboard-sidebar.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .dashboard-sidebar { background-color: $color-white; diff --git a/frontend/resources/styles/main/partials/dashboard.scss b/frontend/resources/styles/main/partials/dashboard.scss index 2a86079f0..5077defd4 100644 --- a/frontend/resources/styles/main/partials/dashboard.scss +++ b/frontend/resources/styles/main/partials/dashboard.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .dashboard-container { background-color: $color-dashboard; diff --git a/frontend/resources/styles/main/partials/handoff.scss b/frontend/resources/styles/main/partials/handoff.scss index 60739b8c4..fab38c7af 100644 --- a/frontend/resources/styles/main/partials/handoff.scss +++ b/frontend/resources/styles/main/partials/handoff.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .handoff-svg-wrapper { width: 100%; diff --git a/frontend/resources/styles/main/partials/sidebar-document-history.scss b/frontend/resources/styles/main/partials/sidebar-document-history.scss index ec00f2f73..93ea710f1 100644 --- a/frontend/resources/styles/main/partials/sidebar-document-history.scss +++ b/frontend/resources/styles/main/partials/sidebar-document-history.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .history-toolbox { display: flex; diff --git a/frontend/resources/styles/main/partials/workspace-header.scss b/frontend/resources/styles/main/partials/workspace-header.scss index 4b220a49d..e191a725f 100644 --- a/frontend/resources/styles/main/partials/workspace-header.scss +++ b/frontend/resources/styles/main/partials/workspace-header.scss @@ -2,10 +2,7 @@ // 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/. // -// This Source Code Form is "Incompatible With Secondary Licenses", as -// defined by the Mozilla Public License, v. 2.0. -// -// Copyright (c) 2020 UXBOX Labs SL +// Copyright (c) UXBOX Labs SL .workspace-header { align-items: center; diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index be85ce6d2..17f67993f 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.config (:require diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 2126bead2..e3ac0b754 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main (:require diff --git a/frontend/src/app/main/constants.cljs b/frontend/src/app/main/constants.cljs index ba2b7b531..86c922f7f 100644 --- a/frontend/src/app/main/constants.cljs +++ b/frontend/src/app/main/constants.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2015-2016 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.main.constants) diff --git a/frontend/src/app/main/data/auth.cljs b/frontend/src/app/main/data/auth.cljs index 972f66362..d86ae2ae0 100644 --- a/frontend/src/app/main/data/auth.cljs +++ b/frontend/src/app/main/data/auth.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.auth diff --git a/frontend/src/app/main/data/colors.cljs b/frontend/src/app/main/data/colors.cljs index 4fe8a6f55..a1b46b11c 100644 --- a/frontend/src/app/main/data/colors.cljs +++ b/frontend/src/app/main/data/colors.cljs @@ -2,7 +2,7 @@ ;; 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) 2015-2019 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.main.data.colors (:require diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs index a9136069c..aeda98fdc 100644 --- a/frontend/src/app/main/data/comments.cljs +++ b/frontend/src/app/main/data/comments.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.comments (:require diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index ad216b03f..55fe95a53 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -2,7 +2,7 @@ ;; 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) 2015-2016 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.main.data.dashboard (:require diff --git a/frontend/src/app/main/data/history.cljs b/frontend/src/app/main/data/history.cljs index 6cce00edb..8da91d387 100644 --- a/frontend/src/app/main/data/history.cljs +++ b/frontend/src/app/main/data/history.cljs @@ -2,7 +2,7 @@ ;; 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) 2016-2019 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.main.data.history (:require diff --git a/frontend/src/app/main/data/media.cljs b/frontend/src/app/main/data/media.cljs index b18eb9185..27794fd77 100644 --- a/frontend/src/app/main/data/media.cljs +++ b/frontend/src/app/main/data/media.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.media (:require diff --git a/frontend/src/app/main/data/messages.cljs b/frontend/src/app/main/data/messages.cljs index f91bd5674..41b8f6db9 100644 --- a/frontend/src/app/main/data/messages.cljs +++ b/frontend/src/app/main/data/messages.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.messages (:require diff --git a/frontend/src/app/main/data/modal.cljs b/frontend/src/app/main/data/modal.cljs index 2d5181bb7..b77f4ec15 100644 --- a/frontend/src/app/main/data/modal.cljs +++ b/frontend/src/app/main/data/modal.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.modal (:refer-clojure :exclude [update]) diff --git a/frontend/src/app/main/data/shortcuts.cljs b/frontend/src/app/main/data/shortcuts.cljs index f8ff70f3b..05a04e42f 100644 --- a/frontend/src/app/main/data/shortcuts.cljs +++ b/frontend/src/app/main/data/shortcuts.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.shortcuts (:require diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index bb15a0526..a0491927f 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.users (:require diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index 21fd7d259..99e42310e 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.viewer (:require diff --git a/frontend/src/app/main/data/viewer/shortcuts.cljs b/frontend/src/app/main/data/viewer/shortcuts.cljs index 8b120f1f5..af1243753 100644 --- a/frontend/src/app/main/data/viewer/shortcuts.cljs +++ b/frontend/src/app/main/data/viewer/shortcuts.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.viewer.shortcuts (:require diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 1bf9a34c5..0ec4e3a66 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace (:require diff --git a/frontend/src/app/main/data/workspace/comments.cljs b/frontend/src/app/main/data/workspace/comments.cljs index ccbbd8c30..08f70ed2f 100644 --- a/frontend/src/app/main/data/workspace/comments.cljs +++ b/frontend/src/app/main/data/workspace/comments.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.comments (:require diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 21654d4f7..a9a669bbc 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.common (:require diff --git a/frontend/src/app/main/data/workspace/drawing.cljs b/frontend/src/app/main/data/workspace/drawing.cljs index ece9fffba..9c011c373 100644 --- a/frontend/src/app/main/data/workspace/drawing.cljs +++ b/frontend/src/app/main/data/workspace/drawing.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.drawing "Drawing interactions." diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index 699302067..bc5309f94 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.drawing.box (:require diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index f0676a6d1..922c796af 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.drawing.common (:require diff --git a/frontend/src/app/main/data/workspace/drawing/curve.cljs b/frontend/src/app/main/data/workspace/drawing/curve.cljs index a5f028a45..02c7bb92d 100644 --- a/frontend/src/app/main/data/workspace/drawing/curve.cljs +++ b/frontend/src/app/main/data/workspace/drawing/curve.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.drawing.curve (:require diff --git a/frontend/src/app/main/data/workspace/drawing/path.cljs b/frontend/src/app/main/data/workspace/drawing/path.cljs index 71b2265a2..494d796af 100644 --- a/frontend/src/app/main/data/workspace/drawing/path.cljs +++ b/frontend/src/app/main/data/workspace/drawing/path.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.drawing.path (:require diff --git a/frontend/src/app/main/data/workspace/grid.cljs b/frontend/src/app/main/data/workspace/grid.cljs index 92a71120c..fe4e439e2 100644 --- a/frontend/src/app/main/data/workspace/grid.cljs +++ b/frontend/src/app/main/data/workspace/grid.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.grid (:require diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 519774d4b..675e50a1f 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.libraries (:require diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index c3c8643aa..01d8a61a8 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.libraries-helpers (:require diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index 437b7d67d..962835216 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.notifications (:require diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 6d987437f..67e4bf8e9 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.persistence (:require diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index ff4dfbb44..8761f0a67 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.selection (:require diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index f9ca932b4..f7dda4240 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.shortcuts (:require diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 0a2ad2205..1d076bf52 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.svg-upload (:require diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index dba5c2ace..525d865f5 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.texts (:require diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 339697a86..12143a43c 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.transforms "Events related with shapes transformations" diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index ae08c7045..1569144f3 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.exports "The main logic for SVG export functionality." diff --git a/frontend/src/app/main/fonts.clj b/frontend/src/app/main/fonts.clj index 2688bc743..d36810d45 100644 --- a/frontend/src/app/main/fonts.clj +++ b/frontend/src/app/main/fonts.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.fonts "A fonts loading macros." diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index be8622cfd..27a208b9b 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.fonts diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index e7bc1e5d6..f5b456938 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.refs "A collection of derived refs." diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 7deeb8dad..51abce3cc 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.repo diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index cc81aae2b..e82c67f00 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.snap (:require diff --git a/frontend/src/app/main/store.clj b/frontend/src/app/main/store.clj index f8510983d..bb2733a6d 100644 --- a/frontend/src/app/main/store.clj +++ b/frontend/src/app/main/store.clj @@ -2,7 +2,7 @@ ;; 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) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.store) diff --git a/frontend/src/app/main/store.cljs b/frontend/src/app/main/store.cljs index 72ec01dab..db162221f 100644 --- a/frontend/src/app/main/store.cljs +++ b/frontend/src/app/main/store.cljs @@ -2,7 +2,7 @@ ;; 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) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.store (:require-macros [app.main.store]) diff --git a/frontend/src/app/main/streams.cljs b/frontend/src/app/main/streams.cljs index 223462ded..c3b41c6b6 100644 --- a/frontend/src/app/main/streams.cljs +++ b/frontend/src/app/main/streams.cljs @@ -2,7 +2,7 @@ ;; 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) 2019 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.main.streams "User interaction events and streams." diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index c1232b886..5e0d543c3 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.ui diff --git a/frontend/src/app/main/ui/auth.cljs b/frontend/src/app/main/ui/auth.cljs index 9a1792233..f5c01f034 100644 --- a/frontend/src/app/main/ui/auth.cljs +++ b/frontend/src/app/main/ui/auth.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.auth (:require diff --git a/frontend/src/app/main/ui/auth/login.cljs b/frontend/src/app/main/ui/auth/login.cljs index db3b56cf7..973ef6c41 100644 --- a/frontend/src/app/main/ui/auth/login.cljs +++ b/frontend/src/app/main/ui/auth/login.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.auth.login (:require diff --git a/frontend/src/app/main/ui/auth/recovery.cljs b/frontend/src/app/main/ui/auth/recovery.cljs index 0def99d08..f437b7fe5 100644 --- a/frontend/src/app/main/ui/auth/recovery.cljs +++ b/frontend/src/app/main/ui/auth/recovery.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.auth.recovery (:require diff --git a/frontend/src/app/main/ui/auth/recovery_request.cljs b/frontend/src/app/main/ui/auth/recovery_request.cljs index 9a0e0db0b..2d8ae58d1 100644 --- a/frontend/src/app/main/ui/auth/recovery_request.cljs +++ b/frontend/src/app/main/ui/auth/recovery_request.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.auth.recovery-request (:require diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 0f3745b5a..34cbfae1f 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.auth.register (:require diff --git a/frontend/src/app/main/ui/auth/verify_token.cljs b/frontend/src/app/main/ui/auth/verify_token.cljs index 408180ce9..a926cf85b 100644 --- a/frontend/src/app/main/ui/auth/verify_token.cljs +++ b/frontend/src/app/main/ui/auth/verify_token.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.auth.verify-token (:require diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 3343403cb..a4a98f35e 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.comments (:require diff --git a/frontend/src/app/main/ui/components/code_block.cljs b/frontend/src/app/main/ui/components/code_block.cljs index f1455233c..5501bdae4 100644 --- a/frontend/src/app/main/ui/components/code_block.cljs +++ b/frontend/src/app/main/ui/components/code_block.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.code-block (:require diff --git a/frontend/src/app/main/ui/components/color_bullet.cljs b/frontend/src/app/main/ui/components/color_bullet.cljs index d4f774674..54872e605 100644 --- a/frontend/src/app/main/ui/components/color_bullet.cljs +++ b/frontend/src/app/main/ui/components/color_bullet.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.color-bullet (:require diff --git a/frontend/src/app/main/ui/components/context_menu.cljs b/frontend/src/app/main/ui/components/context_menu.cljs index 19f9141e5..ef0b15230 100644 --- a/frontend/src/app/main/ui/components/context_menu.cljs +++ b/frontend/src/app/main/ui/components/context_menu.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.context-menu (:require diff --git a/frontend/src/app/main/ui/components/copy_button.cljs b/frontend/src/app/main/ui/components/copy_button.cljs index 2f8e9d610..843648441 100644 --- a/frontend/src/app/main/ui/components/copy_button.cljs +++ b/frontend/src/app/main/ui/components/copy_button.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.copy-button (:require diff --git a/frontend/src/app/main/ui/components/editable_label.cljs b/frontend/src/app/main/ui/components/editable_label.cljs index d8ea4c657..d5d7188ed 100644 --- a/frontend/src/app/main/ui/components/editable_label.cljs +++ b/frontend/src/app/main/ui/components/editable_label.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.editable-label (:require diff --git a/frontend/src/app/main/ui/components/editable_select.cljs b/frontend/src/app/main/ui/components/editable_select.cljs index da11ea4fa..e9de807d7 100644 --- a/frontend/src/app/main/ui/components/editable_select.cljs +++ b/frontend/src/app/main/ui/components/editable_select.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.editable-select (:require diff --git a/frontend/src/app/main/ui/components/file_uploader.cljs b/frontend/src/app/main/ui/components/file_uploader.cljs index 1d13fdade..54898407a 100644 --- a/frontend/src/app/main/ui/components/file_uploader.cljs +++ b/frontend/src/app/main/ui/components/file_uploader.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.file-uploader (:require diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs index 915f5faa2..230f521b1 100644 --- a/frontend/src/app/main/ui/components/forms.cljs +++ b/frontend/src/app/main/ui/components/forms.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.forms (:require diff --git a/frontend/src/app/main/ui/components/fullscreen.cljs b/frontend/src/app/main/ui/components/fullscreen.cljs index c7cf120d2..baf9ceca2 100644 --- a/frontend/src/app/main/ui/components/fullscreen.cljs +++ b/frontend/src/app/main/ui/components/fullscreen.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.fullscreen (:require diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs index 11cc46021..f8b4027b1 100644 --- a/frontend/src/app/main/ui/components/numeric_input.cljs +++ b/frontend/src/app/main/ui/components/numeric_input.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.numeric-input (:require diff --git a/frontend/src/app/main/ui/components/select.cljs b/frontend/src/app/main/ui/components/select.cljs index 87a1da543..c88138c8f 100644 --- a/frontend/src/app/main/ui/components/select.cljs +++ b/frontend/src/app/main/ui/components/select.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.select (:require diff --git a/frontend/src/app/main/ui/components/tab_container.cljs b/frontend/src/app/main/ui/components/tab_container.cljs index 10424269c..25a375fc4 100644 --- a/frontend/src/app/main/ui/components/tab_container.cljs +++ b/frontend/src/app/main/ui/components/tab_container.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.components.tab-container (:require [rumext.alpha :as mf])) diff --git a/frontend/src/app/main/ui/confirm.cljs b/frontend/src/app/main/ui/confirm.cljs index dd1b303af..a62223601 100644 --- a/frontend/src/app/main/ui/confirm.cljs +++ b/frontend/src/app/main/ui/confirm.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.confirm (:require diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index fcc077bc7..fe18ce753 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.context (:require diff --git a/frontend/src/app/main/ui/cursors.clj b/frontend/src/app/main/ui/cursors.clj index 5360356e2..38b40eaa8 100644 --- a/frontend/src/app/main/ui/cursors.clj +++ b/frontend/src/app/main/ui/cursors.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.cursors (:require diff --git a/frontend/src/app/main/ui/cursors.cljs b/frontend/src/app/main/ui/cursors.cljs index 4d2412ba5..5748bb54f 100644 --- a/frontend/src/app/main/ui/cursors.cljs +++ b/frontend/src/app/main/ui/cursors.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.cursors (:require-macros [app.main.ui.cursors :refer [cursor-ref cursor-fn]]) diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 230389399..105ec5663 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard diff --git a/frontend/src/app/main/ui/dashboard/comments.cljs b/frontend/src/app/main/ui/dashboard/comments.cljs index dfc404556..6eec7fc75 100644 --- a/frontend/src/app/main/ui/dashboard/comments.cljs +++ b/frontend/src/app/main/ui/dashboard/comments.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.comments (:require diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 58113df79..5b8f80644 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.file-menu (:require @@ -161,7 +158,7 @@ (mf/deps show?) (fn [] (let [group-by-team (fn [projects] - (reduce + (reduce (fn [teams project] (update teams (:team-id project) #(if (nil? %) diff --git a/frontend/src/app/main/ui/dashboard/files.cljs b/frontend/src/app/main/ui/dashboard/files.cljs index 8893f412c..b3f1557df 100644 --- a/frontend/src/app/main/ui/dashboard/files.cljs +++ b/frontend/src/app/main/ui/dashboard/files.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.files (:require diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index 7c8f7b555..167ba0f11 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.grid (:require diff --git a/frontend/src/app/main/ui/dashboard/inline_edition.cljs b/frontend/src/app/main/ui/dashboard/inline_edition.cljs index 652fa10f5..1840afe7b 100644 --- a/frontend/src/app/main/ui/dashboard/inline_edition.cljs +++ b/frontend/src/app/main/ui/dashboard/inline_edition.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.inline-edition (:require diff --git a/frontend/src/app/main/ui/dashboard/libraries.cljs b/frontend/src/app/main/ui/dashboard/libraries.cljs index ce4f0a2e9..77db054de 100644 --- a/frontend/src/app/main/ui/dashboard/libraries.cljs +++ b/frontend/src/app/main/ui/dashboard/libraries.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.libraries (:require diff --git a/frontend/src/app/main/ui/dashboard/project_menu.cljs b/frontend/src/app/main/ui/dashboard/project_menu.cljs index 8f6a245bb..fc2ace6f0 100644 --- a/frontend/src/app/main/ui/dashboard/project_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/project_menu.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.project-menu (:require diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index 684f11309..5ecf0278a 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.projects (:require diff --git a/frontend/src/app/main/ui/dashboard/search.cljs b/frontend/src/app/main/ui/dashboard/search.cljs index 86e365342..cd839cad9 100644 --- a/frontend/src/app/main/ui/dashboard/search.cljs +++ b/frontend/src/app/main/ui/dashboard/search.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.search (:require diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index 9ef53c900..ab8ce7a3a 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.sidebar (:require diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index ad2857500..f97b1a50d 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.team (:require diff --git a/frontend/src/app/main/ui/dashboard/team_form.cljs b/frontend/src/app/main/ui/dashboard/team_form.cljs index 23839e5a8..e4426f0d8 100644 --- a/frontend/src/app/main/ui/dashboard/team_form.cljs +++ b/frontend/src/app/main/ui/dashboard/team_form.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.ui.dashboard.team-form diff --git a/frontend/src/app/main/ui/handoff.cljs b/frontend/src/app/main/ui/handoff.cljs index 9b6d9873f..0fd481771 100644 --- a/frontend/src/app/main/ui/handoff.cljs +++ b/frontend/src/app/main/ui/handoff.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff (:require diff --git a/frontend/src/app/main/ui/handoff/attributes.cljs b/frontend/src/app/main/ui/handoff/attributes.cljs index b8fac8588..51a59746f 100644 --- a/frontend/src/app/main/ui/handoff/attributes.cljs +++ b/frontend/src/app/main/ui/handoff/attributes.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.attributes (:require diff --git a/frontend/src/app/main/ui/handoff/attributes/blur.cljs b/frontend/src/app/main/ui/handoff/attributes/blur.cljs index 166779332..9d3ca9d96 100644 --- a/frontend/src/app/main/ui/handoff/attributes/blur.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/blur.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.attributes.blur (:require diff --git a/frontend/src/app/main/ui/handoff/attributes/common.cljs b/frontend/src/app/main/ui/handoff/attributes/common.cljs index f2bcce9a4..03c341074 100644 --- a/frontend/src/app/main/ui/handoff/attributes/common.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/common.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.attributes.common (:require diff --git a/frontend/src/app/main/ui/handoff/attributes/fill.cljs b/frontend/src/app/main/ui/handoff/attributes/fill.cljs index cf6a8d48a..fdb15cf5c 100644 --- a/frontend/src/app/main/ui/handoff/attributes/fill.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/fill.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.attributes.fill (:require diff --git a/frontend/src/app/main/ui/handoff/attributes/image.cljs b/frontend/src/app/main/ui/handoff/attributes/image.cljs index 6fff88cc3..238b7dea8 100644 --- a/frontend/src/app/main/ui/handoff/attributes/image.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/image.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.attributes.image (:require diff --git a/frontend/src/app/main/ui/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/handoff/attributes/layout.cljs index bd1304ded..2a55eff53 100644 --- a/frontend/src/app/main/ui/handoff/attributes/layout.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/layout.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.attributes.layout (:require diff --git a/frontend/src/app/main/ui/handoff/attributes/shadow.cljs b/frontend/src/app/main/ui/handoff/attributes/shadow.cljs index 75ee452c4..9d757d788 100644 --- a/frontend/src/app/main/ui/handoff/attributes/shadow.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/shadow.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.attributes.shadow (:require diff --git a/frontend/src/app/main/ui/handoff/attributes/stroke.cljs b/frontend/src/app/main/ui/handoff/attributes/stroke.cljs index 99f5e3741..075daae30 100644 --- a/frontend/src/app/main/ui/handoff/attributes/stroke.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/stroke.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.attributes.stroke (:require diff --git a/frontend/src/app/main/ui/handoff/attributes/svg.cljs b/frontend/src/app/main/ui/handoff/attributes/svg.cljs index b8e80d04f..033ad073f 100644 --- a/frontend/src/app/main/ui/handoff/attributes/svg.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/svg.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.attributes.svg (:require diff --git a/frontend/src/app/main/ui/handoff/attributes/text.cljs b/frontend/src/app/main/ui/handoff/attributes/text.cljs index 2af5f9f31..f49276d6f 100644 --- a/frontend/src/app/main/ui/handoff/attributes/text.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/text.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.attributes.text (:require diff --git a/frontend/src/app/main/ui/handoff/code.cljs b/frontend/src/app/main/ui/handoff/code.cljs index eecdb80f7..2bf92cc3d 100644 --- a/frontend/src/app/main/ui/handoff/code.cljs +++ b/frontend/src/app/main/ui/handoff/code.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.code (:require diff --git a/frontend/src/app/main/ui/handoff/exports.cljs b/frontend/src/app/main/ui/handoff/exports.cljs index f30948cca..6462a8101 100644 --- a/frontend/src/app/main/ui/handoff/exports.cljs +++ b/frontend/src/app/main/ui/handoff/exports.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.exports (:require diff --git a/frontend/src/app/main/ui/handoff/left_sidebar.cljs b/frontend/src/app/main/ui/handoff/left_sidebar.cljs index b9107018d..e378a4d5b 100644 --- a/frontend/src/app/main/ui/handoff/left_sidebar.cljs +++ b/frontend/src/app/main/ui/handoff/left_sidebar.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.left-sidebar (:require diff --git a/frontend/src/app/main/ui/handoff/render.cljs b/frontend/src/app/main/ui/handoff/render.cljs index bcc0574b6..6d28af906 100644 --- a/frontend/src/app/main/ui/handoff/render.cljs +++ b/frontend/src/app/main/ui/handoff/render.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.render "The main container for a frame in handoff mode" diff --git a/frontend/src/app/main/ui/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/handoff/right_sidebar.cljs index 71fed541d..577341752 100644 --- a/frontend/src/app/main/ui/handoff/right_sidebar.cljs +++ b/frontend/src/app/main/ui/handoff/right_sidebar.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.right-sidebar (:require diff --git a/frontend/src/app/main/ui/handoff/selection_feedback.cljs b/frontend/src/app/main/ui/handoff/selection_feedback.cljs index a382b26a4..890accbe2 100644 --- a/frontend/src/app/main/ui/handoff/selection_feedback.cljs +++ b/frontend/src/app/main/ui/handoff/selection_feedback.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.handoff.selection-feedback (:require diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index 81fcba93e..01e7fa5cd 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs S.L +;; Copyright (c) UXBOX Labs S.L (ns app.main.ui.hooks "A collection of general purpose react hooks." diff --git a/frontend/src/app/main/ui/icons.clj b/frontend/src/app/main/ui/icons.clj index c6fcb056a..c44da08e5 100644 --- a/frontend/src/app/main/ui/icons.clj +++ b/frontend/src/app/main/ui/icons.clj @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.icons (:require [rumext.alpha])) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 22ec50cbf..5c41f28ef 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.icons (:require-macros [app.main.ui.icons :refer [icon-xref]]) diff --git a/frontend/src/app/main/ui/loader.cljs b/frontend/src/app/main/ui/loader.cljs index 9981f0fb1..503d59be1 100644 --- a/frontend/src/app/main/ui/loader.cljs +++ b/frontend/src/app/main/ui/loader.cljs @@ -2,7 +2,7 @@ ;; 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) 2016-2019 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.loader (:require diff --git a/frontend/src/app/main/ui/measurements.cljs b/frontend/src/app/main/ui/measurements.cljs index b5b9521f9..73caac6b1 100644 --- a/frontend/src/app/main/ui/measurements.cljs +++ b/frontend/src/app/main/ui/measurements.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.measurements (:require diff --git a/frontend/src/app/main/ui/messages.cljs b/frontend/src/app/main/ui/messages.cljs index 298dbc7c8..e1848c969 100644 --- a/frontend/src/app/main/ui/messages.cljs +++ b/frontend/src/app/main/ui/messages.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.messages (:require diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs index 396aa867b..0e462130e 100644 --- a/frontend/src/app/main/ui/modal.cljs +++ b/frontend/src/app/main/ui/modal.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.modal (:require diff --git a/frontend/src/app/main/ui/onboarding.cljs b/frontend/src/app/main/ui/onboarding.cljs index 282d42b61..339d42dd5 100644 --- a/frontend/src/app/main/ui/onboarding.cljs +++ b/frontend/src/app/main/ui/onboarding.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.onboarding (:require diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs index 79c8d83a4..1060de4d3 100644 --- a/frontend/src/app/main/ui/render.cljs +++ b/frontend/src/app/main/ui/render.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.render (:require diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index 05ec590a2..7ca893dfe 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.settings (:require diff --git a/frontend/src/app/main/ui/settings/change_email.cljs b/frontend/src/app/main/ui/settings/change_email.cljs index 63a1ea84a..36402814e 100644 --- a/frontend/src/app/main/ui/settings/change_email.cljs +++ b/frontend/src/app/main/ui/settings/change_email.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.settings.change-email (:require diff --git a/frontend/src/app/main/ui/settings/delete_account.cljs b/frontend/src/app/main/ui/settings/delete_account.cljs index 880552af9..abf29ccc5 100644 --- a/frontend/src/app/main/ui/settings/delete_account.cljs +++ b/frontend/src/app/main/ui/settings/delete_account.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.settings.delete-account (:require diff --git a/frontend/src/app/main/ui/settings/feedback.cljs b/frontend/src/app/main/ui/settings/feedback.cljs index cef294df2..f8f8b63f1 100644 --- a/frontend/src/app/main/ui/settings/feedback.cljs +++ b/frontend/src/app/main/ui/settings/feedback.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.settings.feedback "Feedback form." diff --git a/frontend/src/app/main/ui/settings/options.cljs b/frontend/src/app/main/ui/settings/options.cljs index 76f6975b1..0b7bfb597 100644 --- a/frontend/src/app/main/ui/settings/options.cljs +++ b/frontend/src/app/main/ui/settings/options.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.settings.options (:require diff --git a/frontend/src/app/main/ui/settings/password.cljs b/frontend/src/app/main/ui/settings/password.cljs index 05c7057ae..6b63fc4d8 100644 --- a/frontend/src/app/main/ui/settings/password.cljs +++ b/frontend/src/app/main/ui/settings/password.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.settings.password (:require diff --git a/frontend/src/app/main/ui/settings/profile.cljs b/frontend/src/app/main/ui/settings/profile.cljs index 389f77998..2ed0cb182 100644 --- a/frontend/src/app/main/ui/settings/profile.cljs +++ b/frontend/src/app/main/ui/settings/profile.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.settings.profile (:require diff --git a/frontend/src/app/main/ui/settings/sidebar.cljs b/frontend/src/app/main/ui/settings/sidebar.cljs index 6bcba0e02..83d4993d5 100644 --- a/frontend/src/app/main/ui/settings/sidebar.cljs +++ b/frontend/src/app/main/ui/settings/sidebar.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.ui.settings.sidebar diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index a2b97fb97..cb624ba17 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2016-2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.attrs (:require diff --git a/frontend/src/app/main/ui/shapes/circle.cljs b/frontend/src/app/main/ui/shapes/circle.cljs index 340186aa3..b3cdecbf0 100644 --- a/frontend/src/app/main/ui/shapes/circle.cljs +++ b/frontend/src/app/main/ui/shapes/circle.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.circle (:require diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index ace3ff232..643f07485 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -2,7 +2,7 @@ ;; 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) 2016-2019 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.custom-stroke (:require @@ -40,7 +40,7 @@ clip-props (obj/merge base-props #js {:transform nil - :style (obj/merge + :style (obj/merge base-style #js {:stroke nil :strokeWidth nil diff --git a/frontend/src/app/main/ui/shapes/filters.cljs b/frontend/src/app/main/ui/shapes/filters.cljs index d7b7ed364..838d7e421 100644 --- a/frontend/src/app/main/ui/shapes/filters.cljs +++ b/frontend/src/app/main/ui/shapes/filters.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.filters (:require diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index cf5c0d138..22aa27c30 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.frame (:require diff --git a/frontend/src/app/main/ui/shapes/gradients.cljs b/frontend/src/app/main/ui/shapes/gradients.cljs index c9df9e474..9475ec8d3 100644 --- a/frontend/src/app/main/ui/shapes/gradients.cljs +++ b/frontend/src/app/main/ui/shapes/gradients.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.gradients (:require diff --git a/frontend/src/app/main/ui/shapes/group.cljs b/frontend/src/app/main/ui/shapes/group.cljs index 30d790ffd..fd7d8552f 100644 --- a/frontend/src/app/main/ui/shapes/group.cljs +++ b/frontend/src/app/main/ui/shapes/group.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.group (:require diff --git a/frontend/src/app/main/ui/shapes/image.cljs b/frontend/src/app/main/ui/shapes/image.cljs index 5eaa16ba2..4ad49835b 100644 --- a/frontend/src/app/main/ui/shapes/image.cljs +++ b/frontend/src/app/main/ui/shapes/image.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.image diff --git a/frontend/src/app/main/ui/shapes/mask.cljs b/frontend/src/app/main/ui/shapes/mask.cljs index 07ccb7cf9..c48f9c9c4 100644 --- a/frontend/src/app/main/ui/shapes/mask.cljs +++ b/frontend/src/app/main/ui/shapes/mask.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.mask (:require diff --git a/frontend/src/app/main/ui/shapes/path.cljs b/frontend/src/app/main/ui/shapes/path.cljs index 295cceb7f..98643cb7f 100644 --- a/frontend/src/app/main/ui/shapes/path.cljs +++ b/frontend/src/app/main/ui/shapes/path.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.path (:require diff --git a/frontend/src/app/main/ui/shapes/rect.cljs b/frontend/src/app/main/ui/shapes/rect.cljs index ad3556180..aeb61b17a 100644 --- a/frontend/src/app/main/ui/shapes/rect.cljs +++ b/frontend/src/app/main/ui/shapes/rect.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.rect (:require diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index 1b145d9c8..95d1007d8 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.shape (:require diff --git a/frontend/src/app/main/ui/shapes/svg_defs.cljs b/frontend/src/app/main/ui/shapes/svg_defs.cljs index ee5857551..4002be456 100644 --- a/frontend/src/app/main/ui/shapes/svg_defs.cljs +++ b/frontend/src/app/main/ui/shapes/svg_defs.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.svg-defs (:require diff --git a/frontend/src/app/main/ui/shapes/svg_raw.cljs b/frontend/src/app/main/ui/shapes/svg_raw.cljs index 401221ce2..7038c9128 100644 --- a/frontend/src/app/main/ui/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/shapes/svg_raw.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.svg-raw (:require diff --git a/frontend/src/app/main/ui/shapes/text.cljs b/frontend/src/app/main/ui/shapes/text.cljs index 0972757b6..7f061eee6 100644 --- a/frontend/src/app/main/ui/shapes/text.cljs +++ b/frontend/src/app/main/ui/shapes/text.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.text (:require diff --git a/frontend/src/app/main/ui/shapes/text/embed.cljs b/frontend/src/app/main/ui/shapes/text/embed.cljs index 0ea0df6c1..11b1dc0ff 100644 --- a/frontend/src/app/main/ui/shapes/text/embed.cljs +++ b/frontend/src/app/main/ui/shapes/text/embed.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.text.embed diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index aaa01bf1f..21825299e 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.text.styles (:require diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index 8b33ecac7..86f002d21 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.static (:require diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index 89fdbc91c..514ada022 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.viewer (:require diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index e2eeff8a5..9753eeba8 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.viewer.header (:require diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index f01080e64..05e5d4836 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.viewer.shapes "The main container for a frame in viewer mode" diff --git a/frontend/src/app/main/ui/viewer/thumbnails.cljs b/frontend/src/app/main/ui/viewer/thumbnails.cljs index 2c740911a..5079c9976 100644 --- a/frontend/src/app/main/ui/viewer/thumbnails.cljs +++ b/frontend/src/app/main/ui/viewer/thumbnails.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.viewer.thumbnails (:require diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 00711c87a..0ed6b0a77 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace (:require diff --git a/frontend/src/app/main/ui/workspace/colorpalette.cljs b/frontend/src/app/main/ui/workspace/colorpalette.cljs index 01e3e2012..dd0840608 100644 --- a/frontend/src/app/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/app/main/ui/workspace/colorpalette.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.colorpalette (:require diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index c0ff40544..2e66abd72 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.colorpicker (:require 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 72a6341bb..c14778cf6 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.colorpicker.color-inputs (:require diff --git a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs index 459274b99..27cf95e2f 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.colorpicker.gradients (:require diff --git a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs index a17a51006..3b33d9a22 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.colorpicker.harmony (:require diff --git a/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs b/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs index 5112f7473..98271f0dd 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.colorpicker.hsva (:require diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs index 827dfa15f..8dd45e186 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.colorpicker.libraries (:require diff --git a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs index f88794bab..7b7d28d55 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.colorpicker.ramp (:require 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 e6b4e0c4f..c76fb8bdc 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.colorpicker.slider-selector (:require diff --git a/frontend/src/app/main/ui/workspace/comments.cljs b/frontend/src/app/main/ui/workspace/comments.cljs index ff5012766..da126962e 100644 --- a/frontend/src/app/main/ui/workspace/comments.cljs +++ b/frontend/src/app/main/ui/workspace/comments.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.comments (:require diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index e153ccc8f..3d39ac33e 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.context-menu "A workspace specific context menu (mouse right click)." diff --git a/frontend/src/app/main/ui/workspace/coordinates.cljs b/frontend/src/app/main/ui/workspace/coordinates.cljs index 296d652c0..bb8ad3e7f 100644 --- a/frontend/src/app/main/ui/workspace/coordinates.cljs +++ b/frontend/src/app/main/ui/workspace/coordinates.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.coordinates (:require diff --git a/frontend/src/app/main/ui/workspace/effects.cljs b/frontend/src/app/main/ui/workspace/effects.cljs index dee7ee88f..b42e1f92b 100644 --- a/frontend/src/app/main/ui/workspace/effects.cljs +++ b/frontend/src/app/main/ui/workspace/effects.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.effects (:require diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 53948281f..77cd0f51a 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.header (:require diff --git a/frontend/src/app/main/ui/workspace/left_toolbar.cljs b/frontend/src/app/main/ui/workspace/left_toolbar.cljs index fe630a01e..9390b62ca 100644 --- a/frontend/src/app/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/left_toolbar.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.left-toolbar (:require diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 1c8e5fb5d..f78a3aef1 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -2,8 +2,7 @@ ;; 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) 2016 Andrey Antukh -;; Copyright (c) 2016 Juan de la Cruz +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.libraries (:require diff --git a/frontend/src/app/main/ui/workspace/presence.cljs b/frontend/src/app/main/ui/workspace/presence.cljs index 1e73484e3..e5a7f0e2e 100644 --- a/frontend/src/app/main/ui/workspace/presence.cljs +++ b/frontend/src/app/main/ui/workspace/presence.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.presence diff --git a/frontend/src/app/main/ui/workspace/rules.cljs b/frontend/src/app/main/ui/workspace/rules.cljs index b8eefa2df..d599a6bac 100644 --- a/frontend/src/app/main/ui/workspace/rules.cljs +++ b/frontend/src/app/main/ui/workspace/rules.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.rules (:require diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 0383ef143..9faf50f85 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes "A workspace specific shapes wrappers. diff --git a/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs b/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs index 830387fad..62746cd99 100644 --- a/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs @@ -2,7 +2,7 @@ ;; 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) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes.bounding-box (:require diff --git a/frontend/src/app/main/ui/workspace/shapes/common.cljs b/frontend/src/app/main/ui/workspace/shapes/common.cljs index 8646bc173..4d5ad63e7 100644 --- a/frontend/src/app/main/ui/workspace/shapes/common.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/common.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes.common (:require diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 51781461b..2f51adba3 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes.frame (:require diff --git a/frontend/src/app/main/ui/workspace/shapes/group.cljs b/frontend/src/app/main/ui/workspace/shapes/group.cljs index e677c7d06..d7ce59151 100644 --- a/frontend/src/app/main/ui/workspace/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/group.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes.group (:require diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs index 874c3d316..33de97772 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes.path (:require diff --git a/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs b/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs index 4f41e9d57..94b98119c 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes.path.actions (:require diff --git a/frontend/src/app/main/ui/workspace/shapes/path/common.cljs b/frontend/src/app/main/ui/workspace/shapes/path/common.cljs index b5f408c92..df228663c 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/common.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/common.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes.path.common (:require 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 2e2dd189d..494761da8 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes.path.editor (:require diff --git a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs index 68ed9d629..a07307048 100644 --- a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes.svg-raw (:require diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs index 91a70165e..084da1e56 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes.text (:require 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 72e6eb92f..f273d4aa0 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.shapes.text.editor (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index 1a4ff4354..da154e26a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/align.cljs b/frontend/src/app/main/ui/workspace/sidebar/align.cljs index d5ef9d6dc..7bf85b1a6 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/align.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/align.cljs @@ -2,8 +2,7 @@ ;; 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) 2015-2016 Andrey Antukh -;; Copyright (c) 2015-2016 Juan de la Cruz +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.align (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 7b8694407..604004d55 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.assets (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/history.cljs b/frontend/src/app/main/ui/workspace/sidebar/history.cljs index 2ad04ab13..d74a904c5 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/history.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/history.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.history (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index b49e90087..388279d48 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.layers (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index a623cf933..ec9424f50 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs index 91f0eb438..5fbabea12 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.common (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs index c2d675f9d..10556bff9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.blur (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index b7cb17fc1..8044aa721 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.component (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs index 4dc5ac0cb..65f05ff67 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.exports diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs index d2e8725e0..e61035935 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.fill (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs index dd6442da0..40bbe0a24 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.frame-grid (:require 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 6e47ee95c..10dad3147 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 @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.interactions (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs index 90d9f62e8..2149367cb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.layer (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index c50843af1..f9fa9df7e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.measures (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs index 5775cbd13..01dda5963 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.shadow (:require 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 f8461f4d5..6a56ec363 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 @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.stroke (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs index 71da8a7d5..3675d3b86 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.svg-attrs (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs index 0ff2b3d38..1d30a456f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.text (:require 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 2ccde0b28..473720192 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 @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.menus.typography (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs index e0a0289ef..c6cbc40f2 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.page "Page options menu entries." diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index 2557a7d14..396a9a719 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.rows.color-row (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs index 7b4086804..b42996c5d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.rows.input-row (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs index 59fbfcdbc..8c8888aaa 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.shapes.circle (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 26244c95a..3990546ee 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -2,11 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2015-2020 Andrey Antukh -;; Copyright (c) 2015-2020 Juan de la Cruz +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.shapes.frame (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index f6e16a568..49d87ddeb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs @@ -2,11 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2015-2020 Andrey Antukh -;; Copyright (c) 2015-2020 Juan de la Cruz +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.shapes.group (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs index 32e2fda46..4db2bb029 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.shapes.image (:require 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 a191e7b41..259ff36a6 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 @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.shapes.multiple (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs index 0faebc12d..00d09a17e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.shapes.path (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs index c64656c31..bf8a4ac47 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.shapes.rect (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs index 88b6577f1..01494340f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.shapes.svg-raw (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 1565c6834..0274d00e9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.options.shapes.text (:require diff --git a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs index aa19ef8f1..c35ff9530 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.sidebar.sitemap (:require diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index ca4bdb610..9e713a107 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport (:require diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 1115991a9..2bcf54cac 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.actions (:require diff --git a/frontend/src/app/main/ui/workspace/viewport/comments.cljs b/frontend/src/app/main/ui/workspace/viewport/comments.cljs index 06dc46bd3..d1a4a75b1 100644 --- a/frontend/src/app/main/ui/workspace/viewport/comments.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/comments.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.comments (:require diff --git a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs index f8d4b6fd2..ebf106595 100644 --- a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs @@ -2,7 +2,7 @@ ;; 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) 2015-2019 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.drawarea "Drawing components." diff --git a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs index f8978ad56..e10f485a9 100644 --- a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.frame-grid (:require diff --git a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs index 146e08d6e..633679725 100644 --- a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.gradients "Gradients handlers and renders" diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 44da4fc07..f2c3ae75b 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.hooks (:require diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs index c77f315da..900d6a396 100644 --- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.interactions "Visually show shape interactions in workspace" diff --git a/frontend/src/app/main/ui/workspace/viewport/outline.cljs b/frontend/src/app/main/ui/workspace/viewport/outline.cljs index b84ef8322..316a311dd 100644 --- a/frontend/src/app/main/ui/workspace/viewport/outline.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/outline.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.outline (:require diff --git a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs index b43775b8a..95aba3048 100644 --- a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.pixel-overlay diff --git a/frontend/src/app/main/ui/workspace/viewport/presence.cljs b/frontend/src/app/main/ui/workspace/viewport/presence.cljs index 588ad81db..272b0628b 100644 --- a/frontend/src/app/main/ui/workspace/viewport/presence.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/presence.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.presence (:require diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index ad21f728b..06845cf63 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.selection "Selection handlers component." diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs index ad0825ada..b940e886f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.snap-distances (:require diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs index bae922653..00ea59083 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.snap-points (:require diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 7d5235483..e3df76657 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.utils (:require diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 64ea03488..d810b8a06 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.widgets (:require diff --git a/frontend/src/app/main/worker.cljs b/frontend/src/app/main/worker.cljs index 8c171f711..a0985d97f 100644 --- a/frontend/src/app/main/worker.cljs +++ b/frontend/src/app/main/worker.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.worker (:require diff --git a/frontend/src/app/util/array.cljs b/frontend/src/app/util/array.cljs index 93bce41f5..0718a204c 100644 --- a/frontend/src/app/util/array.cljs +++ b/frontend/src/app/util/array.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.array "A collection of helpers for work with javascript arrays." diff --git a/frontend/src/app/util/avatars.cljs b/frontend/src/app/util/avatars.cljs index e8c9c75fa..ff5afc9e1 100644 --- a/frontend/src/app/util/avatars.cljs +++ b/frontend/src/app/util/avatars.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.avatars (:require diff --git a/frontend/src/app/util/browser_history.js b/frontend/src/app/util/browser_history.js index 890c69f7d..380cb32ab 100644 --- a/frontend/src/app/util/browser_history.js +++ b/frontend/src/app/util/browser_history.js @@ -3,10 +3,7 @@ * 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/. * - * This Source Code Form is "Incompatible With Secondary Licenses", as - * defined by the Mozilla Public License, v. 2.0. - * - * Copyright (c) 2020 UXBOX Labs SL + * Copyright (c) UXBOX Labs SL */ "use strict"; diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs index 8338fd621..ae0702183 100644 --- a/frontend/src/app/util/code_gen.cljs +++ b/frontend/src/app/util/code_gen.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.code-gen (:require diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index e4fd537bc..6da55669a 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.color "Color conversion utils." diff --git a/frontend/src/app/util/data.cljs b/frontend/src/app/util/data.cljs index 0a6c2889c..c070e31c6 100644 --- a/frontend/src/app/util/data.cljs +++ b/frontend/src/app/util/data.cljs @@ -2,7 +2,7 @@ ;; 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) 2015-2019 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.util.data "A collection of data transformation utils." diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index bc1e4641f..7ee03f6cf 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.dom (:require diff --git a/frontend/src/app/util/dom/dnd.cljs b/frontend/src/app/util/dom/dnd.cljs index a71bf8d00..c6cf9c6a2 100644 --- a/frontend/src/app/util/dom/dnd.cljs +++ b/frontend/src/app/util/dom/dnd.cljs @@ -2,8 +2,7 @@ ;; 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) 2015-2016 Andrey Antukh -;; Copyright (c) 2015-2016 Juan de la Cruz +;; Copyright (c) UXBOX Labs SL (ns app.util.dom.dnd "Drag & Drop interop helpers." diff --git a/frontend/src/app/util/forms.cljs b/frontend/src/app/util/forms.cljs index d7465bd48..9db9ca89a 100644 --- a/frontend/src/app/util/forms.cljs +++ b/frontend/src/app/util/forms.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.forms (:refer-clojure :exclude [uuid]) diff --git a/frontend/src/app/util/geom/grid.cljs b/frontend/src/app/util/geom/grid.cljs index a8093557f..c1f882204 100644 --- a/frontend/src/app/util/geom/grid.cljs +++ b/frontend/src/app/util/geom/grid.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.geom.grid (:require diff --git a/frontend/src/app/util/geom/path.cljs b/frontend/src/app/util/geom/path.cljs index 206fc5b6c..0defe2053 100644 --- a/frontend/src/app/util/geom/path.cljs +++ b/frontend/src/app/util/geom/path.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2016-2017 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.util.geom.path (:require @@ -242,7 +239,7 @@ {from-x :x from-y :y} from-p {:keys [rx ry x-axis-rotation large-arc-flag sweep-flag x y]} (:params command) result (a2c from-x from-y x y large-arc-flag sweep-flag rx ry x-axis-rotation)] - + (mapv to-command result))) (defn smooth->curve @@ -285,20 +282,20 @@ (-> (assoc :relative false) (d/update-in-when [:params :c1x] + (:x prev-pos)) (d/update-in-when [:params :c1y] + (:y prev-pos)) - + (d/update-in-when [:params :c2x] + (:x prev-pos)) (d/update-in-when [:params :c2y] + (:y prev-pos)) (d/update-in-when [:params :cx] + (:x prev-pos)) (d/update-in-when [:params :cy] + (:y prev-pos)) - + (d/update-in-when [:params :x] + (:x prev-pos)) (d/update-in-when [:params :y] + (:y prev-pos)) (cond-> (= :line-to-horizontal (:command command)) (d/update-in-when [:params :value] + (:x prev-pos)) - + (= :line-to-vertical (:command command)) (d/update-in-when [:params :value] + (:y prev-pos))))) @@ -332,7 +329,7 @@ (= :smooth-quadratic-bezier-curve-to (:command command)) (-> (assoc :command :curve-to) (update :params merge (quadratic->curve prev-pos (gpt/point params) (calculate-opposite-handler prev-pos prev-qc))))) - + result (if (= :elliptical-arc (:command command)) (d/concat result (arc->beziers prev-pos command)) (conj result command)) @@ -340,7 +337,7 @@ next-cc (case (:command orig-command) :smooth-curve-to (gpt/point (get-in orig-command [:params :cx]) (get-in orig-command [:params :cy])) - + :curve-to (gpt/point (get-in orig-command [:params :c2x]) (get-in orig-command [:params :c2y])) @@ -352,10 +349,10 @@ next-qc (case (:command orig-command) :quadratic-bezier-curve-to (gpt/point (get-in orig-command [:params :cx]) (get-in orig-command [:params :cy])) - + :smooth-quadratic-bezier-curve-to (calculate-opposite-handler prev-pos prev-qc) - + (gpt/point (get-in orig-command [:params :x]) (get-in orig-command [:params :y]))) next-pos (if (= :close-path (:command command)) diff --git a/frontend/src/app/util/geom/snap_points.cljs b/frontend/src/app/util/geom/snap_points.cljs index 2f9fdb000..b9525be10 100644 --- a/frontend/src/app/util/geom/snap_points.cljs +++ b/frontend/src/app/util/geom/snap_points.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.geom.snap-points (:require diff --git a/frontend/src/app/util/globals.js b/frontend/src/app/util/globals.js index 4b3a2933f..3f132ccf5 100644 --- a/frontend/src/app/util/globals.js +++ b/frontend/src/app/util/globals.js @@ -4,10 +4,7 @@ * 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/. * - * This Source Code Form is "Incompatible With Secondary Licenses", as - * defined by the Mozilla Public License, v. 2.0. - * - * Copyright (c) 2020 UXBOX Labs SL + * Copyright (c) UXBOX Labs SL */ /* diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs index af255185e..f13acf6b3 100644 --- a/frontend/src/app/util/http.cljs +++ b/frontend/src/app/util/http.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.util.http diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 15d00312f..11a7f3ebf 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.i18n "A i18n foundation." diff --git a/frontend/src/app/util/kdtree.cljs b/frontend/src/app/util/kdtree.cljs index 2173ee80c..7bb6c280a 100644 --- a/frontend/src/app/util/kdtree.cljs +++ b/frontend/src/app/util/kdtree.cljs @@ -2,7 +2,7 @@ ;; 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) 2016 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.util.kdtree "A cljs layer on top of js impl of kdtree located in `kdtree_impl.js`." diff --git a/frontend/src/app/util/keyboard.cljs b/frontend/src/app/util/keyboard.cljs index 3aa7681d9..35007bda0 100644 --- a/frontend/src/app/util/keyboard.cljs +++ b/frontend/src/app/util/keyboard.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.keyboard) diff --git a/frontend/src/app/util/logging.cljs b/frontend/src/app/util/logging.cljs index b299a36e1..d54e1d818 100644 --- a/frontend/src/app/util/logging.cljs +++ b/frontend/src/app/util/logging.cljs @@ -2,7 +2,7 @@ ;; 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) 2020 UXBOX Labs SL +;; 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 diff --git a/frontend/src/app/util/object.cljs b/frontend/src/app/util/object.cljs index 25d9b254f..ab14004d4 100644 --- a/frontend/src/app/util/object.cljs +++ b/frontend/src/app/util/object.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.object "A collection of helpers for work with javascript objects." diff --git a/frontend/src/app/util/perf.clj b/frontend/src/app/util/perf.clj index 128d0a1ab..39760cd54 100644 --- a/frontend/src/app/util/perf.clj +++ b/frontend/src/app/util/perf.clj @@ -2,7 +2,7 @@ ;; 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) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.perf "Performance profiling for react components.") diff --git a/frontend/src/app/util/perf.cljs b/frontend/src/app/util/perf.cljs index 419aa57d5..03935f301 100644 --- a/frontend/src/app/util/perf.cljs +++ b/frontend/src/app/util/perf.cljs @@ -2,7 +2,7 @@ ;; 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) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.perf "Performance profiling for react components." diff --git a/frontend/src/app/util/quadtree.js b/frontend/src/app/util/quadtree.js index 823f77de4..a6f904c10 100644 --- a/frontend/src/app/util/quadtree.js +++ b/frontend/src/app/util/quadtree.js @@ -1,6 +1,6 @@ /** - * Copyright © 2012-2020 Timo Hausmann - * Copyright © 2020 Andrey Antukh + * Copyright (c) 2020 Andrey Antukh + * Copyright (c) 2012-2020 Timo Hausmann * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation diff --git a/frontend/src/app/util/range_tree.js b/frontend/src/app/util/range_tree.js index ab3a9b51c..ed62a0200 100644 --- a/frontend/src/app/util/range_tree.js +++ b/frontend/src/app/util/range_tree.js @@ -3,10 +3,7 @@ * 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/. * - * This Source Code Form is "Incompatible With Secondary Licenses", as - * defined by the Mozilla Public License, v. 2.0. - * - * Copyright (c) 2020 UXBOX Labs SL + * Copyright (c) UXBOX Labs SL */ /* diff --git a/frontend/src/app/util/router.cljs b/frontend/src/app/util/router.cljs index 64089c0b5..84c45a968 100644 --- a/frontend/src/app/util/router.cljs +++ b/frontend/src/app/util/router.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.router (:refer-clojure :exclude [resolve]) diff --git a/frontend/src/app/util/storage.cljs b/frontend/src/app/util/storage.cljs index 6950cda6c..e88dc656f 100644 --- a/frontend/src/app/util/storage.cljs +++ b/frontend/src/app/util/storage.cljs @@ -2,7 +2,7 @@ ;; 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) 2016 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.util.storage (:require diff --git a/frontend/src/app/util/svg.cljs b/frontend/src/app/util/svg.cljs index 4547b3091..48e64746e 100644 --- a/frontend/src/app/util/svg.cljs +++ b/frontend/src/app/util/svg.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.svg (:require diff --git a/frontend/src/app/util/text_editor.cljs b/frontend/src/app/util/text_editor.cljs index 31db83fe1..44db82118 100644 --- a/frontend/src/app/util/text_editor.cljs +++ b/frontend/src/app/util/text_editor.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.text-editor "Draft related abstraction functions." diff --git a/frontend/src/app/util/text_editor_impl.js b/frontend/src/app/util/text_editor_impl.js index e81e06375..f97b238cb 100644 --- a/frontend/src/app/util/text_editor_impl.js +++ b/frontend/src/app/util/text_editor_impl.js @@ -3,9 +3,6 @@ * 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/. * - * This Source Code Form is "Incompatible With Secondary Licenses", as - * defined by the Mozilla Public License, v. 2.0. - * * Copyright (c) UXBOX Labs SL */ diff --git a/frontend/src/app/util/theme.cljs b/frontend/src/app/util/theme.cljs index d7611dd19..93e76f8d5 100644 --- a/frontend/src/app/util/theme.cljs +++ b/frontend/src/app/util/theme.cljs @@ -2,9 +2,8 @@ ;; 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) 2015-2016 Juan de la Cruz -;; Copyright (c) 2015-2019 Andrey Antukh -;; Copyright (c) 2019-2020 Mathieu BRUNOT +;; Copyright (c) UXBOX Labs SL +;; Copyright (c) Mathieu BRUNOT (ns app.util.theme "A theme manager." diff --git a/frontend/src/app/util/time.cljs b/frontend/src/app/util/time.cljs index 13e87c19c..70668984c 100644 --- a/frontend/src/app/util/time.cljs +++ b/frontend/src/app/util/time.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2015-2021 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.util.time (:require diff --git a/frontend/src/app/util/timers.cljs b/frontend/src/app/util/timers.cljs index efe5cb934..4675f0262 100644 --- a/frontend/src/app/util/timers.cljs +++ b/frontend/src/app/util/timers.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.timers (:require diff --git a/frontend/src/app/util/transit.cljs b/frontend/src/app/util/transit.cljs index 4187ae82b..49ebf8152 100644 --- a/frontend/src/app/util/transit.cljs +++ b/frontend/src/app/util/transit.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.transit "A lightweight abstraction for transit serialization." diff --git a/frontend/src/app/util/uri.cljs b/frontend/src/app/util/uri.cljs index 9ca4f0f71..fe0182543 100644 --- a/frontend/src/app/util/uri.cljs +++ b/frontend/src/app/util/uri.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.uri (:require diff --git a/frontend/src/app/util/webapi.cljs b/frontend/src/app/util/webapi.cljs index bac663f21..5375703c0 100644 --- a/frontend/src/app/util/webapi.cljs +++ b/frontend/src/app/util/webapi.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.util.webapi diff --git a/frontend/src/app/util/websockets.cljs b/frontend/src/app/util/websockets.cljs index d394354f9..70658f7ba 100644 --- a/frontend/src/app/util/websockets.cljs +++ b/frontend/src/app/util/websockets.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.websockets "A interface to webworkers exposed functionality." diff --git a/frontend/src/app/util/worker.cljs b/frontend/src/app/util/worker.cljs index acf34fd53..4c07a7f08 100644 --- a/frontend/src/app/util/worker.cljs +++ b/frontend/src/app/util/worker.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.util.worker "A lightweight layer on top of webworkers api." diff --git a/frontend/src/app/util/zip.cljs b/frontend/src/app/util/zip.cljs index d119e4ee5..6cc7a6fd8 100644 --- a/frontend/src/app/util/zip.cljs +++ b/frontend/src/app/util/zip.cljs @@ -2,7 +2,7 @@ ;; 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) 2016 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.util.zip "Helpers for make zip file (using jszip)." diff --git a/frontend/src/app/worker.cljs b/frontend/src/app/worker.cljs index 604b7eb4f..8c52c7d1a 100644 --- a/frontend/src/app/worker.cljs +++ b/frontend/src/app/worker.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.worker (:require diff --git a/frontend/src/app/worker/impl.cljs b/frontend/src/app/worker/impl.cljs index 30ec5b8e3..107270387 100644 --- a/frontend/src/app/worker/impl.cljs +++ b/frontend/src/app/worker/impl.cljs @@ -2,7 +2,7 @@ ;; 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) 2016 Andrey Antukh +;; Copyright (c) UXBOX Labs SL (ns app.worker.impl (:require diff --git a/frontend/src/app/worker/selection.cljs b/frontend/src/app/worker/selection.cljs index cba546625..dd29d20cc 100644 --- a/frontend/src/app/worker/selection.cljs +++ b/frontend/src/app/worker/selection.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.worker.selection (:require diff --git a/frontend/src/app/worker/snaps.cljs b/frontend/src/app/worker/snaps.cljs index ecd5ba34d..9da49438b 100644 --- a/frontend/src/app/worker/snaps.cljs +++ b/frontend/src/app/worker/snaps.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.worker.snaps (:require diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index 12620859d..f8db65343 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.worker.thumbnails (:require diff --git a/manage.sh b/manage.sh index 997052e4d..6ea162e26 100755 --- a/manage.sh +++ b/manage.sh @@ -94,9 +94,6 @@ 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/. -This Source Code Form is "Incompatible With Secondary Licenses", as -defined by the Mozilla Public License, v. 2.0. - Copyright (c) UXBOX Labs SL EOF } From f0439da293e202ec95913d41cdf06c53e96c86af Mon Sep 17 00:00:00 2001 From: "mathieu.brunot" Date: Tue, 13 Apr 2021 10:58:30 +0200 Subject: [PATCH 033/155] :sparkles: Frontend can receive hash from CI Signed-off-by: mathieu.brunot --- frontend/scripts/build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/scripts/build b/frontend/scripts/build index 94f7dfeb3..73b50e483 100755 --- a/frontend/scripts/build +++ b/frontend/scripts/build @@ -3,7 +3,7 @@ set -ex CURRENT_VERSION=$1; -CURRENT_HASH=$(git rev-parse --short HEAD); +CURRENT_HASH=${CURRENT_HASH:-$(git rev-parse --short HEAD)}; EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS; yarn install || exit 1; From b9ca4e7f9b49ec27a2e94114e318e77a1eab6826 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 13 Apr 2021 12:14:40 +0200 Subject: [PATCH 034/155] :bug: Fixes issue when parsing exponential numbers in paths --- CHANGES.md | 1 + frontend/src/app/util/geom/path.cljs | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 63cd75150..8ca74a0f1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ ### :bug: Bugs fixed - Fixes problem with pan and space [#811](https://github.com/penpot/penpot/issues/811) +- Fixes issue when parsing exponential numbers in paths ### :arrow_up: Deps updates diff --git a/frontend/src/app/util/geom/path.cljs b/frontend/src/app/util/geom/path.cljs index 0defe2053..454b2da20 100644 --- a/frontend/src/app/util/geom/path.cljs +++ b/frontend/src/app/util/geom/path.cljs @@ -28,11 +28,11 @@ (into [] (impl-simplify/simplify points tolerance true))))) ;; -(def commands-regex #"(?i)[a-z][^a-z]*") +(def commands-regex #"(?i)[mzlhvcsqta][^mzlhvcsqta]*") ;; Matches numbers for path values allows values like... -.01, 10, +12.22 ;; 0 and 1 are special because can refer to flags -(def num-regex #"[+-]?(\d+(\.\d+)?|\.\d+)") +(def num-regex #"[+-]?(\d+(\.\d+)?|\.\d+)(e[+-]?\d+)?") (def flag-regex #"[01]") @@ -370,14 +370,15 @@ (reduce simplify-command [[start] start-pos start-pos start-pos start-pos]) (first)))) -(defn path->content [string] - (let [clean-string (-> string - (str/trim) - ;; Change "commas" for spaces - (str/replace #"," " ") - ;; Remove all consecutive spaces - (str/replace #"\s+" " ")) - commands (re-seq commands-regex clean-string)] +(defn path->content [path-str] + (let [clean-path-str + (-> path-str + (str/trim) + ;; Change "commas" for spaces + (str/replace #"," " ") + ;; Remove all consecutive spaces + (str/replace #"\s+" " ")) + commands (re-seq commands-regex clean-path-str)] (-> (mapcat parse-command commands) (simplify-commands)))) From 4cca8f06007c6a0240cdb74bfba4be058fea7963 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 12 Apr 2021 09:29:09 +0200 Subject: [PATCH 035/155] :recycle: Refactor translations subsystem. Migrate from plain json files to gettext. --- frontend/gulpfile.js | 35 +- frontend/package.json | 1 + frontend/resources/locales.json | 6598 ------------------------------- frontend/src/app/util/i18n.cljs | 9 +- frontend/translations/ca.po | 482 +++ frontend/translations/en.po | 2440 ++++++++++++ frontend/translations/es.po | 2422 ++++++++++++ frontend/translations/fr.po | 2193 ++++++++++ frontend/translations/gr.po | 2361 +++++++++++ frontend/translations/ru.po | 1184 ++++++ frontend/translations/tr.po | 460 +++ frontend/translations/zh_cn.po | 2223 +++++++++++ frontend/yarn.lock | 33 +- 13 files changed, 13827 insertions(+), 6614 deletions(-) delete mode 100644 frontend/resources/locales.json create mode 100644 frontend/translations/ca.po create mode 100644 frontend/translations/en.po create mode 100644 frontend/translations/es.po create mode 100644 frontend/translations/fr.po create mode 100644 frontend/translations/gr.po create mode 100644 frontend/translations/ru.po create mode 100644 frontend/translations/tr.po create mode 100644 frontend/translations/zh_cn.po diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js index dd8922be6..c0450faff 100644 --- a/frontend/gulpfile.js +++ b/frontend/gulpfile.js @@ -16,6 +16,7 @@ const clean = require("postcss-clean"); const mkdirp = require("mkdirp"); const rimraf = require("rimraf"); const sass = require("sass"); +const gettext = require("gettext-parser"); const mapStream = require("map-stream"); const paths = {}; @@ -31,16 +32,30 @@ paths.dist = "./target/dist/"; // Templates function readLocales() { - const path = __dirname + "/resources/locales.json"; - const content = JSON.parse(fs.readFileSync(path, {encoding: "utf8"})); + const langs = ["ca", "gr", "en", "es", "fr", "tr", "ru", "zh_cn"]; + const result = {}; - let result = {}; - for (let key of Object.keys(content)) { - const item = content[key]; - if (l.isString(item)) { - result[key] = {"en": item}; - } else if (l.isPlainObject(item) && l.isPlainObject(item.translations)) { - result[key] = item.translations; + for (let lang of langs) { + const content = fs.readFileSync(`./translations/${lang}.po`); + + lang = lang.toLowerCase(); + + const data = gettext.po.parse(content, "utf-8"); + const trdata = data.translations[""]; + + for (let key of Object.keys(trdata)) { + if (key === "") continue; + + if (l.isNil(result[key])) { + result[key] = {}; + } + + const msgstr = trdata[key].msgstr; + if (msgstr.length === 1) { + result[key][lang] = msgstr[0]; + } else { + result[key][lang] = msgstr; + } } } @@ -189,7 +204,7 @@ gulp.task("watch:main", function() { gulp.watch(paths.resources + "images/**/*", gulp.series("copy:assets:images")); gulp.watch([paths.resources + "templates/*.mustache", - paths.resources + "locales.json"], + "translations/*.po"], gulp.series("templates")); }); diff --git a/frontend/package.json b/frontend/package.json index dd4ff7333..3b8b95580 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ }, "devDependencies": { "autoprefixer": "^10.2.4", + "gettext-parser": "^4.0.4", "gulp": "4.0.2", "gulp-concat": "^2.6.1", "gulp-gzip": "^1.4.2", diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json deleted file mode 100644 index 71fa1f305..000000000 --- a/frontend/resources/locales.json +++ /dev/null @@ -1,6598 +0,0 @@ -{ - "auth.already-have-account" : { - "translations" : { - "ca" : "Ja tens un compte?", - "de" : "Sie haben schon ein Konto?", - "en" : "Already have an account?", - "es" : "¿Tienes ya una cuenta?", - "fr" : "Vous avez déjà un compte ?", - "ru" : "Уже есть аккаунт?", - "tr" : "Zaten hesabın var mı?", - "zh_cn" : "已经有账号了?" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs" ] - }, - "auth.check-your-email" : { - "translations" : { - "ca" : "Revisa el teu email i fes click al link per verificar i començar a utilitzar Penpot.", - "de" : "Überprüfen Sie Ihre E-Mail, klicken Sie auf den Link um sich zu verifizieren und Penpot zu nutzen.", - "en" : "Check your email and click on the link to verify and start using Penpot.", - "fr" : "Vérifiez votre e‑mail et cliquez sur le lien pour vérifier et commencer à utiliser Penpot.", - "tr" : "Penpot hesabını onaylamak ve kullanmaya başlamak için e-postanı kontrol et ve gönderilen bağlantıya tıkla.", - "zh_cn" : "请检查电子邮箱,点击邮件中的超链接来验证,然后开始使用Penpot。" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs" ] - }, - "auth.confirm-password" : { - "translations" : { - "ca" : "Confirmar contrasenya", - "de" : "Passwort bestätigen", - "en" : "Confirm password", - "es" : "Confirmar contraseña", - "fr" : "Confirmez le mot de passe", - "ru" : "Подтвердите пароль", - "tr" : "Parolayı onayla", - "zh_cn" : "确认密码" - }, - "used-in" : [ "src/app/main/ui/auth/recovery.cljs" ] - }, - "auth.create-demo-account" : { - "translations" : { - "ca" : "Crea un compte de proba", - "de" : "Ein Demo-Konto erstellen", - "en" : "Create demo account", - "es" : "Crear cuenta de prueba", - "fr" : "Créer un compte de démonstration", - "ru" : "Хотите попробовать?", - "tr" : "Demo hesabı oluştur", - "zh_cn" : "创建演示账号" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs", "src/app/main/ui/auth/login.cljs" ] - }, - "auth.create-demo-profile" : { - "translations" : { - "ca" : "Vols probar-ho?", - "de" : "Einfach testen?", - "en" : "Just wanna try it?", - "es" : "¿Quieres probar?", - "fr" : "Vous voulez juste essayer ?", - "ru" : "Хотите попробовать?", - "tr" : "Sadece denemek mi istiyorsun?", - "zh_cn" : "只是想试试?" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs", "src/app/main/ui/auth/login.cljs" ] - }, - "auth.demo-warning" : { - "translations" : { - "ca" : "Aquest es un servei de PROBA. NO HO UTILITZIS per feina real, els projectes seran esborrats periòdicament.", - "de" : "Das ist eine DEMO-VERSION, verwenden Sie es NICHT zum Arbeiten, die Projekte werden regelmäßig gelöscht.", - "en" : "This is a DEMO service, DO NOT USE for real work, the projects will be periodicaly wiped.", - "es" : "Este es un servicio de DEMOSTRACIÓN. NO USAR para trabajo real, los proyectos serán borrados periodicamente.", - "fr" : "Il s’agit d’un service DEMO, NE PAS UTILISER pour un travail réel, les projets seront périodiquement supprimés.", - "ru" : "Это ДЕМОНСТРАЦИЯ, НЕ ИСПОЛЬЗУЙТЕ для работы, проекты будут периодически удаляться.", - "tr" : "Bu bir DEMO servis, gerçek işleriniz için KULLANMAYIN, projeler periyodik olarak silinecektir.", - "zh_cn" : "这是一个演示服务,请【不要】用于真实工作,这些项目将被周期性地抹除。" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs" ] - }, - "auth.email" : { - "translations" : { - "ca" : "Correu electrònic", - "de" : "E-Mail", - "en" : "Email", - "es" : "Correo electrónico", - "fr" : "Adresse e‑mail", - "ru" : "Email", - "tr" : "E-posta", - "zh_cn" : "电子邮件" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs", "src/app/main/ui/auth/recovery_request.cljs", "src/app/main/ui/auth/login.cljs" ] - }, - "auth.forgot-password" : { - "translations" : { - "ca" : "Has oblidat la contrasenya?", - "de" : "Passwort vergessen?", - "en" : "Forgot password?", - "es" : "¿Olvidaste tu contraseña?", - "fr" : "Mot de passe oublié ?", - "ru" : "Забыли пароль?", - "tr" : "Parolanı mı unuttun?", - "zh_cn" : "忘记密码?" - }, - "used-in" : [ "src/app/main/ui/auth/login.cljs" ] - }, - "auth.fullname" : { - "translations" : { - "ca" : "Nom complet", - "de" : "Vollständiger Name", - "en" : "Full Name", - "es" : "Nombre completo", - "fr" : "Nom complet", - "ru" : "Полное имя", - "tr" : "Tam Adın", - "zh_cn" : "全名" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs" ] - }, - "auth.go-back-to-login" : { - "translations" : { - "ca" : "Tornar", - "de" : "Zurück!", - "en" : "Go back!", - "es" : "Volver", - "fr" : "Retour !", - "ru" : "Назад!", - "tr" : "Geri dön!", - "zh_cn" : "返回!" - }, - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs" ] - }, - "auth.login-here" : { - "translations" : { - "ca" : "Inicia sessió aquí", - "de" : "Hier einloggen", - "en" : "Login here", - "es" : "Entra aquí", - "fr" : "Se connecter ici", - "ru" : "Войти здесь", - "tr" : "Buradan giriş yap", - "zh_cn" : "在这里登录" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs" ] - }, - "auth.login-submit" : { - "translations" : { - "ca" : "Accedir", - "de" : "Anmelden", - "en" : "Sign in", - "es" : "Entrar", - "fr" : "Se connecter", - "ru" : "Вход", - "tr" : "Giriş yap", - "zh_cn" : "登录" - }, - "used-in" : [ "src/app/main/ui/auth/login.cljs" ] - }, - "auth.login-subtitle" : { - "translations" : { - "ca" : "Introdueix les teves dades aquí", - "de" : "Geben Sie unten Ihre Daten ein", - "en" : "Enter your details below", - "es" : "Introduce tus datos aquí", - "fr" : "Entrez vos informations ci‑dessous", - "ru" : "Введите информацию о себе ниже", - "tr" : "Bilgilerini aşağıdaki alana gir", - "zh_cn" : "请在下面输入你的详细信息" - }, - "used-in" : [ "src/app/main/ui/auth/login.cljs" ] - }, - "auth.login-title" : { - "translations" : { - "ca" : "Encantats de tornar a veure't", - "de" : "Schön, Sie wiederzusehen!", - "en" : "Great to see you again!", - "es" : "Encantados de volverte a ver", - "fr" : "Ravi de vous revoir !", - "ru" : "Рады видеть Вас снова!", - "tr" : "Seni tekrar görmek süper!", - "zh_cn" : "很高兴又见到你!" - }, - "used-in" : [ "src/app/main/ui/auth/login.cljs" ] - }, - "auth.login-with-github-submit" : { - "translations" : { - "ca" : "Accedir amb Github", - "de" : "Einloggen mit Github", - "en" : "Login with Github", - "es" : "Entrar con Github", - "fr" : "Se connecter via Github", - "ru" : "Вход через Gitnub", - "tr" : "Github ile giriş yap", - "zh_cn" : "使用Github登录" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs", "src/app/main/ui/auth/login.cljs" ] - }, - "auth.login-with-gitlab-submit" : { - "translations" : { - "ca" : "Accedir amb Gitlab", - "de" : "Einloggen mit Gitlab", - "en" : "Login with Gitlab", - "es" : "Entrar con Gitlab", - "fr" : "Se connecter via Gitlab", - "ru" : "Вход через Gitlab", - "tr" : "Gitlab ile giriş yap", - "zh_cn" : "使用Gitlab登录" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs", "src/app/main/ui/auth/login.cljs" ] - }, - "auth.login-with-ldap-submit" : { - "translations" : { - "ca" : "Accedir amb LDAP", - "de" : "Anmelden mit LDAP", - "en" : "Sign in with LDAP", - "es" : "Entrar con LDAP", - "fr" : "Se connecter via LDAP", - "ru" : "Вход через LDAP", - "tr" : "LDAP ile giriş yap", - "zh_cn" : "使用LDAP登录" - }, - "used-in" : [ "src/app/main/ui/auth/login.cljs" ] - }, - "auth.new-password" : { - "translations" : { - "ca" : "Introdueix la nova contrasenya", - "de" : "Geben Sie ein neues Passwort ein", - "en" : "Type a new password", - "es" : "Introduce la nueva contraseña", - "fr" : "Saisissez un nouveau mot de passe", - "ru" : "Введите новый пароль", - "tr" : "Yeni bir parola gir", - "zh_cn" : "输入新的密码" - }, - "used-in" : [ "src/app/main/ui/auth/recovery.cljs" ] - }, - "auth.notifications.invalid-token-error" : { - "translations" : { - "ca" : "El codi de recuperació no és vàlid", - "de" : "Der Wiederherstellungscode ist ungültig.", - "en" : "The recovery token is invalid.", - "es" : "El código de recuperación no es válido.", - "fr" : "Le code de récupération n’est pas valide.", - "ru" : "Неверный код восстановления.", - "tr" : "Kurtarma bağlantısı geçerli değil", - "zh_cn" : "恢复令牌无效。" - }, - "used-in" : [ "src/app/main/ui/auth/recovery.cljs" ] - }, - "auth.notifications.password-changed-succesfully" : { - "translations" : { - "ca" : "La contrasenya s'ha canviat correctament", - "de" : "Passwort erfolgreich geändert", - "en" : "Password successfully changed", - "es" : "La contraseña ha sido cambiada", - "fr" : "Mot de passe changé avec succès", - "ru" : "Пароль изменен успешно", - "tr" : "Parola başarıyla değiştirldi", - "zh_cn" : "密码修改成功" - }, - "used-in" : [ "src/app/main/ui/auth/recovery.cljs" ] - }, - "auth.notifications.profile-not-verified" : { - "translations" : { - "ca" : "El perfil encara no s'ha verificat, si us plau verifica-ho abans de continuar.", - "de" : "Profil ist nicht verifiziert. Bevor Sie fortfahren, verifizieren Sie bitte das Profil.", - "en" : "Profile is not verified, please verify profile before continue.", - "es" : "El perfil aun no ha sido verificado, por favor valida el perfil antes de continuar.", - "fr" : "Le profil n’est pas vérifié. Veuillez vérifier le profil avant de continuer.", - "tr" : "Profil onaylanmamış, devam etmeden önce profili onaylayın.", - "zh_cn" : "个人资料未验证,请于验证后继续。" - }, - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs" ] - }, - "auth.notifications.recovery-token-sent" : { - "translations" : { - "ca" : "Hem enviat un link de recuperació de contrasenya al teu email.", - "de" : "Der Link zur Wiederherstellung des Passworts wurde an Ihre E-Mail gesendet.", - "en" : "Password recovery link sent to your inbox.", - "es" : "Hemos enviado a tu buzón un enlace para recuperar tu contraseña.", - "fr" : "Lien de récupération de mot de passe envoyé.", - "ru" : "Ссылка для восстановления пароля отправлена на почту.", - "tr" : "Parola kurtarma bağlantısı e-posta kutuna gönderildi.", - "zh_cn" : "找回密码链接已发至你的收件箱。" - }, - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs" ] - }, - "auth.notifications.team-invitation-accepted" : { - "translations" : { - "ca" : "T'has unit al equip", - "de" : "Sie sind dem Team beigetreten", - "en" : "Joined the team succesfully", - "es" : "Te uniste al equipo", - "fr" : "Vous avez rejoint l’équipe avec succès", - "tr" : "Takıma başarıyla katıldın", - "zh_cn" : "成功加入团队" - }, - "used-in" : [ "src/app/main/ui/auth/verify_token.cljs" ] - }, - "auth.password" : { - "translations" : { - "ca" : "Contrasenya", - "de" : "Passwort", - "en" : "Password", - "es" : "Contraseña", - "fr" : "Mot de passe", - "ru" : "Пароль", - "tr" : "Parola", - "zh_cn" : "密码" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs", "src/app/main/ui/auth/login.cljs" ] - }, - "auth.password-length-hint" : { - "translations" : { - "ca" : "Com a mínim 8 caràcters", - "de" : "Mindestens 8 Zeichen", - "en" : "At least 8 characters", - "es" : "8 caracteres como mínimo", - "fr" : "Au moins 8 caractères", - "ru" : "Минимум 8 символов", - "tr" : "En az 8 karakter", - "zh_cn" : "至少8位字符" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs" ] - }, - "auth.recovery-request-submit" : { - "translations" : { - "ca" : "Recuperar contrasenya", - "de" : "Passwort wiederherstellen", - "en" : "Recover Password", - "es" : "Recuperar contraseña", - "fr" : "Récupérer le mot de passe", - "ru" : "Восстановить пароль", - "tr" : "Parolayı kurtar", - "zh_cn" : "找回密码" - }, - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs" ] - }, - "auth.recovery-request-subtitle" : { - "translations" : { - "ca" : "T'enviarem un correu electrónic amb instruccions", - "de" : "Wir senden Ihnen eine E-Mail mit Anweisungen zu", - "en" : "We'll send you an email with instructions", - "es" : "Te enviaremos un correo electrónico con instrucciones", - "fr" : "Nous vous enverrons un e‑mail avec des instructions", - "ru" : "Письмо с инструкциями отправлено на почту.", - "tr" : "Detayları sana e-posta ile göndereceğiz", - "zh_cn" : "我们将给你发送一封带有说明的电子邮件" - }, - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs" ] - }, - "auth.recovery-request-title" : { - "translations" : { - "ca" : "Has oblidat la teva contrasenya?", - "de" : "Passwort vergessen?", - "en" : "Forgot password?", - "es" : "¿Olvidaste tu contraseña?", - "fr" : "Mot de passe oublié ?", - "ru" : "Забыли пароль?", - "tr" : "Parolanı mı unuttun?", - "zh_cn" : "忘记密码?" - }, - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs" ] - }, - "auth.recovery-submit" : { - "translations" : { - "ca" : "Canvia la teva contrasenya", - "de" : "Passwort ändern", - "en" : "Change your password", - "es" : "Cambiar tu contraseña", - "fr" : "Changez votre mot de passe", - "ru" : "Изменить пароль", - "tr" : "Parolanı değiştir", - "zh_cn" : "修改密码" - }, - "used-in" : [ "src/app/main/ui/auth/recovery.cljs" ] - }, - "auth.register" : { - "translations" : { - "ca" : "Encara no tens compte?", - "de" : "Noch kein Konto?", - "en" : "No account yet?", - "es" : "¿No tienes una cuenta?", - "fr" : "Pas encore de compte ?", - "ru" : "Еще нет аккаунта?", - "tr" : "Henüz hesabın yok mu?", - "zh_cn" : "现在还没有账号?" - }, - "used-in" : [ "src/app/main/ui/auth/login.cljs" ] - }, - "auth.register-submit" : { - "translations" : { - "ca" : "Crea un compte", - "de" : "Konto erstellen", - "en" : "Create an account", - "es" : "Crear una cuenta", - "fr" : "Créer un compte", - "ru" : "Создать аккаунт", - "tr" : "Bir hesap oluştur", - "zh_cn" : "创建账号" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs", "src/app/main/ui/auth/login.cljs" ] - }, - "auth.register-subtitle" : { - "translations" : { - "ca" : "Es gratuit, es Open Source", - "de" : "Kostenlos, es ist Open Source", - "en" : "It's free, it's Open Source", - "es" : "Es gratis, es Open Source", - "fr" : "C’est gratuit, c’est Open Source", - "ru" : "Это бесплатно, это Open Source", - "tr" : "Ücretsiz ve Açık Kaynak", - "zh_cn" : "它免费,它开源" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs" ] - }, - "auth.register-title" : { - "translations" : { - "ca" : "Crea un compte", - "de" : "Konto erstellen", - "en" : "Create an account", - "es" : "Crear una cuenta", - "fr" : "Créer un compte", - "ru" : "Создать аккаунт", - "tr" : "Bir hesap oluştur", - "zh_cn" : "创建账号" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs" ] - }, - "auth.sidebar-tagline" : { - "translations" : { - "ca" : "La solució de codi obert per disenyar i prototipar", - "de" : "Die Open-Source-Lösung für Design und Prototyping.", - "en" : "The open-source solution for design and prototyping.", - "es" : "La solución de código abierto para diseñar y prototipar", - "fr" : "La solution Open Source pour la conception et le prototypage.", - "ru" : "Open Source решение для дизайна и прототипирования.", - "tr" : "Tasarım ve prototipleme için açık-kaynak çözüm.", - "zh_cn" : "设计与原型的开源解决方案" - }, - "used-in" : [ "src/app/main/ui/auth.cljs" ] - }, - "auth.terms-privacy-agreement" : { - "translations" : { - "en" : "When creating a new account, you agree to our terms of service and privacy policy.", - "es" : "Al crear una nueva cuenta, aceptas nuestros términos de servicio y política de privacidad.", - "tr" : "Bir hesap oluştururken, koşullarımızı ve gizlilik politikamızı kabul etmiş sayılırsınız." - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs" ] - }, - "auth.verification-email-sent" : { - "translations" : { - "ca" : "Em enviat un correu de verificació a", - "de" : "Wir haben eine Bestätigungs-E-Mail gesendet an", - "en" : "We've sent a verification email to", - "fr" : "Nous avons envoyé un e-mail de vérification à", - "tr" : "Onay e-postanı şu adrese gönderdik", - "zh_cn" : "我们已经发送了一封验证邮件到" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs" ] - }, - "dashboard.add-shared" : { - "translations" : { - "ca" : "Afegeix una Biblioteca Compartida", - "de" : "Hinzufügen als gemeinsam genutzte Bibliothek", - "en" : "Add as Shared Library", - "es" : "Añadir como Biblioteca Compartida", - "fr" : "Ajouter une Bibliothèque Partagée", - "ru" : "", - "tr" : "Paylaşılan Kitaplık olarak ekle", - "zh_cn" : "添加为共享库" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.change-email" : { - "translations" : { - "ca" : "Canviar correu", - "de" : "E-Mail-Adresse ändern", - "en" : "Change email", - "es" : "Cambiar correo", - "fr" : "Changer adresse e‑mail", - "ru" : "Сменить email адрес", - "tr" : "E-posta adresini değiştir", - "zh_cn" : "修改电子邮件" - }, - "used-in" : [ "src/app/main/ui/settings/profile.cljs" ] - }, - "dashboard.copy-suffix" : { - "translations" : { - "de" : "(Kopie)", - "en" : "(copy)", - "es" : "(copia)", - "tr" : "(kopya)" - }, - "used-in" : [ "src/app/main/data/dashboard.cljs", "src/app/main/data/dashboard.cljs" ] - }, - "dashboard.create-new-team" : { - "translations" : { - "ca" : "+ Crear un nou equip", - "de" : "+ Neues Team erstellen", - "en" : "+ Create new team", - "es" : "+ Crear nuevo equipo", - "fr" : "+ Créer nouvelle équipe", - "tr" : "Yeni takım oluştur", - "zh_cn" : "+ 创建新团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "dashboard.default-team-name" : { - "translations" : { - "ca" : "El teu Penpot", - "de" : "Ihr Penpot", - "en" : "Your Penpot", - "es" : "Tu Penpot", - "fr" : "Votre Penpot", - "tr" : "Penpot'un", - "zh_cn" : "你的Penpot" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "dashboard.delete-team" : { - "translations" : { - "ca" : "Suprimir equip", - "de" : "Team löschen", - "en" : "Delete team", - "es" : "Eliminar equipo", - "fr" : "Supprimer l’équipe", - "tr" : "Takımı sil", - "zh_cn" : "删除团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "dashboard.draft-title" : { - "translations" : { - "ca" : "Esborrany", - "de" : "Entwurf", - "en" : "Draft", - "es" : "Borrador", - "fr" : "Brouillon", - "ru" : "Черновик", - "tr" : "Taslak", - "zh_cn" : "草稿" - }, - "unused" : true - }, - "dashboard.duplicate" : { - "translations" : { - "de" : "Duplizieren", - "en" : "Duplicate", - "es" : "Duplicar", - "tr" : "Kopyasını oluştur" - }, - "used-in" : [ "src/app/main/ui/dashboard/project_menu.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.duplicate-multi" : { - "translations" : { - "en" : "Duplicate %s files", - "es" : "Duplicar %s archivos", - "tr" : "%s dosyanın kopyasını oluştur" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.empty-files" : { - "translations" : { - "ca" : "Encara no hi ha cap arxiu aquí", - "de" : "Sie haben hier noch keine Dateien", - "en" : "You still have no files here", - "es" : "Todavía no hay ningún archivo aquí", - "fr" : "Vous n’avez encore aucun fichier ici", - "ru" : "Файлов пока нет", - "tr" : "Burada hiç dosyan yok", - "zh_cn" : "暂无文档" - }, - "used-in" : [ "src/app/main/ui/dashboard/grid.cljs" ] - }, - "dashboard.invite-profile" : { - "translations" : { - "ca" : "Convidar a l'equip", - "de" : "Zum Team einladen", - "en" : "Invite to team", - "es" : "Invitar al equipo", - "fr" : "Inviter dans l’équipe", - "tr" : "Takıma davet et", - "zh_cn" : "邀请加入团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "dashboard.leave-team" : { - "translations" : { - "ca" : "Abandonar l'equip", - "de" : "Team verlassen", - "en" : "Leave team", - "es" : "Abandonar equipo", - "fr" : "Quitter l’équipe", - "tr" : "Takımdan ayrıl", - "zh_cn" : "退出团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs", "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "dashboard.libraries-title" : { - "translations" : { - "ca" : "Biblioteques Compartides", - "de" : "Gemeinsam genutzte Bibliotheken", - "en" : "Shared Libraries", - "es" : "Bibliotecas Compartidas", - "fr" : "Bibliothèques Partagées", - "ru" : "", - "tr" : "Paylaşılan Kitaplıklar", - "zh_cn" : "共享库" - }, - "used-in" : [ "src/app/main/ui/dashboard/libraries.cljs" ] - }, - "dashboard.loading-files" : { - "translations" : { - "ca" : "carregan els teus fitxers", - "de" : "laden Ihrer Dateien …", - "en" : "loading your files …", - "es" : "cargando tus ficheros …", - "fr" : "chargement de vos fichiers…", - "tr" : "dosyalarınız yükleniyor …", - "zh_cn" : "正在加载文档…" - }, - "used-in" : [ "src/app/main/ui/dashboard/grid.cljs" ] - }, - "dashboard.move-to" : { - "translations" : { - "de" : "Verschieben nach", - "en" : "Move to", - "es" : "Mover a", - "tr" : "Şuraya taşı" - }, - "used-in" : [ "src/app/main/ui/dashboard/project_menu.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.move-to-multi" : { - "translations" : { - "en" : "Move %s files to", - "es" : "Mover %s archivos a", - "tr" : "%s dosyayı şuraya taşı" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.move-to-other-team" : { - "translations" : { - "de" : "Zu anderem Team verschieben", - "en" : "Move to other team", - "es" : "Mover a otro equipo", - "tr" : "Başkta takıma taşı" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.new-file" : { - "translations" : { - "ca" : "+ Nou Arxiu", - "de" : "+ Neue Datei", - "en" : "+ New File", - "es" : "+ Nuevo Archivo", - "fr" : "+ Nouveau fichier", - "ru" : "+ Новый файл", - "tr" : "Yeni Dosya", - "zh_cn" : "+ 新文档" - }, - "used-in" : [ "src/app/main/ui/dashboard/projects.cljs", "src/app/main/ui/dashboard/files.cljs" ] - }, - "dashboard.new-project" : { - "translations" : { - "ca" : "+ Nou projecte", - "de" : "+ Neues Projekt", - "en" : "+ New project", - "es" : "+ Nuevo proyecto", - "fr" : "+ Nouveau projet", - "ru" : "+ Новый проект", - "tr" : "Yeni Proje", - "zh_cn" : "+ 新项目" - }, - "used-in" : [ "src/app/main/ui/dashboard/projects.cljs" ] - }, - "dashboard.no-matches-for" : { - "translations" : { - "ca" : "No s'ha trobat cap coincidència amb “%s“", - "de" : "Keine Übereinstimmungen für “%s“ gefunden", - "en" : "No matches found for “%s“", - "es" : "No se encuentra “%s“", - "fr" : "Aucune correspondance pour « %s »", - "ru" : "Совпадений для “%s“ не найдено", - "tr" : "%s için hiç sonuç bulunamadı", - "zh_cn" : "没有找到“%s”的匹配项" - }, - "used-in" : [ "src/app/main/ui/dashboard/search.cljs" ] - }, - "dashboard.no-projects-placeholder" : { - "translations" : { - "ca" : "Els projectes fixats apareixeran aquí", - "de" : "Angeheftete Projekte werden hier angezeigt", - "en" : "Pinned projects will appear here", - "es" : "Los proyectos fijados aparecerán aquí", - "fr" : "Les projets épinglés apparaîtront ici", - "tr" : "Sabitlenmiş projeler burada görünür", - "zh_cn" : "被钉住的项目会显示在这儿" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "dashboard.notifications.email-changed-successfully" : { - "translations" : { - "ca" : "La teva adreça de correu s'ha actualizat", - "de" : "Ihre E-Mail-Adresse wurde erfolgreich aktualisiert", - "en" : "Your email address has been updated successfully", - "es" : "Tu dirección de correo ha sido actualizada", - "fr" : "Votre adresse e‑mail a été mise à jour avec succès", - "ru" : "Ваш email адрес успешно обновлен", - "tr" : "E-posta adresiniz başarıyla güncellendi", - "zh_cn" : "已经成功更新你的电子邮件" - }, - "used-in" : [ "src/app/main/ui/auth/verify_token.cljs" ] - }, - "dashboard.notifications.email-verified-successfully" : { - "translations" : { - "ca" : "La teva adreça de correu ha sigut verificada", - "de" : "Ihre E-Mail-Adresse wurde erfolgreich verifiziert", - "en" : "Your email address has been verified successfully", - "es" : "Tu dirección de correo ha sido verificada", - "fr" : "Votre adresse e‑mail a été vérifiée avec succès", - "ru" : "Ваш email адрес успешно подтвержден", - "tr" : "E-posta adresin başarıyla doğrulandı", - "zh_cn" : "已经成功验证你的电子邮件" - }, - "used-in" : [ "src/app/main/ui/auth/verify_token.cljs" ] - }, - "dashboard.notifications.password-saved" : { - "translations" : { - "ca" : "La contrasenya s'ha desat correctament", - "de" : "Passwort wurde erfolgreich gespeichert!", - "en" : "Password saved successfully!", - "es" : "¡Contraseña guardada!", - "fr" : "Mot de passe enregistré avec succès !", - "ru" : "Пароль успешно сохранен!", - "tr" : "Parola kaydedildi", - "zh_cn" : "已经成功保存密码!" - }, - "used-in" : [ "src/app/main/ui/settings/password.cljs" ] - }, - "dashboard.num-of-members" : { - "translations" : { - "ca" : "%s membres", - "de" : "%s Mitglieder", - "en" : "%s members", - "es" : "%s integrantes", - "fr" : "%s membres", - "tr" : "%s üye", - "zh_cn" : "成员%s人" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "dashboard.open-in-new-tab" : { - "translations" : { - "de" : "Datei in neuem Tab öffnen", - "en" : "Open file in a new tab", - "es" : "Abrir en una pestaña nueva", - "tr" : "Dosyayı yeni sekmede aç" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.password-change" : { - "translations" : { - "ca" : "Canvia la contrasenya", - "de" : "Passwort ändern", - "en" : "Change password", - "es" : "Cambiar contraseña", - "fr" : "Changer le mot de passe", - "ru" : "Изменить пароль", - "tr" : "Parola değiştir", - "zh_cn" : "修改密码" - }, - "used-in" : [ "src/app/main/ui/settings/password.cljs" ] - }, - "dashboard.pin-unpin" : { - "translations" : { - "de" : "Anheften/Lösen", - "en" : "Pin/Unpin", - "es" : "Fijar/Desfijar", - "tr" : "Sabitle/Sabitleme" - }, - "used-in" : [ "src/app/main/ui/dashboard/project_menu.cljs" ] - }, - "dashboard.projects-title" : { - "translations" : { - "ca" : "Projectes", - "de" : "Projekte", - "en" : "Projects", - "es" : "Proyectos", - "fr" : "Projets", - "ru" : "Проекты", - "tr" : "Projeler", - "zh_cn" : "项目" - }, - "used-in" : [ "src/app/main/ui/dashboard/projects.cljs" ] - }, - "dashboard.promote-to-owner" : { - "translations" : { - "ca" : "Promoure a propietari", - "de" : "Zum Eigentümer befördern", - "en" : "Promote to owner", - "es" : "Promover a dueño", - "fr" : "Promouvoir propriétaire", - "tr" : "Sahibi olarak belirle", - "zh_cn" : "晋级为所有者" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "dashboard.remove-account" : { - "translations" : { - "ca" : "Vols esborrar el teu compte?", - "de" : "Möchten Sie Ihr Konto entfernen?", - "en" : "Want to remove your account?", - "es" : "¿Quieres borrar tu cuenta?", - "fr" : "Vous souhaitez supprimer votre compte ?", - "ru" : "Хотите удалить свой аккаунт?", - "tr" : "Hesabını silmek istediğinden emin misin?", - "zh_cn" : "希望注销您的账号?" - }, - "used-in" : [ "src/app/main/ui/settings/profile.cljs" ] - }, - "dashboard.remove-shared" : { - "translations" : { - "ca" : "Elimina com Biblioteca Compartida", - "de" : "Als gemeinsam genutzte Bibliothek entfernen", - "en" : "Remove as Shared Library", - "es" : "Eliminar como Biblioteca Compartida", - "fr" : "Retirer en tant que Bibliothèque Partagée", - "ru" : "", - "tr" : "Paylaşılan Kitaplık olarak sil", - "zh_cn" : "不再作为共享库" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.search-placeholder" : { - "translations" : { - "ca" : "Cerca…", - "de" : "Suchen…", - "en" : "Search…", - "es" : "Buscar…", - "fr" : "Rechercher…", - "ru" : "Поиск ", - "tr" : "Ara…", - "zh_cn" : "搜索…" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "dashboard.searching-for" : { - "translations" : { - "ca" : "S'está cercant “%s“…", - "de" : "Suche nach “%s“…", - "en" : "Searching for “%s“…", - "es" : "Buscando “%s“…", - "fr" : "Recherche de « %s »…", - "ru" : "Ищу “%s“…", - "tr" : "%s aranıyor", - "zh_cn" : "正在搜索“%s”" - }, - "used-in" : [ "src/app/main/ui/dashboard/search.cljs" ] - }, - "dashboard.select-ui-language" : { - "translations" : { - "ca" : "Selecciona la llengua de la interfície", - "de" : "Sprache der Benutzeroberfläche auswählen", - "en" : "Select UI language", - "es" : "Cambiar el idioma de la interfaz", - "fr" : "Sélectionnez la langue de l’interface", - "ru" : "Выберите язык интерфейса", - "tr" : "Arayüz dilini seç", - "zh_cn" : "选择界面语言" - }, - "used-in" : [ "src/app/main/ui/settings/options.cljs" ] - }, - "dashboard.select-ui-theme" : { - "translations" : { - "ca" : "Selecciona un tema", - "de" : "Theme auswählen", - "en" : "Select theme", - "es" : "Selecciona un tema", - "fr" : "Sélectionnez un thème", - "ru" : "Выберите тему", - "tr" : "Tema seç", - "zh_cn" : "选择界面主题" - }, - "used-in" : [ "src/app/main/ui/settings/options.cljs" ] - }, - "dashboard.show-all-files" : { - "translations" : { - "ca" : "Veure tots els fitxers", - "de" : "Alle Dateien anzeigen", - "en" : "Show all files", - "es" : "Ver todos los ficheros", - "fr" : "Voir tous les fichiers", - "tr" : "Tüm dosyaları göster", - "zh_cn" : "显示全部文档" - }, - "used-in" : [ "src/app/main/ui/dashboard/grid.cljs" ] - }, - "dashboard.success-delete-file" : { - "translations" : { - "de" : "Ihre Datei wurde erfolgreich gelöscht", - "en" : "Your file has been deleted successfully", - "es" : "Tu archivo ha sido borrado con éxito", - "tr" : "Dosyan başarıyla silindi" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.success-delete-project" : { - "translations" : { - "de" : "Ihr Projekt wurde erfolgreich gelöscht", - "en" : "Your project has been deleted successfully", - "es" : "Tu proyecto ha sido borrado con éxito", - "tr" : "Projen başarıyla silindi" - }, - "used-in" : [ "src/app/main/ui/dashboard/project_menu.cljs" ] - }, - "dashboard.success-duplicate-file" : { - "translations" : { - "de" : "Ihre Datei wurde erfolgreich dupliziert", - "en" : "Your file has been duplicated successfully", - "es" : "Tu archivo ha sido duplicado con éxito", - "tr" : "Dosyanın kopyası başarıyla oluşturuldu" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.success-duplicate-project" : { - "translations" : { - "de" : "Ihr Projekt wurde erfolgreich dupliziert", - "en" : "Your project has been duplicated successfully", - "es" : "Tu proyecto ha sido duplicado con éxito", - "tr" : "Projenin kopyası başarıyla oluşturuldu" - }, - "used-in" : [ "src/app/main/ui/dashboard/project_menu.cljs" ] - }, - "dashboard.success-move-file" : { - "translations" : { - "de" : "Ihre Datei wurde erfolgreich verschoben", - "en" : "Your file has been moved successfully", - "es" : "Tu archivo ha sido movido con éxito", - "tr" : "Dosyan başarıyla taşındı" - }, - "used-in" : [ "src/app/main/ui/dashboard/grid.cljs", "src/app/main/ui/dashboard/sidebar.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.success-move-files" : { - "translations" : { - "en" : "Your files has been moved successfully", - "es" : "Tus archivos han sido movidos con éxito", - "tr" : "Dosyaların başarıyla taşındı" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "dashboard.success-move-project" : { - "translations" : { - "de" : "Ihr Projekt wurde erfolgreich verschoben", - "en" : "Your project has been moved successfully", - "es" : "Tu proyecto ha sido movido con éxito", - "tr" : "Projen başarıyla taşındı" - }, - "used-in" : [ "src/app/main/ui/dashboard/project_menu.cljs" ] - }, - "dashboard.switch-team" : { - "translations" : { - "ca" : "Cambiar d'equip", - "de" : "Team wechseln", - "en" : "Switch team", - "es" : "Cambiar equipo", - "fr" : "Changer d’équipe", - "tr" : "Takım değiştir", - "zh_cn" : "切换团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "dashboard.team-info" : { - "translations" : { - "ca" : "Informació de l'equip", - "de" : "Teaminformationen", - "en" : "Team info", - "es" : "Información del equipo", - "fr" : "Information de l’équipe", - "tr" : "Takım bilgisi", - "zh_cn" : "团队信息" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "dashboard.team-members" : { - "translations" : { - "ca" : "Membres de l'equip", - "de" : "Teammitglieder", - "en" : "Team members", - "es" : "Integrantes del equipo", - "fr" : "Membres de l’équipe", - "tr" : "Takım üyeleri", - "zh_cn" : "团队成员" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "dashboard.team-projects" : { - "translations" : { - "ca" : "Projectes de l'equip", - "de" : "Teamprojekte", - "en" : "Team projects", - "es" : "Proyectos del equipo", - "fr" : "Projets de l’équipe", - "tr" : "Takım projeleri", - "zh_cn" : "团队项目" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "dashboard.theme-change" : { - "translations" : { - "ca" : "Tema de l'interfície", - "de" : "UI-Theme", - "en" : "UI theme", - "es" : "Tema visual", - "fr" : "Thème de l’interface", - "ru" : "Тема интерфейса пользователя", - "tr" : "Önyüz teması", - "zh_cn" : "界面主题" - }, - "used-in" : [ "src/app/main/ui/settings/options.cljs" ] - }, - "dashboard.title-search" : { - "translations" : { - "ca" : "Membres de l'equip", - "de" : "Suchergebnisse", - "en" : "Search results", - "es" : "Resultados de búsqueda", - "fr" : "Résultats de recherche", - "ru" : "Результаты поиска", - "tr" : "Arama sonuçları", - "zh_cn" : "搜索结果" - }, - "used-in" : [ "src/app/main/ui/dashboard/search.cljs" ] - }, - "dashboard.type-something" : { - "translations" : { - "ca" : "Escriu per cercar resultats", - "de" : "Zum Suchen etwas eingeben", - "en" : "Type to search results", - "es" : "Escribe algo para buscar", - "fr" : "Écrivez pour rechercher", - "ru" : "Введите для поиска", - "tr" : "Aramak için yazın", - "zh_cn" : "输入关键词进行搜索" - }, - "used-in" : [ "src/app/main/ui/dashboard/search.cljs" ] - }, - "dashboard.update-settings" : { - "translations" : { - "ca" : "Actualitzar opcions", - "de" : "Einstellungen aktualisieren", - "en" : "Update settings", - "es" : "Actualizar opciones", - "fr" : "Mettre à jour les paramètres", - "ru" : "Обновить настройки", - "tr" : "Ayarları güncelle", - "zh_cn" : "保存设置" - }, - "used-in" : [ "src/app/main/ui/settings/profile.cljs", "src/app/main/ui/settings/password.cljs", "src/app/main/ui/settings/options.cljs" ] - }, - "dashboard.your-account-title" : { - "translations" : { - "ca" : "El teu compte", - "de" : "Ihr Konto", - "en" : "Your account", - "es" : "Tu cuenta", - "fr" : "Votre compte", - "tr" : "Hesabın", - "zh_cn" : "你的账号" - }, - "used-in" : [ "src/app/main/ui/settings.cljs" ] - }, - "dashboard.your-email" : { - "translations" : { - "ca" : "Correu electrónic", - "de" : "E-Mail", - "en" : "Email", - "es" : "Correo", - "fr" : "E‑mail", - "ru" : "Email", - "tr" : "E-posta", - "zh_cn" : "电子邮件" - }, - "used-in" : [ "src/app/main/ui/settings/profile.cljs" ] - }, - "dashboard.your-name" : { - "translations" : { - "ca" : "El teu nom", - "de" : "Ihr Name", - "en" : "Your name", - "es" : "Tu nombre", - "fr" : "Votre nom complet", - "ru" : "Ваше имя", - "tr" : "Adın", - "zh_cn" : "你的姓名" - }, - "used-in" : [ "src/app/main/ui/settings/profile.cljs" ] - }, - "dashboard.your-penpot" : { - "translations" : { - "ca" : "El teu Penpot", - "de" : "Ihr Penpot", - "en" : "Your Penpot", - "es" : "Tu Penpot", - "fr" : "Votre Penpot", - "tr" : "Penpot'un", - "zh_cn" : "你的Penpot" - }, - "used-in" : [ "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" ] - }, - "ds.confirm-cancel" : { - "translations" : { - "ca" : "Cancel·lar", - "de" : "Abbrechen", - "en" : "Cancel", - "es" : "Cancelar", - "fr" : "Annuler", - "ru" : "Отмена", - "tr" : "Vazgeç", - "zh_cn" : "取消" - }, - "used-in" : [ "src/app/main/ui/confirm.cljs" ] - }, - "ds.confirm-ok" : { - "translations" : { - "ca" : "Ok", - "de" : "Ok", - "en" : "Ok", - "es" : "Ok", - "fr" : "Ok", - "ru" : "Ok", - "tr" : "Tamam", - "zh_cn" : "OK" - }, - "used-in" : [ "src/app/main/ui/confirm.cljs" ] - }, - "ds.confirm-title" : { - "translations" : { - "ca" : "Estàs segur?", - "de" : "Sind Sie sicher?", - "en" : "Are you sure?", - "es" : "¿Seguro?", - "fr" : "Êtes‑vous sûr ?", - "ru" : "Вы уверены?", - "tr" : "Emin misin?", - "zh_cn" : "你确定?" - }, - "used-in" : [ "src/app/main/ui/confirm.cljs", "src/app/main/ui/confirm.cljs" ] - }, - "ds.updated-at" : { - "translations" : { - "ca" : "Actualitzat: %s", - "de" : "Aktualisiert: %s", - "en" : "Updated: %s", - "es" : "Actualizado: %s", - "fr" : "Mise à jour : %s", - "ru" : "Обновлено: %s", - "tr" : "Güncellendi: %s", - "zh_cn" : "更新了:%s" - }, - "used-in" : [ "src/app/main/ui/dashboard/grid.cljs" ] - }, - "errors.clipboard-not-implemented" : { - "translations" : { - "ca" : "El teu navegador no pot realitzar aquesta operació", - "de" : "Ihr Browser kann diese Funktion nicht ausführen", - "en" : "Your browser cannot do this operation", - "es" : "Tu navegador no puede realizar esta operación", - "fr" : "Votre navigateur ne peut pas effectuer cette opération", - "ru" : "", - "tr" : "Tarayıcın bu işlemi gerçekleştiremiyor", - "zh_cn" : "你的浏览器不支持该操作" - }, - "used-in" : [ "src/app/main/data/workspace.cljs" ] - }, - "errors.email-already-exists" : { - "translations" : { - "ca" : "El correu ja està en ús", - "de" : "E-Mail-Adresse wird bereits verwendet", - "en" : "Email already used", - "es" : "Este correo ya está en uso", - "fr" : "Adresse e‑mail déjà utilisée", - "ru" : "Такой email уже используется", - "tr" : "E-posta zaten kullanımda", - "zh_cn" : "电子邮件已被占用" - }, - "used-in" : [ "src/app/main/ui/auth/verify_token.cljs", "src/app/main/ui/settings/change_email.cljs" ] - }, - "errors.email-already-validated" : { - "translations" : { - "ca" : "El correu ja està validat", - "de" : "E-Mail-Adresse wurde bereits validiert.", - "en" : "Email already validated.", - "es" : "Este correo ya está validado.", - "fr" : "Adresse e‑mail déjà validée.", - "ru" : "Электронная почта уже подтверждена.", - "tr" : "E-posta zaten doğrulandı", - "zh_cn" : "电子邮件已经验证通过" - }, - "used-in" : [ "src/app/main/ui/auth/verify_token.cljs" ] - }, - "errors.email-has-permanent-bounces" : { - "translations" : { - "ca" : "El correu «%s» té molts informes de rebot permanents", - "de" : "Die E-Mail-Adresse «%s» hat viele permanente Unzustellbarkeitsberichte.", - "en" : "The email «%s» has many permanent bounce reports.", - "es" : "El email «%s» tiene varios reportes de rebote permanente.", - "tr" : "«%s» adresi için çok fazla geri dönme raporu var.", - "zh_cn" : "电子邮件“%s”收到了非常多的永久退信报告" - }, - "used-in" : [ "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" ] - }, - "errors.email-invalid-confirmation" : { - "translations" : { - "ca" : "El correu de confirmació ha de coincidir", - "de" : "Bestätigungs-E-Mail muss übereinstimmen", - "en" : "Confirmation email must match", - "es" : "El correo de confirmación debe coincidir", - "fr" : "L’adresse e‑mail de confirmation doit correspondre", - "ru" : "Email для подтверждения должен совпадать", - "tr" : "Doğrulama e-postası eşleşmiyor", - "zh_cn" : "确认电子邮件必须保持一致" - }, - "used-in" : [ "src/app/main/ui/settings/change_email.cljs" ] - }, - "errors.generic" : { - "translations" : { - "ca" : "Alguna cosa ha anat malament", - "de" : "Etwas ist schief gelaufen.", - "en" : "Something wrong has happened.", - "es" : "Ha ocurrido algún error.", - "fr" : "Un problème s’est produit.", - "ru" : "Что-то пошло не так.", - "tr" : "Bir şeyler ters gitti.", - "zh_cn" : "发生了某种错误。" - }, - "used-in" : [ "src/app/main/ui/auth/verify_token.cljs", "src/app/main/ui/settings/feedback.cljs", "src/app/main/ui/dashboard/team.cljs" ] - }, - "errors.google-auth-not-enabled" : { - "translations" : { - "ca" : "L'autenticació amb google ha estat desactivada a aquest servidor", - "de" : "Die Authentifizierung mit Google ist im Backend deaktiviert", - "en" : "Authentication with google disabled on backend", - "es" : "Autenticación con google esta dehabilitada en el servidor", - "tr" : "Google ile oturum açma devre dışı bırakıldı", - "zh_cn" : "后端禁用了Google授权" - }, - "used-in" : [ "src/app/main/ui/auth/login.cljs" ] - }, - "errors.ldap-disabled" : { - "translations" : { - "de" : "Die LDAP-Authentifizierung ist deaktiviert.", - "en" : "LDAP authentication is disabled.", - "es" : "La autheticacion via LDAP esta deshabilitada.", - "tr" : "LDAP ile oturum açma devre dışı bırakıldı.", - "zh_cn" : "仅用了LDAP授权。" - }, - "used-in" : [ "src/app/main/ui/auth/login.cljs" ] - }, - "errors.media-format-unsupported" : { - "translations" : { - "ca" : "El format d'imatge no està suportat (deu ser svg, jpg o png),", - "de" : "Das Bildformat wird nicht unterstützt (es muss ein SVG, JPG oder PNG sein).", - "en" : "The image format is not supported (must be svg, jpg or png).", - "es" : "No se reconoce el formato de imagen (debe ser svg, jpg o png).", - "fr" : "Le format d’image n’est pas supporté (doit être svg, jpg ou png).", - "ru" : "Формат изображения не поддерживается (должен быть svg, jpg или png).", - "tr" : "Görsel formatı desteklenmiyor (svg, jpg veya png olmalı).", - "zh_cn" : "不支持该图片格式(只能是svg、jpg或png)。" - }, - "unused" : true - }, - "errors.media-too-large" : { - "translations" : { - "ca" : "La imatge es massa gran (ha de tenir menys de 5 mb).", - "de" : "Das Bild ist zu groß, um eingefügt zu werden (es muss unter 5MB sein).", - "en" : "The image is too large to be inserted (must be under 5mb).", - "es" : "La imagen es demasiado grande (debe tener menos de 5mb).", - "fr" : "L’image est trop grande (doit être inférieure à 5 Mo).", - "ru" : "Изображение слишком большое для вставки (должно быть меньше 5mb).", - "tr" : "Bu görsel eklemek için çok büyük (5MB altında olmalı)", - "zh_cn" : "图片尺寸过大,故无法插入(不能超过5MB)。" - }, - "used-in" : [ "src/app/main/data/workspace/persistence.cljs" ] - }, - "errors.media-type-mismatch" : { - "translations" : { - "ca" : "Sembla que el contingut de la imatge no coincideix amb l'extensió del arxiu", - "de" : "Es scheint, dass der Bildinhalt nicht mit der Dateierweiterung übereinstimmt.", - "en" : "Seems that the contents of the image does not match the file extension.", - "es" : "Parece que el contenido de la imagen no coincide con la extensión del archivo.", - "fr" : "Il semble que le contenu de l’image ne correspond pas à l’extension de fichier.", - "ru" : "", - "tr" : "Dosya içeriği, uzantısı ile eşleşmiyor gibi görünüyor.", - "zh_cn" : "图片内容好像与文档扩展名不匹配。" - }, - "used-in" : [ "src/app/main/data/workspace/persistence.cljs", "src/app/main/data/media.cljs" ] - }, - "errors.media-type-not-allowed" : { - "translations" : { - "ca" : "La imatge no sembla pas vàlida", - "de" : "Es scheint, dass dies kein gültiges Bild ist.", - "en" : "Seems that this is not a valid image.", - "es" : "Parece que no es una imagen válida.", - "fr" : "L’image ne semble pas être valide.", - "ru" : "", - "tr" : "Geçerli bir görsel gibi görünmüyor.", - "zh_cn" : "该图片好像不可用。" - }, - "used-in" : [ "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" ] - }, - "errors.member-is-muted" : { - "translations" : { - "ca" : "El perfil que estàs invitant té els emails mutejats (per informes de spam o rebots alts", - "de" : "In dem von Ihnen eingeladenen Profil sind E-Mails stummgeschaltet (Spam-Berichte oder hohe Unzustellbarkeitsberichte).", - "en" : "The profile you inviting has emails muted (spam reports or high bounces).", - "es" : "El perfil que esta invitando tiene los emails silenciados (por reportes de spam o alto indice de rebote).", - "tr" : "Davet ettiğiniz profilin e-posta adresine ait çok fazla geri dönme raporu var veya spam olarak bildirilmiş.", - "zh_cn" : "你邀请的人设置了邮件免打扰(报告垃圾邮件或者多次退信)。" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "errors.network" : { - "translations" : { - "ca" : "Impossible connectar amb el servidor principal", - "de" : "Es kann keine Verbindung zum Server hergestellt werden.", - "en" : "Unable to connect to backend server.", - "es" : "Ha sido imposible conectar con el servidor principal.", - "fr" : "Impossible de se connecter au serveur principal.", - "ru" : "Невозможно подключиться к серверу.", - "tr" : "Sunucuya bağlanılamıyor", - "zh_cn" : "无法连接到后端服务器。" - }, - "unused" : true - }, - "errors.password-invalid-confirmation" : { - "translations" : { - "ca" : "La contrasenya de confirmació ha de coincidir", - "de" : "Bestätigungspasswort muss übereinstimmen", - "en" : "Confirmation password must match", - "es" : "La contraseña de confirmación debe coincidir", - "fr" : "Le mot de passe de confirmation doit correspondre", - "ru" : "Пароль для подтверждения должен совпадать", - "tr" : "Parolalar eşleşmedi", - "zh_cn" : "确认密码必须保持一致。" - }, - "used-in" : [ "src/app/main/ui/settings/password.cljs" ] - }, - "errors.password-too-short" : { - "translations" : { - "ca" : "La contrasenya ha de tenir 8 com a mínim 8 caràcters", - "de" : "Das Passwort sollte mindestens 8 Zeichen lang sein", - "en" : "Password should at least be 8 characters", - "es" : "La contraseña debe tener 8 caracteres como mínimo", - "fr" : "Le mot de passe doit contenir au moins 8 caractères", - "ru" : "Пароль должен быть минимум 8 символов", - "tr" : "Parola en az 8 karakterden oluşmalı", - "zh_cn" : "密码最少需要8位字符。" - }, - "used-in" : [ "src/app/main/ui/settings/password.cljs" ] - }, - "errors.profile-is-muted" : { - "translations" : { - "ca" : "El teu perfil te els emails mutejats (per informes de spam o rebots alts).", - "de" : "Ihr Profil hat stummgeschaltete E-Mails (Spam-Berichte oder hohe Unzustellbarkeitsberichte).", - "en" : "Your profile has emails muted (spam reports or high bounces).", - "es" : "Tu perfil tiene los emails silenciados (por reportes de spam o alto indice de rebote).", - "zh_cn" : "你设置了邮件免打扰(报告垃圾邮件或者多次退信)。" - }, - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs", "src/app/main/ui/settings/change_email.cljs", "src/app/main/ui/dashboard/team.cljs" ] - }, - "errors.registration-disabled" : { - "translations" : { - "ca" : "El registre està desactivat actualment", - "de" : "Die Registrierung ist derzeit deaktiviert.", - "en" : "The registration is currently disabled.", - "es" : "El registro está actualmente desactivado.", - "fr" : "L’enregistrement est actuellement désactivé.", - "ru" : "Регистрация сейчас отключена.", - "zh_cn" : "当前禁止注册。" - }, - "used-in" : [ "src/app/main/ui/auth/register.cljs" ] - }, - "errors.terms-privacy-agreement-invalid" : { - "translations" : { - "en" : "You must accept our terms of service and privacy policy.", - "es" : "Debes aceptar nuestros términos de servicio y política de privacidad." - }, - "unused" : true - }, - "errors.unexpected-error" : { - "translations" : { - "ca" : "S'ha produït un error inesperat.", - "de" : "Ein unerwarteter Fehler ist aufgetreten.", - "en" : "An unexpected error occurred.", - "es" : "Ha ocurrido un error inesperado.", - "fr" : "Une erreur inattendue s’est produite", - "ru" : "Произошла ошибка.", - "zh_cn" : "发生了意料之外的错误。" - }, - "used-in" : [ "src/app/main/data/media.cljs", "src/app/main/ui/workspace/sidebar/options/menus/exports.cljs", "src/app/main/ui/handoff/exports.cljs" ] - }, - "errors.unexpected-token" : { - "translations" : { - "ca" : "Token desconegut", - "de" : "Unbekannter Token", - "en" : "Unknown token", - "es" : "Token desconocido", - "zh_cn" : "未知的TOKEN。" - }, - "used-in" : [ "src/app/main/ui/auth/verify_token.cljs" ] - }, - "errors.wrong-credentials" : { - "translations" : { - "ca" : "El nom d'usuari o la contrasenya sembla incorrecte", - "de" : "Der Benutzername oder das Passwort ist falsch.", - "en" : "Username or password seems to be wrong.", - "es" : "El nombre o la contraseña parece incorrecto.", - "fr" : "Le nom d’utilisateur ou le mot de passe semble être faux.", - "ru" : "Неверное имя пользователя или пароль.", - "zh_cn" : "用户名或密码错误。" - }, - "used-in" : [ "src/app/main/ui/auth/login.cljs" ] - }, - "errors.wrong-old-password" : { - "translations" : { - "ca" : "La contrasenya anterior no és correcte", - "de" : "Altes Passwort ist falsch", - "en" : "Old password is incorrect", - "es" : "La contraseña anterior no es correcta", - "fr" : "L’ancien mot de passe est incorrect", - "ru" : "Старый пароль неверный", - "zh_cn" : "旧密码不正确" - }, - "used-in" : [ "src/app/main/ui/settings/password.cljs" ] - }, - "feedback.chat-start" : { - "translations" : { - "ca" : "Uneix-te al xat.", - "de" : "Dem Chat beitreten", - "en" : "Join the chat", - "es" : "Unirse al chat", - "fr" : "Rejoindre le chat", - "zh_cn" : "加入聊天" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "feedback.chat-subtitle" : { - "translations" : { - "ca" : "Et ve de gust parlar? Xateja amb nosaltres a Gitter", - "de" : "Möchten Sie sprechen? Chatten Sie mit uns bei Gitter", - "en" : "Feeling like talking? Chat with us at Gitter", - "es" : "¿Deseas conversar? Entra al nuestro chat de la comunidad en Gitter", - "fr" : "Envie de parler? Discutez avec nous sur Gitter", - "zh_cn" : "想说两句?来Gitter和我们聊聊" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "feedback.description" : { - "translations" : { - "ca" : "Descripció", - "de" : "Beschreibung", - "en" : "Description", - "es" : "Descripción", - "fr" : "Description", - "zh_cn" : "描述" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "feedback.discussions-go-to" : { - "translations" : { - "ca" : "", - "de" : "Zur Diskussion", - "en" : "Go to discussions", - "es" : "Ir a las discusiones", - "fr" : "Aller aux discussions", - "zh_cn" : "前往讨论" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "feedback.discussions-subtitle1" : { - "translations" : { - "ca" : "Uneix-te al fòrum colaboratiu de Penpot.", - "de" : "Treten Sie dem kollaborativen Kommunikationsforum des Penpot-Teams bei.", - "en" : "Join Penpot team collaborative communication forum.", - "es" : "Entra al foro colaborativo de Penpot", - "fr" : "Rejoignez le forum de communication collaborative de l'équipe Penpot.", - "zh_cn" : "加入Penpot团队协作交流论坛。" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "feedback.discussions-subtitle2" : { - "translations" : { - "ca" : "Pots fer i respondre preguntes, tenir converses obertes i seguir les decisións que afecten al projecte", - "de" : "Sie können Fragen stellen und beantworten, offene Gespräche führen und Entscheidungen verfolgen, die das Projekt betreffen.", - "en" : "You can ask and answer questions, have open-ended conversations, and follow along on decisions affecting the project.", - "es" : "", - "fr" : "Vous pouvez poser des questions et y répondre, avoir des conversations ouvertes et suivre les décisions affectant le projet.", - "zh_cn" : "你可以提问、回答问题,来一场开放的对话,并对影响项目的决策保持关注。" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "feedback.discussions-title" : { - "translations" : { - "ca" : "", - "de" : "Teambesprechungen", - "en" : "Team discussions", - "es" : "", - "fr" : "Discussions en équipe", - "zh_cn" : "团队讨论" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "feedback.subject" : { - "translations" : { - "ca" : "Tema", - "de" : "Betreff", - "en" : "Subject", - "es" : "Asunto", - "fr" : "Sujet", - "zh_cn" : "话题" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "feedback.subtitle" : { - "translations" : { - "ca" : "Si us plau descriu la raó del teu correu, especificant si es una incidència, una idea o un dubte. Un membre del nostre equip respondrà tan aviat como pugui.", - "de" : "Bitte beschreiben Sie den Grund Ihrer E-Mail und geben Sie an, ob es sich um ein Problem, eine Idee oder einem Bedenken handelt. Ein Mitglied unseres Teams wird Ihnen so schnell wie möglich antworten.", - "en" : "Please describe the reason of your email, specifying if is an issue, an idea or a doubt. A member of our team will respond as soon as possible.", - "es" : "", - "fr" : "Veuillez décrire la raison de votre e-mail, en précisant s'il s'agit d'un problème, d'une idée ou d'un doute. Un membre de notre équipe vous répondra dans les plus brefs délais.", - "zh_cn" : "请说明你发邮件的原因,详细说明这是一个问题反馈、一个点子还是一个疑问。我们会尽快回复。" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "feedback.title" : { - "translations" : { - "ca" : "Correu electrònic", - "de" : "E-Mail", - "en" : "Email", - "es" : "Correo electrónico", - "fr" : "Email", - "ru" : "Email", - "zh_cn" : "电子邮件" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "generic.error" : { - "translations" : { - "ca" : "S'ha produït un error", - "de" : "Ein Fehler ist aufgetreten", - "en" : "An error has occurred", - "es" : "Ha ocurrido un error", - "fr" : "Une erreur s’est produite", - "ru" : "Произошла ошибка", - "zh_cn" : "发生了一个错误" - }, - "used-in" : [ "src/app/main/ui/settings/password.cljs" ] - }, - "handoff.attributes.blur" : { - "translations" : { - "de" : "Weichzeichnen", - "en" : "Blur", - "es" : "Desenfocado", - "fr" : "Flou", - "zh_cn" : "模糊" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/blur.cljs" ] - }, - "handoff.attributes.blur.value" : { - "translations" : { - "de" : "Wert", - "en" : "Value", - "es" : "Valor", - "fr" : "Valeur", - "zh_cn" : "值" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/blur.cljs" ] - }, - "handoff.attributes.color.hex" : { - "translations" : { - "de" : "HEX", - "en" : "HEX", - "es" : "HEX", - "fr" : "HEX", - "zh_cn" : "HEX" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/common.cljs" ] - }, - "handoff.attributes.color.hsla" : { - "translations" : { - "de" : "HSLA", - "en" : "HSLA", - "es" : "HSLA", - "fr" : "HSLA", - "zh_cn" : "HSLA" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/common.cljs" ] - }, - "handoff.attributes.color.rgba" : { - "translations" : { - "de" : "RGBA", - "en" : "RGBA", - "es" : "RGBA", - "fr" : "RGBA", - "zh_cn" : "RGBA" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/common.cljs" ] - }, - "handoff.attributes.fill" : { - "translations" : { - "de" : "Fläche", - "en" : "Fill", - "es" : "Relleno", - "fr" : "Remplir", - "zh_cn" : "填充" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/fill.cljs" ] - }, - "handoff.attributes.image.download" : { - "translations" : { - "de" : "Originalbild herunterladen", - "en" : "Download source image", - "es" : "Descargar imagen original", - "fr" : "Télécharger l’image source", - "zh_cn" : "下载原图" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs" ] - }, - "handoff.attributes.image.height" : { - "translations" : { - "de" : "Höhe", - "en" : "Height", - "es" : "Altura", - "fr" : "Hauteur", - "zh_cn" : "高" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs" ] - }, - "handoff.attributes.image.width" : { - "translations" : { - "de" : "Breite", - "en" : "Width", - "es" : "Ancho", - "fr" : "Largeur", - "zh_cn" : "宽" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs" ] - }, - "handoff.attributes.layout" : { - "translations" : { - "de" : "Layout", - "en" : "Layout", - "es" : "Estructura", - "fr" : "Mise en page", - "zh_cn" : "布局" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/layout.cljs" ] - }, - "handoff.attributes.layout.height" : { - "translations" : { - "de" : "Höhe", - "en" : "Height", - "es" : "Altura", - "fr" : "Hauteur", - "zh_cn" : "高" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/layout.cljs" ] - }, - "handoff.attributes.layout.left" : { - "translations" : { - "de" : "Links", - "en" : "Left", - "es" : "Izquierda", - "fr" : "Gauche", - "zh_cn" : "左" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/layout.cljs" ] - }, - "handoff.attributes.layout.radius" : { - "translations" : { - "de" : "Radius", - "en" : "Radius", - "es" : "Derecha", - "fr" : "Rayon", - "zh_cn" : "圆角" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/layout.cljs", "src/app/main/ui/handoff/attributes/layout.cljs" ] - }, - "handoff.attributes.layout.rotation" : { - "translations" : { - "de" : "Drehung", - "en" : "Rotation", - "es" : "Rotación", - "fr" : "Rotation", - "zh_cn" : "旋转" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/layout.cljs" ] - }, - "handoff.attributes.layout.top" : { - "translations" : { - "de" : "Oben", - "en" : "Top", - "es" : "Arriba", - "fr" : "Haut", - "zh_cn" : "顶" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/layout.cljs" ] - }, - "handoff.attributes.layout.width" : { - "translations" : { - "de" : "Breite", - "en" : "Width", - "es" : "Ancho", - "fr" : "Largeur", - "zh_cn" : "宽" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/layout.cljs" ] - }, - "handoff.attributes.shadow" : { - "translations" : { - "de" : "Schatten", - "en" : "Shadow", - "es" : "Sombra", - "fr" : "Ombre", - "zh_cn" : "阴影" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/shadow.cljs" ] - }, - "handoff.attributes.shadow.shorthand.blur" : { - "translations" : { - "de" : "B", - "en" : "B", - "es" : "B", - "fr" : "B", - "zh_cn" : "B" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/shadow.cljs" ] - }, - "handoff.attributes.shadow.shorthand.offset-x" : { - "translations" : { - "de" : "X", - "en" : "X", - "es" : "X", - "fr" : "X", - "zh_cn" : "X" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/shadow.cljs" ] - }, - "handoff.attributes.shadow.shorthand.offset-y" : { - "translations" : { - "de" : "Y", - "en" : "Y", - "es" : "Y", - "fr" : "Y", - "zh_cn" : "Y" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/shadow.cljs" ] - }, - "handoff.attributes.shadow.shorthand.spread" : { - "translations" : { - "de" : "S", - "en" : "S", - "es" : "S", - "fr" : "S", - "zh_cn" : "S" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/shadow.cljs" ] - }, - "handoff.attributes.stroke" : { - "translations" : { - "de" : "Rahmen", - "en" : "Stroke", - "es" : "Borde", - "fr" : "Contour", - "zh_cn" : "边框" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/stroke.cljs" ] - }, - "handoff.attributes.stroke.alignment.center" : { - "permanent" : true, - "translations" : { - "de" : "Zentriert", - "en" : "Center", - "es" : "Centro", - "fr" : "Centre", - "ru" : "Центр", - "zh_cn" : "居中" - }, - "used-in" : [ ] - }, - "handoff.attributes.stroke.alignment.inner" : { - "permanent" : true, - "translations" : { - "de" : "Innen", - "en" : "Inside", - "es" : "Interior", - "fr" : "Intérieur", - "ru" : "Внутрь", - "zh_cn" : "内部" - }, - "used-in" : [ ] - }, - "handoff.attributes.stroke.alignment.outer" : { - "permanent" : true, - "translations" : { - "de" : "Außen", - "en" : "Outside", - "es" : "Exterior", - "fr" : "Extérieur", - "ru" : "Наружу", - "zh_cn" : "外部" - }, - "used-in" : [ ] - }, - "handoff.attributes.stroke.style.dotted" : { - "translations" : { - "de" : "Gepunktet", - "en" : "Dotted", - "es" : "Punteado", - "fr" : "Pointillé", - "zh_cn" : "虚线" - }, - "unused" : true - }, - "handoff.attributes.stroke.style.mixed" : { - "translations" : { - "de" : "Mehrere", - "en" : "Mixed", - "es" : "Mixto", - "fr" : "Mixte", - "zh_cn" : "混合" - }, - "unused" : true - }, - "handoff.attributes.stroke.style.none" : { - "translations" : { - "de" : "Keine", - "en" : "None", - "es" : "Ninguno", - "fr" : "Aucun", - "zh_cn" : "无" - }, - "unused" : true - }, - "handoff.attributes.stroke.style.solid" : { - "translations" : { - "de" : "Solid", - "en" : "Solid", - "es" : "Sólido", - "fr" : "Solide", - "zh_cn" : "实线" - }, - "unused" : true - }, - "handoff.attributes.stroke.width" : { - "translations" : { - "de" : "Breite", - "en" : "Width", - "es" : "Ancho", - "fr" : "Épaisseur", - "zh_cn" : "宽" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/stroke.cljs" ] - }, - "handoff.attributes.typography" : { - "translations" : { - "de" : "Typografie", - "en" : "Typography", - "es" : "Tipografía", - "fr" : "Typographie", - "zh_cn" : "排版" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/text.cljs" ] - }, - "handoff.attributes.typography.font-family" : { - "translations" : { - "de" : "Schriftart", - "en" : "Font Family", - "es" : "Familia tipográfica", - "fr" : "Police de caractères", - "zh_cn" : "字体" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/text.cljs" ] - }, - "handoff.attributes.typography.font-size" : { - "translations" : { - "de" : "Schriftgröße", - "en" : "Font Size", - "es" : "Tamaño de fuente", - "fr" : "Taille de police", - "zh_cn" : "字号" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/text.cljs" ] - }, - "handoff.attributes.typography.font-style" : { - "translations" : { - "de" : "Schriftstil", - "en" : "Font Style", - "es" : "Estilo de fuente", - "fr" : "Style de police", - "zh_cn" : "文字风格" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/text.cljs" ] - }, - "handoff.attributes.typography.letter-spacing" : { - "translations" : { - "de" : "Zeichenabstand", - "en" : "Letter Spacing", - "es" : "Espaciado de letras", - "fr" : "Interlettrage", - "zh_cn" : "字距" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/text.cljs" ] - }, - "handoff.attributes.typography.line-height" : { - "translations" : { - "de" : "Zeilenabstand", - "en" : "Line Height", - "es" : "Interlineado", - "fr" : "Interlignage", - "zh_cn" : "行高" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/text.cljs" ] - }, - "handoff.attributes.typography.text-decoration" : { - "translations" : { - "de" : "Textdekoration", - "en" : "Text Decoration", - "es" : "Decoración de texto", - "fr" : "Décoration de texte", - "zh_cn" : "文字装饰" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/text.cljs" ] - }, - "handoff.attributes.typography.text-decoration.none" : { - "translations" : { - "de" : "Keine", - "en" : "None", - "es" : "Ninguna", - "fr" : "Aucune", - "zh_cn" : "无" - }, - "unused" : true - }, - "handoff.attributes.typography.text-decoration.strikethrough" : { - "translations" : { - "de" : "Durchgestrichen", - "en" : "Strikethrough", - "es" : "Tachar", - "fr" : "Barré", - "zh_cn" : "删除线" - }, - "unused" : true - }, - "handoff.attributes.typography.text-decoration.underline" : { - "translations" : { - "de" : "Unterstrichen", - "en" : "Underline", - "es" : "Subrayar", - "fr" : "Soulignage", - "zh_cn" : "下划线" - }, - "unused" : true - }, - "handoff.attributes.typography.text-transform" : { - "translations" : { - "de" : "Texttransformation", - "en" : "Text Transform", - "es" : "Transformación de texto", - "fr" : "Transformation de texte", - "zh_cn" : "文本变换" - }, - "used-in" : [ "src/app/main/ui/handoff/attributes/text.cljs" ] - }, - "handoff.attributes.typography.text-transform.lowercase" : { - "translations" : { - "de" : "Kleinbuchstaben", - "en" : "Lower Case", - "es" : "Minúsculas", - "fr" : "Minuscule", - "zh_cn" : "小写" - }, - "unused" : true - }, - "handoff.attributes.typography.text-transform.none" : { - "translations" : { - "de" : "Keine", - "en" : "None", - "es" : "Ninguna", - "fr" : "Aucune", - "zh_cn" : "无" - }, - "unused" : true - }, - "handoff.attributes.typography.text-transform.titlecase" : { - "translations" : { - "de" : "Kapitälchen", - "en" : "Title Case", - "es" : "Primera en mayúscula", - "fr" : "Premières Lettres en Capitales", - "zh_cn" : "首字母大写" - }, - "unused" : true - }, - "handoff.attributes.typography.text-transform.uppercase" : { - "translations" : { - "de" : "Großbuchstaben", - "en" : "Upper Case", - "es" : "Mayúsculas", - "fr" : "Capitales", - "zh_cn" : "大写" - }, - "unused" : true - }, - "handoff.tabs.code" : { - "translations" : { - "de" : "Code", - "en" : "Code", - "es" : "Código", - "fr" : "Code", - "zh_cn" : "码" - }, - "used-in" : [ "src/app/main/ui/handoff/right_sidebar.cljs" ] - }, - "handoff.tabs.code.selected.circle" : { - "translations" : { - "de" : "Kreis", - "en" : "Circle", - "es" : "Círculo", - "fr" : "Cercle", - "zh_cn" : "圆" - }, - "unused" : true - }, - "handoff.tabs.code.selected.curve" : { - "translations" : { - "de" : "Kurve", - "en" : "Curve", - "es" : "Curva", - "fr" : "Courbe", - "zh_cn" : "曲线" - }, - "unused" : true - }, - "handoff.tabs.code.selected.frame" : { - "translations" : { - "de" : "Zeichenfläche", - "en" : "Artboard", - "es" : "Mesa de trabajo", - "fr" : "Plan de travail", - "zh_cn" : "画板" - }, - "unused" : true - }, - "handoff.tabs.code.selected.group" : { - "translations" : { - "de" : "Gruppe", - "en" : "Group", - "es" : "Grupo", - "fr" : "Groupe", - "zh_cn" : "编组" - }, - "unused" : true - }, - "handoff.tabs.code.selected.image" : { - "translations" : { - "de" : "Bild", - "en" : "Image", - "es" : "Imagen", - "fr" : "Image", - "zh_cn" : "图片" - }, - "unused" : true - }, - "handoff.tabs.code.selected.multiple" : { - "translations" : { - "de" : "%s Ausgewählt(e)", - "en" : "%s Selected", - "es" : "%s Seleccionado", - "fr" : "%s Sélectionné", - "zh_cn" : "已选中%s项" - }, - "used-in" : [ "src/app/main/ui/handoff/right_sidebar.cljs" ] - }, - "handoff.tabs.code.selected.path" : { - "translations" : { - "de" : "Pfad", - "en" : "Path", - "es" : "Trazado", - "fr" : "Chemin", - "zh_cn" : "路径" - }, - "unused" : true - }, - "handoff.tabs.code.selected.rect" : { - "translations" : { - "de" : "Rechteck", - "en" : "Rectangle", - "es" : "Rectángulo", - "fr" : "Rectangle", - "zh_cn" : "矩形" - }, - "unused" : true - }, - "handoff.tabs.code.selected.svg-raw" : { - "translations" : { - "de" : "SVG", - "en" : "SVG", - "es" : "SVG", - "fr" : "SVG", - "zh_cn" : "SVG" - }, - "unused" : true - }, - "handoff.tabs.code.selected.text" : { - "translations" : { - "de" : "Text", - "en" : "Text", - "es" : "Texto", - "fr" : "Texte", - "zh_cn" : "文本" - }, - "unused" : true - }, - "handoff.tabs.info" : { - "translations" : { - "de" : "Info", - "en" : "Info", - "es" : "Información", - "fr" : "Information", - "zh_cn" : "信息" - }, - "used-in" : [ "src/app/main/ui/handoff/right_sidebar.cljs" ] - }, - "history.alert-message" : { - "translations" : { - "de" : "Sie sehen Version %s", - "en" : "You are seeing version %s", - "es" : "Estás viendo la versión %s", - "fr" : "Vous voyez la version %s", - "ru" : "Ваша версия %s", - "zh_cn" : "你正在查看%s版本" - }, - "unused" : true - }, - "labels.accept" : { - "translations" : { - "ca" : "Acceptar", - "de" : "Akzeptieren", - "en" : "Accept", - "es" : "Aceptar", - "fr" : "Accepter", - "ru" : "Принять", - "zh_cn" : "接受" - }, - "unused" : true - }, - "labels.admin" : { - "translations" : { - "de" : "Admin", - "en" : "Admin", - "es" : "Administración", - "fr" : "Administration", - "zh_cn" : "管理员" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs", "src/app/main/ui/dashboard/team.cljs", "src/app/main/ui/dashboard/team.cljs" ] - }, - "labels.all" : { - "translations" : { - "de" : "Alles", - "en" : "All", - "es" : "Todo", - "fr" : "Tous", - "zh_cn" : "全部" - }, - "used-in" : [ "src/app/main/ui/workspace/comments.cljs" ] - }, - "labels.bad-gateway.desc-message" : { - "translations" : { - "de" : "Sie müssen ein wenig warten und es erneut versuchen. Wir führen eine kurze Wartung an unseren Servern durch.", - "en" : "Looks like you need to wait a bit and retry; we are performing small maintenance of our servers.", - "es" : "Parece que necesitas esperar un poco y volverlo a intentar; estamos realizando operaciones de mantenimiento en nuestros servidores.", - "fr" : "Il semble que vous deviez attendre un peu et réessayer ; nous effectuons une petite maintenance de nos serveurs.", - "zh_cn" : "请过会儿再来试试,我们正在对服务器进行一些简单维护。" - }, - "used-in" : [ "src/app/main/ui/static.cljs" ] - }, - "labels.bad-gateway.main-message" : { - "translations" : { - "de" : "Bad Gateway", - "en" : "Bad Gateway", - "es" : "Bad Gateway", - "fr" : "Bad Gateway", - "zh_cn" : "网关错误" - }, - "used-in" : [ "src/app/main/ui/static.cljs" ] - }, - "labels.cancel" : { - "translations" : { - "de" : "Abbrechen", - "en" : "Cancel", - "es" : "Cancelar", - "fr" : "Annuler", - "ru" : "Отмена", - "zh_cn" : "取消" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "labels.centered" : { - "translations" : { - "de" : "Zentriert", - "en" : "Center", - "es" : "Centrado", - "fr" : "Centré", - "zh_cn" : "中心" - }, - "unused" : true - }, - "labels.comments" : { - "translations" : { - "de" : "Kommentare", - "en" : "Comments", - "es" : "Comentarios", - "fr" : "Commentaires", - "zh_cn" : "评论" - }, - "used-in" : [ "src/app/main/ui/dashboard/comments.cljs" ] - }, - "labels.confirm-password" : { - "translations" : { - "de" : "Passwort bestätigen", - "en" : "Confirm password", - "es" : "Confirmar contraseña", - "fr" : "Confirmer le mot de passe", - "ru" : "Подтвердите пароль", - "zh_cn" : "确认密码" - }, - "used-in" : [ "src/app/main/ui/settings/password.cljs" ] - }, - "labels.content" : { - "translations" : { - "de" : "Inhalt", - "en" : "Content", - "es" : "Contenido", - "fr" : "Contenu", - "zh_cn" : "内容" - }, - "unused" : true - }, - "labels.create-team" : { - "translations" : { - "de" : "Neues Team erstellen", - "en" : "Create new team", - "es" : "Crea un nuevo equipo", - "fr" : "Créer nouvelle équipe", - "zh_cn" : "创建新团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/team_form.cljs", "src/app/main/ui/dashboard/team_form.cljs" ] - }, - "labels.create-team.placeholder" : { - "translations" : { - "en" : "Enter new team name" - }, - "used-in" : [ "src/app/main/ui/dashboard/team_form.cljs" ] - }, - "labels.dashboard" : { - "translations" : { - "de" : "Dashboard", - "en" : "Dashboard", - "es" : "Panel", - "fr" : "Tableau de bord", - "zh_cn" : "面板" - }, - "used-in" : [ "src/app/main/ui/settings/sidebar.cljs" ] - }, - "labels.delete" : { - "translations" : { - "de" : "Löschen", - "en" : "Delete", - "es" : "Borrar", - "fr" : "Supprimer", - "ru" : "Удалить", - "zh_cn" : "删除" - }, - "used-in" : [ "src/app/main/ui/dashboard/project_menu.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "labels.delete-comment" : { - "translations" : { - "de" : "Kommentar löschen", - "en" : "Delete comment", - "es" : "Eliminar comentario", - "fr" : "Supprimer le commentaire", - "zh_cn" : "删除该评论" - }, - "used-in" : [ "src/app/main/ui/comments.cljs" ] - }, - "labels.delete-comment-thread" : { - "translations" : { - "de" : "Thread löschen", - "en" : "Delete thread", - "es" : "Eliminar hilo", - "fr" : "Supprimer le fil", - "zh_cn" : "删除该讨论串" - }, - "used-in" : [ "src/app/main/ui/comments.cljs" ] - }, - "labels.delete-multi-files" : { - "translations" : { - "en" : "Delete %s files", - "es" : "Borrar %s archivos" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "labels.drafts" : { - "translations" : { - "de" : "Entwürfe", - "en" : "Drafts", - "es" : "Borradores", - "fr" : "Brouillons", - "ru" : "Черновики", - "zh_cn" : "草稿" - }, - "used-in" : [ "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" ] - }, - "labels.edit" : { - "translations" : { - "de" : "Bearbeiten", - "en" : "Edit", - "es" : "Editar", - "fr" : "Modifier", - "zh_cn" : "编辑" - }, - "used-in" : [ "src/app/main/ui/comments.cljs" ] - }, - "labels.editor" : { - "translations" : { - "de" : "Editor", - "en" : "Editor", - "es" : "Editor", - "fr" : "Éditeur", - "zh_cn" : "编辑者" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs", "src/app/main/ui/dashboard/team.cljs", "src/app/main/ui/dashboard/team.cljs" ] - }, - "labels.email" : { - "translations" : { - "de" : "E-Mail", - "en" : "Email", - "es" : "Correo electrónico", - "fr" : "Adresse e‑mail", - "ru" : "Email", - "zh_cn" : "电子邮件" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs", "src/app/main/ui/dashboard/team.cljs" ] - }, - "labels.feedback-disabled" : { - "translations" : { - "de" : "Feedback deaktiviert", - "en" : "Feedback disabled", - "es" : "El modulo de recepción de opiniones esta deshabilitado.", - "zh_cn" : "反馈被禁止" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "labels.feedback-sent" : { - "translations" : { - "de" : "Feedback gesendet", - "en" : "Feedback sent", - "es" : "Opinión enviada", - "zh_cn" : "反馈已发出" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "labels.give-feedback" : { - "translations" : { - "de" : "Feedback geben", - "en" : "Give feedback", - "es" : "Danos tu opinión", - "fr" : "Donnez votre avis", - "ru" : "Дать обратную связь", - "zh_cn" : "提交反馈" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs", "src/app/main/ui/settings/sidebar.cljs", "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "labels.hide-resolved-comments" : { - "translations" : { - "de" : "Erledigte Kommentare ausblenden", - "en" : "Hide resolved comments", - "es" : "Ocultar comentarios resueltos", - "fr" : "Masquer les commentaires résolus", - "zh_cn" : "隐藏已决定的评论" - }, - "used-in" : [ "src/app/main/ui/workspace/comments.cljs", "src/app/main/ui/viewer/header.cljs" ] - }, - "labels.icons" : { - "translations" : { - "de" : "Icons", - "en" : "Icons", - "es" : "Iconos", - "fr" : "Icônes", - "ru" : "Иконки", - "zh_cn" : "图标" - }, - "unused" : true - }, - "labels.images" : { - "translations" : { - "de" : "Bilder", - "en" : "Images", - "es" : "Imágenes", - "fr" : "Images", - "ru" : "Изображения", - "zh_cn" : "图片" - }, - "unused" : true - }, - "labels.internal-error.desc-message" : { - "translations" : { - "de" : "Etwas ist schiefgegangen. Bitte versuchen Sie den Vorgang erneut und wenn das Problem weiterhin besteht, kontaktieren Sie den Support.", - "en" : "Something bad happened. Please retry the operation and if the problem persists, contact with support.", - "es" : "Ha ocurrido algo extraño. Por favor, reintenta la operación, y si el problema persiste, contacta con el servicio técnico.", - "fr" : "Un problème s’est produit. Veuillez réessayer l’opération et, si le problème persiste, contacter le service technique.", - "zh_cn" : "发生了一些不妙的事。请尝试重新操作。如果问题仍然存在,请联系我们以取得支持。" - }, - "used-in" : [ "src/app/main/ui/static.cljs" ] - }, - "labels.internal-error.main-message" : { - "translations" : { - "de" : "Interner Fehler", - "en" : "Internal Error", - "es" : "Error interno", - "fr" : "Erreur interne", - "zh_cn" : "内部错误" - }, - "used-in" : [ "src/app/main/ui/static.cljs" ] - }, - "labels.language" : { - "translations" : { - "de" : "Sprache", - "en" : "Language", - "es" : "Idioma", - "fr" : "Langue", - "ru" : "Язык", - "zh_cn" : "语言" - }, - "used-in" : [ "src/app/main/ui/settings/options.cljs" ] - }, - "labels.logout" : { - "translations" : { - "de" : "Abmelden", - "en" : "Logout", - "es" : "Salir", - "fr" : "Se déconnecter", - "ru" : "Выход", - "zh_cn" : "登出" - }, - "used-in" : [ "src/app/main/ui/settings.cljs", "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "labels.members" : { - "translations" : { - "de" : "Mitglieder", - "en" : "Members", - "es" : "Integrantes", - "fr" : "Membres", - "zh_cn" : "成员" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs", "src/app/main/ui/dashboard/team.cljs", "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "labels.name" : { - "translations" : { - "de" : "Name", - "en" : "Name", - "es" : "Nombre", - "fr" : "Nom", - "ru" : "Имя", - "zh_cn" : "名字" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "labels.new-password" : { - "translations" : { - "de" : "Neues Passwort", - "en" : "New password", - "es" : "Nueva contraseña", - "fr" : "Nouveau mot de passe", - "ru" : "Новый пароль", - "zh_cn" : "新密码" - }, - "used-in" : [ "src/app/main/ui/settings/password.cljs" ] - }, - "labels.no-comments-available" : { - "translations" : { - "de" : "Sie haben keine ausstehenden Kommentarbenachrichtigungen", - "en" : "You have no pending comment notifications", - "es" : "No tienes notificaciones de comentarios pendientes", - "fr" : "Vous n’avez aucune notification de commentaire en attente", - "zh_cn" : "没有待表决的评论通知" - }, - "used-in" : [ "src/app/main/ui/workspace/comments.cljs", "src/app/main/ui/dashboard/comments.cljs" ] - }, - "labels.not-found.auth-info" : { - "translations" : { - "de" : "Sie sind angemeldet als", - "en" : "You’re signed in as", - "es" : "Estás identificado como", - "fr" : "Vous êtes connecté en tant que", - "zh_cn" : "你已登陆为" - }, - "used-in" : [ "src/app/main/ui/static.cljs" ] - }, - "labels.not-found.desc-message" : { - "translations" : { - "de" : "Möglicherweise existiert diese Seite nicht oder Sie haben keine Zugriffsrechte darauf.", - "en" : "This page might not exist or you don’t have permissions to access to it.", - "es" : "Esta página no existe o no tienes permisos para verla.", - "fr" : "Cette page n’existe pas ou vous ne disposez pas des permissions nécessaires pour y accéder.", - "zh_cn" : "可能该页面不存在,也可能你没有访问权限。" - }, - "used-in" : [ "src/app/main/ui/static.cljs" ] - }, - "labels.not-found.main-message" : { - "translations" : { - "de" : "Ups!", - "en" : "Oops!", - "es" : "¡Huy!", - "fr" : "Oups !", - "zh_cn" : "嚯!" - }, - "used-in" : [ "src/app/main/ui/static.cljs" ] - }, - "labels.num-of-files" : { - "translations" : { - "de" : [ "1 Datei", "%s Dateien" ], - "en" : [ "1 file", "%s files" ], - "es" : [ "1 archivo", "%s archivos" ], - "fr" : [ "1 fichier", "%s fichiers" ], - "zh_cn" : [ "1 个文档", "共 %s 个文档" ] - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "labels.num-of-projects" : { - "translations" : { - "de" : [ "1 Projekt", "%s Projekte" ], - "en" : [ "1 project", "%s projects" ], - "es" : [ "1 proyecto", "%s proyectos" ], - "fr" : [ "1 projet", "%s projets" ], - "zh_cn" : [ "1 个项目", "共 %s 个项目" ] - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "labels.old-password" : { - "translations" : { - "de" : "Altes Passwort", - "en" : "Old password", - "es" : "Contraseña anterior", - "fr" : "Ancien mot de passe", - "ru" : "Старый пароль", - "zh_cn" : "旧密码" - }, - "used-in" : [ "src/app/main/ui/settings/password.cljs" ] - }, - "labels.only-yours" : { - "translations" : { - "de" : "Nur Ihre", - "en" : "Only yours", - "es" : "Sólo los tuyos", - "fr" : "Seulement les vôtres", - "zh_cn" : "仅你的" - }, - "used-in" : [ "src/app/main/ui/workspace/comments.cljs" ] - }, - "labels.owner" : { - "translations" : { - "de" : "Eigentümer", - "en" : "Owner", - "es" : "Dueño", - "fr" : "Propriétaire", - "zh_cn" : "所有者" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs", "src/app/main/ui/dashboard/team.cljs" ] - }, - "labels.password" : { - "translations" : { - "de" : "Passwort", - "en" : "Password", - "es" : "Contraseña", - "fr" : "Mot de passe", - "ru" : "Пароль", - "zh_cn" : "密码" - }, - "used-in" : [ "src/app/main/ui/settings/sidebar.cljs", "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "labels.permissions" : { - "translations" : { - "de" : "Berechtigungen", - "en" : "Permissions", - "es" : "Permisos", - "fr" : "Permissions", - "zh_cn" : "许可" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "labels.profile" : { - "translations" : { - "de" : "Profil", - "en" : "Profile", - "es" : "Perfil", - "fr" : "Profil", - "ru" : "Профиль", - "zh_cn" : "个人资料" - }, - "used-in" : [ "src/app/main/ui/settings/sidebar.cljs", "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "labels.projects" : { - "translations" : { - "de" : "Projekte", - "en" : "Projects", - "es" : "Proyectos", - "fr" : "Projets", - "ru" : "Проекты", - "zh_cn" : "项目" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "labels.recent" : { - "translations" : { - "ca" : "Recent", - "de" : "Zuletzt", - "en" : "Recent", - "es" : "Reciente", - "fr" : "Récent", - "ru" : "Недавние", - "zh_cn" : "最近" - }, - "unused" : true - }, - "labels.release-notes" : { - "translations" : { - "de" : "Versionshinweise", - "en" : "Release notes", - "es" : "Notas de versión", - "fr" : "Notes de version", - "ru" : "примечания к выпуску", - "zh_cn" : "發行說明" - }, - "used-in" : [ "src/app/main/ui/settings/sidebar.cljs" ] - }, - "labels.remove" : { - "translations" : { - "de" : "Entfernen", - "en" : "Remove", - "es" : "Quitar", - "fr" : "Retirer", - "ru" : "", - "zh_cn" : "移除" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs", "src/app/main/ui/dashboard/team.cljs" ] - }, - "labels.rename" : { - "translations" : { - "de" : "Umbenennen", - "en" : "Rename", - "es" : "Renombrar", - "fr" : "Renommer", - "zh_cn" : "重命名" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs", "src/app/main/ui/dashboard/project_menu.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "labels.rename-team" : { - "translations" : { - "de" : "Team umbenennen", - "en" : "Rename team", - "es" : "Renomba el equipo", - "zh_cn" : "重命名团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/team_form.cljs" ] - }, - "labels.retry" : { - "translations" : { - "de" : "Wiederholen", - "en" : "Retry", - "es" : "Reintentar", - "fr" : "Réessayer", - "zh_cn" : "重试" - }, - "used-in" : [ "src/app/main/ui/static.cljs", "src/app/main/ui/static.cljs", "src/app/main/ui/static.cljs" ] - }, - "labels.role" : { - "translations" : { - "de" : "Rolle", - "en" : "Role", - "es" : "Cargo", - "fr" : "Rôle", - "zh_cn" : "角色" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "labels.save" : { - "translations" : { - "ca" : "Desa", - "de" : "Speichern", - "en" : "Save", - "es" : "Guardar", - "fr" : "Enregistrer", - "ru" : "Сохранить", - "zh_cn" : "保存" - }, - "unused" : true - }, - "labels.send" : { - "translations" : { - "de" : "Senden", - "en" : "Send", - "es" : "Enviar", - "zh_cn" : "发送" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "labels.sending" : { - "translations" : { - "de" : "Senden…", - "en" : "Sending...", - "es" : "Enviando...", - "zh_cn" : "正在发送…" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "labels.service-unavailable.desc-message" : { - "translations" : { - "de" : "Wir befinden uns in der planmäßigen Wartung unserer Systeme.", - "en" : "We are in programmed maintenance of our systems.", - "es" : "Estamos en una operación de mantenimiento programado de nuestros sistemas.", - "fr" : "Nous sommes en maintenance planifiée de nos systèmes.", - "zh_cn" : "我们正在进行系统的程序维护。" - }, - "used-in" : [ "src/app/main/ui/static.cljs" ] - }, - "labels.service-unavailable.main-message" : { - "translations" : { - "de" : "Service nicht verfügbar", - "en" : "Service Unavailable", - "es" : "El servicio no está disponible", - "fr" : "Service non disponible", - "zh_cn" : "服务不可用" - }, - "used-in" : [ "src/app/main/ui/static.cljs" ] - }, - "labels.settings" : { - "translations" : { - "de" : "Einstellungen", - "en" : "Settings", - "es" : "Configuración", - "fr" : "Configuration", - "ru" : "Параметры", - "zh_cn" : "设置" - }, - "used-in" : [ "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" ] - }, - "labels.shared-libraries" : { - "translations" : { - "de" : "Gemeinsam genutzte Bibliotheken", - "en" : "Shared Libraries", - "es" : "Bibliotecas Compartidas", - "fr" : "Bibliothèques Partagées", - "ru" : "", - "zh_cn" : "共享库" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "labels.show-all-comments" : { - "translations" : { - "de" : "Alle Kommentare anzeigen", - "en" : "Show all comments", - "es" : "Mostrar todos los comentarios", - "fr" : "Afficher tous les commentaires", - "zh_cn" : "显示所有评论" - }, - "used-in" : [ "src/app/main/ui/workspace/comments.cljs", "src/app/main/ui/viewer/header.cljs" ] - }, - "labels.show-your-comments" : { - "translations" : { - "de" : "Nur eigene Kommentare anzeigen", - "en" : "Show only yours comments", - "es" : "Mostrar sólo tus comentarios", - "fr" : "Afficher uniquement vos commentaires", - "zh_cn" : "只显示你的评论" - }, - "used-in" : [ "src/app/main/ui/workspace/comments.cljs", "src/app/main/ui/viewer/header.cljs" ] - }, - "labels.sign-out" : { - "translations" : { - "de" : "Abmelden", - "en" : "Sign out", - "es" : "Salir", - "fr" : "Se déconnecter", - "ru" : "Выход", - "zh_cn" : "登出" - }, - "used-in" : [ "src/app/main/ui/static.cljs" ] - }, - "labels.update" : { - "translations" : { - "de" : "Aktualisieren", - "en" : "Update", - "es" : "Actualizar", - "fr" : "Actualiser", - "ru" : "Обновить", - "zh_cn" : "更新" - }, - "used-in" : [ "src/app/main/ui/settings/profile.cljs" ] - }, - "labels.update-team" : { - "translations" : { - "de" : "Team aktualisieren", - "en" : "Update team", - "es" : "Actualiza el equipo", - "zh_cn" : "更新团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/team_form.cljs" ] - }, - "labels.viewer" : { - "translations" : { - "de" : "Zuschauer", - "en" : "Viewer", - "es" : "Visualizador", - "fr" : "Spectateur", - "zh_cn" : "查看者" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "labels.write-new-comment" : { - "translations" : { - "de" : "Neuen Kommentar schreiben", - "en" : "Write new comment", - "es" : "Escribir un nuevo comentario", - "fr" : "Écrire un nouveau commentaire", - "zh_cn" : "写一条新评论" - }, - "used-in" : [ "src/app/main/ui/comments.cljs" ] - }, - "media.loading" : { - "translations" : { - "de" : "Bild laden…", - "en" : "Loading image…", - "es" : "Cargando imagen…", - "fr" : "Chargement de l’image…", - "ru" : "Загружаю изображение…", - "zh_cn" : "正在加载图片…" - }, - "used-in" : [ "src/app/main/data/workspace/persistence.cljs", "src/app/main/data/workspace/persistence.cljs", "src/app/main/data/media.cljs" ] - }, - "modals.add-shared-confirm.accept" : { - "translations" : { - "de" : "Hinzufügen als gemeinsam genutzte Bibliothek", - "en" : "Add as Shared Library", - "es" : "Añadir como Biblioteca Compartida", - "fr" : "Ajouter comme Bibliothèque Partagée", - "ru" : "", - "zh_cn" : "添加为共享库" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.add-shared-confirm.hint" : { - "translations" : { - "de" : "Sobald es als gemeinsam genutzte Bibliothek hinzugefügt wurde, können die Assets dieser Dateibibliothek von den anderen Dateien verwendet werden.", - "en" : "Once added as Shared Library, the assets of this file library will be available to be used among the rest of your files.", - "es" : "Una vez añadido como Biblioteca Compartida, los recursos de este archivo estarán disponibles para ser usado por el resto de tus archivos.", - "fr" : "Une fois ajoutées en tant que Bibliothèque Partagée, les ressources de cette bibliothèque de fichiers seront disponibles pour être utilisées parmi le reste de vos fichiers.", - "ru" : "", - "zh_cn" : "一旦添加为共享库,此文档库中的素材就可被用于你的其他文档中。" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.add-shared-confirm.message" : { - "translations" : { - "de" : "Hinzufügen von “%s” als gemeinsam genutzte Bibliothek", - "en" : "Add “%s” as Shared Library", - "es" : "Añadir “%s” como Biblioteca Compartida", - "fr" : "Ajouter « %s » comme Bibliothèque Partagée", - "ru" : "", - "zh_cn" : "将“%s”添加为共享库" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.change-email.confirm-email" : { - "translations" : { - "de" : "Neue E-Mail-Adresse verifizieren", - "en" : "Verify new email", - "es" : "Verificar el nuevo correo", - "fr" : "Vérifier la nouvelle adresse e‑mail", - "ru" : "Подтвердить новый email адрес", - "zh_cn" : "验证新的邮件" - }, - "used-in" : [ "src/app/main/ui/settings/change_email.cljs" ] - }, - "modals.change-email.info" : { - "translations" : { - "de" : "Wir senden Ihnen eine Nachricht an Ihre aktuelle E-Mail-Adresse “%s”, um Ihre Identität zu überprüfen.", - "en" : "We'll send you an email to your current email “%s” to verify your identity.", - "es" : "Enviaremos un mensaje a tu correo actual “%s” para verificar tu identidad.", - "fr" : "Nous enverrons un e‑mail à votre adresse actuelle « %s » pour vérifier votre identité.", - "ru" : "Мы отправим письмо для подтверждения подлиности на текущий email адрес “%s”.", - "zh_cn" : "我们会发送一封信的邮件到当前的电子邮件“%s”,以验证你的身份。" - }, - "used-in" : [ "src/app/main/ui/settings/change_email.cljs" ] - }, - "modals.change-email.new-email" : { - "translations" : { - "de" : "Neue E-Mail-Adresse", - "en" : "New email", - "es" : "Nuevo correo", - "fr" : "Nouvel e‑mail", - "ru" : "Новый email адрес", - "zh_cn" : "新电子邮件" - }, - "used-in" : [ "src/app/main/ui/settings/change_email.cljs" ] - }, - "modals.change-email.submit" : { - "translations" : { - "de" : "E-Mail-Adresse ändern", - "en" : "Change email", - "es" : "Cambiar correo", - "fr" : "Changer adresse e‑mail", - "ru" : "Сменить email адрес", - "zh_cn" : "修改电子邮件" - }, - "used-in" : [ "src/app/main/ui/settings/change_email.cljs" ] - }, - "modals.change-email.title" : { - "translations" : { - "de" : "Ihre E-Mail-Adresse ändern", - "en" : "Change your email", - "es" : "Cambiar tu correo", - "fr" : "Changez votre adresse e‑mail", - "ru" : "Сменить email адрес", - "zh_cn" : "修改你的电子邮件" - }, - "used-in" : [ "src/app/main/ui/settings/change_email.cljs" ] - }, - "modals.delete-account.cancel" : { - "translations" : { - "de" : "Abbrechen und mein Konto behalten", - "en" : "Cancel and keep my account", - "es" : "Cancelar y mantener mi cuenta", - "fr" : "Annuler et conserver mon compte", - "ru" : "Отменить и сохранить мой аккаунт", - "zh_cn" : "取消操作并保留我的账号" - }, - "used-in" : [ "src/app/main/ui/settings/delete_account.cljs" ] - }, - "modals.delete-account.confirm" : { - "translations" : { - "de" : "Ja, mein Konto löschen", - "en" : "Yes, delete my account", - "es" : "Si, borrar mi cuenta", - "fr" : "Oui, supprimer mon compte", - "ru" : "Да, удалить мой аккаунт", - "zh_cn" : "是的,删除我的账号" - }, - "used-in" : [ "src/app/main/ui/settings/delete_account.cljs" ] - }, - "modals.delete-account.info" : { - "translations" : { - "de" : "Wenn Sie Ihr Konto löschen, verlieren Sie alle Ihre aktuellen Projekte und Archive.", - "en" : "By removing your account you’ll lose all your current projects and archives.", - "es" : "Si borras tu cuenta perderás todos tus proyectos y archivos.", - "fr" : "En supprimant votre compte, vous perdrez tous vos projets et archives actuelles.", - "ru" : "Удалив аккаунт Вы потеряете все прокты и архивы.", - "zh_cn" : "删除账号后,你会失去所有项目和存档。" - }, - "used-in" : [ "src/app/main/ui/settings/delete_account.cljs" ] - }, - "modals.delete-account.title" : { - "translations" : { - "de" : "Sind Sie sicher, dass Sie Ihr Konto löschen möchten?", - "en" : "Are you sure you want to delete your account?", - "es" : "¿Seguro que quieres borrar tu cuenta?", - "fr" : "Êtes‑vous sûr de vouloir supprimer votre compte ?", - "ru" : "Вы уверены, что хотите удалить аккаунт?", - "zh_cn" : "你确定想要删除你的账号?" - }, - "used-in" : [ "src/app/main/ui/settings/delete_account.cljs" ] - }, - "modals.delete-comment-thread.accept" : { - "translations" : { - "de" : "Konversation löschen", - "en" : "Delete conversation", - "es" : "Eliminar conversación", - "fr" : "Supprimer la conversation", - "zh_cn" : "删除对话" - }, - "used-in" : [ "src/app/main/ui/comments.cljs" ] - }, - "modals.delete-comment-thread.message" : { - "translations" : { - "de" : "Sind Sie sicher, dass Sie diese Konversation löschen möchten? Alle Kommentare in diesem Thread werden gelöscht.", - "en" : "Are you sure you want to delete this conversation? All comments in this thread will be deleted.", - "es" : "¿Seguro que quieres eliminar esta conversación? Todos los comentarios en este hilo serán eliminados.", - "fr" : "Êtes‑vous sûr de vouloir supprimer cette conversation ? Tous les commentaires de ce fil seront supprimés.", - "zh_cn" : "你确定想要删除这个对话?该讨论串里的所有评论都会被一同删除。" - }, - "used-in" : [ "src/app/main/ui/comments.cljs" ] - }, - "modals.delete-comment-thread.title" : { - "translations" : { - "de" : "Konversation löschen", - "en" : "Delete conversation", - "es" : "Eliminar conversación", - "fr" : "Supprimer une conversation", - "zh_cn" : "删除对话" - }, - "used-in" : [ "src/app/main/ui/comments.cljs" ] - }, - "modals.delete-file-confirm.accept" : { - "translations" : { - "de" : "Datei löschen", - "en" : "Delete file", - "es" : "Eliminar archivo", - "fr" : "Supprimer le fichier", - "zh_cn" : "删除文档" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.delete-file-confirm.message" : { - "translations" : { - "de" : "Sind Sie sicher, dass Sie diese Datei löschen wollen?", - "en" : "Are you sure you want to delete this file?", - "es" : "¿Seguro que quieres eliminar este archivo?", - "fr" : "Êtes‑vous sûr de vouloir supprimer ce fichier ?", - "zh_cn" : "你确定想要删除这个文档?" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.delete-file-confirm.title" : { - "translations" : { - "de" : "Datei löschen", - "en" : "Deleting file", - "es" : "Eliminando archivo", - "fr" : "Supprimer un fichier", - "zh_cn" : "正在删除文档" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.delete-file-multi-confirm.accept" : { - "translations" : { - "en" : "Delete files", - "es" : "Eliminar archivos" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.delete-file-multi-confirm.message" : { - "translations" : { - "en" : "Are you sure you want to delete %s files?", - "es" : "¿Seguro que quieres eliminar %s archivos?" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.delete-file-multi-confirm.title" : { - "translations" : { - "en" : "Deleting %s files", - "es" : "Eliminando %s archivos" - }, - "used-in" : [ "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.delete-page.body" : { - "translations" : { - "de" : "Sind Sie sicher, dass Sie diese Seite löschen wollen?", - "en" : "Are you sure you want to delete this page?", - "es" : "¿Seguro que quieres borrar esta página?", - "fr" : "Êtes‑vous sûr de vouloir supprimer cette page ?", - "zh_cn" : "你确定想要删除这个页面?" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs" ] - }, - "modals.delete-page.title" : { - "translations" : { - "de" : "Seite löschen", - "en" : "Delete page", - "es" : "Borrar página", - "fr" : "Supprimer une page", - "zh_cn" : "删除页面" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs" ] - }, - "modals.delete-project-confirm.accept" : { - "translations" : { - "de" : "Projekt löschen", - "en" : "Delete project", - "es" : "Eliminar proyecto", - "fr" : "Supprimer le projet", - "zh_cn" : "删除项目" - }, - "used-in" : [ "src/app/main/ui/dashboard/project_menu.cljs" ] - }, - "modals.delete-project-confirm.message" : { - "translations" : { - "de" : "Sind Sie sicher, dass Sie dieses Projekt löschen möchten?", - "en" : "Are you sure you want to delete this project?", - "es" : "¿Seguro que quieres eliminar este proyecto?", - "fr" : "Êtes‑vous sûr de vouloir supprimer ce projet ?", - "zh_cn" : "你确定想要删除这个项目?" - }, - "used-in" : [ "src/app/main/ui/dashboard/project_menu.cljs" ] - }, - "modals.delete-project-confirm.title" : { - "translations" : { - "de" : "Projekt löschen", - "en" : "Delete project", - "es" : "Eliminar proyecto", - "fr" : "Supprimer un projet", - "zh_cn" : "删除项目" - }, - "used-in" : [ "src/app/main/ui/dashboard/project_menu.cljs" ] - }, - "modals.delete-team-confirm.accept" : { - "translations" : { - "de" : "Team löschen", - "en" : "Delete team", - "es" : "Eliminar equipo", - "fr" : "Supprimer l’équipe", - "zh_cn" : "删除团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "modals.delete-team-confirm.message" : { - "translations" : { - "de" : "Sind Sie sicher, dass Sie dieses Team löschen möchten? Alle Projekte und Dateien, die mit dem Team verbunden sind, werden dauerhaft gelöscht.", - "en" : "Are you sure you want to delete this team? All projects and files associated with team will be permanently deleted.", - "es" : "¿Seguro que quieres eliminar este equipo? Todos los proyectos y archivos asociados con el equipo serán eliminados permamentemente.", - "fr" : "Êtes‑vous sûr de vouloir supprimer cette équipe ? Tous les projets et fichiers associés à l’équipe seront définitivement supprimés.", - "zh_cn" : "你确定想要删除这个团队?与该团队关联的所有项目和文档都会被永久删除。" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "modals.delete-team-confirm.title" : { - "translations" : { - "de" : "Team löschen", - "en" : "Deleting team", - "es" : "Eliminando equipo", - "fr" : "Suppression d’une équipe", - "zh_cn" : "正在删除团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "modals.delete-team-member-confirm.accept" : { - "translations" : { - "de" : "Mitglied löschen", - "en" : "Delete member", - "es" : "Eliminando miembro", - "fr" : "Supprimer le membre", - "zh_cn" : "删除成员" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "modals.delete-team-member-confirm.message" : { - "translations" : { - "de" : "Sind Sie sicher, dass Sie dieses Mitglied aus dem Team löschen möchten?", - "en" : "Are you sure you want to delete this member from the team?", - "es" : "¿Seguro que quieres eliminar este integrante del equipo?", - "fr" : "Êtes‑vous sûr de vouloir supprimer ce membre de l’équipe ?", - "zh_cn" : "你确定想要从团队中删除这个成员?" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "modals.delete-team-member-confirm.title" : { - "translations" : { - "de" : "Teammitglied löschen", - "en" : "Delete team member", - "es" : "Eliminar integrante del equipo", - "fr" : "Supprimer un membre d’équipe", - "zh_cn" : "删除团队成员" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "modals.invite-member-confirm.accept" : { - "translations" : { - "de" : "Einladung senden", - "en" : "Send invitation", - "es" : "Enviar invitacion", - "fr" : "Envoyer l'invitation" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "modals.invite-member.title" : { - "translations" : { - "de" : "Einladen, dem Team beizutreten", - "en" : "Invite to join the team", - "es" : "Invitar a unirse al equipo", - "fr" : "Inviter à rejoindre l’équipe", - "zh_cn" : "邀请加入团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "modals.leave-and-reassign.hint1" : { - "translations" : { - "de" : "Sie sind der Eigentümer von %s.", - "en" : "You are %s owner.", - "es" : "Eres %s dueño.", - "fr" : "Vous êtes le propriétaire de %s.", - "zh_cn" : "你是%s的所有者。" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "modals.leave-and-reassign.hint2" : { - "translations" : { - "de" : "Befördern Sie ein anderes Mitglied zum Eigentümer, bevor Sie das Team verlassen", - "en" : "Select other member to promote before leave", - "es" : "Promociona otro miembro a dueño antes de abandonar el equipo", - "fr" : "Sélectionnez un autre membre à promouvoir avant de quitter l’équipe", - "zh_cn" : "请在退出前,从其他成员中选择一位晋升。" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "modals.leave-and-reassign.promote-and-leave" : { - "translations" : { - "de" : "Befördern und verlassen", - "en" : "Promote and leave", - "es" : "Promocionar y abandonar", - "fr" : "Promouvoir et quitter", - "zh_cn" : "晋升并退出" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "modals.leave-and-reassign.select-memeber-to-promote" : { - "translations" : { - "de" : "Wählen Sie ein Mitglied aus, das befördert werden soll", - "en" : "Select a member to promote", - "es" : "Selecciona un miembro a promocionar", - "fr" : "Sélectionnez un membre à promouvoir", - "zh_cn" : "选择一位成员晋升" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "modals.leave-and-reassign.title" : { - "translations" : { - "de" : "Wählen Sie ein Mitglied aus, das befördert werden soll", - "en" : "Select a member to promote", - "es" : "Selecciona un miembro a promocionar", - "fr" : "Sélectionnez un membre à promouvoir", - "zh_cn" : "选择一位成员晋升" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "modals.leave-confirm.accept" : { - "translations" : { - "de" : "Team verlassen", - "en" : "Leave team", - "es" : "Abandonar el equipo", - "fr" : "Quitter l’équipe", - "zh_cn" : "退出团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "modals.leave-confirm.message" : { - "translations" : { - "de" : "Sind Sie sicher, dass Sie dieses Team verlassen wollen?", - "en" : "Are you sure you want to leave this team?", - "es" : "¿Seguro que quieres abandonar este equipo?", - "fr" : "Êtes‑vous sûr de vouloir quitter cette équipe ?", - "zh_cn" : "选择一位成员晋升" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "modals.leave-confirm.title" : { - "translations" : { - "de" : "Team verlassen", - "en" : "Leaving team", - "es" : "Abandonando el equipo", - "fr" : "Quitter l’équipe", - "zh_cn" : "正在退出团队" - }, - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs" ] - }, - "modals.promote-owner-confirm.accept" : { - "translations" : { - "de" : "Befördern", - "en" : "Promote", - "es" : "Promocionar", - "fr" : "Promouvoir", - "zh_cn" : "晋升" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "modals.promote-owner-confirm.message" : { - "translations" : { - "de" : "Sind Sie sicher, dass Sie diesen Benutzer zum Eigentümer befördern wollen?", - "en" : "Are you sure you want to promote this user to owner?", - "es" : "¿Seguro que quieres promocionar este usuario a dueño?", - "fr" : "Êtes‑vous sûr de vouloir promouvoir cette personne propriétaire ?", - "zh_cn" : "你确定想要晋升该用户为所有者?" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "modals.promote-owner-confirm.title" : { - "translations" : { - "de" : "Zum Eigentümer befördern", - "en" : "Promote to owner", - "es" : "Promocionar a dueño", - "fr" : "Promouvoir propriétaire", - "zh_cn" : "晋升为所有者" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "modals.remove-shared-confirm.accept" : { - "translations" : { - "de" : "Als gemeinsam genutzte Bibliothek entfernen", - "en" : "Remove as Shared Library", - "es" : "Eliminar como Biblioteca Compartida", - "fr" : "Supprimer en tant que Bibliothèque Partagée", - "ru" : "", - "zh_cn" : "不再作为共享库" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.remove-shared-confirm.hint" : { - "translations" : { - "de" : "Nach dem Entfernen als gemeinsam genutzte Bibliothek steht die Bibliothek dieser Datei nicht mehr für den Rest Ihrer Dateien zur Verfügung.", - "en" : "Once removed as Shared Library, the File Library of this file will stop being available to be used among the rest of your files.", - "es" : "Una vez eliminado como Biblioteca Compartida, la Biblioteca de este archivo dejará de estar disponible para ser usada por el resto de tus archivos.", - "fr" : "Une fois supprimée en tant que Bibliothèque Partagée, la Bibliothèque de ce fichier ne pourra plus être utilisée par le reste de vos fichiers.", - "ru" : "", - "zh_cn" : "一旦不再作为共享库,该文档库就不能继续用于你的其他文档中。" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.remove-shared-confirm.message" : { - "translations" : { - "de" : "Entfernen Sie “%s” als gemeinsam genutzte Bibliothek", - "en" : "Remove “%s” as Shared Library", - "es" : "Añadir “%s” como Biblioteca Compartida", - "fr" : "Retirer « %s » en tant que Bibliothèque Partagée", - "ru" : "", - "zh_cn" : "不再将“%s”作为共享库" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs", "src/app/main/ui/dashboard/file_menu.cljs" ] - }, - "modals.update-remote-component.accept" : { - "translations" : { - "de" : "Komponente aktualisieren", - "en" : "Update component", - "es" : "Actualizar componente", - "fr" : "Actualiser le composant", - "ru" : "", - "zh_cn" : "更新组件" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/component.cljs", "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "modals.update-remote-component.cancel" : { - "translations" : { - "de" : "Abbrechen", - "en" : "Cancel", - "es" : "Cancelar", - "fr" : "Annuler", - "ru" : "", - "zh_cn" : "取消" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/component.cljs", "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "modals.update-remote-component.hint" : { - "translations" : { - "de" : "Sie sind dabei, eine Komponente in einer gemeinsam genutzten Bibliothek zu aktualisieren. Dies kann sich auf andere Dateien auswirken, die es verwenden.", - "en" : "You are about to update a component in a shared library. This may affect other files that use it.", - "es" : "Vas a actualizar un componente en una librería compartida. Esto puede afectar a otros archivos que la usen.", - "fr" : "Vous êtes sur le point de mettre à jour le composant d’une Bibliothèque Partagée. Cela peut affecter d’autres fichiers qui l’utilisent.", - "ru" : "", - "zh_cn" : "你即将更新共享库中的一个组件。这可能会对使用该组件的其他文档产生影响。" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/component.cljs", "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "modals.update-remote-component.message" : { - "translations" : { - "de" : "Aktualisieren einer Komponente in einer gemeinsam genutzten Bibliothek", - "en" : "Update a component in a shared library", - "es" : "Actualizar un componente en librería", - "fr" : "Actualiser le composant d’une bibliothèque", - "ru" : "", - "zh_cn" : "更新共享库中的一个组件" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/component.cljs", "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "notifications.invitation-email-sent" : { - "translations" : { - "de" : "Einladung erfolgreich gesendet", - "en" : "Invitation sent successfully", - "es" : "Invitación enviada con éxito", - "fr" : "E‑mail d'invitation envoyé!" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "notifications.profile-deletion-not-allowed" : { - "translations" : { - "de" : "Sie können Ihr Profil nicht löschen. Weisen Sie Ihre Teams neu zu, bevor Sie fortfahren.", - "en" : "You can't delete you profile. Reassign your teams before proceed.", - "es" : "No puedes borrar tu perfil. Reasigna tus equipos antes de seguir.", - "fr" : "Vous ne pouvez pas supprimer votre profil. Réassignez vos équipes avant de continuer.", - "ru" : "Вы не можете удалить профиль. Сначала смените команду.", - "zh_cn" : "你无法删除你的个人资料。请先转让你的团队。" - }, - "used-in" : [ "src/app/main/ui/settings/delete_account.cljs" ] - }, - "notifications.profile-saved" : { - "translations" : { - "de" : "Profil erfolgreich gespeichert!", - "en" : "Profile saved successfully!", - "es" : "Perfil guardado correctamente!", - "fr" : "Profil enregistré avec succès !", - "ru" : "Профиль успешно сохранен!", - "zh_cn" : "个人资料保存成功!" - }, - "used-in" : [ "src/app/main/ui/settings/profile.cljs", "src/app/main/ui/settings/options.cljs" ] - }, - "notifications.validation-email-sent" : { - "translations" : { - "de" : "Verifizierungs-E-Mail an %s gesendet. Prüfen Sie Ihren Posteingang!", - "en" : "Verification email sent to %s. Check your email!", - "es" : "Verificación de email enviada a %s. Comprueba tu correo.", - "fr" : "E‑mail de vérification envoyé à %s. Vérifiez votre e‑mail !", - "zh_cn" : "验证邮件已发至%s。请检查电子邮箱。" - }, - "used-in" : [ "src/app/main/ui/settings/change_email.cljs" ] - }, - "profile.recovery.go-to-login" : { - "translations" : { - "de" : "Zur Anmeldung", - "en" : "Go to login", - "es" : null, - "fr" : "Aller à la page de connexion", - "ru" : null, - "zh_cn" : "去登录" - }, - "used-in" : [ "src/app/main/ui/auth/recovery.cljs" ] - }, - "settings.multiple" : { - "translations" : { - "de" : "Mehrere", - "en" : "Mixed", - "es" : "Varios", - "fr" : "Divers", - "ru" : "Смешаный", - "zh_cn" : "混合" - }, - "used-in" : [ "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" ] - }, - "title.dashboard.files" : { - "translations" : { - "en" : "%s - Penpot" - }, - "used-in" : [ "src/app/main/ui/dashboard/files.cljs" ] - }, - "title.dashboard.projects" : { - "translations" : { - "ca" : "Projectes - %s - Penpot", - "de" : "Projekte - %s - Penpot", - "en" : "Projects - %s - Penpot", - "es" : "Proyectos - %s - Penpot", - "fr" : "Projets - %s - Penpot", - "ru" : "Проекты - %s - Penpot", - "zh_cn" : "项目 - %s - Penpot" - }, - "used-in" : [ "src/app/main/ui/dashboard/projects.cljs" ] - }, - "title.dashboard.search" : { - "translations" : { - "ca" : "Cerca - %s - Penpot", - "de" : "Suchen - %s - Penpot", - "en" : "Search - %s - Penpot", - "es" : "Buscar - %s - Penpot", - "fr" : "Rechercher - %s - Penpot", - "ru" : "Поиск - %s - Penpot", - "zh_cn" : "搜索 - %s - Penpot" - }, - "used-in" : [ "src/app/main/ui/dashboard/search.cljs" ] - }, - "title.dashboard.shared-libraries" : { - "translations" : { - "de" : "Gemeinsam genutzte Bibliotheken - %s - Penpot", - "en" : "Shared Libraries - %s - Penpot", - "es" : "Bibliotecas Compartidas - %s - Penpot", - "fr" : "Bibliothèques Partagées - %s - Penpot", - "ru" : "", - "zh_cn" : "共享库 - %s - Penpot" - }, - "used-in" : [ "src/app/main/ui/dashboard/libraries.cljs" ] - }, - "title.default" : { - "translations" : { - "en" : "Penpot - Design Freedom for Teams", - "es" : "Penpot - Diseño Libre para Equipos" - }, - "used-in" : [ "src/app/main/ui/auth/verify_token.cljs", "src/app/main/ui/auth.cljs" ] - }, - "title.settings.feedback" : { - "translations" : { - "de" : "Feedback geben - Penpot", - "en" : "Give feedback - Penpot", - "es" : "Danos tu opinión - Penpot", - "fr" : "Donnez votre avis - Penpot", - "ru" : "Дать обратную связь - Penpot", - "zh_cn" : "提交反馈 - Penpot" - }, - "used-in" : [ "src/app/main/ui/settings/feedback.cljs" ] - }, - "title.settings.options" : { - "translations" : { - "de" : "Einstellungen - Penpot", - "en" : "Settings - Penpot", - "es" : "Configuración - Penpot", - "fr" : "Configuration - Penpot", - "ru" : "Параметры - Penpot", - "zh_cn" : "设置 - Penpot" - }, - "used-in" : [ "src/app/main/ui/settings/options.cljs" ] - }, - "title.settings.password" : { - "translations" : { - "de" : "Passwort - Penpot", - "en" : "Password - Penpot", - "es" : "Contraseña - Penpot", - "fr" : "Mot de passe - Penpot", - "ru" : "Пароль - Penpot", - "zh_cn" : "密码 - Penpot" - }, - "used-in" : [ "src/app/main/ui/settings/password.cljs" ] - }, - "title.settings.profile" : { - "translations" : { - "de" : "Profil - Penpot", - "en" : "Profile - Penpot", - "es" : "Perfil - Penpot", - "fr" : "Profil - Penpot", - "ru" : "Профиль - Penpot", - "zh_cn" : "个人资料 - Penpot" - }, - "used-in" : [ "src/app/main/ui/settings/profile.cljs" ] - }, - "title.team-members" : { - "translations" : { - "de" : "Mitglieder - %s - Penpot", - "en" : "Members - %s - Penpot", - "es" : "Integrantes - %s - Penpot", - "fr" : "Membres - %s - Penpot", - "zh_cn" : "成员 - %s - Penpot" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "title.team-settings" : { - "translations" : { - "de" : "Einstellungen - %s - Penpot", - "en" : "Settings - %s - Penpot", - "es" : "Configuración - %s - Penpot", - "fr" : "Configuration - %s - Penpot", - "ru" : "Параметры - %s - Penpot", - "zh_cn" : "设置 - %s - Penpot" - }, - "used-in" : [ "src/app/main/ui/dashboard/team.cljs" ] - }, - "title.viewer" : { - "translations" : { - "de" : "%s - Ansichtsmodus - Penpot", - "en" : "%s - View mode - Penpot", - "es" : "%s - Modo de visualización - Penpot", - "fr" : "%s - Mode spectateur - Penpot", - "ru" : "%s - Режим просмотра - Penpot", - "zh_cn" : "%s - 预览模式)- Penpot" - }, - "used-in" : [ "src/app/main/ui/handoff.cljs", "src/app/main/ui/viewer.cljs" ] - }, - "title.workspace" : { - "translations" : { - "en" : "%s - Penpot" - }, - "used-in" : [ "src/app/main/ui/workspace.cljs" ] - }, - "viewer.empty-state" : { - "translations" : { - "de" : "Keine Zeichenflächen auf der Seite gefunden.", - "en" : "No frames found on the page.", - "es" : "No se ha encontrado ningún tablero.", - "fr" : "Aucun cadre trouvé sur la page.", - "ru" : "На странице не найдено ни одного кадра", - "zh_cn" : "该页面上未找到任何画框。" - }, - "used-in" : [ "src/app/main/ui/handoff.cljs", "src/app/main/ui/viewer.cljs" ] - }, - "viewer.frame-not-found" : { - "translations" : { - "de" : "Keine Zeichenfläche gefunden.", - "en" : "Frame not found.", - "es" : "No se encuentra el tablero.", - "fr" : "Cadre introuvable.", - "ru" : "Кадры не найдены.", - "zh_cn" : "画框未找到。" - }, - "used-in" : [ "src/app/main/ui/handoff.cljs", "src/app/main/ui/viewer.cljs" ] - }, - "viewer.header.dont-show-interactions" : { - "translations" : { - "de" : "Interaktionen nicht anzeigen", - "en" : "Don't show interactions", - "es" : "No mostrar interacciones", - "fr" : "Ne pas afficher les interactions", - "ru" : "Не показывать взаимодействия", - "zh_cn" : "不显示交互" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs" ] - }, - "viewer.header.edit-page" : { - "translations" : { - "de" : "Seite bearbeiten", - "en" : "Edit page", - "es" : "Editar página", - "fr" : "Modifier la page", - "ru" : "Редактировать страницу", - "zh_cn" : "编辑页面" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs" ] - }, - "viewer.header.fullscreen" : { - "translations" : { - "de" : "Vollbildmodus", - "en" : "Full Screen", - "es" : "Pantalla completa", - "fr" : "Plein écran", - "ru" : "Полный экран", - "zh_cn" : "全屏" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs" ] - }, - "viewer.header.share.copy-link" : { - "translations" : { - "de" : "Link kopieren", - "en" : "Copy link", - "es" : "Copiar enlace", - "fr" : "Copier le lien", - "ru" : "Копировать ссылку", - "zh_cn" : "复制链接" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs" ] - }, - "viewer.header.share.create-link" : { - "translations" : { - "de" : "Link erstellen", - "en" : "Create link", - "es" : "Crear enlace", - "fr" : "Créer le lien", - "ru" : "Создать ссылку", - "zh_cn" : "创建链接" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs" ] - }, - "viewer.header.share.placeholder" : { - "translations" : { - "de" : "Der Link fürs Teilen wird hier angezeigt", - "en" : "Share link will appear here", - "es" : "El enlace para compartir aparecerá aquí", - "fr" : "Le lien de partage apparaîtra ici", - "ru" : "Здесь будет ссылка для обмена", - "zh_cn" : "分享链接将会显示在这里" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs" ] - }, - "viewer.header.share.remove-link" : { - "translations" : { - "de" : "Link entfernen", - "en" : "Remove link", - "es" : "Eliminar enlace", - "fr" : "Supprimer le lien", - "ru" : "Удалить ссылку", - "zh_cn" : "移除链接" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs" ] - }, - "viewer.header.share.subtitle" : { - "translations" : { - "de" : "Jeder mit dem Link hat Zugriff", - "en" : "Anyone with the link will have access", - "es" : "Cualquiera con el enlace podrá acceder", - "fr" : "Toute personne disposant du lien aura accès", - "ru" : "Любой, у кого есть ссылка будет иметь доступ", - "zh_cn" : "任何人都可以通过本链接访问" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs" ] - }, - "viewer.header.share.title" : { - "translations" : { - "de" : "Link teilen", - "en" : "Share link", - "es" : "Enlace", - "fr" : "Lien de partage", - "ru" : "Поделиться ссылкой", - "zh_cn" : "分享链接" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs", "src/app/main/ui/viewer/header.cljs", "src/app/main/ui/viewer/header.cljs" ] - }, - "viewer.header.show-interactions" : { - "translations" : { - "de" : "Interaktionen anzeigen", - "en" : "Show interactions", - "es" : "Mostrar interacciones", - "fr" : "Afficher les interactions", - "ru" : "Показывать взаимодействия", - "zh_cn" : "显示交互" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs" ] - }, - "viewer.header.show-interactions-on-click" : { - "translations" : { - "de" : "Interaktionen beim Klicken anzeigen", - "en" : "Show interactions on click", - "es" : "Mostrar interacciones al hacer click", - "fr" : "Afficher les interactions au clic", - "ru" : "Показывать взаимодействия по клику", - "zh_cn" : "点击时显示交互" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs" ] - }, - "viewer.header.sitemap" : { - "translations" : { - "de" : "Sitemap", - "en" : "Sitemap", - "es" : "Mapa del sitio", - "fr" : "Plan du site", - "ru" : "План сайта", - "zh_cn" : "站点地图" - }, - "used-in" : [ "src/app/main/ui/viewer/header.cljs" ] - }, - "workspace.align.hcenter" : { - "translations" : { - "de" : "Zentrieren (horizontal)", - "en" : "Align horizontal center", - "es" : "Alinear al centro", - "fr" : "Aligner horizontalement au centre", - "ru" : "Выровнять по горизонтали", - "zh_cn" : "水平居中对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/align.cljs" ] - }, - "workspace.align.hdistribute" : { - "translations" : { - "de" : "Horizontal verteilen", - "en" : "Distribute horizontal spacing", - "es" : "Distribuir espacio horizontal", - "fr" : "Répartir l’espacement horizontal", - "ru" : "Распределить горизонтальное пространство", - "zh_cn" : "水平均匀分布" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/align.cljs" ] - }, - "workspace.align.hleft" : { - "translations" : { - "de" : "Linksbündig ausrichten", - "en" : "Align left", - "es" : "Alinear a la izquierda", - "fr" : "Aligner à gauche", - "ru" : "Выровнять по левому краю", - "zh_cn" : "靠左对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/align.cljs" ] - }, - "workspace.align.hright" : { - "translations" : { - "de" : "Rechtsbündig ausrichten", - "en" : "Align right", - "es" : "Alinear a la derecha", - "fr" : "Aligner à droite", - "ru" : "Выровнять по правому краю", - "zh_cn" : "靠右对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/align.cljs" ] - }, - "workspace.align.vbottom" : { - "translations" : { - "de" : "Unten ausrichten", - "en" : "Align bottom", - "es" : "Alinear abajo", - "fr" : "Aligner en bas", - "ru" : "Выровнять по нижнему краю", - "zh_cn" : "底部对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/align.cljs" ] - }, - "workspace.align.vcenter" : { - "translations" : { - "de" : "Mittig ausrichten (vertikal)", - "en" : "Align vertical center", - "es" : "Alinear al centro", - "fr" : "Aligner verticalement au centre", - "ru" : "Выровнять по вертикали", - "zh_cn" : "垂直居中对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/align.cljs" ] - }, - "workspace.align.vdistribute" : { - "translations" : { - "de" : "Vertikal verteilen", - "en" : "Distribute vertical spacing", - "es" : "Distribuir espacio vertical", - "fr" : "Répartir l’espacement vertical", - "ru" : "Распределить вертикальное пространство", - "zh_cn" : "垂直均匀分布" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/align.cljs" ] - }, - "workspace.align.vtop" : { - "translations" : { - "de" : "Oben ausrichten", - "en" : "Align top", - "es" : "Alinear arriba", - "fr" : "Aligner en haut", - "ru" : "Выровнять по верхнему краю", - "zh_cn" : "顶部对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/align.cljs" ] - }, - "workspace.assets.assets" : { - "translations" : { - "de" : "Assets", - "en" : "Assets", - "es" : "Recursos", - "fr" : "Ressources", - "ru" : "", - "zh_cn" : "素材" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.box-filter-all" : { - "translations" : { - "de" : "Alle Assets", - "en" : "All assets", - "es" : "Todos", - "fr" : "Toutes", - "ru" : "", - "zh_cn" : "所有素材" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.box-filter-graphics" : { - "translations" : { - "de" : "Grafiken", - "en" : "Graphics", - "es" : "Gráficos", - "fr" : "Graphiques", - "ru" : "", - "zh_cn" : "图形" - }, - "unused" : true - }, - "workspace.assets.colors" : { - "translations" : { - "de" : "Farben", - "en" : "Colors", - "es" : "Colores", - "fr" : "Couleurs", - "ru" : "", - "zh_cn" : "颜色" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs", "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.components" : { - "translations" : { - "de" : "Komponente", - "en" : "Components", - "es" : "Componentes", - "fr" : "Composants", - "ru" : "", - "zh_cn" : "组件" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs", "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.delete" : { - "translations" : { - "de" : "Löschen", - "en" : "Delete", - "es" : "Borrar", - "fr" : "Supprimer", - "ru" : "", - "zh_cn" : "删除" - }, - "used-in" : [ "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" ] - }, - "workspace.assets.duplicate" : { - "translations" : { - "de" : "Duplizieren", - "en" : "Duplicate", - "es" : "Duplicar", - "fr" : "Dupliquer", - "ru" : "", - "zh_cn" : "创建副本" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs", "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.edit" : { - "translations" : { - "de" : "Bearbeiten", - "en" : "Edit", - "es" : "Editar", - "fr" : "Modifier", - "ru" : "", - "zh_cn" : "编辑" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs", "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.file-library" : { - "translations" : { - "de" : "Dateibibliothek", - "en" : "File library", - "es" : "Biblioteca del archivo", - "fr" : "Bibliothèque du fichier", - "ru" : "", - "zh_cn" : "文档库" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.graphics" : { - "translations" : { - "de" : "Grafiken", - "en" : "Graphics", - "es" : "Gráficos", - "fr" : "Graphiques", - "ru" : "", - "zh_cn" : "图形" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs", "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.libraries" : { - "translations" : { - "de" : "Bibliotheken", - "en" : "Libraries", - "es" : "Bibliotecas", - "fr" : "Bibliothèques", - "ru" : "", - "zh_cn" : "库" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.not-found" : { - "translations" : { - "de" : "Keine Assets gefunden", - "en" : "No assets found", - "es" : "No se encontraron recursos", - "fr" : "Aucune ressource trouvée", - "ru" : "", - "zh_cn" : "未找到素材" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.rename" : { - "translations" : { - "de" : "Umbenennen", - "en" : "Rename", - "es" : "Renombrar", - "fr" : "Renommer", - "ru" : "", - "zh_cn" : "重命名" - }, - "used-in" : [ "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" ] - }, - "workspace.assets.search" : { - "translations" : { - "de" : "Assets suchen", - "en" : "Search assets", - "es" : "Buscar recursos", - "fr" : "Chercher des ressources", - "ru" : "", - "zh_cn" : "搜索素材" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.shared" : { - "translations" : { - "de" : "GETEILT", - "en" : "SHARED", - "es" : "COMPARTIDA", - "fr" : "PARTAGÉ", - "ru" : "", - "zh_cn" : "共享的" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.typography" : { - "translations" : { - "de" : "Typografien", - "en" : "Typographies", - "es" : "Tipografías", - "fr" : "Typographies", - "zh_cn" : "排版" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs", "src/app/main/ui/workspace/sidebar/assets.cljs" ] - }, - "workspace.assets.typography.font-id" : { - "translations" : { - "de" : "Schriftart", - "en" : "Font", - "es" : "Fuente", - "fr" : "Police", - "zh_cn" : "字体" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.assets.typography.font-size" : { - "translations" : { - "de" : "Größe", - "en" : "Size", - "es" : "Tamaño", - "fr" : "Taille", - "zh_cn" : "尺寸" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.assets.typography.font-variant-id" : { - "translations" : { - "de" : "Variante", - "en" : "Variant", - "es" : "Variante", - "fr" : "Variante", - "zh_cn" : "变体" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.assets.typography.go-to-edit" : { - "translations" : { - "de" : "Wechseln Sie zur Stilbibliotheksdatei, um sie zu bearbeiten", - "en" : "Go to style library file to edit", - "es" : "Ir al archivo de la biblioteca del estilo para editar", - "fr" : "Accéder au fichier de bibliothèque de styles à modifier", - "zh_cn" : "前往样式库文件进行编辑" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.assets.typography.letter-spacing" : { - "translations" : { - "de" : "Zeichenabstand", - "en" : "Letter Spacing", - "es" : "Interletrado", - "fr" : "Interlettrage", - "zh_cn" : "字距" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.assets.typography.line-height" : { - "translations" : { - "de" : "Zeilenabstand", - "en" : "Line Height", - "es" : "Interlineado", - "fr" : "Interlignage", - "zh_cn" : "行高" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.assets.typography.sample" : { - "translations" : { - "de" : "Ag", - "en" : "Ag", - "es" : "Ag", - "fr" : "Ag", - "zh_cn" : "Ag" - }, - "used-in" : [ "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" ] - }, - "workspace.assets.typography.text-transform" : { - "translations" : { - "de" : "Texttransformation", - "en" : "Text Transform", - "es" : "Transformar texto", - "fr" : "Transformer le texte", - "zh_cn" : "文本变换" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.gradients.linear" : { - "translations" : { - "de" : "Linearer Farbverlauf", - "en" : "Linear gradient", - "es" : "Degradado lineal", - "fr" : "Dégradé linéaire", - "zh_cn" : "线性渐变" - }, - "used-in" : [ "src/app/main/data/workspace/libraries.cljs", "src/app/main/ui/components/color_bullet.cljs" ] - }, - "workspace.gradients.radial" : { - "translations" : { - "de" : "Radialer Farbverlauf", - "en" : "Radial gradient", - "es" : "Degradado radial", - "fr" : "Dégradé radial", - "zh_cn" : "放射渐变" - }, - "used-in" : [ "src/app/main/data/workspace/libraries.cljs", "src/app/main/ui/components/color_bullet.cljs" ] - }, - "workspace.header.menu.disable-dynamic-alignment" : { - "translations" : { - "de" : "Dynamische Ausrichtung deaktivieren", - "en" : "Disable dynamic alignment", - "es" : "Desactivar alineamiento dinámico", - "fr" : "Désactiver l’alignement dynamique", - "ru" : "Отключить активное выравнивание", - "zh_cn" : "禁用动态对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.disable-snap-grid" : { - "translations" : { - "de" : "Am Raster ausrichten deaktivieren", - "en" : "Disable snap to grid", - "es" : "Desactivar alinear a la rejilla", - "fr" : "Désactiver l’alignement sur la grille", - "ru" : "Отключить привязку к сетке", - "zh_cn" : "禁用吸附到网格" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.enable-dynamic-alignment" : { - "translations" : { - "de" : "Dynamische Ausrichtung aktivieren", - "en" : "Enable dynamic aligment", - "es" : "Activar alineamiento dinámico", - "fr" : "Activer l’alignement dynamique", - "ru" : "Включить активное выравнивание", - "zh_cn" : "启用动态对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.enable-snap-grid" : { - "translations" : { - "de" : "Am Raster ausrichten", - "en" : "Snap to grid", - "es" : "Alinear a la rejilla", - "fr" : "Aligner sur la grille", - "ru" : "Привяка к сетке", - "zh_cn" : "吸附到网格" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.hide-assets" : { - "translations" : { - "de" : "Assets ausblenden", - "en" : "Hide assets", - "es" : "Ocultar recursos", - "fr" : "Masquer les ressources", - "ru" : "", - "zh_cn" : "隐藏素材" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.hide-grid" : { - "translations" : { - "de" : "Raster ausblenden", - "en" : "Hide grids", - "es" : "Ocultar rejillas", - "fr" : "Masquer la grille", - "ru" : "Спрятать сетку", - "zh_cn" : "隐藏网格" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.hide-layers" : { - "translations" : { - "de" : "Ebenen ausblenden", - "en" : "Hide layers", - "es" : "Ocultar capas", - "fr" : "Masquer les calques", - "ru" : "Спрятать слои", - "zh_cn" : "隐藏图层" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.hide-palette" : { - "translations" : { - "de" : "Farbpalette ausblenden", - "en" : "Hide color palette", - "es" : "Ocultar paleta de colores", - "fr" : "Masquer la palette de couleurs", - "ru" : "Спрятать палитру цветов", - "zh_cn" : "隐藏调色盘" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.hide-rules" : { - "translations" : { - "de" : "Lineale ausblenden", - "en" : "Hide rules", - "es" : "Ocultar reglas", - "fr" : "Masquer les règles", - "ru" : "Спрятать линейки", - "zh_cn" : "隐藏标尺" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.select-all" : { - "translations" : { - "de" : "Alles auswählen", - "en" : "Select all", - "es" : "Seleccionar todo", - "fr" : "Tout sélectionner", - "ru" : "", - "zh_cn" : "全选" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.show-assets" : { - "translations" : { - "de" : "Assets einblenden", - "en" : "Show assets", - "es" : "Mostrar recursos", - "fr" : "Montrer les ressources", - "ru" : "", - "zh_cn" : "显示素材" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.show-grid" : { - "translations" : { - "de" : "Raster einblenden", - "en" : "Show grid", - "es" : "Mostrar rejilla", - "fr" : "Montrer la grille", - "ru" : "Показать сетку", - "zh_cn" : "显示网格" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.show-layers" : { - "translations" : { - "de" : "Ebenen einblenden", - "en" : "Show layers", - "es" : "Mostrar capas", - "fr" : "Montrer les calques", - "ru" : "Показать слои", - "zh_cn" : "显示图层" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.show-palette" : { - "translations" : { - "de" : "Farbpalette einblenden", - "en" : "Show color palette", - "es" : "Mostrar paleta de colores", - "fr" : "Montrer la palette de couleurs", - "ru" : "Показать палитру цветов", - "zh_cn" : "显示调色盘" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.menu.show-rules" : { - "translations" : { - "de" : "Lineale einblenden", - "en" : "Show rules", - "es" : "Mostrar reglas", - "fr" : "Montrer les règles", - "ru" : "Показать линейки", - "zh_cn" : "显示标尺" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.save-error" : { - "translations" : { - "de" : "Fehler beim Speichern", - "en" : "Error on saving", - "es" : "Error al guardar", - "fr" : "Erreur d’enregistrement", - "zh_cn" : "保存时发生错误" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.saved" : { - "translations" : { - "de" : "Gespeichert", - "en" : "Saved", - "es" : "Guardado", - "fr" : "Enregistré", - "zh_cn" : "已保存" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.saving" : { - "translations" : { - "de" : "Speichern", - "en" : "Saving", - "es" : "Guardando", - "fr" : "Enregistrement", - "zh_cn" : "正在保存" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.unsaved" : { - "translations" : { - "de" : "Ungespeicherte Änderungen", - "en" : "Unsaved changes", - "es" : "Cambios sin guardar", - "fr" : "Modifications non sauvegardées", - "zh_cn" : "未保存的修改" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.header.viewer" : { - "translations" : { - "de" : "Ansichtsmodus (%s)", - "en" : "View mode (%s)", - "es" : "Modo de visualización (%s)", - "fr" : "Mode spectateur (%s)", - "ru" : "Режим просмотра (%s)", - "zh_cn" : "预览模式(%s)" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.libraries.add" : { - "translations" : { - "de" : "Hinzufügen", - "en" : "Add", - "es" : "Añadir", - "fr" : "Ajouter", - "ru" : "", - "zh_cn" : "添加" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.colors" : { - "translations" : { - "de" : "%s Farben", - "en" : "%s colors", - "es" : "%s colors", - "fr" : "%s couleurs", - "ru" : "", - "zh_cn" : "%s种颜色" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.colors.big-thumbnails" : { - "translations" : { - "de" : "Große Miniaturen", - "en" : "Big thumbnails", - "es" : "Miniaturas grandes", - "fr" : "Grandes vignettes", - "zh_cn" : "大缩略图" - }, - "used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs" ] - }, - "workspace.libraries.colors.file-library" : { - "translations" : { - "de" : "Dateibibliothek", - "en" : "File library", - "es" : "Biblioteca del archivo", - "fr" : "Bibliothèque du fichier", - "zh_cn" : "文档库" - }, - "used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs", "src/app/main/ui/workspace/colorpalette.cljs" ] - }, - "workspace.libraries.colors.recent-colors" : { - "translations" : { - "de" : "Aktuelle Farben", - "en" : "Recent colors", - "es" : "Colores recientes", - "fr" : "Couleurs récentes", - "zh_cn" : "最近使用的颜色" - }, - "used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs", "src/app/main/ui/workspace/colorpalette.cljs" ] - }, - "workspace.libraries.colors.save-color" : { - "translations" : { - "de" : "Farbstil speichern", - "en" : "Save color style", - "es" : "Guardar estilo de color", - "fr" : "Enregistrer le style de couleur", - "zh_cn" : "保存颜色风格" - }, - "used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs" ] - }, - "workspace.libraries.colors.small-thumbnails" : { - "translations" : { - "de" : "Kleine Miniaturen", - "en" : "Small thumbnails", - "es" : "Miniaturas pequeñas", - "fr" : "Petites vignettes", - "zh_cn" : "小缩略图" - }, - "used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs" ] - }, - "workspace.libraries.components" : { - "translations" : { - "de" : "%s Komponenten", - "en" : "%s components", - "es" : "%s componentes", - "fr" : "%s composants", - "ru" : "", - "zh_cn" : "%s个组件" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.file-library" : { - "translations" : { - "de" : "Dateibibliothek", - "en" : "File library", - "es" : "Biblioteca de este archivo", - "fr" : "Bibliothèque du fichier", - "ru" : "", - "zh_cn" : "文档库" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.graphics" : { - "translations" : { - "de" : "%s Grafik(en)", - "en" : "%s graphics", - "es" : "%s gráficos", - "fr" : "%s graphiques", - "ru" : "", - "zh_cn" : "%s个图形" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.in-this-file" : { - "translations" : { - "de" : "BIBLIOTHEKEN IN DIESER DATEI", - "en" : "LIBRARIES IN THIS FILE", - "es" : "BIBLIOTECAS EN ESTE ARCHIVO", - "fr" : "BIBLIOTHÈQUES DANS CE FICHIER", - "ru" : "", - "zh_cn" : "本文档中的库" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.libraries" : { - "translations" : { - "de" : "BIBLIOTHEKEN", - "en" : "LIBRARIES", - "es" : "BIBLIOTECAS", - "fr" : "BIBLIOTHÈQUES", - "ru" : "", - "zh_cn" : "库" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.library" : { - "translations" : { - "de" : "BIBLIOTHEK", - "en" : "LIBRARY", - "es" : "BIBLIOTECA", - "fr" : "BIBLIOTHÈQUE", - "ru" : "", - "zh_cn" : "库" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.no-libraries-need-sync" : { - "translations" : { - "de" : "Es gibt keine gemeinsam genutzte Bibliotheken, die aktualisiert werden müssen", - "en" : "There are no Shared Libraries that need update", - "es" : "No hay bibliotecas que necesiten ser actualizadas", - "fr" : "Aucune Bibliothèque Partagée n’a besoin d’être mise à jour", - "ru" : "", - "zh_cn" : "没有需要更新的共享库" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.no-matches-for" : { - "translations" : { - "de" : "Keine Übereinstimmungen für “%s“ gefunden", - "en" : "No matches found for “%s“", - "es" : "No se encuentra “%s“", - "fr" : "Aucune correspondance pour « %s »", - "ru" : "Совпадений для “%s“ не найдено", - "zh_cn" : "没有找到“%s”的匹配项" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.no-shared-libraries-available" : { - "translations" : { - "de" : "Es sind keine gemeinsam genutzte Bibliotheken verfügbar", - "en" : "There are no Shared Libraries available", - "es" : "No hay bibliotecas compartidas disponibles", - "fr" : "Aucune Bibliothèque Partagée disponible", - "ru" : "", - "zh_cn" : "没有可用的共享库" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.search-shared-libraries" : { - "translations" : { - "de" : "Suche nach gemeinsam genutzten Bibliotheken", - "en" : "Search shared libraries", - "es" : "Buscar bibliotecas compartidas", - "fr" : "Rechercher des Bibliothèques Partagées", - "ru" : "", - "zh_cn" : "搜索共享库" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.shared-libraries" : { - "translations" : { - "de" : "GEMEINSAM GENUTZTE BIBLIOTHEKEN", - "en" : "SHARED LIBRARIES", - "es" : "BIBLIOTECAS COMPARTIDAS", - "fr" : "BIBLIOTHÈQUES PARTAGÉES", - "ru" : "", - "zh_cn" : "共享库" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.text.multiple-typography" : { - "translations" : { - "de" : "Mehrere Typografien", - "en" : "Multiple typographies", - "es" : "Varias tipografías", - "fr" : "Multiple typographies", - "zh_cn" : "复合排版" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.libraries.text.multiple-typography-tooltip" : { - "translations" : { - "de" : "Verknüpfung aller Typografien aufheben", - "en" : "Unlink all typographies", - "es" : "Desvincular todas las tipografías", - "fr" : "Dissocier toutes les typographies", - "zh_cn" : "断开所有排版的链接" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.libraries.typography" : { - "translations" : { - "de" : "%s Typografien", - "en" : "%s typographies", - "es" : "%s tipografías", - "fr" : "%s typographies", - "zh_cn" : "%s个排版" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.update" : { - "translations" : { - "de" : "Aktualisieren", - "en" : "Update", - "es" : "Actualizar", - "fr" : "Actualiser", - "ru" : "", - "zh_cn" : "更新" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.libraries.updates" : { - "translations" : { - "de" : "AKTUALISIERUNG", - "en" : "UPDATES", - "es" : "ACTUALIZACIONES", - "fr" : "MISES À JOUR", - "ru" : "", - "zh_cn" : "更新" - }, - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs" ] - }, - "workspace.library.all" : { - "translations" : { - "de" : "Alle Bibliotheken", - "en" : "All libraries", - "es" : "Todas", - "fr" : "Toutes les bibliothèques", - "ru" : "Все библиотеки", - "zh_cn" : "所有库" - }, - "unused" : true - }, - "workspace.library.libraries" : { - "translations" : { - "de" : "Bibliotheken", - "en" : "Libraries", - "es" : "Bibliotecas", - "fr" : "Bibliothèques", - "ru" : "Библиотеки", - "zh_cn" : "库" - }, - "unused" : true - }, - "workspace.library.own" : { - "translations" : { - "de" : "Meine Bibliotheken", - "en" : "My libraries", - "es" : "Mis bibliotecas", - "fr" : "Mes bibliothèques", - "ru" : "Мои библиотеки", - "zh_cn" : "我的库" - }, - "unused" : true - }, - "workspace.library.store" : { - "translations" : { - "de" : "Gespeicherte Bibliotheken", - "en" : "Store libraries", - "es" : "Predefinidas", - "fr" : "Prédéfinies", - "ru" : "Сохраненные библиотеки", - "zh_cn" : "来自商店的库" - }, - "unused" : true - }, - "workspace.options.blur-options.background-blur" : { - "translations" : { - "de" : "Hintergrund", - "en" : "Background", - "es" : "Fondo", - "fr" : "Fond", - "zh_cn" : "背景" - }, - "unused" : true - }, - "workspace.options.blur-options.layer-blur" : { - "translations" : { - "de" : "Ebene", - "en" : "Layer", - "es" : "Capa", - "fr" : "Calque", - "zh_cn" : "图层" - }, - "unused" : true - }, - "workspace.options.blur-options.title" : { - "translations" : { - "de" : "Weichzeichnen", - "en" : "Blur", - "es" : "Desenfoque", - "fr" : "Flou", - "zh_cn" : "模糊" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/blur.cljs" ] - }, - "workspace.options.blur-options.title.group" : { - "translations" : { - "de" : "Gruppe weichzeichnen", - "en" : "Group blur", - "es" : "Desenfoque del grupo", - "fr" : "Flou de groupe", - "zh_cn" : "编组模糊" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/blur.cljs" ] - }, - "workspace.options.blur-options.title.multiple" : { - "translations" : { - "de" : "Auswahl weichzeichnen", - "en" : "Selection blur", - "es" : "Desenfoque de la selección", - "fr" : "Flou de sélection", - "zh_cn" : "选项模糊" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/blur.cljs" ] - }, - "workspace.options.canvas-background" : { - "translations" : { - "de" : "Hintergrundfarbe", - "en" : "Canvas background", - "es" : "Color de fondo", - "fr" : "Couleur de fond du canvas", - "ru" : "Фон холста", - "zh_cn" : "画布背景" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/page.cljs" ] - }, - "workspace.options.component" : { - "translations" : { - "de" : "Komponente", - "en" : "Component", - "es" : "Componente", - "fr" : "Composant", - "zh_cn" : "组件" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/component.cljs" ] - }, - "workspace.options.design" : { - "translations" : { - "de" : "Design", - "en" : "Design", - "es" : "Diseño", - "fr" : "Conception", - "ru" : "Дизайн", - "zh_cn" : "设计" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options.cljs" ] - }, - "workspace.options.export" : { - "translations" : { - "de" : "Exportieren", - "en" : "Export", - "es" : "Exportar", - "fr" : "Export", - "ru" : "Экспорт", - "zh_cn" : "导出" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/exports.cljs", "src/app/main/ui/handoff/exports.cljs" ] - }, - "workspace.options.export-object" : { - "translations" : { - "de" : "Form exportieren", - "en" : "Export shape", - "es" : "Exportar forma", - "fr" : "Exporter la forme", - "ru" : "Экспорт фигуры", - "zh_cn" : "导出形状" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/exports.cljs", "src/app/main/ui/handoff/exports.cljs" ] - }, - "workspace.options.export.suffix" : { - "translations" : { - "de" : "Suffix", - "en" : "Suffix", - "es" : "Sufijo", - "fr" : "Suffixe", - "zh_cn" : "后缀" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/exports.cljs" ] - }, - "workspace.options.exporting-object" : { - "translations" : { - "de" : "Exportiere…", - "en" : "Exporting…", - "es" : "Exportando", - "fr" : "Export en cours…", - "ru" : "Экспортирую…", - "zh_cn" : "正在导出…" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/exports.cljs", "src/app/main/ui/handoff/exports.cljs" ] - }, - "workspace.options.fill" : { - "translations" : { - "de" : "Fläche", - "en" : "Fill", - "es" : "Relleno", - "fr" : "Remplissage", - "ru" : "Заливка", - "zh_cn" : "填充" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/fill.cljs" ] - }, - "workspace.options.grid.auto" : { - "translations" : { - "de" : "Automatisch", - "en" : "Auto", - "es" : "Automático", - "fr" : "Automatique", - "ru" : "Авто", - "zh_cn" : "自动" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.column" : { - "translations" : { - "de" : "Spalten", - "en" : "Columns", - "es" : "Columnas", - "fr" : "Colonnes", - "ru" : "Колонки", - "zh_cn" : "列" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.columns" : { - "translations" : { - "de" : "Spalten", - "en" : "Columns", - "es" : "Columnas", - "fr" : "Colonnes", - "ru" : "Колонки", - "zh_cn" : "列" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.gutter" : { - "translations" : { - "de" : "Zwischenräume", - "en" : "Gutter", - "es" : "Espaciado", - "fr" : "Gouttière", - "ru" : "Желоб", - "zh_cn" : "栅格间隔" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.height" : { - "translations" : { - "de" : "Höhe", - "en" : "Height", - "es" : "Altura", - "fr" : "Hauteur", - "ru" : "Высота", - "zh_cn" : "高" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.margin" : { - "translations" : { - "de" : "Rand", - "en" : "Margin", - "es" : "Margen", - "fr" : "Marge", - "ru" : "Поле", - "zh_cn" : "外边距" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.rows" : { - "translations" : { - "de" : "Zeilen", - "en" : "Rows", - "es" : "Filas", - "fr" : "Lignes", - "ru" : "Строки", - "zh_cn" : "行" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.set-default" : { - "translations" : { - "de" : "Als Standard festlegen", - "en" : "Set as default", - "es" : "Establecer valor por defecto", - "fr" : "Définir par défaut", - "ru" : "Установить по умолчанию", - "zh_cn" : "设为默认" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.size" : { - "translations" : { - "de" : "Größe", - "en" : "Size", - "es" : "Tamaño", - "fr" : "Taille", - "ru" : "Размер", - "zh_cn" : "尺寸" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.type" : { - "translations" : { - "de" : "Art", - "en" : "Type", - "es" : "Tipo", - "fr" : "Type", - "ru" : "Тип", - "zh_cn" : "类型" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.type.bottom" : { - "translations" : { - "de" : "Unten", - "en" : "Bottom", - "es" : "Abajo", - "fr" : "Bas", - "ru" : "Низ", - "zh_cn" : "底" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.type.center" : { - "translations" : { - "de" : "Zentriert", - "en" : "Center", - "es" : "Centro", - "fr" : "Centre", - "ru" : "Центр", - "zh_cn" : "居中" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.type.left" : { - "translations" : { - "de" : "Links", - "en" : "Left", - "es" : "Izquierda", - "fr" : "Gauche", - "ru" : "Левый", - "zh_cn" : "左" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.type.right" : { - "translations" : { - "de" : "Rechts", - "en" : "Right", - "es" : "Derecha", - "fr" : "Droite", - "ru" : "Правый", - "zh_cn" : "右" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.type.stretch" : { - "translations" : { - "de" : "Gestreckt", - "en" : "Stretch", - "es" : "Estirar", - "fr" : "Étirer", - "ru" : "Растягивать", - "zh_cn" : "拉伸" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.type.top" : { - "translations" : { - "de" : "Oben", - "en" : "Top", - "es" : "Arriba", - "fr" : "Haut", - "ru" : "Верх", - "zh_cn" : "顶" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.use-default" : { - "translations" : { - "de" : "Standardwerte verwenden", - "en" : "Use default", - "es" : "Usar valor por defecto", - "fr" : "Utiliser la valeur par défaut", - "ru" : "Использовать значение по умолчанию", - "zh_cn" : "使用默认" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.params.width" : { - "translations" : { - "de" : "Breite", - "en" : "Width", - "es" : "Ancho", - "fr" : "Largeur", - "ru" : "Ширина", - "zh_cn" : "宽" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.row" : { - "translations" : { - "de" : "Zeile", - "en" : "Rows", - "es" : "Filas", - "fr" : "Lignes", - "ru" : "Строки", - "zh_cn" : "行" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.square" : { - "translations" : { - "de" : "Quadrat", - "en" : "Square", - "es" : "Cuadros", - "fr" : "Carré", - "ru" : "Квадрат", - "zh_cn" : "正方形" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.grid.title" : { - "translations" : { - "de" : "Raster & Layouts", - "en" : "Grid & Layouts", - "es" : "Rejilla & Estructuras", - "fr" : "Grille & Calques", - "ru" : "Сетка и Макеты", - "zh_cn" : "网格与布局" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs" ] - }, - "workspace.options.group-fill" : { - "translations" : { - "de" : "Gruppe füllen", - "en" : "Group fill", - "es" : "Relleno de grupo", - "fr" : "Remplissage de groupe", - "ru" : "Заливка для группы", - "zh_cn" : "编组填充" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/fill.cljs" ] - }, - "workspace.options.group-stroke" : { - "translations" : { - "de" : "Gruppe einrahmen", - "en" : "Group stroke", - "es" : "Borde de grupo", - "fr" : "Contour de groupe", - "ru" : "Обводка для группы", - "zh_cn" : "编组边框" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs" ] - }, - "workspace.options.layer-options.blend-mode.color" : { - "translations" : { - "de" : "Farbe", - "en" : "Color", - "es" : "Color" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.color-burn" : { - "translations" : { - "de" : "Farbig nachbelichten", - "en" : "Color burn", - "es" : "Color más oscuro" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.color-dodge" : { - "translations" : { - "de" : "Farbig abwedeln", - "en" : "Color dodge", - "es" : "Color más suave" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.darken" : { - "translations" : { - "de" : "Abdunkeln", - "en" : "Darken", - "es" : "Oscurecer" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.difference" : { - "translations" : { - "de" : "Differenz", - "en" : "Difference", - "es" : "Diferencia" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.exclusion" : { - "translations" : { - "de" : "Ausschluss", - "en" : "Exclusion", - "es" : "Exclusión" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.hard-light" : { - "translations" : { - "de" : "Hartes Licht", - "en" : "Hard light", - "es" : "Luz fuerte" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.hue" : { - "translations" : { - "de" : "Farbton", - "en" : "Hue", - "es" : "Tono" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.lighten" : { - "translations" : { - "de" : "Aufhellen", - "en" : "Lighten", - "es" : "Aclarar" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.luminosity" : { - "translations" : { - "de" : "Luminanz", - "en" : "Luminosity", - "es" : "Luminosidad" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.multiply" : { - "translations" : { - "de" : "Multiplizieren", - "en" : "Multiply", - "es" : "Multiplicar" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.normal" : { - "translations" : { - "de" : "Normal", - "en" : "Normal", - "es" : "Normal" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.overlay" : { - "translations" : { - "de" : "Ineinanderkopieren", - "en" : "Overlay", - "es" : "Superponer" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.saturation" : { - "translations" : { - "de" : "Sättigung", - "en" : "Saturation", - "es" : "Saturación" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.screen" : { - "translations" : { - "de" : "Negativ multiplizieren", - "en" : "Screen", - "es" : "Trama" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.blend-mode.soft-light" : { - "translations" : { - "de" : "Weiches Licht", - "en" : "Soft light", - "es" : "Luz suave" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.title" : { - "translations" : { - "de" : "Ebene", - "en" : "Layer", - "es" : "Capa" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.title.group" : { - "translations" : { - "de" : "Ebenen gruppieren", - "en" : "Group layers", - "es" : "Capas de grupo" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.layer-options.title.multiple" : { - "translations" : { - "de" : "Ausgewählte Ebenen", - "en" : "Selected layers", - "es" : "Capas seleccionadas" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/layer.cljs" ] - }, - "workspace.options.navigate-to" : { - "translations" : { - "de" : "Navigiere zu", - "en" : "Navigate to", - "es" : "Navegar a", - "fr" : "Naviguer vers", - "ru" : "Перейти к", - "zh_cn" : "导航到" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs" ] - }, - "workspace.options.none" : { - "translations" : { - "de" : "Keine", - "en" : "None", - "es" : "Ninguno", - "fr" : "Aucun", - "ru" : "Не задано", - "zh_cn" : "无" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs" ] - }, - "workspace.options.position" : { - "translations" : { - "de" : "Position", - "en" : "Position", - "es" : "Posición", - "fr" : "Position", - "ru" : "Позиция", - "zh_cn" : "位置" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs", "src/app/main/ui/workspace/sidebar/options/menus/measures.cljs" ] - }, - "workspace.options.prototype" : { - "translations" : { - "de" : "Prototyp", - "en" : "Prototype", - "es" : "Prototipo", - "fr" : "Prototype", - "ru" : "Прототип", - "zh_cn" : "原型" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options.cljs" ] - }, - "workspace.options.radius" : { - "translations" : { - "de" : "Radius", - "en" : "Radius", - "es" : "Radio", - "fr" : "Rayon", - "ru" : "Радиус", - "zh_cn" : "圆角" - }, - "unused" : true - }, - "workspace.options.radius.all-corners" : { - "translations" : { - "de" : "Alle Ecken", - "en" : "All corners", - "es" : "Todas las esquinas", - "zh_cn" : "所有角" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/measures.cljs" ] - }, - "workspace.options.radius.single-corners" : { - "translations" : { - "de" : "Einzelne Ecken", - "en" : "Single corners", - "es" : "Esquinas individuales", - "zh_cn" : "单个角" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/measures.cljs" ] - }, - "workspace.options.rotation" : { - "translations" : { - "de" : "Drehung", - "en" : "Rotation", - "es" : "Rotación", - "fr" : "Rotation", - "ru" : "Вращение", - "zh_cn" : "旋转" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/measures.cljs" ] - }, - "workspace.options.select-a-shape" : { - "translations" : { - "de" : "Wählen Sie eine Form, Zeichenfläche oder Gruppe aus, um eine Verbindung zu einer anderen Zeichenfläche herzustellen.", - "en" : "Select a shape, artboard or group to drag a connection to other artboard.", - "es" : "Selecciona una figura, tablero o grupo para arrastrar una conexión a otro tablero.", - "fr" : "Sélectionnez une forme, un plan de travail ou un groupe pour faire glisser une connexion vers un autre plan de travail.", - "ru" : "Выберите фигуру, рабочую область или группу чтобы перенести связь на другую рабочую область.", - "zh_cn" : "选择一个形状、画板或编组,拖至另一个画板,以创建关联。" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs" ] - }, - "workspace.options.select-artboard" : { - "translations" : { - "de" : "Wählen Sie eine Zeichenfläche aus", - "en" : "Select artboard", - "es" : "Selecciona un tablero", - "fr" : "Sélectionner un plan de travail", - "ru" : "Выберите рабочую область", - "zh_cn" : "选择画板" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs" ] - }, - "workspace.options.selection-fill" : { - "translations" : { - "de" : "Auswahl füllen", - "en" : "Selection fill", - "es" : "Relleno de selección", - "fr" : "Remplissage de sélection", - "ru" : "Заливка выбранного", - "zh_cn" : "选项填充" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/fill.cljs" ] - }, - "workspace.options.selection-stroke" : { - "translations" : { - "de" : "Auswahl einrahmen", - "en" : "Selection stroke", - "es" : "Borde de selección", - "fr" : "Contour de sélection", - "ru" : "Обводка выбранного", - "zh_cn" : "选项边框" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs" ] - }, - "workspace.options.shadow-options.blur" : { - "translations" : { - "de" : "Weichzeichnen", - "en" : "Blur", - "es" : "Desenfoque", - "fr" : "Flou", - "zh_cn" : "模糊" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs" ] - }, - "workspace.options.shadow-options.drop-shadow" : { - "translations" : { - "de" : "Schlagschatten", - "en" : "Drop shadow", - "es" : "Sombra arrojada", - "fr" : "Ombre portée", - "zh_cn" : "外阴影" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs" ] - }, - "workspace.options.shadow-options.inner-shadow" : { - "translations" : { - "de" : "Schatten nach innen", - "en" : "Inner shadow", - "es" : "Sombra interior", - "fr" : "Ombre intérieure", - "zh_cn" : "内阴影" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs" ] - }, - "workspace.options.shadow-options.offsetx" : { - "translations" : { - "de" : "X", - "en" : "X", - "es" : "X", - "fr" : "X", - "zh_cn" : "X" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs" ] - }, - "workspace.options.shadow-options.offsety" : { - "translations" : { - "de" : "Y", - "en" : "Y", - "es" : "Y", - "fr" : "Y", - "zh_cn" : "Y" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs" ] - }, - "workspace.options.shadow-options.spread" : { - "translations" : { - "de" : "Streuung", - "en" : "Spread", - "es" : "Difusión", - "fr" : "Diffusion", - "zh_cn" : "展开" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs" ] - }, - "workspace.options.shadow-options.title" : { - "translations" : { - "de" : "Schatten", - "en" : "Shadow", - "es" : "Sombra", - "fr" : "Ombre", - "zh_cn" : "阴影" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs" ] - }, - "workspace.options.shadow-options.title.group" : { - "translations" : { - "de" : "Gruppe schattieren", - "en" : "Group shadow", - "es" : "Sombra del grupo", - "fr" : "Ombre de groupe", - "zh_cn" : "编组阴影" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs" ] - }, - "workspace.options.shadow-options.title.multiple" : { - "translations" : { - "de" : "Auswahl schattieren", - "en" : "Selection shadows", - "es" : "Sombras de la seleccíón", - "fr" : "Ombres de la sélection", - "zh_cn" : "选项阴影" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs" ] - }, - "workspace.options.size" : { - "translations" : { - "de" : "Größe", - "en" : "Size", - "es" : "Tamaño", - "fr" : "Taille", - "ru" : "Размер", - "zh_cn" : "尺寸" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs", "src/app/main/ui/workspace/sidebar/options/menus/measures.cljs" ] - }, - "workspace.options.size-presets" : { - "translations" : { - "de" : "Größenvoreinstellungen", - "en" : "Size presets", - "es" : "Tamaños predefinidos", - "fr" : "Tailles prédéfinies", - "ru" : "Предустановки для размеров", - "zh_cn" : "尺寸预设" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs" ] - }, - "workspace.options.stroke" : { - "translations" : { - "de" : "Rahmen", - "en" : "Stroke", - "es" : "Borde", - "fr" : "Bordure", - "ru" : "Обводка", - "zh_cn" : "边框" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs" ] - }, - "workspace.options.stroke.center" : { - "translations" : { - "de" : "Zentriert", - "en" : "Center", - "es" : "Centro", - "fr" : "Centre", - "ru" : "Центр", - "zh_cn" : "居中" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs" ] - }, - "workspace.options.stroke.dashed" : { - "translations" : { - "de" : "Gestrichelt", - "en" : "Dashed", - "es" : "Rayado", - "fr" : "Tirets", - "ru" : "Пунктирный", - "zh_cn" : "长虚线" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs" ] - }, - "workspace.options.stroke.dotted" : { - "translations" : { - "de" : "Gepunktet", - "en" : "Dotted", - "es" : "Punteado", - "fr" : "Pointillé", - "ru" : "Точечный", - "zh_cn" : "虚线" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs" ] - }, - "workspace.options.stroke.inner" : { - "translations" : { - "de" : "Innen", - "en" : "Inside", - "es" : "Interior", - "fr" : "Intérieur", - "ru" : "Внутрь", - "zh_cn" : "内部" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs" ] - }, - "workspace.options.stroke.mixed" : { - "translations" : { - "de" : "Mehrere", - "en" : "Mixed", - "es" : "Mezclado", - "fr" : "Mixte", - "ru" : "Смешаный", - "zh_cn" : "混合" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs" ] - }, - "workspace.options.stroke.outer" : { - "translations" : { - "de" : "Außen", - "en" : "Outside", - "es" : "Exterior", - "fr" : "Extérieur", - "ru" : "Наружу", - "zh_cn" : "外部" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs" ] - }, - "workspace.options.stroke.solid" : { - "translations" : { - "de" : "Solid", - "en" : "Solid", - "es" : "Sólido", - "fr" : "Solide", - "ru" : "Сплошной", - "zh_cn" : "实线" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs" ] - }, - "workspace.options.text-options.align-bottom" : { - "translations" : { - "de" : "Unten ausrichten", - "en" : "Align bottom", - "es" : "Alinear abajo", - "fr" : "Aligner en bas", - "ru" : "Выровнять низ", - "zh_cn" : "底部对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.align-center" : { - "translations" : { - "de" : "Zentrieren", - "en" : "Align center", - "es" : "Aliniear al centro", - "fr" : "Aligner au centre", - "ru" : "Выравнивание по центру", - "zh_cn" : "居中对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.align-justify" : { - "translations" : { - "de" : "Ausrichtung in der Breite", - "en" : "Justify", - "es" : "Justificar", - "fr" : "Justifier", - "ru" : "Выравнивание по ширине", - "zh_cn" : "整理" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.align-left" : { - "translations" : { - "de" : "Linksbündig ausrichten", - "en" : "Align left", - "es" : "Alinear a la izquierda", - "fr" : "Aligner à gauche", - "ru" : "Выравнивание по левому краю", - "zh_cn" : "靠左对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.align-middle" : { - "translations" : { - "de" : "An Mitte ausrichten", - "en" : "Align middle", - "es" : "Alinear al centro", - "fr" : "Aligner verticalement au milieu", - "ru" : "Выравнивание по центру", - "zh_cn" : "中间对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.align-right" : { - "translations" : { - "de" : "Rechtsbündig ausrichten", - "en" : "Align right", - "es" : "Alinear a la derecha", - "fr" : "Aligner à droite", - "ru" : "Выравнивание по правому краю", - "zh_cn" : "靠右对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.align-top" : { - "translations" : { - "de" : "Oben ausrichten", - "en" : "Align top", - "es" : "Alinear arriba", - "fr" : "Aligner en haut", - "ru" : "Выравнивание по верхнему краю", - "zh_cn" : "顶部对齐" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.decoration" : { - "translations" : { - "de" : "Textdekoration", - "en" : "Decoration", - "es" : "Decoración", - "fr" : "Décoration", - "ru" : "Оформление", - "zh_cn" : "装饰" - }, - "unused" : true - }, - "workspace.options.text-options.direction-ltr" : { - "translations" : { - "en" : "LTR", - "es" : "LTR" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.direction-rtl" : { - "translations" : { - "en" : "RTL", - "es" : "RTL" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.google" : { - "translations" : { - "de" : "Google", - "en" : "Google", - "es" : "Google", - "zh_cn" : "谷歌" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.options.text-options.grow-auto-height" : { - "translations" : { - "de" : "Automatische Höhe", - "en" : "Auto height", - "es" : "Alto automático", - "fr" : "Hauteur automatique", - "zh_cn" : "自动高度" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.grow-auto-width" : { - "translations" : { - "de" : "Automatische Breite", - "en" : "Auto width", - "es" : "Ancho automático", - "fr" : "Largeur automatique", - "zh_cn" : "自动宽度" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.grow-fixed" : { - "translations" : { - "de" : "Feste Größe", - "en" : "Fixed", - "es" : "Fijo", - "fr" : "Fixe", - "zh_cn" : "固定" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.letter-spacing" : { - "translations" : { - "de" : "Zeichenabstand", - "en" : "Letter Spacing", - "es" : "Espaciado entre letras", - "fr" : "Interlettrage", - "ru" : "Межсимвольный интервал", - "zh_cn" : "字距" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.options.text-options.line-height" : { - "translations" : { - "de" : "Zeilenabstand", - "en" : "Line height", - "es" : "Altura de línea", - "fr" : "Interlignage", - "ru" : "Высота строки", - "zh_cn" : "行高" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.options.text-options.lowercase" : { - "translations" : { - "de" : "Kleinbuchstaben", - "en" : "Lowercase", - "es" : "Minúsculas", - "fr" : "Minuscule", - "ru" : "Нижний регистр", - "zh_cn" : "小写" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.options.text-options.none" : { - "translations" : { - "de" : "Keine", - "en" : "None", - "es" : "Nada", - "fr" : "Aucune", - "ru" : "Не задано", - "zh_cn" : "无" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs", "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.options.text-options.preset" : { - "translations" : { - "de" : "Vordefiniert", - "en" : "Preset", - "es" : "Predefinidos", - "zh_cn" : "预设" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.options.text-options.strikethrough" : { - "translations" : { - "de" : "Durchgestrichen", - "en" : "Strikethrough", - "es" : "Tachado", - "fr" : "Barré", - "ru" : "Перечеркнутый", - "zh_cn" : "删除线" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.text-case" : { - "translations" : { - "de" : "Schriftauszeichnung", - "en" : "Case", - "es" : "Mayús/minús", - "fr" : "Casse", - "ru" : "Регистр", - "zh_cn" : "大小写模式" - }, - "unused" : true - }, - "workspace.options.text-options.title" : { - "translations" : { - "de" : "Text", - "en" : "Text", - "es" : "Texto", - "fr" : "Texte", - "ru" : "Текст", - "zh_cn" : "文本" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.title-group" : { - "translations" : { - "de" : "Gruppe Text", - "en" : "Group text", - "es" : "Texto de grupo", - "fr" : "Texte de groupe", - "ru" : "Текст группы", - "zh_cn" : "编组文本" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.title-selection" : { - "translations" : { - "de" : "Ausgewählter Text", - "en" : "Selection text", - "es" : "Texto de selección", - "fr" : "Texte de la sélection", - "ru" : "Выбранный текст", - "zh_cn" : "选项文本" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.titlecase" : { - "translations" : { - "de" : "Kapitälchen", - "en" : "Title Case", - "es" : "Título", - "fr" : "Premières Lettres en Capitales", - "ru" : "Каждое слово с заглавной буквы", - "zh_cn" : "首字母大写" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.options.text-options.underline" : { - "translations" : { - "de" : "Unterstrichen", - "en" : "Underline", - "es" : "Subrayado", - "fr" : "Soulignage", - "ru" : "Подчеркнутый", - "zh_cn" : "下划线" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/text.cljs" ] - }, - "workspace.options.text-options.uppercase" : { - "translations" : { - "de" : "Großbuchstaben", - "en" : "Uppercase", - "es" : "Mayúsculas", - "fr" : "Majuscule", - "ru" : "Верхний регистр", - "zh_cn" : "大写" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/typography.cljs" ] - }, - "workspace.options.text-options.vertical-align" : { - "translations" : { - "de" : "Vertikal ausrichten", - "en" : "Vertical align", - "es" : "Alineación vertical", - "fr" : "Alignement vertical", - "ru" : "Вертикальное выравнивание", - "zh_cn" : "垂直对齐" - }, - "unused" : true - }, - "workspace.options.use-play-button" : { - "translations" : { - "de" : "Verwenden Sie die Wiedergabetaste in der Kopfzeile, um die Prototypansicht zu wechseln.", - "en" : "Use the play button at the header to run the prototype view.", - "es" : "Usa el botón de play de la cabecera para arrancar la vista de prototipo.", - "fr" : "Utilisez le bouton de lecture dans l’en‑tête pour exécuter la vue du prototype.", - "ru" : "Используй кнопку запуск в заголовке чтобы перейти на экран прототипа.", - "zh_cn" : "点击页面顶端的播放按钮预览原型。" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs" ] - }, - "workspace.shape.menu.back" : { - "translations" : { - "de" : "In den Hintergrund", - "en" : "Send to back", - "es" : "Enviar al fondo", - "fr" : "Envoyer au fond", - "zh_cn" : "移至底层" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.backward" : { - "translations" : { - "de" : "Eins nach hinten", - "en" : "Send backward", - "es" : "Enviar atrás", - "fr" : "Éloigner", - "zh_cn" : "向下移动一层" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.copy" : { - "translations" : { - "de" : "Kopieren", - "en" : "Copy", - "es" : "Copiar", - "fr" : "Copier", - "zh_cn" : "复制" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.create-component" : { - "translations" : { - "de" : "Komponente erstellen", - "en" : "Create component", - "es" : "Crear componente", - "fr" : "Créer un composant", - "zh_cn" : "创建组件" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.cut" : { - "translations" : { - "de" : "Ausschneiden", - "en" : "Cut", - "es" : "Cortar", - "fr" : "Couper", - "zh_cn" : "剪切" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.delete" : { - "translations" : { - "de" : "Löschen", - "en" : "Delete", - "es" : "Eliminar", - "fr" : "Supprimer", - "zh_cn" : "删除" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.detach-instance" : { - "translations" : { - "de" : "Instanz trennen", - "en" : "Detach instance", - "es" : "Desacoplar instancia", - "fr" : "Détacher l’instance", - "zh_cn" : "解绑实例" - }, - "used-in" : [ "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" ] - }, - "workspace.shape.menu.duplicate" : { - "translations" : { - "de" : "Duplizieren", - "en" : "Duplicate", - "es" : "Duplicar", - "fr" : "Dupliquer", - "zh_cn" : "创建副本" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.edit" : { - "translations" : { - "de" : "Bearbeiten", - "en" : "Edit", - "es" : "Editar", - "zh_cn" : "编辑" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.flip-horizontal" : { - "translations" : { - "de" : "Horizontal spiegeln", - "en" : "Flip horizontal", - "es" : "Voltear horizontal", - "zh_cn" : "水平翻转" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.flip-vertical" : { - "translations" : { - "de" : "Vertikal spiegeln", - "en" : "Flip vertical", - "es" : "Voltear vertical", - "zh_cn" : "垂直翻转" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.forward" : { - "translations" : { - "de" : "Eins nach vorne", - "en" : "Bring forward", - "es" : "Mover hacia delante", - "fr" : "Avancer", - "zh_cn" : "向上移动一层" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.front" : { - "translations" : { - "de" : "In den Vordergrund", - "en" : "Bring to front", - "es" : "Mover al frente", - "fr" : "Amener au premier plan", - "zh_cn" : "移至顶层" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.go-main" : { - "translations" : { - "de" : "Zur Hauptkomponentendatei wechseln", - "en" : "Go to main component file", - "es" : "Ir al archivo del componente principal", - "fr" : "Aller au fichier du composant principal", - "zh_cn" : "前往主组件文档" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/component.cljs", "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.group" : { - "translations" : { - "de" : "Gruppieren", - "en" : "Group", - "es" : "Grupo", - "fr" : "Groupe", - "zh_cn" : "编组" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.hide" : { - "translations" : { - "de" : "Ausblenden", - "en" : "Hide", - "es" : "Ocultar", - "fr" : "Masquer", - "zh_cn" : "隐藏" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.lock" : { - "translations" : { - "de" : "Sperren", - "en" : "Lock", - "es" : "Bloquear", - "fr" : "Bloquer", - "zh_cn" : "锁定" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.mask" : { - "translations" : { - "de" : "Maskieren", - "en" : "Mask", - "es" : "Máscara", - "fr" : "Masque", - "zh_cn" : "蒙板" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs", "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.paste" : { - "translations" : { - "de" : "Einfügen", - "en" : "Paste", - "es" : "Pegar", - "fr" : "Coller", - "zh_cn" : "粘贴" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs", "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.reset-overrides" : { - "translations" : { - "de" : "Änderungen zurücksetzen", - "en" : "Reset overrides", - "es" : "Deshacer modificaciones", - "fr" : "Annuler les modifications", - "zh_cn" : "还原自定义选项" - }, - "used-in" : [ "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" ] - }, - "workspace.shape.menu.show" : { - "translations" : { - "de" : "Anzeigen", - "en" : "Show", - "es" : "Mostrar", - "fr" : "Montrer", - "zh_cn" : "显示" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.show-main" : { - "translations" : { - "de" : "Hauptkomponente anzeigen", - "en" : "Show main component", - "es" : "Ver componente principal", - "fr" : "Afficher le composant principal", - "zh_cn" : "显示主组件" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/component.cljs", "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.ungroup" : { - "translations" : { - "de" : "Gruppierung aufheben", - "en" : "Ungroup", - "es" : "Desagrupar", - "fr" : "Dégrouper", - "zh_cn" : "取消编组" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.unlock" : { - "translations" : { - "de" : "Entsperren", - "en" : "Unlock", - "es" : "Desbloquear", - "fr" : "Débloquer", - "zh_cn" : "取消锁定" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.unmask" : { - "translations" : { - "de" : "Maske entfernen", - "en" : "Unmask", - "es" : "Quitar máscara", - "fr" : "Supprimer le masque", - "zh_cn" : "取消蒙版" - }, - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs" ] - }, - "workspace.shape.menu.update-main" : { - "translations" : { - "de" : "Hauptkomponente aktualisieren", - "en" : "Update main component", - "es" : "Actualizar componente principal", - "fr" : "Actualiser le composant principal", - "zh_cn" : "更新主组件" - }, - "used-in" : [ "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" ] - }, - "workspace.sidebar.history" : { - "translations" : { - "de" : "Verlauf (%s)", - "en" : "History (%s)", - "es" : "Historial (%s)", - "fr" : "Historique (%s)", - "zh_cn" : "历史(%s)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.sidebar.layers" : { - "translations" : { - "de" : "Ebenen (%s)", - "en" : "Layers (%s)", - "es" : "Capas (%s)", - "fr" : "Calques (%s)", - "zh_cn" : "图层(%s)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.sidebar.options.svg-attrs.title" : { - "translations" : { - "de" : "Importierte SVG-Attribute", - "en" : "Imported SVG Attributes", - "es" : "Atributos del SVG Importado" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs", "src/app/main/ui/handoff/attributes/svg.cljs" ] - }, - "workspace.sidebar.sitemap" : { - "translations" : { - "de" : "Seiten", - "en" : "Pages", - "es" : "Páginas", - "fr" : "Pages", - "ru" : "Страницы", - "zh_cn" : "页面" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs" ] - }, - "workspace.sitemap" : { - "translations" : { - "de" : "Sitemap", - "en" : "Sitemap", - "es" : "Mapa del sitio", - "fr" : "Plan du site", - "ru" : "Карта сайта", - "zh_cn" : "站点地图" - }, - "used-in" : [ "src/app/main/ui/workspace/header.cljs" ] - }, - "workspace.toolbar.assets" : { - "translations" : { - "de" : "Assets (%s)", - "en" : "Assets (%s)", - "es" : "Recursos (%s)", - "fr" : "Ressources (%s)", - "ru" : "", - "zh_cn" : "素材(%s)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.toolbar.color-palette" : { - "translations" : { - "de" : "Farbpalette (%s)", - "en" : "Color Palette (%s)", - "es" : "Paleta de colores (%s)", - "fr" : "Palette de couleurs (%s)", - "ru" : "Палитра цветов (%s)", - "zh_cn" : "调色盘(%s)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.toolbar.comments" : { - "translations" : { - "de" : "Kommentare (%s)", - "en" : "Comments (%s)", - "es" : "Comentarios (%s)", - "fr" : "Commentaires (%s)", - "zh_cn" : "评论(%s)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.toolbar.curve" : { - "translations" : { - "de" : "Stift (%s)", - "en" : "Curve (%s)", - "es" : "Curva (%s)", - "fr" : "Courbe (%s)", - "ru" : "Кривая (%s)", - "zh_cn" : "曲线(%s)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.toolbar.ellipse" : { - "translations" : { - "de" : "Ellipse (E)", - "en" : "Ellipse (E)", - "es" : "Elipse (E)", - "fr" : "Ellipse (E)", - "ru" : "", - "zh_cn" : "椭圆(E)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.toolbar.frame" : { - "translations" : { - "de" : "Zeichenfläche (A)", - "en" : "Artboard (A)", - "es" : "Tablero (A)", - "fr" : "Plan de travail (A)", - "ru" : "Рабочая область (A)", - "zh_cn" : "画板(A)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.toolbar.image" : { - "translations" : { - "de" : "Bild (K)", - "en" : "Image (K)", - "es" : "Imagen (K)", - "fr" : "Image (K)", - "ru" : "Изображение (K)", - "zh_cn" : "图片(K)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.toolbar.move" : { - "translations" : { - "de" : "Verschieben", - "en" : "Move", - "es" : "Mover", - "fr" : "Déplacer", - "ru" : "Вытеснить", - "zh_cn" : "移动" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.toolbar.path" : { - "translations" : { - "de" : "Pfad (P)", - "en" : "Path (P)", - "es" : "Ruta (P)", - "fr" : "Chemin (P)", - "ru" : "Линия (P)", - "zh_cn" : "路径(P)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.toolbar.rect" : { - "translations" : { - "de" : "Rechteck (R)", - "en" : "Rectangle (R)", - "es" : "Rectángulo (R)", - "fr" : "Rectangle (R)", - "ru" : "Прямоугольник (R)", - "zh_cn" : "矩形(R)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.toolbar.text" : { - "translations" : { - "de" : "Text (T)", - "en" : "Text (T)", - "es" : "Texto (T)", - "fr" : "Texte (T)", - "ru" : "Текст (T)", - "zh_cn" : "文本(T)" - }, - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs" ] - }, - "workspace.undo.empty" : { - "translations" : { - "de" : "Es gibt bisher keine Änderungen im Verlauf", - "en" : "There are no history changes so far", - "es" : "Todavía no hay cambios en el histórico", - "fr" : "Il n’y a aucun changement dans l’historique pour l’instant", - "zh_cn" : "目前没有历史修改" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs" ] - }, - "workspace.undo.entry.delete" : { - "translations" : { - "de" : "%s gelöscht", - "en" : "Deleted %s", - "es" : "%s eliminado", - "fr" : "Supprimé %s", - "zh_cn" : "%s已删除" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs" ] - }, - "workspace.undo.entry.modify" : { - "translations" : { - "de" : "%s verändert", - "en" : "Modified %s", - "es" : "%s modificado", - "fr" : "Modifié %s", - "zh_cn" : "%s已修改" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs" ] - }, - "workspace.undo.entry.move" : { - "translations" : { - "de" : "Verschobene Objekte", - "en" : "Moved objects", - "es" : "Objetos movidos", - "fr" : "Objets déplacés", - "zh_cn" : "对象已移动" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs" ] - }, - "workspace.undo.entry.multiple.circle" : { - "translations" : { - "de" : "Kreise", - "en" : "circles", - "es" : "círculos", - "fr" : "cercles", - "zh_cn" : "圆" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.color" : { - "translations" : { - "de" : "Farben", - "en" : "color assets", - "es" : "colores", - "fr" : "couleurs", - "zh_cn" : "颜色素材" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.component" : { - "translations" : { - "de" : "Komponenten", - "en" : "components", - "es" : "componentes", - "fr" : "composants", - "zh_cn" : "组件" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.curve" : { - "translations" : { - "de" : "Kurven", - "en" : "curves", - "es" : "curvas", - "fr" : "courbes", - "zh_cn" : "曲线" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.frame" : { - "translations" : { - "de" : "Zeichenfläche", - "en" : "artboard", - "es" : "mesa de trabajo", - "fr" : "plan de travail", - "zh_cn" : "画板" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.group" : { - "translations" : { - "de" : "Gruppen", - "en" : "groups", - "es" : "grupos", - "fr" : "groupes", - "zh_cn" : "编组" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.media" : { - "translations" : { - "de" : "Grafiken", - "en" : "graphic assets", - "es" : "gráficos", - "fr" : "graphiques", - "zh_cn" : "图形素材" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.multiple" : { - "translations" : { - "de" : "Objekte", - "en" : "objects", - "es" : "objetos", - "fr" : "objets", - "zh_cn" : "对象" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.page" : { - "translations" : { - "de" : "Seiten", - "en" : "pages", - "es" : "páginas", - "fr" : "pages", - "zh_cn" : "页面" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.path" : { - "translations" : { - "de" : "Pfade", - "en" : "paths", - "es" : "trazos", - "fr" : "chemins", - "zh_cn" : "路径" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.rect" : { - "translations" : { - "de" : "Rechtecke", - "en" : "rectangles", - "es" : "rectángulos", - "fr" : "rectangles", - "zh_cn" : "矩形" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.shape" : { - "translations" : { - "de" : "Formen", - "en" : "shapes", - "es" : "formas", - "fr" : "formes", - "zh_cn" : "形状" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.text" : { - "translations" : { - "de" : "Texte", - "en" : "texts", - "es" : "textos", - "fr" : "textes", - "zh_cn" : "文本" - }, - "unused" : true - }, - "workspace.undo.entry.multiple.typography" : { - "translations" : { - "de" : "Typografie", - "en" : "typography assets", - "es" : "tipografías", - "fr" : "typographie", - "zh_cn" : "排版素材" - }, - "unused" : true - }, - "workspace.undo.entry.new" : { - "translations" : { - "de" : "Neu: %s", - "en" : "New %s", - "es" : "Nuevo %s", - "fr" : "Nouveau %s", - "zh_cn" : "新建%s" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs" ] - }, - "workspace.undo.entry.single.circle" : { - "translations" : { - "de" : "Kreis", - "en" : "circle", - "es" : "círculo", - "fr" : "cercle", - "zh_cn" : "圆" - }, - "unused" : true - }, - "workspace.undo.entry.single.color" : { - "translations" : { - "de" : "Farben", - "en" : "color asset", - "es" : "color", - "fr" : "couleur", - "zh_cn" : "颜色素材" - }, - "unused" : true - }, - "workspace.undo.entry.single.component" : { - "translations" : { - "de" : "Komponente", - "en" : "component", - "es" : "componente", - "fr" : "composant", - "zh_cn" : "组件" - }, - "unused" : true - }, - "workspace.undo.entry.single.curve" : { - "translations" : { - "de" : "Kurve", - "en" : "curve", - "es" : "curva", - "fr" : "courbe", - "zh_cn" : "曲线" - }, - "unused" : true - }, - "workspace.undo.entry.single.frame" : { - "translations" : { - "de" : "Zeichenfläche", - "en" : "artboard", - "es" : "mesa de trabajo", - "fr" : "plan de travail", - "zh_cn" : "画板" - }, - "unused" : true - }, - "workspace.undo.entry.single.group" : { - "translations" : { - "de" : "Gruppe", - "en" : "group", - "es" : "grupo", - "fr" : "groupe", - "zh_cn" : "编组" - }, - "unused" : true - }, - "workspace.undo.entry.single.image" : { - "translations" : { - "de" : "Bild", - "en" : "image", - "es" : "imagen", - "fr" : "image", - "zh_cn" : "图片" - }, - "unused" : true - }, - "workspace.undo.entry.single.media" : { - "translations" : { - "de" : "Grafik", - "en" : "graphic asset", - "es" : "gráfico", - "fr" : "graphique", - "zh_cn" : "图形素材" - }, - "unused" : true - }, - "workspace.undo.entry.single.multiple" : { - "translations" : { - "de" : "Objekt", - "en" : "object", - "es" : "objeto", - "fr" : "objet", - "zh_cn" : "对象" - }, - "unused" : true - }, - "workspace.undo.entry.single.page" : { - "translations" : { - "de" : "Seite", - "en" : "page", - "es" : "página", - "fr" : "page", - "zh_cn" : "页面" - }, - "unused" : true - }, - "workspace.undo.entry.single.path" : { - "translations" : { - "de" : "Pfad", - "en" : "path", - "es" : "trazo", - "fr" : "chemin", - "zh_cn" : "路径" - }, - "unused" : true - }, - "workspace.undo.entry.single.rect" : { - "translations" : { - "de" : "Rechteck", - "en" : "rectangle", - "es" : "rectángulo", - "fr" : "rectangle", - "zh_cn" : "矩形" - }, - "unused" : true - }, - "workspace.undo.entry.single.shape" : { - "translations" : { - "de" : "Form", - "en" : "shape", - "es" : "forma", - "fr" : "forme", - "zh_cn" : "形状" - }, - "unused" : true - }, - "workspace.undo.entry.single.text" : { - "translations" : { - "de" : "Text", - "en" : "text", - "es" : "texto", - "fr" : "texte", - "zh_cn" : "文本" - }, - "unused" : true - }, - "workspace.undo.entry.single.typography" : { - "translations" : { - "de" : "Typografie", - "en" : "typography asset", - "es" : "tipografía", - "fr" : "typographie", - "zh_cn" : "排版素材" - }, - "unused" : true - }, - "workspace.undo.entry.unknown" : { - "translations" : { - "de" : "Vorgang über %s", - "en" : "Operation over %s", - "es" : "Operación sobre %s", - "fr" : "Opération sur %s", - "zh_cn" : "操作覆盖%s" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs" ] - }, - "workspace.undo.title" : { - "translations" : { - "de" : "Verlauf", - "en" : "History", - "es" : "Histórico", - "fr" : "Historique", - "zh_cn" : "历史" - }, - "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs" ] - }, - "workspace.updates.dismiss" : { - "translations" : { - "de" : "Ignorieren", - "en" : "Dismiss", - "es" : "Ignorar", - "fr" : "Ignorer", - "ru" : "", - "zh_cn" : "忽略" - }, - "used-in" : [ "src/app/main/data/workspace/libraries.cljs" ] - }, - "workspace.updates.there-are-updates" : { - "translations" : { - "de" : "Es gibt Updates in gemeinsam genutzten Bibliotheken", - "en" : "There are updates in shared libraries", - "es" : "Hay actualizaciones en librerías compartidas", - "fr" : "Il y a des mises à jour dans les Bibliothèques Partagées", - "ru" : "", - "zh_cn" : "共享库有更新" - }, - "used-in" : [ "src/app/main/data/workspace/libraries.cljs" ] - }, - "workspace.updates.update" : { - "translations" : { - "de" : "Aktualisieren", - "en" : "Update", - "es" : "Actualizar", - "fr" : "Actualiser", - "ru" : "", - "zh_cn" : "更新" - }, - "used-in" : [ "src/app/main/data/workspace/libraries.cljs" ] - }, - "workspace.viewport.click-to-close-path" : { - "translations" : { - "de" : "Klicken Sie, um den Pfad zu schließen", - "en" : "Click to close the path", - "es" : "Pulsar para cerrar la ruta", - "fr" : "Cliquez pour fermer le chemin", - "ru" : "Кликни чтобы закончить фигуру", - "zh_cn" : "单击以闭合路径" - }, - "unused" : true - } -} diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 11a7f3ebf..9a32d5ebc 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -7,14 +7,14 @@ (ns app.util.i18n "A i18n foundation." (:require + [app.config :as cfg] + [app.util.storage :refer [storage]] + [app.util.transit :as t] [beicon.core :as rx] [cuerdas.core :as str] [goog.object :as gobj] [okulary.core :as l] - [rumext.alpha :as mf] - [app.config :as cfg] - [app.util.storage :refer [storage]] - [app.util.transit :as t])) + [rumext.alpha :as mf])) (def supported-locales [{:label "English" :value "en"} @@ -23,6 +23,7 @@ {:label "Deutsch (community)" :value "de"} {:label "Русский (community)" :value "ru"} {:label "Türkçe (community)" :value "tr"} + {:label "Ελληνική γλώσσα (community)" :value "gr"} {:label "简体中文 (community)" :value "zh_cn"}]) (defn- parse-locale diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po new file mode 100644 index 000000000..0944d4574 --- /dev/null +++ b/frontend/translations/ca.po @@ -0,0 +1,482 @@ +msgid "" +msgstr "" +"Language: ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Ja tens un compte?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "" +"Revisa el teu email i fes click al link per verificar i començar a " +"utilitzar Penpot." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "Confirmar contrasenya" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Crea un compte de proba" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "Vols probar-ho?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"Aquest es un servei de PROBA. NO HO UTILITZIS per feina real, els projectes " +"seran esborrats periòdicament." + +#: 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 "Correu electrònic" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "Has oblidat la contrasenya?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "Nom complet" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.go-back-to-login" +msgstr "Tornar" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Inicia sessió aquí" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Accedir" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-subtitle" +msgstr "Introdueix les teves dades aquí" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Encantats de tornar a veure't" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Accedir amb Github" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "Accedir amb Gitlab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "Accedir amb LDAP" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Introdueix la nova contrasenya" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "El codi de recuperació no és vàlid" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-succesfully" +msgstr "La contrasenya s'ha canviat correctament" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "" +"El perfil encara no s'ha verificat, si us plau verifica-ho abans de " +"continuar." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Hem enviat un link de recuperació de contrasenya al teu email." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "T'has unit al equip" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Contrasenya" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "Com a mínim 8 caràcters" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Recuperar contrasenya" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "T'enviarem un correu electrónic amb instruccions" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Has oblidat la teva contrasenya?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Canvia la teva contrasenya" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Encara no tens compte?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Crea un compte" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "Es gratuit, es Open Source" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Crea un compte" + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "La solució de codi obert per disenyar i prototipar" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.verification-email-sent" +msgstr "Em enviat un correu de verificació a" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Afegeix una Biblioteca Compartida" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Canviar correu" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ Crear un nou equip" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "El teu Penpot" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Suprimir equip" + +msgid "dashboard.draft-title" +msgstr "Esborrany" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Encara no hi ha cap arxiu aquí" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "Convidar a l'equip" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Abandonar l'equip" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "Biblioteques Compartides" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "carregan els teus fitxers" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ Nou Arxiu" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ Nou projecte" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "No s'ha trobat cap coincidència amb “%s“" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "Els projectes fixats apareixeran aquí" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "La teva adreça de correu s'ha actualizat" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "La teva adreça de correu ha sigut verificada" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "La contrasenya s'ha desat correctament" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "%s membres" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "Canvia la contrasenya" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "Projectes" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.promote-to-owner" +msgstr "Promoure a propietari" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.remove-account" +msgstr "Vols esborrar el teu compte?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.remove-shared" +msgstr "Elimina com Biblioteca Compartida" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +msgstr "Cerca…" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.searching-for" +msgstr "S'está cercant “%s“…" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-language" +msgstr "Selecciona la llengua de la interfície" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-theme" +msgstr "Selecciona un tema" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.show-all-files" +msgstr "Veure tots els fitxers" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "Cambiar d'equip" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-info" +msgstr "Informació de l'equip" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-members" +msgstr "Membres de l'equip" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-projects" +msgstr "Projectes de l'equip" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.theme-change" +msgstr "Tema de l'interfície" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.title-search" +msgstr "Membres de l'equip" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.type-something" +msgstr "Escriu per cercar resultats" + +#: 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 "Actualitzar opcions" + +#: src/app/main/ui/settings.cljs +msgid "dashboard.your-account-title" +msgstr "El teu compte" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "Correu electrónic" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-name" +msgstr "El teu nom" + +#: 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 "El teu Penpot" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +msgstr "Cancel·lar" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "Ok" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "Estàs segur?" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "ds.updated-at" +msgstr "Actualitzat: %s" + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "El teu navegador no pot realitzar aquesta operació" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +msgid "errors.email-already-exists" +msgstr "El correu ja està en ús" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "El correu ja està validat" + +#: 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 "El correu «%s» té molts informes de rebot permanents" + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +msgstr "El correu de confirmació ha de coincidir" + +#: 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 "Alguna cosa ha anat malament" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.google-auth-not-enabled" +msgstr "L'autenticació amb google ha estat desactivada a aquest servidor" + +msgid "errors.media-format-unsupported" +msgstr "El format d'imatge no està suportat (deu ser svg, jpg o png)," + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "La imatge es massa gran (ha de tenir menys de 5 mb)." + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "errors.media-type-mismatch" +msgstr "Sembla que el contingut de la imatge no coincideix amb l'extensió del arxiu" + +#: 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 "La imatge no sembla pas vàlida" + +#: src/app/main/ui/dashboard/team.cljs +msgid "errors.member-is-muted" +msgstr "" +"El perfil que estàs invitant té els emails mutejats (per informes de spam o " +"rebots alts" + +msgid "errors.network" +msgstr "Impossible connectar amb el servidor principal" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-invalid-confirmation" +msgstr "La contrasenya de confirmació ha de coincidir" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "La contrasenya ha de tenir 8 com a mínim 8 caràcters" + +#: 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 "El teu perfil te els emails mutejats (per informes de spam o rebots alts)." + +#: src/app/main/ui/auth/register.cljs +msgid "errors.registration-disabled" +msgstr "El registre està desactivat actualment" + +#: 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 "S'ha produït un error inesperat." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.unexpected-token" +msgstr "Token desconegut" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.wrong-credentials" +msgstr "El nom d'usuari o la contrasenya sembla incorrecte" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.wrong-old-password" +msgstr "La contrasenya anterior no és correcte" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-start" +msgstr "Uneix-te al xat." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-subtitle" +msgstr "Et ve de gust parlar? Xateja amb nosaltres a Gitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "Descripció" + +#: 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 "Uneix-te al fòrum colaboratiu de Penpot." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle2" +msgstr "" +"Pots fer i respondre preguntes, tenir converses obertes i seguir les " +"decisións que afecten al projecte" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-title" +msgstr "" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subject" +msgstr "Tema" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subtitle" +msgstr "" +"Si us plau descriu la raó del teu correu, especificant si es una " +"incidència, una idea o un dubte. Un membre del nostre equip respondrà tan " +"aviat como pugui." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "Correu electrònic" + +#: src/app/main/ui/settings/password.cljs +msgid "generic.error" +msgstr "S'ha produït un error" + +msgid "labels.accept" +msgstr "Acceptar" + +msgid "labels.recent" +msgstr "Recent" + +msgid "labels.save" +msgstr "Desa" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "title.dashboard.projects" +msgstr "Projectes - %s - Penpot" + +#: src/app/main/ui/dashboard/search.cljs +msgid "title.dashboard.search" +msgstr "Cerca - %s - Penpot" \ No newline at end of file diff --git a/frontend/translations/en.po b/frontend/translations/en.po new file mode 100644 index 000000000..06bd26481 --- /dev/null +++ b/frontend/translations/en.po @@ -0,0 +1,2440 @@ +msgid "" +msgstr "" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Already have an account?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "Check your email and click on the link to verify and start using Penpot." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "Confirm password" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Create demo account" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "Just wanna try it?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"This is a DEMO service, DO NOT USE for real work, the projects will be " +"periodicaly wiped." + +#: 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 "Email" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "Forgot password?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "Full Name" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.go-back-to-login" +msgstr "Go back!" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Login here" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Sign in" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-subtitle" +msgstr "Enter your details below" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Great to see you again!" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Login with Github" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "Login with Gitlab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "Sign in with LDAP" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Type a new password" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "The recovery token is invalid." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-succesfully" +msgstr "Password successfully changed" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "Profile is not verified, please verify profile before continue." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Password recovery link sent to your inbox." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Joined the team succesfully" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Password" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "At least 8 characters" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Recover Password" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "We'll send you an email with instructions" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Forgot password?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Change your password" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "No account yet?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Create an account" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "It's free, it's Open Source" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Create an account" + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "The open-source solution for design and prototyping." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"When creating a new account, you agree to our terms of service and privacy " +"policy." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.verification-email-sent" +msgstr "We've sent a verification email to" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Add as Shared Library" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Change email" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(copy)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ Create new team" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Your Penpot" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Delete team" + +msgid "dashboard.draft-title" +msgstr "Draft" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Duplicate" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "Duplicate %s files" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "You still have no files here" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "Invite to team" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Leave team" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "Shared Libraries" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "loading your files …" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Move to" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "Move %s files to" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Move to other team" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ New File" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ New project" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "No matches found for “%s“" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "Pinned projects will appear here" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "Your email address has been updated successfully" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "Your email address has been verified successfully" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "Password saved successfully!" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "%s members" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "Open file in a new tab" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "Change password" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Pin/Unpin" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "Projects" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.promote-to-owner" +msgstr "Promote to owner" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.remove-account" +msgstr "Want to remove your account?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.remove-shared" +msgstr "Remove as Shared Library" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +msgstr "Search…" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.searching-for" +msgstr "Searching for “%s“…" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-language" +msgstr "Select UI language" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-theme" +msgstr "Select theme" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.show-all-files" +msgstr "Show all files" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-delete-file" +msgstr "Your file has been deleted successfully" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "Your project has been deleted successfully" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "Your file has been duplicated successfully" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-duplicate-project" +msgstr "Your project has been duplicated successfully" + +#: 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 "Your file has been moved successfully" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +msgstr "Your files has been moved successfully" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "Your project has been moved successfully" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "Switch team" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-info" +msgstr "Team info" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-members" +msgstr "Team members" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-projects" +msgstr "Team projects" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.theme-change" +msgstr "UI theme" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.title-search" +msgstr "Search results" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.type-something" +msgstr "Type to search results" + +#: 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 "Update settings" + +#: src/app/main/ui/settings.cljs +msgid "dashboard.your-account-title" +msgstr "Your account" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "Email" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-name" +msgstr "Your name" + +#: 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 "Your Penpot" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +msgstr "Cancel" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "Ok" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "Are you sure?" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "ds.updated-at" +msgstr "Updated: %s" + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "Your browser cannot do this operation" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +msgid "errors.email-already-exists" +msgstr "Email already used" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "Email already validated." + +#: 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 "The email «%s» has many permanent bounce reports." + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +msgstr "Confirmation email must match" + +#: 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 "Something wrong has happened." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.google-auth-not-enabled" +msgstr "Authentication with google disabled on backend" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.ldap-disabled" +msgstr "LDAP authentication is disabled." + +msgid "errors.media-format-unsupported" +msgstr "The image format is not supported (must be svg, jpg or png)." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "The image is too large to be inserted (must be under 5mb)." + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "errors.media-type-mismatch" +msgstr "Seems that the contents of the image does not match the file extension." + +#: 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 "Seems that this is not a valid image." + +#: src/app/main/ui/dashboard/team.cljs +msgid "errors.member-is-muted" +msgstr "The profile you inviting has emails muted (spam reports or high bounces)." + +msgid "errors.network" +msgstr "Unable to connect to backend server." + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-invalid-confirmation" +msgstr "Confirmation password must match" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "Password should at least be 8 characters" + +#: 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 "Your profile has emails muted (spam reports or high bounces)." + +#: src/app/main/ui/auth/register.cljs +msgid "errors.registration-disabled" +msgstr "The registration is currently disabled." + +msgid "errors.terms-privacy-agreement-invalid" +msgstr "You must accept our terms of service and privacy policy." + +#: 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 "An unexpected error occurred." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.unexpected-token" +msgstr "Unknown token" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.wrong-credentials" +msgstr "Username or password seems to be wrong." + +#: src/app/main/ui/settings/password.cljs +msgid "errors.wrong-old-password" +msgstr "Old password is incorrect" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-start" +msgstr "Join the chat" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-subtitle" +msgstr "Feeling like talking? Chat with us at Gitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "Description" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-go-to" +msgstr "Go to discussions" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle1" +msgstr "Join Penpot team collaborative communication forum." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle2" +msgstr "" +"You can ask and answer questions, have open-ended conversations, and follow " +"along on decisions affecting the project." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-title" +msgstr "Team discussions" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subject" +msgstr "Subject" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subtitle" +msgstr "" +"Please describe the reason of your email, specifying if is an issue, an " +"idea or a doubt. A member of our team will respond as soon as possible." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "Email" + +#: src/app/main/ui/settings/password.cljs +msgid "generic.error" +msgstr "An error has occurred" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur" +msgstr "Blur" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur.value" +msgstr "Value" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.hex" +msgstr "HEX" + +#: 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 "Fill" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.download" +msgstr "Download source image" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.height" +msgstr "Height" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "Width" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout" +msgstr "Layout" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Height" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Left" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Radius" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Rotation" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Top" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Width" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "Shadow" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.blur" +msgstr "B" + +#: 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 "S" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Stroke" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Center" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "Inside" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Outside" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Dotted" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Mixed" + +msgid "handoff.attributes.stroke.style.none" +msgstr "None" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Solid" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Width" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Typography" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-family" +msgstr "Font Family" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-size" +msgstr "Font Size" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "Font Style" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "Letter Spacing" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Line Height" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Text Decoration" + +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "None" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Strikethrough" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Underline" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Text Transform" + +msgid "handoff.attributes.typography.text-transform.lowercase" +msgstr "Lower Case" + +msgid "handoff.attributes.typography.text-transform.none" +msgstr "None" + +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "Title Case" + +msgid "handoff.attributes.typography.text-transform.uppercase" +msgstr "Upper Case" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Code" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Circle" + +msgid "handoff.tabs.code.selected.curve" +msgstr "Curve" + +msgid "handoff.tabs.code.selected.frame" +msgstr "Artboard" + +msgid "handoff.tabs.code.selected.group" +msgstr "Group" + +msgid "handoff.tabs.code.selected.image" +msgstr "Image" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "%s Selected" + +msgid "handoff.tabs.code.selected.path" +msgstr "Path" + +msgid "handoff.tabs.code.selected.rect" +msgstr "Rectangle" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.text" +msgstr "Text" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Info" + +msgid "history.alert-message" +msgstr "You are seeing version %s" + +msgid "labels.accept" +msgstr "Accept" + +#: 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" + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.all" +msgstr "All" + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.desc-message" +msgstr "" +"Looks like you need to wait a bit and retry; we are performing small " +"maintenance of our servers." + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.main-message" +msgstr "Bad Gateway" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.cancel" +msgstr "Cancel" + +msgid "labels.centered" +msgstr "Center" + +#: src/app/main/ui/dashboard/comments.cljs +msgid "labels.comments" +msgstr "Comments" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.confirm-password" +msgstr "Confirm password" + +msgid "labels.content" +msgstr "Content" + +#: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs +msgid "labels.create-team" +msgstr "Create new team" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.create-team.placeholder" +msgstr "Enter new team name" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.dashboard" +msgstr "Dashboard" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.delete" +msgstr "Delete" + +#: src/app/main/ui/comments.cljs +msgid "labels.delete-comment" +msgstr "Delete comment" + +#: src/app/main/ui/comments.cljs +msgid "labels.delete-comment-thread" +msgstr "Delete thread" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.delete-multi-files" +msgstr "Delete %s files" + +#: 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 "Drafts" + +#: src/app/main/ui/comments.cljs +msgid "labels.edit" +msgstr "Edit" + +#: 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" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.email" +msgstr "Email" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-disabled" +msgstr "Feedback disabled" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-sent" +msgstr "Feedback sent" + +#: 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 "Give feedback" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.hide-resolved-comments" +msgstr "Hide resolved comments" + +msgid "labels.icons" +msgstr "Icons" + +msgid "labels.images" +msgstr "Images" + +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.desc-message" +msgstr "" +"Something bad happened. Please retry the operation and if the problem " +"persists, contact with support." + +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.main-message" +msgstr "Internal Error" + +#: src/app/main/ui/settings/options.cljs +msgid "labels.language" +msgstr "Language" + +#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.logout" +msgstr "Logout" + +#: 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 "Members" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.name" +msgstr "Name" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.new-password" +msgstr "New password" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs +msgid "labels.no-comments-available" +msgstr "You have no pending comment notifications" + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.auth-info" +msgstr "You’re signed in as" + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.desc-message" +msgstr "This page might not exist or you don’t have permissions to access to it." + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.main-message" +msgstr "Oops!" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-files" +msgid_plural "labels.num-of-files" +msgstr[0] "1 file" +msgstr[1] "%s files" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-projects" +msgid_plural "labels.num-of-projects" +msgstr[0] "1 project" +msgstr[1] "%s projects" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.old-password" +msgstr "Old password" + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.only-yours" +msgstr "Only yours" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.owner" +msgstr "Owner" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.password" +msgstr "Password" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.permissions" +msgstr "Permissions" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.profile" +msgstr "Profile" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.projects" +msgstr "Projects" + +msgid "labels.recent" +msgstr "Recent" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.release-notes" +msgstr "Release notes" + +#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.remove" +msgstr "Remove" + +#: 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 "Rename" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.rename-team" +msgstr "Rename team" + +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +msgid "labels.retry" +msgstr "Retry" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.role" +msgstr "Role" + +msgid "labels.save" +msgstr "Save" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.send" +msgstr "Send" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.sending" +msgstr "Sending..." + +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.desc-message" +msgstr "We are in programmed maintenance of our systems." + +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.main-message" +msgstr "Service Unavailable" + +#: 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 "Settings" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.shared-libraries" +msgstr "Shared Libraries" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.show-all-comments" +msgstr "Show all comments" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.show-your-comments" +msgstr "Show only yours comments" + +#: src/app/main/ui/static.cljs +msgid "labels.sign-out" +msgstr "Sign out" + +#: src/app/main/ui/settings/profile.cljs +msgid "labels.update" +msgstr "Update" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "Update team" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "Viewer" + +#: src/app/main/ui/comments.cljs +msgid "labels.write-new-comment" +msgstr "Write new comment" + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "media.loading" +msgstr "Loading image…" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.accept" +msgstr "Add as Shared Library" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.hint" +msgstr "" +"Once added as Shared Library, the assets of this file library will be " +"available to be used among the rest of your files." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.message" +msgstr "Add “%s” as Shared Library" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.confirm-email" +msgstr "Verify new email" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.info" +msgstr "We'll send you an email to your current email “%s” to verify your identity." + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.new-email" +msgstr "New email" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.submit" +msgstr "Change email" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.title" +msgstr "Change your email" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.cancel" +msgstr "Cancel and keep my account" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.confirm" +msgstr "Yes, delete my account" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.info" +msgstr "By removing your account you’ll lose all your current projects and archives." + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.title" +msgstr "Are you sure you want to delete your account?" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.accept" +msgstr "Delete conversation" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.message" +msgstr "" +"Are you sure you want to delete this conversation? All comments in this " +"thread will be deleted." + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.title" +msgstr "Delete conversation" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.accept" +msgstr "Delete file" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.message" +msgstr "Are you sure you want to delete this file?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.title" +msgstr "Deleting file" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.accept" +msgstr "Delete files" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.message" +msgstr "Are you sure you want to delete %s files?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.title" +msgstr "Deleting %s files" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.body" +msgstr "Are you sure you want to delete this page?" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Delete page" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Delete project" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "Are you sure you want to delete this project?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Delete project" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.accept" +msgstr "Delete team" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.message" +msgstr "" +"Are you sure you want to delete this team? All projects and files " +"associated with team will be permanently deleted." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.title" +msgstr "Deleting team" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Delete member" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "Are you sure you want to delete this member from the team?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Delete team member" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Send invitation" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member.title" +msgstr "Invite to join the team" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "You are %s owner." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint2" +msgstr "Select other member to promote before leave" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.promote-and-leave" +msgstr "Promote and leave" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.select-memeber-to-promote" +msgstr "Select a member to promote" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.title" +msgstr "Select a member to promote" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.accept" +msgstr "Leave team" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.message" +msgstr "Are you sure you want to leave this team?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.title" +msgstr "Leaving team" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.accept" +msgstr "Promote" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.message" +msgstr "Are you sure you want to promote this user to owner?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.title" +msgstr "Promote to owner" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.accept" +msgstr "Remove as Shared Library" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.hint" +msgstr "" +"Once removed as Shared Library, the File Library of this file will stop " +"being available to be used among the rest of your files." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.message" +msgstr "Remove “%s” as Shared Library" + +#: 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 "Update component" + +#: 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 "Cancel" + +#: 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 "" +"You are about to update a component in a shared library. This may affect " +"other files that use it." + +#: 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 "Update a component in a shared library" + +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Invitation sent successfully" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" +msgstr "You can't delete you profile. Reassign your teams before proceed." + +#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs +msgid "notifications.profile-saved" +msgstr "Profile saved successfully!" + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "Verification email sent to %s. Check your email!" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Go to login" + +#: 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 "Mixed" + +#: src/app/main/ui/dashboard/files.cljs +msgid "title.dashboard.files" +msgstr "%s - Penpot" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "title.dashboard.projects" +msgstr "Projects - %s - Penpot" + +#: src/app/main/ui/dashboard/search.cljs +msgid "title.dashboard.search" +msgstr "Search - %s - Penpot" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Shared Libraries - %s - Penpot" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot - Design Freedom for Teams" + +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Give feedback - Penpot" + +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Settings - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Password - Penpot" + +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "Profile - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-members" +msgstr "Members - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "Settings - %s - Penpot" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - View mode - 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 "No frames found on the page." + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.frame-not-found" +msgstr "Frame not found." + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "Don't show interactions" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.edit-page" +msgstr "Edit page" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +msgstr "Full Screen" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.copy-link" +msgstr "Copy link" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.create-link" +msgstr "Create link" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.placeholder" +msgstr "Share link will appear here" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.remove-link" +msgstr "Remove link" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.subtitle" +msgstr "Anyone with the link will have access" + +#: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.title" +msgstr "Share link" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions" +msgstr "Show interactions" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions-on-click" +msgstr "Show interactions on click" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.sitemap" +msgstr "Sitemap" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hcenter" +msgstr "Align horizontal center" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hdistribute" +msgstr "Distribute horizontal spacing" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hleft" +msgstr "Align left" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hright" +msgstr "Align right" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vbottom" +msgstr "Align bottom" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vcenter" +msgstr "Align vertical center" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vdistribute" +msgstr "Distribute vertical spacing" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vtop" +msgstr "Align top" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Assets" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.box-filter-all" +msgstr "All assets" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Graphics" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Colors" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Components" + +#: 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 "Delete" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.duplicate" +msgstr "Duplicate" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.edit" +msgstr "Edit" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.file-library" +msgstr "File library" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Graphics" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Libraries" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.not-found" +msgstr "No assets found" + +#: 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 "Rename" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.search" +msgstr "Search assets" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "SHARED" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Typographies" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Font" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Size" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Variant" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Go to style library file to edit" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.letter-spacing" +msgstr "Letter Spacing" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.line-height" +msgstr "Line Height" + +#: 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 "Text Transform" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.linear" +msgstr "Linear gradient" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.radial" +msgstr "Radial gradient" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-dynamic-alignment" +msgstr "Disable dynamic alignment" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-grid" +msgstr "Disable snap to grid" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-dynamic-alignment" +msgstr "Enable dynamic aligment" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-snap-grid" +msgstr "Snap to grid" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-assets" +msgstr "Hide assets" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-grid" +msgstr "Hide grids" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-layers" +msgstr "Hide layers" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-palette" +msgstr "Hide color palette" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-rules" +msgstr "Hide rules" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.select-all" +msgstr "Select all" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-assets" +msgstr "Show assets" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-grid" +msgstr "Show grid" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-layers" +msgstr "Show layers" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Show color palette" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-rules" +msgstr "Show rules" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.save-error" +msgstr "Error on saving" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Saved" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Saving" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "Unsaved changes" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.viewer" +msgstr "View mode (%s)" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Add" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.colors" +msgstr "%s colors" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.big-thumbnails" +msgstr "Big thumbnails" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "File library" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Recent colors" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Save color style" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.small-thumbnails" +msgstr "Small thumbnails" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.components" +msgstr "%s components" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.file-library" +msgstr "File library" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.graphics" +msgstr "%s graphics" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.in-this-file" +msgstr "LIBRARIES IN THIS FILE" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "LIBRARIES" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "LIBRARY" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-libraries-need-sync" +msgstr "There are no Shared Libraries that need update" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-matches-for" +msgstr "No matches found for “%s“" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-shared-libraries-available" +msgstr "There are no Shared Libraries available" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.search-shared-libraries" +msgstr "Search shared libraries" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.shared-libraries" +msgstr "SHARED LIBRARIES" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography" +msgstr "Multiple typographies" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography-tooltip" +msgstr "Unlink all typographies" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.typography" +msgstr "%s typographies" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Update" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "UPDATES" + +msgid "workspace.library.all" +msgstr "All libraries" + +msgid "workspace.library.libraries" +msgstr "Libraries" + +msgid "workspace.library.own" +msgstr "My libraries" + +msgid "workspace.library.store" +msgstr "Store libraries" + +msgid "workspace.options.blur-options.background-blur" +msgstr "Background" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Layer" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "Blur" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.group" +msgstr "Group blur" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.multiple" +msgstr "Selection blur" + +#: src/app/main/ui/workspace/sidebar/options/page.cljs +msgid "workspace.options.canvas-background" +msgstr "Canvas background" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Component" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.design" +msgstr "Design" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export" +msgstr "Export" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-object" +msgstr "Export shape" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +msgid "workspace.options.export.suffix" +msgstr "Suffix" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.exporting-object" +msgstr "Exporting…" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.fill" +msgstr "Fill" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.auto" +msgstr "Auto" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.column" +msgstr "Columns" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.columns" +msgstr "Columns" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.gutter" +msgstr "Gutter" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.height" +msgstr "Height" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.margin" +msgstr "Margin" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.rows" +msgstr "Rows" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.set-default" +msgstr "Set as default" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.size" +msgstr "Size" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type" +msgstr "Type" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Bottom" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.center" +msgstr "Center" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.left" +msgstr "Left" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.right" +msgstr "Right" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.stretch" +msgstr "Stretch" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Top" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Use default" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.width" +msgstr "Width" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.row" +msgstr "Rows" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.square" +msgstr "Square" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.title" +msgstr "Grid & Layouts" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.group-fill" +msgstr "Group fill" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.group-stroke" +msgstr "Group stroke" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color" +msgstr "Color" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-burn" +msgstr "Color burn" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-dodge" +msgstr "Color dodge" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.darken" +msgstr "Darken" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.difference" +msgstr "Difference" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.exclusion" +msgstr "Exclusion" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hard-light" +msgstr "Hard light" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hue" +msgstr "Hue" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.lighten" +msgstr "Lighten" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.luminosity" +msgstr "Luminosity" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.multiply" +msgstr "Multiply" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.normal" +msgstr "Normal" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.overlay" +msgstr "Overlay" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.saturation" +msgstr "Saturation" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.screen" +msgstr "Screen" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.soft-light" +msgstr "Soft light" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title" +msgstr "Layer" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title.group" +msgstr "Group layers" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +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" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.prototype" +msgstr "Prototype" + +msgid "workspace.options.radius" +msgstr "Radius" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "All corners" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.single-corners" +msgstr "Single corners" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.rotation" +msgstr "Rotation" + +#: 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." + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-artboard" +msgstr "Select artboard" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.selection-fill" +msgstr "Selection fill" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.selection-stroke" +msgstr "Selection stroke" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.blur" +msgstr "Blur" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Drop shadow" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.inner-shadow" +msgstr "Inner shadow" + +#: 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 "Spread" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title" +msgstr "Shadow" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.group" +msgstr "Group shadow" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.multiple" +msgstr "Selection shadows" + +#: 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 "Size" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +msgid "workspace.options.size-presets" +msgstr "Size presets" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke" +msgstr "Stroke" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Center" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Dashed" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Dotted" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.inner" +msgstr "Inside" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.mixed" +msgstr "Mixed" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.outer" +msgstr "Outside" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.solid" +msgstr "Solid" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-bottom" +msgstr "Align bottom" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-center" +msgstr "Align center" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-justify" +msgstr "Justify" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-left" +msgstr "Align left" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-middle" +msgstr "Align middle" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-right" +msgstr "Align right" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-top" +msgstr "Align top" + +msgid "workspace.options.text-options.decoration" +msgstr "Decoration" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-ltr" +msgstr "LTR" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-rtl" +msgstr "RTL" + +#: 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 "Auto height" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-width" +msgstr "Auto width" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-fixed" +msgstr "Fixed" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.letter-spacing" +msgstr "Letter Spacing" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.line-height" +msgstr "Line height" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.lowercase" +msgstr "Lowercase" + +#: 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 "None" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.preset" +msgstr "Preset" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.strikethrough" +msgstr "Strikethrough" + +msgid "workspace.options.text-options.text-case" +msgstr "Case" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title" +msgstr "Text" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-group" +msgstr "Group text" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-selection" +msgstr "Selection text" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.titlecase" +msgstr "Title Case" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.underline" +msgstr "Underline" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.uppercase" +msgstr "Uppercase" + +msgid "workspace.options.text-options.vertical-align" +msgstr "Vertical align" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.use-play-button" +msgstr "Use the play button at the header to run the prototype view." + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.back" +msgstr "Send to back" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.backward" +msgstr "Send backward" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Copy" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Create component" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Cut" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Delete" + +#: 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" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.duplicate" +msgstr "Duplicate" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.edit" +msgstr "Edit" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-horizontal" +msgstr "Flip horizontal" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-vertical" +msgstr "Flip vertical" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.forward" +msgstr "Bring forward" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.front" +msgstr "Bring to front" + +#: 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 "Go to main component file" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.group" +msgstr "Group" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Hide" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.lock" +msgstr "Lock" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.mask" +msgstr "Mask" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.paste" +msgstr "Paste" + +#: 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" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show" +msgstr "Show" + +#: 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 "Show main component" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.ungroup" +msgstr "Ungroup" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unlock" +msgstr "Unlock" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unmask" +msgstr "Unmask" + +#: 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 "Update main component" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "History (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.layers" +msgstr "Layers (%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 "Imported SVG Attributes" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "workspace.sidebar.sitemap" +msgstr "Pages" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.sitemap" +msgstr "Sitemap" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.assets" +msgstr "Assets (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.color-palette" +msgstr "Color Palette (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.comments" +msgstr "Comments (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.curve" +msgstr "Curve (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.ellipse" +msgstr "Ellipse (E)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "Artboard (A)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "Image (K)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.move" +msgstr "Move" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.path" +msgstr "Path (P)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "Rectangle (R)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "Text (T)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.empty" +msgstr "There are no history changes so far" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.delete" +msgstr "Deleted %s" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.modify" +msgstr "Modified %s" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.move" +msgstr "Moved objects" + +msgid "workspace.undo.entry.multiple.circle" +msgstr "circles" + +msgid "workspace.undo.entry.multiple.color" +msgstr "color assets" + +msgid "workspace.undo.entry.multiple.component" +msgstr "components" + +msgid "workspace.undo.entry.multiple.curve" +msgstr "curves" + +msgid "workspace.undo.entry.multiple.frame" +msgstr "artboard" + +msgid "workspace.undo.entry.multiple.group" +msgstr "groups" + +msgid "workspace.undo.entry.multiple.media" +msgstr "graphic assets" + +msgid "workspace.undo.entry.multiple.multiple" +msgstr "objects" + +msgid "workspace.undo.entry.multiple.page" +msgstr "pages" + +msgid "workspace.undo.entry.multiple.path" +msgstr "paths" + +msgid "workspace.undo.entry.multiple.rect" +msgstr "rectangles" + +msgid "workspace.undo.entry.multiple.shape" +msgstr "shapes" + +msgid "workspace.undo.entry.multiple.text" +msgstr "texts" + +msgid "workspace.undo.entry.multiple.typography" +msgstr "typography assets" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.new" +msgstr "New %s" + +msgid "workspace.undo.entry.single.circle" +msgstr "circle" + +msgid "workspace.undo.entry.single.color" +msgstr "color asset" + +msgid "workspace.undo.entry.single.component" +msgstr "component" + +msgid "workspace.undo.entry.single.curve" +msgstr "curve" + +msgid "workspace.undo.entry.single.frame" +msgstr "artboard" + +msgid "workspace.undo.entry.single.group" +msgstr "group" + +msgid "workspace.undo.entry.single.image" +msgstr "image" + +msgid "workspace.undo.entry.single.media" +msgstr "graphic asset" + +msgid "workspace.undo.entry.single.multiple" +msgstr "object" + +msgid "workspace.undo.entry.single.page" +msgstr "page" + +msgid "workspace.undo.entry.single.path" +msgstr "path" + +msgid "workspace.undo.entry.single.rect" +msgstr "rectangle" + +msgid "workspace.undo.entry.single.shape" +msgstr "shape" + +msgid "workspace.undo.entry.single.text" +msgstr "text" + +msgid "workspace.undo.entry.single.typography" +msgstr "typography asset" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.unknown" +msgstr "Operation over %s" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.title" +msgstr "History" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.dismiss" +msgstr "Dismiss" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "There are updates in shared libraries" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.update" +msgstr "Update" + +msgid "workspace.viewport.click-to-close-path" +msgstr "Click to close the path" \ No newline at end of file diff --git a/frontend/translations/es.po b/frontend/translations/es.po new file mode 100644 index 000000000..52d06a8b1 --- /dev/null +++ b/frontend/translations/es.po @@ -0,0 +1,2422 @@ +msgid "" +msgstr "" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "¿Tienes ya una cuenta?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "Confirmar contraseña" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Crear cuenta de prueba" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "¿Quieres probar?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"Este es un servicio de DEMOSTRACIÓN. NO USAR para trabajo real, los " +"proyectos serán borrados periodicamente." + +#: 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 "Correo electrónico" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "¿Olvidaste tu contraseña?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "Nombre completo" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.go-back-to-login" +msgstr "Volver" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Entra aquí" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Entrar" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-subtitle" +msgstr "Introduce tus datos aquí" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Encantados de volverte a ver" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Entrar con Github" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "Entrar con Gitlab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "Entrar con LDAP" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Introduce la nueva contraseña" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "El código de recuperación no es válido." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-succesfully" +msgstr "La contraseña ha sido cambiada" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "" +"El perfil aun no ha sido verificado, por favor valida el perfil antes de " +"continuar." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Hemos enviado a tu buzón un enlace para recuperar tu contraseña." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Te uniste al equipo" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Contraseña" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "8 caracteres como mínimo" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Recuperar contraseña" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "Te enviaremos un correo electrónico con instrucciones" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "¿Olvidaste tu contraseña?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Cambiar tu contraseña" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "¿No tienes una cuenta?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Crear una cuenta" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "Es gratis, es Open Source" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Crear una cuenta" + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "La solución de código abierto para diseñar y prototipar" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Al crear una nueva cuenta, aceptas nuestros términos de servicio y política " +"de privacidad." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Añadir como Biblioteca Compartida" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Cambiar correo" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(copia)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ Crear nuevo equipo" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Tu Penpot" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Eliminar equipo" + +msgid "dashboard.draft-title" +msgstr "Borrador" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Duplicar" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "Duplicar %s archivos" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Todavía no hay ningún archivo aquí" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "Invitar al equipo" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Abandonar equipo" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "Bibliotecas Compartidas" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "cargando tus ficheros …" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Mover a" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "Mover %s archivos a" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Mover a otro equipo" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ Nuevo Archivo" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ Nuevo proyecto" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "No se encuentra “%s“" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "Los proyectos fijados aparecerán aquí" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "Tu dirección de correo ha sido actualizada" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "Tu dirección de correo ha sido verificada" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "¡Contraseña guardada!" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "%s integrantes" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "Abrir en una pestaña nueva" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "Cambiar contraseña" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Fijar/Desfijar" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "Proyectos" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.promote-to-owner" +msgstr "Promover a dueño" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.remove-account" +msgstr "¿Quieres borrar tu cuenta?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.remove-shared" +msgstr "Eliminar como Biblioteca Compartida" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +msgstr "Buscar…" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.searching-for" +msgstr "Buscando “%s“…" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-language" +msgstr "Cambiar el idioma de la interfaz" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-theme" +msgstr "Selecciona un tema" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.show-all-files" +msgstr "Ver todos los ficheros" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-delete-file" +msgstr "Tu archivo ha sido borrado con éxito" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "Tu proyecto ha sido borrado con éxito" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "Tu archivo ha sido duplicado con éxito" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-duplicate-project" +msgstr "Tu proyecto ha sido duplicado con éxito" + +#: 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 "Tu archivo ha sido movido con éxito" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +msgstr "Tus archivos han sido movidos con éxito" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "Tu proyecto ha sido movido con éxito" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "Cambiar equipo" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-info" +msgstr "Información del equipo" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-members" +msgstr "Integrantes del equipo" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-projects" +msgstr "Proyectos del equipo" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.theme-change" +msgstr "Tema visual" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.title-search" +msgstr "Resultados de búsqueda" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.type-something" +msgstr "Escribe algo para buscar" + +#: 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 "Actualizar opciones" + +#: src/app/main/ui/settings.cljs +msgid "dashboard.your-account-title" +msgstr "Tu cuenta" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "Correo" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-name" +msgstr "Tu nombre" + +#: 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 "Tu Penpot" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +msgstr "Cancelar" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "Ok" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "¿Seguro?" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "ds.updated-at" +msgstr "Actualizado: %s" + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "Tu navegador no puede realizar esta operación" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +msgid "errors.email-already-exists" +msgstr "Este correo ya está en uso" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "Este correo ya está validado." + +#: 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 "El email «%s» tiene varios reportes de rebote permanente." + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +msgstr "El correo de confirmación debe coincidir" + +#: 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 "Ha ocurrido algún error." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.google-auth-not-enabled" +msgstr "Autenticación con google esta dehabilitada en el servidor" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.ldap-disabled" +msgstr "La autheticacion via LDAP esta deshabilitada." + +msgid "errors.media-format-unsupported" +msgstr "No se reconoce el formato de imagen (debe ser svg, jpg o png)." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "La imagen es demasiado grande (debe tener menos de 5mb)." + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "errors.media-type-mismatch" +msgstr "" +"Parece que el contenido de la imagen no coincide con la extensión del " +"archivo." + +#: 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 "Parece que no es una imagen válida." + +#: src/app/main/ui/dashboard/team.cljs +msgid "errors.member-is-muted" +msgstr "" +"El perfil que esta invitando tiene los emails silenciados (por reportes de " +"spam o alto indice de rebote)." + +msgid "errors.network" +msgstr "Ha sido imposible conectar con el servidor principal." + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-invalid-confirmation" +msgstr "La contraseña de confirmación debe coincidir" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "La contraseña debe tener 8 caracteres como mínimo" + +#: 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 "" +"Tu perfil tiene los emails silenciados (por reportes de spam o alto indice " +"de rebote)." + +#: src/app/main/ui/auth/register.cljs +msgid "errors.registration-disabled" +msgstr "El registro está actualmente desactivado." + +msgid "errors.terms-privacy-agreement-invalid" +msgstr "Debes aceptar nuestros términos de servicio y política de privacidad." + +#: 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 "Ha ocurrido un error inesperado." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.unexpected-token" +msgstr "Token desconocido" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.wrong-credentials" +msgstr "El nombre o la contraseña parece incorrecto." + +#: src/app/main/ui/settings/password.cljs +msgid "errors.wrong-old-password" +msgstr "La contraseña anterior no es correcta" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-start" +msgstr "Unirse al chat" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-subtitle" +msgstr "¿Deseas conversar? Entra al nuestro chat de la comunidad en Gitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "Descripción" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-go-to" +msgstr "Ir a las discusiones" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle1" +msgstr "Entra al foro colaborativo de 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 "Asunto" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subtitle" +msgstr "" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "Correo electrónico" + +#: src/app/main/ui/settings/password.cljs +msgid "generic.error" +msgstr "Ha ocurrido un error" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur" +msgstr "Desenfocado" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur.value" +msgstr "Valor" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.hex" +msgstr "HEX" + +#: 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 "Relleno" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.download" +msgstr "Descargar imagen original" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.height" +msgstr "Altura" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "Ancho" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout" +msgstr "Estructura" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Altura" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Izquierda" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Derecha" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Rotación" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Arriba" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Ancho" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "Sombra" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.blur" +msgstr "B" + +#: 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 "S" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Borde" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Centro" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "Interior" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Exterior" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Punteado" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Mixto" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Ninguno" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Sólido" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Ancho" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Tipografía" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-family" +msgstr "Familia tipográfica" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-size" +msgstr "Tamaño de fuente" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "Estilo de fuente" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "Espaciado de letras" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Interlineado" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Decoración de texto" + +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "Ninguna" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Tachar" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Subrayar" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Transformación de texto" + +msgid "handoff.attributes.typography.text-transform.lowercase" +msgstr "Minúsculas" + +msgid "handoff.attributes.typography.text-transform.none" +msgstr "Ninguna" + +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "Primera en mayúscula" + +msgid "handoff.attributes.typography.text-transform.uppercase" +msgstr "Mayúsculas" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Código" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Círculo" + +msgid "handoff.tabs.code.selected.curve" +msgstr "Curva" + +msgid "handoff.tabs.code.selected.frame" +msgstr "Mesa de trabajo" + +msgid "handoff.tabs.code.selected.group" +msgstr "Grupo" + +msgid "handoff.tabs.code.selected.image" +msgstr "Imagen" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "%s Seleccionado" + +msgid "handoff.tabs.code.selected.path" +msgstr "Trazado" + +msgid "handoff.tabs.code.selected.rect" +msgstr "Rectángulo" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.text" +msgstr "Texto" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Información" + +msgid "history.alert-message" +msgstr "Estás viendo la versión %s" + +msgid "labels.accept" +msgstr "Aceptar" + +#: 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 "Administración" + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.all" +msgstr "Todo" + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.desc-message" +msgstr "" +"Parece que necesitas esperar un poco y volverlo a intentar; estamos " +"realizando operaciones de mantenimiento en nuestros servidores." + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.main-message" +msgstr "Bad Gateway" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.cancel" +msgstr "Cancelar" + +msgid "labels.centered" +msgstr "Centrado" + +#: src/app/main/ui/dashboard/comments.cljs +msgid "labels.comments" +msgstr "Comentarios" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.confirm-password" +msgstr "Confirmar contraseña" + +msgid "labels.content" +msgstr "Contenido" + +#: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs +msgid "labels.create-team" +msgstr "Crea un nuevo equipo" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.dashboard" +msgstr "Panel" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.delete" +msgstr "Borrar" + +#: src/app/main/ui/comments.cljs +msgid "labels.delete-comment" +msgstr "Eliminar comentario" + +#: src/app/main/ui/comments.cljs +msgid "labels.delete-comment-thread" +msgstr "Eliminar hilo" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.delete-multi-files" +msgstr "Borrar %s archivos" + +#: 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 "Borradores" + +#: src/app/main/ui/comments.cljs +msgid "labels.edit" +msgstr "Editar" + +#: 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" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.email" +msgstr "Correo electrónico" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-disabled" +msgstr "El modulo de recepción de opiniones esta deshabilitado." + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-sent" +msgstr "Opinión enviada" + +#: 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 "Danos tu opinión" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.hide-resolved-comments" +msgstr "Ocultar comentarios resueltos" + +msgid "labels.icons" +msgstr "Iconos" + +msgid "labels.images" +msgstr "Imágenes" + +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.desc-message" +msgstr "" +"Ha ocurrido algo extraño. Por favor, reintenta la operación, y si el " +"problema persiste, contacta con el servicio técnico." + +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.main-message" +msgstr "Error interno" + +#: src/app/main/ui/settings/options.cljs +msgid "labels.language" +msgstr "Idioma" + +#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.logout" +msgstr "Salir" + +#: 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 "Integrantes" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.name" +msgstr "Nombre" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.new-password" +msgstr "Nueva contraseña" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs +msgid "labels.no-comments-available" +msgstr "No tienes notificaciones de comentarios pendientes" + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.auth-info" +msgstr "Estás identificado como" + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.desc-message" +msgstr "Esta página no existe o no tienes permisos para verla." + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.main-message" +msgstr "¡Huy!" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-files" +msgid_plural "labels.num-of-files" +msgstr[0] "1 archivo" +msgstr[1] "%s archivos" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-projects" +msgid_plural "labels.num-of-projects" +msgstr[0] "1 proyecto" +msgstr[1] "%s proyectos" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.old-password" +msgstr "Contraseña anterior" + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.only-yours" +msgstr "Sólo los tuyos" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.owner" +msgstr "Dueño" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.password" +msgstr "Contraseña" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.permissions" +msgstr "Permisos" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.profile" +msgstr "Perfil" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.projects" +msgstr "Proyectos" + +msgid "labels.recent" +msgstr "Reciente" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.release-notes" +msgstr "Notas de versión" + +#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.remove" +msgstr "Quitar" + +#: 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 "Renombrar" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.rename-team" +msgstr "Renomba el equipo" + +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +msgid "labels.retry" +msgstr "Reintentar" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.role" +msgstr "Cargo" + +msgid "labels.save" +msgstr "Guardar" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.send" +msgstr "Enviar" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.sending" +msgstr "Enviando..." + +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.desc-message" +msgstr "Estamos en una operación de mantenimiento programado de nuestros sistemas." + +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.main-message" +msgstr "El servicio no está disponible" + +#: 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 "Configuración" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.shared-libraries" +msgstr "Bibliotecas Compartidas" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.show-all-comments" +msgstr "Mostrar todos los comentarios" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.show-your-comments" +msgstr "Mostrar sólo tus comentarios" + +#: src/app/main/ui/static.cljs +msgid "labels.sign-out" +msgstr "Salir" + +#: src/app/main/ui/settings/profile.cljs +msgid "labels.update" +msgstr "Actualizar" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "Actualiza el equipo" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "Visualizador" + +#: src/app/main/ui/comments.cljs +msgid "labels.write-new-comment" +msgstr "Escribir un nuevo comentario" + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "media.loading" +msgstr "Cargando imagen…" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.accept" +msgstr "Añadir como Biblioteca Compartida" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.hint" +msgstr "" +"Una vez añadido como Biblioteca Compartida, los recursos de este archivo " +"estarán disponibles para ser usado por el resto de tus archivos." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.message" +msgstr "Añadir “%s” como Biblioteca Compartida" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.confirm-email" +msgstr "Verificar el nuevo correo" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.info" +msgstr "Enviaremos un mensaje a tu correo actual “%s” para verificar tu identidad." + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.new-email" +msgstr "Nuevo correo" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.submit" +msgstr "Cambiar correo" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.title" +msgstr "Cambiar tu correo" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.cancel" +msgstr "Cancelar y mantener mi cuenta" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.confirm" +msgstr "Si, borrar mi cuenta" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.info" +msgstr "Si borras tu cuenta perderás todos tus proyectos y archivos." + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.title" +msgstr "¿Seguro que quieres borrar tu cuenta?" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.accept" +msgstr "Eliminar conversación" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.message" +msgstr "" +"¿Seguro que quieres eliminar esta conversación? Todos los comentarios en " +"este hilo serán eliminados." + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.title" +msgstr "Eliminar conversación" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.accept" +msgstr "Eliminar archivo" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.message" +msgstr "¿Seguro que quieres eliminar este archivo?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.title" +msgstr "Eliminando archivo" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.accept" +msgstr "Eliminar archivos" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.message" +msgstr "¿Seguro que quieres eliminar %s archivos?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.title" +msgstr "Eliminando %s archivos" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.body" +msgstr "¿Seguro que quieres borrar esta página?" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Borrar página" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Eliminar proyecto" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "¿Seguro que quieres eliminar este proyecto?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Eliminar proyecto" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.accept" +msgstr "Eliminar equipo" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.message" +msgstr "" +"¿Seguro que quieres eliminar este equipo? Todos los proyectos y archivos " +"asociados con el equipo serán eliminados permamentemente." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.title" +msgstr "Eliminando equipo" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Eliminando miembro" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "¿Seguro que quieres eliminar este integrante del equipo?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Eliminar integrante del equipo" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Enviar invitacion" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member.title" +msgstr "Invitar a unirse al equipo" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "Eres %s dueño." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint2" +msgstr "Promociona otro miembro a dueño antes de abandonar el equipo" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.promote-and-leave" +msgstr "Promocionar y abandonar" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.select-memeber-to-promote" +msgstr "Selecciona un miembro a promocionar" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.title" +msgstr "Selecciona un miembro a promocionar" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.accept" +msgstr "Abandonar el equipo" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.message" +msgstr "¿Seguro que quieres abandonar este equipo?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.title" +msgstr "Abandonando el equipo" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.accept" +msgstr "Promocionar" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.message" +msgstr "¿Seguro que quieres promocionar este usuario a dueño?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.title" +msgstr "Promocionar a dueño" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.accept" +msgstr "Eliminar como Biblioteca Compartida" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.hint" +msgstr "" +"Una vez eliminado como Biblioteca Compartida, la Biblioteca de este archivo " +"dejará de estar disponible para ser usada por el resto de tus archivos." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.message" +msgstr "Añadir “%s” como Biblioteca Compartida" + +#: 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 "Actualizar componente" + +#: 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 "Cancelar" + +#: 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 "" +"Vas a actualizar un componente en una librería compartida. Esto puede " +"afectar a otros archivos que la usen." + +#: 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 "Actualizar un componente en librería" + +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Invitación enviada con éxito" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" +msgstr "No puedes borrar tu perfil. Reasigna tus equipos antes de seguir." + +#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs +msgid "notifications.profile-saved" +msgstr "Perfil guardado correctamente!" + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "Verificación de email enviada a %s. Comprueba tu correo." + +#: 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 "Varios" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "title.dashboard.projects" +msgstr "Proyectos - %s - Penpot" + +#: src/app/main/ui/dashboard/search.cljs +msgid "title.dashboard.search" +msgstr "Buscar - %s - Penpot" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Bibliotecas Compartidas - %s - Penpot" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot - Diseño Libre para Equipos" + +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Danos tu opinión - Penpot" + +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Configuración - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Contraseña - Penpot" + +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "Perfil - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-members" +msgstr "Integrantes - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "Configuración - %s - Penpot" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - Modo de visualización - Penpot" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.empty-state" +msgstr "No se ha encontrado ningún tablero." + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.frame-not-found" +msgstr "No se encuentra el tablero." + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "No mostrar interacciones" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.edit-page" +msgstr "Editar página" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +msgstr "Pantalla completa" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.copy-link" +msgstr "Copiar enlace" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.create-link" +msgstr "Crear enlace" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.placeholder" +msgstr "El enlace para compartir aparecerá aquí" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.remove-link" +msgstr "Eliminar enlace" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.subtitle" +msgstr "Cualquiera con el enlace podrá acceder" + +#: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.title" +msgstr "Enlace" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions" +msgstr "Mostrar interacciones" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions-on-click" +msgstr "Mostrar interacciones al hacer click" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.sitemap" +msgstr "Mapa del sitio" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hcenter" +msgstr "Alinear al centro" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hdistribute" +msgstr "Distribuir espacio horizontal" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hleft" +msgstr "Alinear a la izquierda" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hright" +msgstr "Alinear a la derecha" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vbottom" +msgstr "Alinear abajo" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vcenter" +msgstr "Alinear al centro" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vdistribute" +msgstr "Distribuir espacio vertical" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vtop" +msgstr "Alinear arriba" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Recursos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.box-filter-all" +msgstr "Todos" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Gráficos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Colores" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Componentes" + +#: 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 "Borrar" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.duplicate" +msgstr "Duplicar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.edit" +msgstr "Editar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.file-library" +msgstr "Biblioteca del archivo" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Gráficos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Bibliotecas" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.not-found" +msgstr "No se encontraron recursos" + +#: 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 "Renombrar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.search" +msgstr "Buscar recursos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "COMPARTIDA" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Tipografías" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Fuente" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Tamaño" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Variante" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Ir al archivo de la biblioteca del estilo para editar" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.letter-spacing" +msgstr "Interletrado" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.line-height" +msgstr "Interlineado" + +#: 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 "Transformar texto" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.linear" +msgstr "Degradado lineal" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.radial" +msgstr "Degradado radial" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-dynamic-alignment" +msgstr "Desactivar alineamiento dinámico" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-grid" +msgstr "Desactivar alinear a la rejilla" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-dynamic-alignment" +msgstr "Activar alineamiento dinámico" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-snap-grid" +msgstr "Alinear a la rejilla" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-assets" +msgstr "Ocultar recursos" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-grid" +msgstr "Ocultar rejillas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-layers" +msgstr "Ocultar capas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-palette" +msgstr "Ocultar paleta de colores" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-rules" +msgstr "Ocultar reglas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.select-all" +msgstr "Seleccionar todo" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-assets" +msgstr "Mostrar recursos" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-grid" +msgstr "Mostrar rejilla" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-layers" +msgstr "Mostrar capas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Mostrar paleta de colores" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-rules" +msgstr "Mostrar reglas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.save-error" +msgstr "Error al guardar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Guardado" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Guardando" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "Cambios sin guardar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.viewer" +msgstr "Modo de visualización (%s)" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Añadir" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.colors" +msgstr "%s colors" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.big-thumbnails" +msgstr "Miniaturas grandes" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "Biblioteca del archivo" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Colores recientes" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Guardar estilo de color" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.small-thumbnails" +msgstr "Miniaturas pequeñas" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.components" +msgstr "%s componentes" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.file-library" +msgstr "Biblioteca de este archivo" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.graphics" +msgstr "%s gráficos" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.in-this-file" +msgstr "BIBLIOTECAS EN ESTE ARCHIVO" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "BIBLIOTECAS" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "BIBLIOTECA" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-libraries-need-sync" +msgstr "No hay bibliotecas que necesiten ser actualizadas" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-matches-for" +msgstr "No se encuentra “%s“" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-shared-libraries-available" +msgstr "No hay bibliotecas compartidas disponibles" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.search-shared-libraries" +msgstr "Buscar bibliotecas compartidas" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.shared-libraries" +msgstr "BIBLIOTECAS COMPARTIDAS" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography" +msgstr "Varias tipografías" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography-tooltip" +msgstr "Desvincular todas las tipografías" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.typography" +msgstr "%s tipografías" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Actualizar" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "ACTUALIZACIONES" + +msgid "workspace.library.all" +msgstr "Todas" + +msgid "workspace.library.libraries" +msgstr "Bibliotecas" + +msgid "workspace.library.own" +msgstr "Mis bibliotecas" + +msgid "workspace.library.store" +msgstr "Predefinidas" + +msgid "workspace.options.blur-options.background-blur" +msgstr "Fondo" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Capa" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "Desenfoque" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.group" +msgstr "Desenfoque del grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.multiple" +msgstr "Desenfoque de la selección" + +#: src/app/main/ui/workspace/sidebar/options/page.cljs +msgid "workspace.options.canvas-background" +msgstr "Color de fondo" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Componente" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.design" +msgstr "Diseño" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export" +msgstr "Exportar" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-object" +msgstr "Exportar forma" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +msgid "workspace.options.export.suffix" +msgstr "Sufijo" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.exporting-object" +msgstr "Exportando" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.fill" +msgstr "Relleno" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.auto" +msgstr "Automático" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.column" +msgstr "Columnas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.columns" +msgstr "Columnas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.gutter" +msgstr "Espaciado" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.height" +msgstr "Altura" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.margin" +msgstr "Margen" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.rows" +msgstr "Filas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.set-default" +msgstr "Establecer valor por defecto" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.size" +msgstr "Tamaño" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type" +msgstr "Tipo" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Abajo" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.left" +msgstr "Izquierda" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.right" +msgstr "Derecha" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.stretch" +msgstr "Estirar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Arriba" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Usar valor por defecto" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.width" +msgstr "Ancho" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.row" +msgstr "Filas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.square" +msgstr "Cuadros" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.title" +msgstr "Rejilla & Estructuras" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.group-fill" +msgstr "Relleno de grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.group-stroke" +msgstr "Borde de grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color" +msgstr "Color" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-burn" +msgstr "Color más oscuro" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-dodge" +msgstr "Color más suave" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.darken" +msgstr "Oscurecer" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.difference" +msgstr "Diferencia" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.exclusion" +msgstr "Exclusión" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hard-light" +msgstr "Luz fuerte" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hue" +msgstr "Tono" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.lighten" +msgstr "Aclarar" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.luminosity" +msgstr "Luminosidad" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.multiply" +msgstr "Multiplicar" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.normal" +msgstr "Normal" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.overlay" +msgstr "Superponer" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.saturation" +msgstr "Saturación" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.screen" +msgstr "Trama" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.soft-light" +msgstr "Luz suave" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title" +msgstr "Capa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title.group" +msgstr "Capas de grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +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" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.prototype" +msgstr "Prototipo" + +msgid "workspace.options.radius" +msgstr "Radio" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "Todas las esquinas" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.single-corners" +msgstr "Esquinas individuales" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.rotation" +msgstr "Rotación" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-a-shape" +msgstr "" +"Selecciona una figura, tablero o grupo para arrastrar una conexión a otro " +"tablero." + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-artboard" +msgstr "Selecciona un tablero" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.selection-fill" +msgstr "Relleno de selección" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.selection-stroke" +msgstr "Borde de selección" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.blur" +msgstr "Desenfoque" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Sombra arrojada" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.inner-shadow" +msgstr "Sombra interior" + +#: 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 "Difusión" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title" +msgstr "Sombra" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.group" +msgstr "Sombra del grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.multiple" +msgstr "Sombras de la seleccíón" + +#: 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 "Tamaño" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +msgid "workspace.options.size-presets" +msgstr "Tamaños predefinidos" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke" +msgstr "Borde" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Rayado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Punteado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.inner" +msgstr "Interior" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.mixed" +msgstr "Mezclado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.outer" +msgstr "Exterior" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.solid" +msgstr "Sólido" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-bottom" +msgstr "Alinear abajo" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-center" +msgstr "Aliniear al centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-justify" +msgstr "Justificar" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-left" +msgstr "Alinear a la izquierda" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-middle" +msgstr "Alinear al centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-right" +msgstr "Alinear a la derecha" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-top" +msgstr "Alinear arriba" + +msgid "workspace.options.text-options.decoration" +msgstr "Decoración" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-ltr" +msgstr "LTR" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-rtl" +msgstr "RTL" + +#: 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 "Alto automático" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-width" +msgstr "Ancho automático" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-fixed" +msgstr "Fijo" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.letter-spacing" +msgstr "Espaciado entre letras" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.line-height" +msgstr "Altura de línea" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.lowercase" +msgstr "Minúsculas" + +#: 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 "Nada" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.preset" +msgstr "Predefinidos" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.strikethrough" +msgstr "Tachado" + +msgid "workspace.options.text-options.text-case" +msgstr "Mayús/minús" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title" +msgstr "Texto" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-group" +msgstr "Texto de grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-selection" +msgstr "Texto de selección" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.titlecase" +msgstr "Título" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.underline" +msgstr "Subrayado" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.uppercase" +msgstr "Mayúsculas" + +msgid "workspace.options.text-options.vertical-align" +msgstr "Alineación vertical" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.use-play-button" +msgstr "Usa el botón de play de la cabecera para arrancar la vista de prototipo." + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.back" +msgstr "Enviar al fondo" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.backward" +msgstr "Enviar atrás" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Copiar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Crear componente" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Cortar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Eliminar" + +#: 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" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.duplicate" +msgstr "Duplicar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.edit" +msgstr "Editar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-horizontal" +msgstr "Voltear horizontal" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-vertical" +msgstr "Voltear vertical" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.forward" +msgstr "Mover hacia delante" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.front" +msgstr "Mover al frente" + +#: 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 "Ir al archivo del componente principal" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.group" +msgstr "Grupo" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Ocultar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.lock" +msgstr "Bloquear" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.mask" +msgstr "Máscara" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.paste" +msgstr "Pegar" + +#: 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" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show" +msgstr "Mostrar" + +#: 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 "Ver componente principal" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.ungroup" +msgstr "Desagrupar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unlock" +msgstr "Desbloquear" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unmask" +msgstr "Quitar máscara" + +#: 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 "Actualizar componente principal" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "Historial (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.layers" +msgstr "Capas (%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 "Atributos del SVG Importado" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "workspace.sidebar.sitemap" +msgstr "Páginas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.sitemap" +msgstr "Mapa del sitio" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.assets" +msgstr "Recursos (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.color-palette" +msgstr "Paleta de colores (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.comments" +msgstr "Comentarios (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.curve" +msgstr "Curva (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.ellipse" +msgstr "Elipse (E)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "Tablero (A)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "Imagen (K)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.move" +msgstr "Mover" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.path" +msgstr "Ruta (P)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "Rectángulo (R)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "Texto (T)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.empty" +msgstr "Todavía no hay cambios en el histórico" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.delete" +msgstr "%s eliminado" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.modify" +msgstr "%s modificado" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.move" +msgstr "Objetos movidos" + +msgid "workspace.undo.entry.multiple.circle" +msgstr "círculos" + +msgid "workspace.undo.entry.multiple.color" +msgstr "colores" + +msgid "workspace.undo.entry.multiple.component" +msgstr "componentes" + +msgid "workspace.undo.entry.multiple.curve" +msgstr "curvas" + +msgid "workspace.undo.entry.multiple.frame" +msgstr "mesa de trabajo" + +msgid "workspace.undo.entry.multiple.group" +msgstr "grupos" + +msgid "workspace.undo.entry.multiple.media" +msgstr "gráficos" + +msgid "workspace.undo.entry.multiple.multiple" +msgstr "objetos" + +msgid "workspace.undo.entry.multiple.page" +msgstr "páginas" + +msgid "workspace.undo.entry.multiple.path" +msgstr "trazos" + +msgid "workspace.undo.entry.multiple.rect" +msgstr "rectángulos" + +msgid "workspace.undo.entry.multiple.shape" +msgstr "formas" + +msgid "workspace.undo.entry.multiple.text" +msgstr "textos" + +msgid "workspace.undo.entry.multiple.typography" +msgstr "tipografías" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.new" +msgstr "Nuevo %s" + +msgid "workspace.undo.entry.single.circle" +msgstr "círculo" + +msgid "workspace.undo.entry.single.color" +msgstr "color" + +msgid "workspace.undo.entry.single.component" +msgstr "componente" + +msgid "workspace.undo.entry.single.curve" +msgstr "curva" + +msgid "workspace.undo.entry.single.frame" +msgstr "mesa de trabajo" + +msgid "workspace.undo.entry.single.group" +msgstr "grupo" + +msgid "workspace.undo.entry.single.image" +msgstr "imagen" + +msgid "workspace.undo.entry.single.media" +msgstr "gráfico" + +msgid "workspace.undo.entry.single.multiple" +msgstr "objeto" + +msgid "workspace.undo.entry.single.page" +msgstr "página" + +msgid "workspace.undo.entry.single.path" +msgstr "trazo" + +msgid "workspace.undo.entry.single.rect" +msgstr "rectángulo" + +msgid "workspace.undo.entry.single.shape" +msgstr "forma" + +msgid "workspace.undo.entry.single.text" +msgstr "texto" + +msgid "workspace.undo.entry.single.typography" +msgstr "tipografía" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.unknown" +msgstr "Operación sobre %s" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.title" +msgstr "Histórico" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.dismiss" +msgstr "Ignorar" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "Hay actualizaciones en librerías compartidas" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.update" +msgstr "Actualizar" + +msgid "workspace.viewport.click-to-close-path" +msgstr "Pulsar para cerrar la ruta" \ No newline at end of file diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po new file mode 100644 index 000000000..4f7151394 --- /dev/null +++ b/frontend/translations/fr.po @@ -0,0 +1,2193 @@ +msgid "" +msgstr "" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Vous avez déjà un compte ?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "" +"Vérifiez votre e‑mail et cliquez sur le lien pour vérifier et commencer à " +"utiliser Penpot." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "Confirmez le mot de passe" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Créer un compte de démonstration" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "Vous voulez juste essayer ?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"Il s’agit d’un service DEMO, NE PAS UTILISER pour un travail réel, les " +"projets seront périodiquement supprimés." + +#: 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 "Adresse e‑mail" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "Mot de passe oublié ?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "Nom complet" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.go-back-to-login" +msgstr "Retour !" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Se connecter ici" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Se connecter" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-subtitle" +msgstr "Entrez vos informations ci‑dessous" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Ravi de vous revoir !" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Se connecter via Github" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "Se connecter via Gitlab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "Se connecter via LDAP" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Saisissez un nouveau mot de passe" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "Le code de récupération n’est pas valide." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-succesfully" +msgstr "Mot de passe changé avec succès" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "Le profil n’est pas vérifié. Veuillez vérifier le profil avant de continuer." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Lien de récupération de mot de passe envoyé." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Vous avez rejoint l’équipe avec succès" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Mot de passe" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "Au moins 8 caractères" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Récupérer le mot de passe" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "Nous vous enverrons un e‑mail avec des instructions" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Mot de passe oublié ?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Changez votre mot de passe" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Pas encore de compte ?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Créer un compte" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "C’est gratuit, c’est Open Source" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Créer un compte" + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "La solution Open Source pour la conception et le prototypage." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.verification-email-sent" +msgstr "Nous avons envoyé un e-mail de vérification à" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Ajouter une Bibliothèque Partagée" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Changer adresse e‑mail" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ Créer nouvelle équipe" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Votre Penpot" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Supprimer l’équipe" + +msgid "dashboard.draft-title" +msgstr "Brouillon" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Vous n’avez encore aucun fichier ici" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "Inviter dans l’équipe" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Quitter l’équipe" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "Bibliothèques Partagées" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "chargement de vos fichiers…" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ Nouveau fichier" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ Nouveau projet" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "Aucune correspondance pour « %s »" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "Les projets épinglés apparaîtront ici" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "Votre adresse e‑mail a été mise à jour avec succès" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "Votre adresse e‑mail a été vérifiée avec succès" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "Mot de passe enregistré avec succès !" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "%s membres" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "Changer le mot de passe" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "Projets" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.promote-to-owner" +msgstr "Promouvoir propriétaire" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.remove-account" +msgstr "Vous souhaitez supprimer votre compte ?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.remove-shared" +msgstr "Retirer en tant que Bibliothèque Partagée" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +msgstr "Rechercher…" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.searching-for" +msgstr "Recherche de « %s »…" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-language" +msgstr "Sélectionnez la langue de l’interface" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-theme" +msgstr "Sélectionnez un thème" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.show-all-files" +msgstr "Voir tous les fichiers" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "Changer d’équipe" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-info" +msgstr "Information de l’équipe" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-members" +msgstr "Membres de l’équipe" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-projects" +msgstr "Projets de l’équipe" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.theme-change" +msgstr "Thème de l’interface" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.title-search" +msgstr "Résultats de recherche" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.type-something" +msgstr "Écrivez pour rechercher" + +#: 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 "Mettre à jour les paramètres" + +#: src/app/main/ui/settings.cljs +msgid "dashboard.your-account-title" +msgstr "Votre compte" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "E‑mail" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-name" +msgstr "Votre nom complet" + +#: 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 "Votre Penpot" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +msgstr "Annuler" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "Ok" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "Êtes‑vous sûr ?" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "ds.updated-at" +msgstr "Mise à jour : %s" + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "Votre navigateur ne peut pas effectuer cette opération" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +msgid "errors.email-already-exists" +msgstr "Adresse e‑mail déjà utilisée" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "Adresse e‑mail déjà validée." + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +msgstr "L’adresse e‑mail de confirmation doit correspondre" + +#: 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 "Un problème s’est produit." + +msgid "errors.media-format-unsupported" +msgstr "Le format d’image n’est pas supporté (doit être svg, jpg ou png)." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "L’image est trop grande (doit être inférieure à 5 Mo)." + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "errors.media-type-mismatch" +msgstr "" +"Il semble que le contenu de l’image ne correspond pas à l’extension de " +"fichier." + +#: 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 "L’image ne semble pas être valide." + +msgid "errors.network" +msgstr "Impossible de se connecter au serveur principal." + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-invalid-confirmation" +msgstr "Le mot de passe de confirmation doit correspondre" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "Le mot de passe doit contenir au moins 8 caractères" + +#: src/app/main/ui/auth/register.cljs +msgid "errors.registration-disabled" +msgstr "L’enregistrement est actuellement désactivé." + +#: 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 "Une erreur inattendue s’est produite" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.wrong-credentials" +msgstr "Le nom d’utilisateur ou le mot de passe semble être faux." + +#: src/app/main/ui/settings/password.cljs +msgid "errors.wrong-old-password" +msgstr "L’ancien mot de passe est incorrect" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-start" +msgstr "Rejoindre le chat" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-subtitle" +msgstr "Envie de parler? Discutez avec nous sur Gitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "Description" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-go-to" +msgstr "Aller aux discussions" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle1" +msgstr "Rejoignez le forum de communication collaborative de l'équipe Penpot." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle2" +msgstr "" +"Vous pouvez poser des questions et y répondre, avoir des conversations " +"ouvertes et suivre les décisions affectant le projet." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-title" +msgstr "Discussions en équipe" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subject" +msgstr "Sujet" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subtitle" +msgstr "" +"Veuillez décrire la raison de votre e-mail, en précisant s'il s'agit d'un " +"problème, d'une idée ou d'un doute. Un membre de notre équipe vous répondra " +"dans les plus brefs délais." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "Email" + +#: src/app/main/ui/settings/password.cljs +msgid "generic.error" +msgstr "Une erreur s’est produite" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur" +msgstr "Flou" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur.value" +msgstr "Valeur" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.hex" +msgstr "HEX" + +#: 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 "Remplir" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.download" +msgstr "Télécharger l’image source" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.height" +msgstr "Hauteur" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "Largeur" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout" +msgstr "Mise en page" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Hauteur" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Gauche" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Rayon" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Rotation" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Haut" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Largeur" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "Ombre" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.blur" +msgstr "B" + +#: 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 "S" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Contour" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Centre" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "Intérieur" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Extérieur" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Pointillé" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Mixte" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Aucun" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Solide" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Épaisseur" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Typographie" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-family" +msgstr "Police de caractères" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-size" +msgstr "Taille de police" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "Style de police" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "Interlettrage" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Interlignage" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Décoration de texte" + +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "Aucune" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Barré" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Soulignage" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Transformation de texte" + +msgid "handoff.attributes.typography.text-transform.lowercase" +msgstr "Minuscule" + +msgid "handoff.attributes.typography.text-transform.none" +msgstr "Aucune" + +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "Premières Lettres en Capitales" + +msgid "handoff.attributes.typography.text-transform.uppercase" +msgstr "Capitales" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Code" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Cercle" + +msgid "handoff.tabs.code.selected.curve" +msgstr "Courbe" + +msgid "handoff.tabs.code.selected.frame" +msgstr "Plan de travail" + +msgid "handoff.tabs.code.selected.group" +msgstr "Groupe" + +msgid "handoff.tabs.code.selected.image" +msgstr "Image" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "%s Sélectionné" + +msgid "handoff.tabs.code.selected.path" +msgstr "Chemin" + +msgid "handoff.tabs.code.selected.rect" +msgstr "Rectangle" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.text" +msgstr "Texte" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Information" + +msgid "history.alert-message" +msgstr "Vous voyez la version %s" + +msgid "labels.accept" +msgstr "Accepter" + +#: 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 "Administration" + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.all" +msgstr "Tous" + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.desc-message" +msgstr "" +"Il semble que vous deviez attendre un peu et réessayer ; nous effectuons " +"une petite maintenance de nos serveurs." + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.main-message" +msgstr "Bad Gateway" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.cancel" +msgstr "Annuler" + +msgid "labels.centered" +msgstr "Centré" + +#: src/app/main/ui/dashboard/comments.cljs +msgid "labels.comments" +msgstr "Commentaires" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.confirm-password" +msgstr "Confirmer le mot de passe" + +msgid "labels.content" +msgstr "Contenu" + +#: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs +msgid "labels.create-team" +msgstr "Créer nouvelle équipe" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.dashboard" +msgstr "Tableau de bord" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.delete" +msgstr "Supprimer" + +#: src/app/main/ui/comments.cljs +msgid "labels.delete-comment" +msgstr "Supprimer le commentaire" + +#: src/app/main/ui/comments.cljs +msgid "labels.delete-comment-thread" +msgstr "Supprimer le fil" + +#: 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 "Brouillons" + +#: src/app/main/ui/comments.cljs +msgid "labels.edit" +msgstr "Modifier" + +#: 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 "Éditeur" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.email" +msgstr "Adresse e‑mail" + +#: 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 "Donnez votre avis" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.hide-resolved-comments" +msgstr "Masquer les commentaires résolus" + +msgid "labels.icons" +msgstr "Icônes" + +msgid "labels.images" +msgstr "Images" + +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.desc-message" +msgstr "" +"Un problème s’est produit. Veuillez réessayer l’opération et, si le " +"problème persiste, contacter le service technique." + +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.main-message" +msgstr "Erreur interne" + +#: src/app/main/ui/settings/options.cljs +msgid "labels.language" +msgstr "Langue" + +#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.logout" +msgstr "Se déconnecter" + +#: 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 "Membres" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.name" +msgstr "Nom" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.new-password" +msgstr "Nouveau mot de passe" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs +msgid "labels.no-comments-available" +msgstr "Vous n’avez aucune notification de commentaire en attente" + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.auth-info" +msgstr "Vous êtes connecté en tant que" + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.desc-message" +msgstr "" +"Cette page n’existe pas ou vous ne disposez pas des permissions nécessaires " +"pour y accéder." + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.main-message" +msgstr "Oups !" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-files" +msgid_plural "labels.num-of-files" +msgstr[0] "1 fichier" +msgstr[1] "%s fichiers" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-projects" +msgid_plural "labels.num-of-projects" +msgstr[0] "1 projet" +msgstr[1] "%s projets" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.old-password" +msgstr "Ancien mot de passe" + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.only-yours" +msgstr "Seulement les vôtres" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.owner" +msgstr "Propriétaire" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.password" +msgstr "Mot de passe" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.permissions" +msgstr "Permissions" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.profile" +msgstr "Profil" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.projects" +msgstr "Projets" + +msgid "labels.recent" +msgstr "Récent" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.release-notes" +msgstr "Notes de version" + +#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.remove" +msgstr "Retirer" + +#: 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 "Renommer" + +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +msgid "labels.retry" +msgstr "Réessayer" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.role" +msgstr "Rôle" + +msgid "labels.save" +msgstr "Enregistrer" + +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.desc-message" +msgstr "Nous sommes en maintenance planifiée de nos systèmes." + +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.main-message" +msgstr "Service non disponible" + +#: 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 "Configuration" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.shared-libraries" +msgstr "Bibliothèques Partagées" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.show-all-comments" +msgstr "Afficher tous les commentaires" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.show-your-comments" +msgstr "Afficher uniquement vos commentaires" + +#: src/app/main/ui/static.cljs +msgid "labels.sign-out" +msgstr "Se déconnecter" + +#: src/app/main/ui/settings/profile.cljs +msgid "labels.update" +msgstr "Actualiser" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "Spectateur" + +#: src/app/main/ui/comments.cljs +msgid "labels.write-new-comment" +msgstr "Écrire un nouveau commentaire" + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "media.loading" +msgstr "Chargement de l’image…" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.accept" +msgstr "Ajouter comme Bibliothèque Partagée" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.hint" +msgstr "" +"Une fois ajoutées en tant que Bibliothèque Partagée, les ressources de " +"cette bibliothèque de fichiers seront disponibles pour être utilisées parmi " +"le reste de vos fichiers." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.message" +msgstr "Ajouter « %s » comme Bibliothèque Partagée" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.confirm-email" +msgstr "Vérifier la nouvelle adresse e‑mail" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.info" +msgstr "" +"Nous enverrons un e‑mail à votre adresse actuelle « %s » pour vérifier " +"votre identité." + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.new-email" +msgstr "Nouvel e‑mail" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.submit" +msgstr "Changer adresse e‑mail" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.title" +msgstr "Changez votre adresse e‑mail" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.cancel" +msgstr "Annuler et conserver mon compte" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.confirm" +msgstr "Oui, supprimer mon compte" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.info" +msgstr "" +"En supprimant votre compte, vous perdrez tous vos projets et archives " +"actuelles." + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.title" +msgstr "Êtes‑vous sûr de vouloir supprimer votre compte ?" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.accept" +msgstr "Supprimer la conversation" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.message" +msgstr "" +"Êtes‑vous sûr de vouloir supprimer cette conversation ? Tous les " +"commentaires de ce fil seront supprimés." + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.title" +msgstr "Supprimer une conversation" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.accept" +msgstr "Supprimer le fichier" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.message" +msgstr "Êtes‑vous sûr de vouloir supprimer ce fichier ?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.title" +msgstr "Supprimer un fichier" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.body" +msgstr "Êtes‑vous sûr de vouloir supprimer cette page ?" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Supprimer une page" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Supprimer le projet" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "Êtes‑vous sûr de vouloir supprimer ce projet ?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Supprimer un projet" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.accept" +msgstr "Supprimer l’équipe" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.message" +msgstr "" +"Êtes‑vous sûr de vouloir supprimer cette équipe ? Tous les projets et " +"fichiers associés à l’équipe seront définitivement supprimés." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.title" +msgstr "Suppression d’une équipe" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Supprimer le membre" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "Êtes‑vous sûr de vouloir supprimer ce membre de l’équipe ?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Supprimer un membre d’équipe" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Envoyer l'invitation" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member.title" +msgstr "Inviter à rejoindre l’équipe" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "Vous êtes le propriétaire de %s." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint2" +msgstr "Sélectionnez un autre membre à promouvoir avant de quitter l’équipe" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.promote-and-leave" +msgstr "Promouvoir et quitter" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.select-memeber-to-promote" +msgstr "Sélectionnez un membre à promouvoir" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.title" +msgstr "Sélectionnez un membre à promouvoir" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.accept" +msgstr "Quitter l’équipe" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.message" +msgstr "Êtes‑vous sûr de vouloir quitter cette équipe ?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.title" +msgstr "Quitter l’équipe" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.accept" +msgstr "Promouvoir" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.message" +msgstr "Êtes‑vous sûr de vouloir promouvoir cette personne propriétaire ?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.title" +msgstr "Promouvoir propriétaire" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.accept" +msgstr "Supprimer en tant que Bibliothèque Partagée" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.hint" +msgstr "" +"Une fois supprimée en tant que Bibliothèque Partagée, la Bibliothèque de ce " +"fichier ne pourra plus être utilisée par le reste de vos fichiers." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.message" +msgstr "Retirer « %s » en tant que Bibliothèque Partagée" + +#: 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 "Actualiser le composant" + +#: 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 "Annuler" + +#: 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 "" +"Vous êtes sur le point de mettre à jour le composant d’une Bibliothèque " +"Partagée. Cela peut affecter d’autres fichiers qui l’utilisent." + +#: 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 "Actualiser le composant d’une bibliothèque" + +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "E‑mail d'invitation envoyé!" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" +msgstr "" +"Vous ne pouvez pas supprimer votre profil. Réassignez vos équipes avant de " +"continuer." + +#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs +msgid "notifications.profile-saved" +msgstr "Profil enregistré avec succès !" + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "E‑mail de vérification envoyé à %s. Vérifiez votre e‑mail !" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Aller à la page de connexion" + +#: 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 "Divers" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "title.dashboard.projects" +msgstr "Projets - %s - Penpot" + +#: src/app/main/ui/dashboard/search.cljs +msgid "title.dashboard.search" +msgstr "Rechercher - %s - Penpot" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Bibliothèques Partagées - %s - Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Donnez votre avis - Penpot" + +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Configuration - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Mot de passe - Penpot" + +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "Profil - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-members" +msgstr "Membres - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "Configuration - %s - Penpot" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - Mode spectateur - Penpot" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.empty-state" +msgstr "Aucun cadre trouvé sur la page." + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.frame-not-found" +msgstr "Cadre introuvable." + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "Ne pas afficher les interactions" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.edit-page" +msgstr "Modifier la page" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +msgstr "Plein écran" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.copy-link" +msgstr "Copier le lien" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.create-link" +msgstr "Créer le lien" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.placeholder" +msgstr "Le lien de partage apparaîtra ici" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.remove-link" +msgstr "Supprimer le lien" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.subtitle" +msgstr "Toute personne disposant du lien aura accès" + +#: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.title" +msgstr "Lien de partage" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions" +msgstr "Afficher les interactions" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions-on-click" +msgstr "Afficher les interactions au clic" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.sitemap" +msgstr "Plan du site" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hcenter" +msgstr "Aligner horizontalement au centre" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hdistribute" +msgstr "Répartir l’espacement horizontal" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hleft" +msgstr "Aligner à gauche" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hright" +msgstr "Aligner à droite" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vbottom" +msgstr "Aligner en bas" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vcenter" +msgstr "Aligner verticalement au centre" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vdistribute" +msgstr "Répartir l’espacement vertical" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vtop" +msgstr "Aligner en haut" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Ressources" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.box-filter-all" +msgstr "Toutes" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Graphiques" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Couleurs" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Composants" + +#: 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 "Supprimer" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.duplicate" +msgstr "Dupliquer" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.edit" +msgstr "Modifier" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.file-library" +msgstr "Bibliothèque du fichier" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Graphiques" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Bibliothèques" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.not-found" +msgstr "Aucune ressource trouvée" + +#: 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 "Renommer" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.search" +msgstr "Chercher des ressources" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "PARTAGÉ" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Typographies" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Police" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Taille" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Variante" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Accéder au fichier de bibliothèque de styles à modifier" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.letter-spacing" +msgstr "Interlettrage" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.line-height" +msgstr "Interlignage" + +#: 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 "Transformer le texte" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.linear" +msgstr "Dégradé linéaire" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.radial" +msgstr "Dégradé radial" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-dynamic-alignment" +msgstr "Désactiver l’alignement dynamique" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-grid" +msgstr "Désactiver l’alignement sur la grille" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-dynamic-alignment" +msgstr "Activer l’alignement dynamique" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-snap-grid" +msgstr "Aligner sur la grille" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-assets" +msgstr "Masquer les ressources" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-grid" +msgstr "Masquer la grille" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-layers" +msgstr "Masquer les calques" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-palette" +msgstr "Masquer la palette de couleurs" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-rules" +msgstr "Masquer les règles" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.select-all" +msgstr "Tout sélectionner" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-assets" +msgstr "Montrer les ressources" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-grid" +msgstr "Montrer la grille" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-layers" +msgstr "Montrer les calques" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Montrer la palette de couleurs" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-rules" +msgstr "Montrer les règles" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.save-error" +msgstr "Erreur d’enregistrement" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Enregistré" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Enregistrement" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "Modifications non sauvegardées" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.viewer" +msgstr "Mode spectateur (%s)" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Ajouter" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.colors" +msgstr "%s couleurs" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.big-thumbnails" +msgstr "Grandes vignettes" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "Bibliothèque du fichier" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Couleurs récentes" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Enregistrer le style de couleur" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.small-thumbnails" +msgstr "Petites vignettes" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.components" +msgstr "%s composants" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.file-library" +msgstr "Bibliothèque du fichier" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.graphics" +msgstr "%s graphiques" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.in-this-file" +msgstr "BIBLIOTHÈQUES DANS CE FICHIER" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "BIBLIOTHÈQUES" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "BIBLIOTHÈQUE" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-libraries-need-sync" +msgstr "Aucune Bibliothèque Partagée n’a besoin d’être mise à jour" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-matches-for" +msgstr "Aucune correspondance pour « %s »" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-shared-libraries-available" +msgstr "Aucune Bibliothèque Partagée disponible" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.search-shared-libraries" +msgstr "Rechercher des Bibliothèques Partagées" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.shared-libraries" +msgstr "BIBLIOTHÈQUES PARTAGÉES" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography" +msgstr "Multiple typographies" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography-tooltip" +msgstr "Dissocier toutes les typographies" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.typography" +msgstr "%s typographies" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Actualiser" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "MISES À JOUR" + +msgid "workspace.library.all" +msgstr "Toutes les bibliothèques" + +msgid "workspace.library.libraries" +msgstr "Bibliothèques" + +msgid "workspace.library.own" +msgstr "Mes bibliothèques" + +msgid "workspace.library.store" +msgstr "Prédéfinies" + +msgid "workspace.options.blur-options.background-blur" +msgstr "Fond" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Calque" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "Flou" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.group" +msgstr "Flou de groupe" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.multiple" +msgstr "Flou de sélection" + +#: src/app/main/ui/workspace/sidebar/options/page.cljs +msgid "workspace.options.canvas-background" +msgstr "Couleur de fond du canvas" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Composant" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.design" +msgstr "Conception" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export" +msgstr "Export" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-object" +msgstr "Exporter la forme" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +msgid "workspace.options.export.suffix" +msgstr "Suffixe" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.exporting-object" +msgstr "Export en cours…" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.fill" +msgstr "Remplissage" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.auto" +msgstr "Automatique" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.column" +msgstr "Colonnes" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.columns" +msgstr "Colonnes" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.gutter" +msgstr "Gouttière" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.height" +msgstr "Hauteur" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.margin" +msgstr "Marge" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.rows" +msgstr "Lignes" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.set-default" +msgstr "Définir par défaut" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.size" +msgstr "Taille" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type" +msgstr "Type" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Bas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.center" +msgstr "Centre" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.left" +msgstr "Gauche" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.right" +msgstr "Droite" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.stretch" +msgstr "Étirer" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Haut" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Utiliser la valeur par défaut" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.width" +msgstr "Largeur" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.row" +msgstr "Lignes" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.square" +msgstr "Carré" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.title" +msgstr "Grille & Calques" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.group-fill" +msgstr "Remplissage de groupe" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.group-stroke" +msgstr "Contour de groupe" + +#: 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" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.prototype" +msgstr "Prototype" + +msgid "workspace.options.radius" +msgstr "Rayon" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.rotation" +msgstr "Rotation" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-a-shape" +msgstr "" +"Sélectionnez une forme, un plan de travail ou un groupe pour faire glisser " +"une connexion vers un autre plan de travail." + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-artboard" +msgstr "Sélectionner un plan de travail" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.selection-fill" +msgstr "Remplissage de sélection" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.selection-stroke" +msgstr "Contour de sélection" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.blur" +msgstr "Flou" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Ombre portée" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.inner-shadow" +msgstr "Ombre intérieure" + +#: 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 "Diffusion" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title" +msgstr "Ombre" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.group" +msgstr "Ombre de groupe" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.multiple" +msgstr "Ombres de la sélection" + +#: 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 "Taille" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +msgid "workspace.options.size-presets" +msgstr "Tailles prédéfinies" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke" +msgstr "Bordure" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Centre" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Tirets" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Pointillé" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.inner" +msgstr "Intérieur" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.mixed" +msgstr "Mixte" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.outer" +msgstr "Extérieur" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.solid" +msgstr "Solide" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-bottom" +msgstr "Aligner en bas" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-center" +msgstr "Aligner au centre" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-justify" +msgstr "Justifier" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-left" +msgstr "Aligner à gauche" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-middle" +msgstr "Aligner verticalement au milieu" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-right" +msgstr "Aligner à droite" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-top" +msgstr "Aligner en haut" + +msgid "workspace.options.text-options.decoration" +msgstr "Décoration" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-height" +msgstr "Hauteur automatique" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-width" +msgstr "Largeur automatique" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-fixed" +msgstr "Fixe" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.letter-spacing" +msgstr "Interlettrage" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.line-height" +msgstr "Interlignage" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.lowercase" +msgstr "Minuscule" + +#: 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 "Aucune" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.strikethrough" +msgstr "Barré" + +msgid "workspace.options.text-options.text-case" +msgstr "Casse" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title" +msgstr "Texte" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-group" +msgstr "Texte de groupe" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-selection" +msgstr "Texte de la sélection" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.titlecase" +msgstr "Premières Lettres en Capitales" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.underline" +msgstr "Soulignage" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.uppercase" +msgstr "Majuscule" + +msgid "workspace.options.text-options.vertical-align" +msgstr "Alignement vertical" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.use-play-button" +msgstr "" +"Utilisez le bouton de lecture dans l’en‑tête pour exécuter la vue du " +"prototype." + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.back" +msgstr "Envoyer au fond" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.backward" +msgstr "Éloigner" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Copier" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Créer un composant" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Couper" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Supprimer" + +#: 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 "Détacher l’instance" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.duplicate" +msgstr "Dupliquer" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.forward" +msgstr "Avancer" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.front" +msgstr "Amener au premier plan" + +#: 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 "Aller au fichier du composant principal" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.group" +msgstr "Groupe" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Masquer" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.lock" +msgstr "Bloquer" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.mask" +msgstr "Masque" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.paste" +msgstr "Coller" + +#: 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 "Annuler les modifications" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show" +msgstr "Montrer" + +#: 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 "Afficher le composant principal" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.ungroup" +msgstr "Dégrouper" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unlock" +msgstr "Débloquer" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unmask" +msgstr "Supprimer le masque" + +#: 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 "Actualiser le composant principal" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "Historique (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.layers" +msgstr "Calques (%s)" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "workspace.sidebar.sitemap" +msgstr "Pages" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.sitemap" +msgstr "Plan du site" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.assets" +msgstr "Ressources (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.color-palette" +msgstr "Palette de couleurs (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.comments" +msgstr "Commentaires (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.curve" +msgstr "Courbe (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.ellipse" +msgstr "Ellipse (E)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "Plan de travail (A)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "Image (K)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.move" +msgstr "Déplacer" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.path" +msgstr "Chemin (P)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "Rectangle (R)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "Texte (T)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.empty" +msgstr "Il n’y a aucun changement dans l’historique pour l’instant" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.delete" +msgstr "Supprimé %s" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.modify" +msgstr "Modifié %s" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.move" +msgstr "Objets déplacés" + +msgid "workspace.undo.entry.multiple.circle" +msgstr "cercles" + +msgid "workspace.undo.entry.multiple.color" +msgstr "couleurs" + +msgid "workspace.undo.entry.multiple.component" +msgstr "composants" + +msgid "workspace.undo.entry.multiple.curve" +msgstr "courbes" + +msgid "workspace.undo.entry.multiple.frame" +msgstr "plan de travail" + +msgid "workspace.undo.entry.multiple.group" +msgstr "groupes" + +msgid "workspace.undo.entry.multiple.media" +msgstr "graphiques" + +msgid "workspace.undo.entry.multiple.multiple" +msgstr "objets" + +msgid "workspace.undo.entry.multiple.page" +msgstr "pages" + +msgid "workspace.undo.entry.multiple.path" +msgstr "chemins" + +msgid "workspace.undo.entry.multiple.rect" +msgstr "rectangles" + +msgid "workspace.undo.entry.multiple.shape" +msgstr "formes" + +msgid "workspace.undo.entry.multiple.text" +msgstr "textes" + +msgid "workspace.undo.entry.multiple.typography" +msgstr "typographie" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.new" +msgstr "Nouveau %s" + +msgid "workspace.undo.entry.single.circle" +msgstr "cercle" + +msgid "workspace.undo.entry.single.color" +msgstr "couleur" + +msgid "workspace.undo.entry.single.component" +msgstr "composant" + +msgid "workspace.undo.entry.single.curve" +msgstr "courbe" + +msgid "workspace.undo.entry.single.frame" +msgstr "plan de travail" + +msgid "workspace.undo.entry.single.group" +msgstr "groupe" + +msgid "workspace.undo.entry.single.image" +msgstr "image" + +msgid "workspace.undo.entry.single.media" +msgstr "graphique" + +msgid "workspace.undo.entry.single.multiple" +msgstr "objet" + +msgid "workspace.undo.entry.single.page" +msgstr "page" + +msgid "workspace.undo.entry.single.path" +msgstr "chemin" + +msgid "workspace.undo.entry.single.rect" +msgstr "rectangle" + +msgid "workspace.undo.entry.single.shape" +msgstr "forme" + +msgid "workspace.undo.entry.single.text" +msgstr "texte" + +msgid "workspace.undo.entry.single.typography" +msgstr "typographie" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.unknown" +msgstr "Opération sur %s" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.title" +msgstr "Historique" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.dismiss" +msgstr "Ignorer" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "Il y a des mises à jour dans les Bibliothèques Partagées" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.update" +msgstr "Actualiser" + +msgid "workspace.viewport.click-to-close-path" +msgstr "Cliquez pour fermer le chemin" \ No newline at end of file diff --git a/frontend/translations/gr.po b/frontend/translations/gr.po new file mode 100644 index 000000000..98eb07d7f --- /dev/null +++ b/frontend/translations/gr.po @@ -0,0 +1,2361 @@ +msgid "" +msgstr "" +"Language: gr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\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 "" +"Ελέγξτε το email σας και κάντε κλικ στον σύνδεσμο για επαλήθευση και έναρξη " +"χρήσης του 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 "" +"Αυτή είναι μια υπηρεσία DEMO, ΜΗ ΧΡΗΣΙΜΟΠΟΙΕΙΤΕ για πραγματική εργασία, τα " +"έργα θα σβήνονται περιοδικά." + +#: 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 "Email" + +#: 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/recovery_request.cljs +msgid "auth.go-back-to-login" +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/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Συνδεθείτε με το Github" + +#: src/app/main/ui/auth/register.cljs, 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-ldap-submit" +msgstr "Συνδεθείτε με το LDAP" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Πληκτρολογήστε έναν νέο κωδικό πρόσβασης." + +#: 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 "" +"Στείλαμε στο email σας έναν link για να ανακτήσουμε τον κωδικό πρόσβασής " +"σας." + +#: 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 "Θα σας στείλουμε ένα email με οδηγίες " + +#: 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 "Είναι δωρεάν, είναι Open Source" + +#: 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 "Εχουμε στείλει ενα mail επαλήθευσης " + +#: 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 "Αλλαγή email" + +#: 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/grid.cljs +msgid "dashboard.empty-files" +msgstr "Δεν έχετε ακόμα αρχεία εδώ" + +#: 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 "φόρτωση των αρχείων σας …" + +#: 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-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/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +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 "Η διεύθυνση email σας ενημερώθηκε με επιτυχία" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "Η διεύθυνση email σας έχει επαληθευτεί" + +#: 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 "Άνοιγμα αρχείου σε νέα καρτέλα" + +#: 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/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 "Email" + +#: 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 "Ok" + +#: 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 "Το email έχει ήδη χρησιμοποιηθεί" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "Αυτό το email έχει ήδη επικυρωθεί." + +#: 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 "Το email «%s» έχει πολλές μόνιμες αναφορές αναπήδησης." + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +msgstr "Το email επιβεβαίωσης πρέπει να ταιριάζει" + +#: 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 απενεργοποιήθηκε στο backend" + +#: 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 "Η εικόνα είναι πολύ μεγάλη (πρέπει να είναι μικρότερη από 5mb)." + +#: 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 "Δεν είναι δυνατή η σύνδεση με διακομιστή backend." + +#: 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/register.cljs +msgid "errors.registration-disabled" +msgstr "Η εγγραφή είναι απενεργοποιημένη αυτήν τη στιγμή." + +msgid "errors.terms-privacy-agreement-invalid" +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 "" +"Περιγράψτε τον λόγο του email σας, προσδιορίζοντας εάν πρόκειται για " +"ζήτημα, ιδέα ή αμφιβολία. Ένα μέλος της ομάδας μας θα απαντήσει το " +"συντομότερο δυνατό." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "Email" + +#: 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 "HEX" + +#: 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 "Aκτίνα" + +#: 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 "B" + +#: 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 "S" + +#: 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 "Αποδέχομαι" + +#: 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 "Bad Gateway" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.cancel" +msgstr "ακύρωση" + +msgid "labels.centered" +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 "περιεχόμενα" + +#: 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/settings/sidebar.cljs +msgid "labels.dashboard" +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/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 "Edit" + +#: 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" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.email" +msgstr "Email" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-disabled" +msgstr "Τα σχόλια απενεργοποιήθηκαν" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-sent" +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 "Δώστε μας τη γνώμη σας" + +#: 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 "εικόνες" + +#: 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 "Γλώσσα" + +#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.logout" +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] "1 αρχείο" +msgstr[1] "% s αρχεία" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-projects" +msgid_plural "labels.num-of-projects" +msgstr[0] "1 έργο" +msgstr[1] "% 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 "Μόνο το δικό σου" + +#: 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/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 "Αποθηκεύση" + +#: 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 "Σύνθεση" + +#: 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 "Αποσύνδεση" + +#: src/app/main/ui/settings/profile.cljs +msgid "labels.update" +msgstr "Ενημέρωση" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "Ενημερώστε τον εξοπλισμό" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +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 "16 / 5000 Resultados de traducción Επιβεβαιώστε νέο e-mail" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.info" +msgstr "" +"Θα σας στείλουμε ένα email στο τρέχον email σας “%s” για να επαληθεύσουμε " +"την ταυτότητά σας." + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.new-email" +msgstr "Νέο email" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.submit" +msgstr "Αλλαγή email" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.title" +msgstr "Αλλάξτε το email σας" + +#: 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/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 "Προσκαλέστε να συμμετάσχετε στην ομάδα" + +#: 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 "Το email επαλήθευσης εστάλη στο% s. Ελέγξτε το email σας!" + +#: 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/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 "Το πλαίσιο δεν βρέθηκε." + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "Μην εμφανίζετε αλληλεπιδράσεις" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.edit-page" +msgstr "Επεξεργασία σελίδας" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +msgstr "Πλήρης οθόνη" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.copy-link" +msgstr "Αντιγραφή link" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.create-link" +msgstr "Δημιουργία link" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.placeholder" +msgstr "Μοιραστείτε το link θα εμφανιστεί εδώ" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.remove-link" +msgstr "Κατάργηση link" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.subtitle" +msgstr "Όποιος έχει τον link θα έχει πρόσβαση" + +#: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.title" +msgstr "Μοιραστείτε το link" + +#: 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 "Εμφάνιση αλληλεπιδράσεων με click" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.sitemap" +msgstr "Sitemap" + +#: 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/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.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.search" +msgstr "Αναζήτηση στοιχείων" + +#: 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 "Ag" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.text-transform" +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-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-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 "Aποθήκευση " + +#: 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/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.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.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 "" +"Επιλέξτε ένα σχήμα, ένα artboard ή μια ομάδα για να σύρετε μια σύνδεση με " +"άλλο artboard." + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-artboard" +msgstr "Επιλέξτε artboard" + +#: 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 "Χ" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.offsety" +msgstr "Υ" + +#: 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.center" +msgstr "Κέντρο" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Dashed" + +#: 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/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 "" +"Χρησιμοποιήστε το κουμπί αναπαραγωγής στην κεφαλίδα για να εκτελέσετε την " +"προβολή πρωτοτύπου." + +#: 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 "Φέρτε μπροστά" + +msgid "workspace.shape.menu.go-master" +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 "προβολή" + +msgid "workspace.shape.menu.show-master" +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 "Ανακάλυψη" + +msgid "workspace.shape.menu.update-master" +msgstr "23 / 5000 Resultados de traducción Ενημέρωση κύριου στοιχείου" + +#: 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 "Έλλειψη (Ε)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "Artboard (Α)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "Εικόνα (Κ)" + +#: 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 "Path (Ρ)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "Ορθογώνιο (R)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "Κείμενο (Τ)" + +#: 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 "" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.move" +msgstr "Τροποποιήθηκε% s" + +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 "Kαμπύλες" + +msgid "workspace.undo.entry.multiple.frame" +msgstr "artboard" + +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 "paths" + +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 "artboard" + +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 "path" + +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/ru.po b/frontend/translations/ru.po new file mode 100644 index 000000000..3cb0f1e42 --- /dev/null +++ b/frontend/translations/ru.po @@ -0,0 +1,1184 @@ +msgid "" +msgstr "" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Уже есть аккаунт?" + +#: 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 "Email" + +#: 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/recovery_request.cljs +msgid "auth.go-back-to-login" +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/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Вход через Gitnub" + +#: src/app/main/ui/auth/register.cljs, 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-ldap-submit" +msgstr "Вход через LDAP" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Введите новый пароль" + +#: 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.recovery-token-sent" +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 "Это бесплатно, это Open Source" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Создать аккаунт" + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "Open Source решение для дизайна и прототипирования." + +#: 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 "Сменить email адрес" + +msgid "dashboard.draft-title" +msgstr "Черновик" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Файлов пока нет" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ Новый файл" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ Новый проект" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "Совпадений для “%s“ не найдено" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "Ваш email адрес успешно обновлен" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "Ваш email адрес успешно подтвержден" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "Пароль успешно сохранен!" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "Изменить пароль" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +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/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/profile.cljs +msgid "dashboard.your-email" +msgstr "Email" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-name" +msgstr "Ваше имя" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +msgstr "Отмена" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "Ok" + +#: 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 "Такой email уже используется" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "Электронная почта уже подтверждена." + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +msgstr "Email для подтверждения должен совпадать" + +#: 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 "Что-то пошло не так." + +msgid "errors.media-format-unsupported" +msgstr "Формат изображения не поддерживается (должен быть svg, jpg или png)." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "Изображение слишком большое для вставки (должно быть меньше 5mb)." + +#: 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 "" + +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/register.cljs +msgid "errors.registration-disabled" +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/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.title" +msgstr "Email" + +#: src/app/main/ui/settings/password.cljs +msgid "generic.error" +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 "history.alert-message" +msgstr "Ваша версия %s" + +msgid "labels.accept" +msgstr "Принять" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.cancel" +msgstr "Отмена" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.confirm-password" +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/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/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.email" +msgstr "Email" + +#: 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.icons" +msgstr "Иконки" + +msgid "labels.images" +msgstr "Изображения" + +#: src/app/main/ui/settings/options.cljs +msgid "labels.language" +msgstr "Язык" + +#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.logout" +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/settings/password.cljs +msgid "labels.old-password" +msgstr "Старый пароль" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.password" +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 "" + +msgid "labels.save" +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 "Параметры" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.shared-libraries" +msgstr "" + +#: src/app/main/ui/static.cljs +msgid "labels.sign-out" +msgstr "Выход" + +#: src/app/main/ui/settings/profile.cljs +msgid "labels.update" +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 "" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.confirm-email" +msgstr "Подтвердить новый email адрес" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.info" +msgstr "Мы отправим письмо для подтверждения подлиности на текущий email адрес “%s”." + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.new-email" +msgstr "Новый email адрес" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.submit" +msgstr "Сменить email адрес" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.title" +msgstr "Сменить email адрес" + +#: 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/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 "" + +#: 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/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/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/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 "" + +#: 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-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/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 "Кадры не найдены." + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "Не показывать взаимодействия" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.edit-page" +msgstr "Редактировать страницу" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +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, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.title" +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/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.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.search" +msgstr "" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +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-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-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.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 "" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.components" +msgstr "" + +#: 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 "" + +#: 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/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 "Сохраненные библиотеки" + +#: src/app/main/ui/workspace/sidebar/options/page.cljs +msgid "workspace.options.canvas-background" +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, 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/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.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/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.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/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/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 "Используй кнопку запуск в заголовке чтобы перейти на экран прототипа." + +#: 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 "" + +#: 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.curve" +msgstr "Кривая (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.ellipse" +msgstr "" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "Рабочая область (A)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "Изображение (K)" + +#: 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 "Линия (P)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "Прямоугольник (R)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "Текст (T)" + +#: 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/tr.po b/frontend/translations/tr.po new file mode 100644 index 000000000..97dfb17d1 --- /dev/null +++ b/frontend/translations/tr.po @@ -0,0 +1,460 @@ +msgid "" +msgstr "" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Zaten hesabın var mı?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "" +"Penpot hesabını onaylamak ve kullanmaya başlamak için e-postanı kontrol et " +"ve gönderilen bağlantıya tıkla." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "Parolayı onayla" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Demo hesabı oluştur" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "Sadece denemek mi istiyorsun?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"Bu bir DEMO servis, gerçek işleriniz için KULLANMAYIN, projeler periyodik " +"olarak silinecektir." + +#: 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 "E-posta" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "Parolanı mı unuttun?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "Tam Adın" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.go-back-to-login" +msgstr "Geri dön!" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Buradan giriş yap" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Giriş yap" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-subtitle" +msgstr "Bilgilerini aşağıdaki alana gir" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Seni tekrar görmek süper!" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Github ile giriş yap" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "Gitlab ile giriş yap" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "LDAP ile giriş yap" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Yeni bir parola gir" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "Kurtarma bağlantısı 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" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "Profil onaylanmamış, devam etmeden önce profili onaylayın." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Parola kurtarma bağlantısı e-posta kutuna gönderildi." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Takıma başarıyla katıldın" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Parola" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "En az 8 karakter" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Parolayı kurtar" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "Detayları sana e-posta ile göndereceğiz" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Parolanı mı unuttun?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Parolanı değiştir" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Henüz hesabın yok mu?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Bir hesap oluştur" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "Ücretsiz ve Açık Kaynak" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Bir hesap oluştur" + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "Tasarım ve prototipleme için açık-kaynak çözüm." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Bir hesap oluştururken, koşullarımızı ve gizlilik politikamızı kabul etmiş " +"sayılırsınız." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.verification-email-sent" +msgstr "Onay e-postanı şu adrese gönderdik" + +#: 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" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "E-posta adresini değiştir" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(kopya)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "Yeni takım oluştur" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Penpot'un" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Takımı sil" + +msgid "dashboard.draft-title" +msgstr "Taslak" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Kopyasını oluştur" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "%s dosyanın kopyasını oluştur" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Burada hiç dosyan yok" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "Takıma davet et" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Takımdan ayrıl" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "Paylaşılan Kitaplıklar" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "dosyalarınız yükleniyor …" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Şuraya taşı" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +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şı" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "Yeni Dosya" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "Yeni Proje" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "%s için hiç sonuç bulunamadı" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "Sabitlenmiş projeler burada görünür" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "E-posta adresiniz başarıyla güncellendi" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "E-posta adresin başarıyla doğrulandı" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "Parola kaydedildi" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "%s üye" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "Dosyayı yeni sekmede aç" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "Parola değiştir" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Sabitle/Sabitleme" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "Projeler" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.promote-to-owner" +msgstr "Sahibi olarak belirle" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.remove-account" +msgstr "Hesabını silmek istediğinden emin misin?" + +#: 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" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +msgstr "Ara…" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.searching-for" +msgstr "%s aranıyor" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-language" +msgstr "Arayüz dilini seç" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-theme" +msgstr "Tema seç" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.show-all-files" +msgstr "Tüm dosyaları göster" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-delete-file" +msgstr "Dosyan başarıyla silindi" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "Projen başarıyla silindi" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "Dosyanın kopyası başarıyla oluşturuldu" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-duplicate-project" +msgstr "Projenin kopyası başarıyla oluşturuldu" + +#: 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 "Dosyan başarıyla taşındı" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +msgstr "Dosyaların başarıyla taşındı" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "Projen başarıyla taşındı" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "Takım değiştir" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-info" +msgstr "Takım bilgisi" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-members" +msgstr "Takım üyeleri" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-projects" +msgstr "Takım projeleri" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.theme-change" +msgstr "Önyüz teması" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.title-search" +msgstr "Arama sonuçları" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.type-something" +msgstr "Aramak için yazın" + +#: 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 "Ayarları güncelle" + +#: src/app/main/ui/settings.cljs +msgid "dashboard.your-account-title" +msgstr "Hesabın" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "E-posta" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-name" +msgstr "Adın" + +#: 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'un" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +msgstr "Vazgeç" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "Tamam" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "Emin misin?" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "ds.updated-at" +msgstr "Güncellendi: %s" + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "Tarayıcın bu işlemi gerçekleştiremiyor" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +msgid "errors.email-already-exists" +msgstr "E-posta zaten kullanımda" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "E-posta zaten doğrulandı" + +#: 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» adresi için çok fazla geri dönme raporu var." + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +msgstr "Doğrulama e-postası eşleşmiyor" + +#: 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 "Bir şeyler ters gitti." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.google-auth-not-enabled" +msgstr "Google ile oturum açma devre dışı bırakıldı" + +#: src/app/main/ui/auth/login.cljs +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ı)." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +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." + +#: 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 "Geçerli bir görsel gibi görünmüyor." + +#: src/app/main/ui/dashboard/team.cljs +msgid "errors.member-is-muted" +msgstr "" +"Davet ettiğiniz profilin e-posta adresine ait çok fazla geri dönme raporu " +"var veya spam olarak bildirilmiş." + +msgid "errors.network" +msgstr "Sunucuya bağlanılamıyor" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-invalid-confirmation" +msgstr "Parolalar eşleşmedi" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "Parola en az 8 karakterden oluşmalı" \ No newline at end of file diff --git a/frontend/translations/zh_cn.po b/frontend/translations/zh_cn.po new file mode 100644 index 000000000..9ade5b982 --- /dev/null +++ b/frontend/translations/zh_cn.po @@ -0,0 +1,2223 @@ +msgid "" +msgstr "" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\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/recovery_request.cljs +msgid "auth.go-back-to-login" +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/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "使用Github登录" + +#: src/app/main/ui/auth/register.cljs, 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-ldap-submit" +msgstr "使用LDAP登录" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "输入新的密码" + +#: 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.verification-email-sent" +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/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/grid.cljs +msgid "dashboard.empty-files" +msgstr "暂无文档" + +#: 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 "正在加载文档…" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ 新文档" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +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/settings/password.cljs +msgid "dashboard.password-change" +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/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 "OK" + +#: 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/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 "图片尺寸过大,故无法插入(不能超过5MB)。" + +#: 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 "当前禁止注册。" + +#: 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 "未知的TOKEN。" + +#: 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 "HEX" + +#: 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 "B" + +#: 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 "S" + +#: 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 "接受" + +#: 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 "中心" + +#: 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 "内容" + +#: 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/settings/sidebar.cljs +msgid "labels.dashboard" +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/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 "编辑" + +#: 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 "电子邮件" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-disabled" +msgstr "反馈被禁止" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-sent" +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 "提交反馈" + +#: 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 "图片" + +#: 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 "语言" + +#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.logout" +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] "1 个文档" +msgstr[1] "共 %s 个文档" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-projects" +msgid_plural "labels.num-of-projects" +msgstr[0] "1 个项目" +msgstr[1] "共 %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 "仅你的" + +#: 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 "保存" + +#: 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 "设置" + +#: 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 "登出" + +#: src/app/main/ui/settings/profile.cljs +msgid "labels.update" +msgstr "更新" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "更新团队" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +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/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.title" +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/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。请检查电子邮箱。" + +#: 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/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/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/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 "画框未找到。" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "不显示交互" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.edit-page" +msgstr "编辑页面" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +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, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.title" +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/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.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.search" +msgstr "搜索素材" + +#: 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 "Ag" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.text-transform" +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-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-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/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.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.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/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.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/typography.cljs +msgid "workspace.options.text-options.google" +msgstr "谷歌" + +#: 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 "点击页面顶端的播放按钮预览原型。" + +#: 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/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 "椭圆(E)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "画板(A)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "图片(K)" + +#: 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 "路径(P)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "矩形(R)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "文本(T)" + +#: 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/yarn.lock b/frontend/yarn.lock index 8b338f6d4..119d530e1 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -976,6 +976,11 @@ constants-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= +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== + convert-source-map@^1.0.0, convert-source-map@^1.5.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" @@ -1426,6 +1431,13 @@ enabled@2.0.x: resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== +encoding@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -1956,6 +1968,16 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +gettext-parser@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gettext-parser/-/gettext-parser-4.0.4.tgz#bd5eb4af282336c8bf83f607d35f0839853b9670" + integrity sha512-VDZEeOIYd0veZXt5iAn0SS3I0Fz14fJw+59avRNa7VIslEDriRLxcfrBd/xeQyOcm6nyS4uuufxm2iw88qirAg== + dependencies: + content-type "^1.0.4" + encoding "^0.1.13" + readable-stream "^3.6.0" + safe-buffer "^5.2.1" + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -2351,6 +2373,13 @@ humanize-duration@~3.25.0: resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.25.1.tgz#50e12bf4b3f515ec91106107ee981e8cfe955d6f" integrity sha512-P+dRo48gpLgc2R9tMRgiDRNULPKCmqFYgguwqOO2C0fjO35TgdURDQDANSR1Nt92iHlbHGMxOTnsB8H8xnMa2Q== +iconv-lite@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" + integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -4386,7 +4415,7 @@ rxjs@~7.0.0-beta.12: dependencies: tslib "~2.1.0" -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: +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.1, 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== @@ -4403,7 +4432,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== From 6e80a2f9fbe74621b91c48ff580b5225fc633ef4 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 12 Apr 2021 16:57:23 +0200 Subject: [PATCH 036/155] :paperclip: Update changelog. --- CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 8ca74a0f1..c150c2c0c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ### :sparkles: New features - Add integration with gitpod.io (an online IDE) [#807](https://github.com/penpot/penpot/pull/807) +- Internal: refactor of http client, replace internal xhr usage with more modern Fetch API. ### :bug: Bugs fixed @@ -14,6 +15,12 @@ ### :arrow_up: Deps updates +### :boom: Breaking changes + +- Translations refactor: now penpot uses gettext instead of a custom + JSON, and each locale has its own separated file. All translations + should be contributed via the weblate.org service. + ### :heart: Community contributions by (Thank you!) - madmath03 (by [Monogramm](https://github.com/Monogramm)) [#807](https://github.com/penpot/penpot/pull/807) From 03a031091fc11df621e4079c4e2756f178cdf348 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Apr 2021 16:05:28 +0200 Subject: [PATCH 037/155] :tada: Allow copy&paste from inkscape. --- frontend/src/app/main/data/workspace.cljs | 24 ++- .../src/app/main/data/workspace/common.cljs | 3 +- .../app/main/data/workspace/persistence.cljs | 193 +++++++++--------- .../app/main/data/workspace/svg_upload.cljs | 58 +++--- .../app/main/ui/workspace/left_toolbar.cljs | 18 +- .../app/main/ui/workspace/sidebar/assets.cljs | 8 +- .../main/ui/workspace/viewport/actions.cljs | 19 +- 7 files changed, 169 insertions(+), 154 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 0ec4e3a66..b5ce81ae2 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -31,6 +31,7 @@ [app.main.data.workspace.persistence :as dwp] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.transforms :as dwt] + [app.main.data.workspace.svg-upload :as svg] [app.main.repo :as rp] [app.main.store :as st] [app.main.streams :as ms] @@ -1372,6 +1373,7 @@ (declare paste-shape) (declare paste-text) (declare paste-image) +(declare paste-svg) (def paste (ptk/reify ::paste @@ -1428,6 +1430,10 @@ is-editing-text? (and edit-id (= :text (get-in objects [edit-id :type])))] (cond + (and (string? text-data) + (str/includes? text-data "> (dwp/parse-svg ["svg" text]) + (rx/map #(svg/svg-uploaded % file-id position))))))) (defn- paste-image [image] @@ -1635,8 +1652,9 @@ (watch [_ state stream] (let [file-id (get-in state [:workspace-file :id]) params {:file-id file-id - :data [image]}] - (rx/of (dwp/upload-media-workspace params @ms/mouse-position)))))) + :blobs [image] + :position @ms/mouse-position}] + (rx/of (dwp/upload-media-workspace params)))))) (defn toggle-distances-display [value] (ptk/reify ::toggle-distances-display diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index a9a669bbc..afec9744c 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -800,7 +800,8 @@ (gsh/setup-selrect))] (rx/of (add-shape shape)))))) -(defn image-uploaded [image x y] +(defn image-uploaded + [image {:keys [x y]}] (ptk/reify ::image-uploaded ptk/WatchEvent (watch [_ state stream] diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 67e4bf8e9..37b0cfb2a 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -376,21 +376,6 @@ ;; --- Upload File Media objects -(s/def ::local? ::us/boolean) -(s/def ::data ::di/blobs) -(s/def ::name ::us/string) -(s/def ::uri ::us/string) -(s/def ::uris (s/coll-of ::uri)) -(s/def ::mtype ::us/string) - -(s/def ::upload-media-objects - (s/and - (s/keys :req-un [::file-id ::local?] - :opt-in [::name ::data ::uris ::mtype]) - (fn [props] - (or (contains? props :data) - (contains? props :uris))))) - (defn parse-svg [[name text]] (->> (rp/query! :parse-svg {:data text}) @@ -402,46 +387,48 @@ (or name (uu/uri-name uri)) (:body %))))) -(defn- handle-upload-error [on-error stream] - (->> stream - (rx/catch - (fn on-error* [error] - (if (ex/ex-info? error) - (on-error* (ex-data error)) - (cond - (= (:code error) :invalid-svg-file) - (rx/of (dm/error (tr "errors.media-type-not-allowed"))) +(defn- handle-upload-error + "Generic error handler for all upload methods." + [on-error stream] + (letfn [(on-error* [error] + (if (ex/ex-info? error) + (on-error* (ex-data error)) + (cond + (= (:code error) :invalid-svg-file) + (rx/of (dm/error (tr "errors.media-type-not-allowed"))) - (= (:code error) :media-type-not-allowed) - (rx/of (dm/error (tr "errors.media-type-not-allowed"))) + (= (:code error) :media-type-not-allowed) + (rx/of (dm/error (tr "errors.media-type-not-allowed"))) - (= (:code error) :ubable-to-access-to-url) - (rx/of (dm/error (tr "errors.media-type-not-allowed"))) + (= (:code error) :ubable-to-access-to-url) + (rx/of (dm/error (tr "errors.media-type-not-allowed"))) - (= (:code error) :invalid-image) - (rx/of (dm/error (tr "errors.media-type-not-allowed"))) + (= (:code error) :invalid-image) + (rx/of (dm/error (tr "errors.media-type-not-allowed"))) - (= (:code error) :media-too-large) - (rx/of (dm/error (tr "errors.media-too-large"))) + (= (:code error) :media-too-large) + (rx/of (dm/error (tr "errors.media-too-large"))) - (= (:code error) :media-type-mismatch) - (rx/of (dm/error (tr "errors.media-type-mismatch"))) + (= (:code error) :media-type-mismatch) + (rx/of (dm/error (tr "errors.media-type-mismatch"))) - (= (:code error) :unable-to-optimize) - (rx/of (dm/error (:hint error))) + (= (:code error) :unable-to-optimize) + (rx/of (dm/error (:hint error))) - (fn? on-error) - (on-error error) + (fn? on-error) + (on-error error) - :else - (rx/throw error))))))) + :else + (rx/throw error))))] + (rx/catch on-error* stream))) -(defn- upload-uris [file-id local? name uris mtype on-image on-svg] +(defn- process-uris + [{:keys [file-id local? name uris mtype on-image on-svg]}] (letfn [(svg-url? [url] (or (and mtype (= mtype "image/svg+xml")) (str/ends-with? url ".svg"))) - (prepare-uri [uri] + (prepare [uri] {:file-id file-id :is-local local? :name (or name (uu/uri-name uri)) @@ -449,7 +436,7 @@ (rx/merge (->> (rx/from uris) (rx/filter (comp not svg-url?)) - (rx/map prepare-uri) + (rx/map prepare) (rx/mapcat #(rp/mutation! :create-file-media-object-from-url %)) (rx/do on-image)) @@ -459,81 +446,91 @@ (rx/merge-map parse-svg) (rx/do on-svg))))) -(defn- upload-data [file-id local? name data force-media on-image on-svg] - (let [svg-blob? (fn [blob] - (and (not force-media) - (= (.-type blob) "image/svg+xml"))) - prepare-file - (fn [blob] - (let [name (or name (if (di/file? blob) (.-name blob) "blob"))] - {:file-id file-id - :name name - :is-local local? - :content blob})) +(defn- process-blobs + [{:keys [file-id local? name blobs force-media on-image on-svg]}] + (letfn [(svg-blob? [blob] + (and (not force-media) + (= (.-type blob) "image/svg+xml"))) - extract-content - (fn [blob] - (let [name (or name (.-name blob))] - (-> (.text blob) - (p/then #(vector name %))))) + (prepare-blob [blob] + (let [name (or name (if (di/file? blob) (.-name blob) "blob"))] + {:file-id file-id + :name name + :is-local local? + :content blob})) + + (extract-content [blob] + (let [name (or name (.-name blob))] + (-> (.text ^js blob) + (p/then #(vector name %)))))] - file-stream (->> (rx/from data) - (rx/map di/validate-file))] (rx/merge - (->> file-stream + (->> (rx/from blobs) + (rx/map di/validate-file) (rx/filter (comp not svg-blob?)) - (rx/map prepare-file) + (rx/map prepare-blob) (rx/mapcat #(rp/mutation! :upload-file-media-object %)) (rx/do on-image)) - (->> file-stream + (->> (rx/from blobs) + (rx/map di/validate-file) (rx/filter svg-blob?) (rx/merge-map extract-content) (rx/merge-map parse-svg) (rx/do on-svg))))) -(defn- upload-media-objects - [{:keys [file-id local? data name uris mtype svg-as-images] :as params}] - (us/assert ::upload-media-objects params) - (ptk/reify ::upload-media-objects +(s/def ::local? ::us/boolean) +(s/def ::blobs ::di/blobs) +(s/def ::name ::us/string) +(s/def ::uris (s/coll-of ::us/string)) +(s/def ::mtype ::us/string) + +(s/def ::process-media-objects + (s/and + (s/keys :req-un [::file-id ::local?] + :opt-in [::name ::data ::uris ::mtype]) + (fn [props] + (or (contains? props :blobs) + (contains? props :uris))))) + +(defn- process-media-objects + [{:keys [uris on-error] :as params}] + (us/assert ::process-media-objects params) + (ptk/reify ::process-media-objects ptk/WatchEvent (watch [_ state stream] - (let [{:keys [on-image on-svg on-error] - :or {on-image identity - on-svg identity}} (meta params)] - (rx/concat - (rx/of (dm/show {:content (tr "media.loading") - :type :info - :timeout nil - :tag :media-loading})) - (->> (if (seq uris) - ;; Media objects is a list of URL's pointing to the path - (upload-uris file-id local? name uris mtype on-image on-svg) - ;; Media objects are blob of data to be upload - (upload-data file-id local? name data svg-as-images on-image on-svg)) - ;; Every stream has its own sideffect. We need to ignore the result - (rx/ignore) - (handle-upload-error on-error) - (rx/finalize (st/emitf (dm/hide-tag :media-loading))))))))) + (rx/concat + (rx/of (dm/show {:content (tr "media.loading") + :type :info + :timeout nil + :tag :media-loading})) + (->> (if (seq uris) + ;; Media objects is a list of URL's pointing to the path + (process-uris params) + ;; Media objects are blob of data to be upload + (process-blobs params)) + + ;; Every stream has its own sideffect. We need to ignore the result + (rx/ignore) + (handle-upload-error on-error) + (rx/finalize (st/emitf (dm/hide-tag :media-loading)))))))) (defn upload-media-asset [params] - (let [params (-> params - (assoc :svg-as-images true) - (assoc :local? false) - (with-meta {:on-image #(st/emit! (dwl/add-media %))}))] - (upload-media-objects params))) + (let [params (assoc params + :force-media true + :local? false + :on-image #(st/emit! (dwl/add-media %)))] + (process-media-objects params))) (defn upload-media-workspace - [params position] - (let [{:keys [x y]} position - mdata {:on-image #(st/emit! (dwc/image-uploaded % x y)) - :on-svg #(st/emit! (svg/svg-uploaded % (:file-id params) x y))} + [{:keys [position file-id] :as params}] + (let [params (assoc params + :local? true + :on-image #(st/emit! (dwc/image-uploaded % position)) + :on-svg #(st/emit! (svg/svg-uploaded % file-id position)))] - params (-> (assoc params :local? true) - (with-meta mdata))] - - (upload-media-objects params))) + (process-media-objects params))) ;; --- Upload File Media objects diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 1d076bf52..f2dca9d4e 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -211,7 +211,7 @@ (defn create-rect-shape [name frame-id svg-data {:keys [attrs] :as data}] (let [svg-transform (usvg/parse-transform (:transform attrs)) - transform (->> svg-transform + transform (->> svg-transform (gmt/transform-in (gpt/point svg-data))) rect (->> (select-keys attrs [:x :y :width :height]) @@ -239,7 +239,7 @@ (defn create-circle-shape [name frame-id svg-data {:keys [attrs] :as data}] (let [svg-transform (usvg/parse-transform (:transform attrs)) - transform (->> svg-transform + transform (->> svg-transform (gmt/transform-in (gpt/point svg-data))) circle (->> (select-keys attrs [:r :ry :rx :cx :cy]) @@ -273,7 +273,7 @@ (defn create-image-shape [name frame-id svg-data {:keys [attrs] :as data}] (let [svg-transform (usvg/parse-transform (:transform attrs)) - transform (->> svg-transform + transform (->> svg-transform (gmt/transform-in (gpt/point svg-data))) image-url (:xlink:href attrs) @@ -327,7 +327,7 @@ (update :attrs usvg/add-transform disp-matrix) (assoc :content [use-data]))] (parse-svg-element frame-id svg-data element-data unames)) - + ;; SVG graphic elements ;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use (let [shape (-> (case tag @@ -381,42 +381,42 @@ (declare create-svg-shapes) -(defn svg-uploaded [svg-data file-id x y] +(defn svg-uploaded + [svg-data file-id position] (ptk/reify ::svg-uploaded ptk/WatchEvent (watch [_ state stream] - (let [images-to-upload (-> svg-data (usvg/collect-images)) + ;; Once the SVG is uploaded, we need to extract all the bitmap + ;; images and upload them separatelly, then proceed to create + ;; all shapes. + (->> (rx/from (usvg/collect-images svg-data)) + (rx/map (fn [uri] + (d/merge + {:file-id file-id + :is-local true + :url uri} - prepare-uri - (fn [uri] - (merge - {:file-id file-id - :is-local true - :url uri} + (if (str/starts-with? uri "data:") + {:name "image" + :content (uu/data-uri->blob uri)} + {:name (uu/uri-name uri)})))) + (rx/mapcat (fn [uri-data] + (->> (rp/mutation! (if (contains? uri-data :content) + :upload-file-media-object + :create-file-media-object-from-url) uri-data) + (rx/map #(vector (:url uri-data) %))))) + (rx/reduce (fn [acc [url image]] (assoc acc url image)) {}) + (rx/map #(create-svg-shapes (assoc svg-data :image-data %) position)))))) - (if (str/starts-with? uri "data:") - {:name "image" - :content (uu/data-uri->blob uri)} - {:name (uu/uri-name uri)})))] - - (->> (rx/from images-to-upload) - (rx/map prepare-uri) - (rx/mapcat (fn [uri-data] - (->> (rp/mutation! (if (contains? uri-data :content) - :upload-file-media-object - :create-file-media-object-from-url) uri-data) - (rx/map #(vector (:url uri-data) %))))) - (rx/reduce (fn [acc [url image]] (assoc acc url image)) {}) - (rx/map #(create-svg-shapes (assoc svg-data :image-data %) x y))))))) - -(defn create-svg-shapes [svg-data x y] +(defn create-svg-shapes + [svg-data {:keys [x y] :as position}] (ptk/reify ::create-svg-shapes ptk/WatchEvent (watch [_ state stream] (try (let [page-id (:current-page-id state) objects (dwc/lookup-page-objects state page-id) - frame-id (cp/frame-id-by-position objects {:x x :y y}) + frame-id (cp/frame-id-by-position objects position) selected (get-in state [:workspace-local :selected]) [vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) diff --git a/frontend/src/app/main/ui/workspace/left_toolbar.cljs b/frontend/src/app/main/ui/workspace/left_toolbar.cljs index 9390b62ca..fd7053d5e 100644 --- a/frontend/src/app/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/left_toolbar.cljs @@ -33,14 +33,16 @@ (mf/use-callback (mf/deps file) (fn [blobs] - (let [params {:file-id (:id file) - :data (seq blobs)} - ;; We don't want to add a ref because that redraws the component - ;; for everychange. Better direct access on the callback - vbox (get-in @st/state [:workspace-local :vbox]) - x (mth/round (+ (:x vbox) (/ (:width vbox) 2))) - y (mth/round (+ (:y vbox) (/ (:height vbox) 2)))] - (st/emit! (dw/upload-media-workspace params (gpt/point x y))))))] + ;; We don't want to add a ref because that redraws the component + ;; for everychange. Better direct access on the callback + ;; vbox (get-in @st/state [:workspace-local :vbox]) + (let [vbox (:vbox @refs/workspace-local) + x (mth/round (+ (:x vbox) (/ (:width vbox) 2))) + y (mth/round (+ (:y vbox) (/ (:height vbox) 2))) + params {:file-id (:id file) + :blobs (seq blobs) + :position (gpt/point x y)}] + (st/emit! (dw/upload-media-workspace params)))))] [:li.tooltip.tooltip-right {:alt (tr "workspace.toolbar.image") diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 604004d55..6a2abb5b0 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -152,18 +152,12 @@ (st/emitf (dwl/set-assets-box-open file-id :graphics true)) (dom/click (mf/ref-val input-ref)))) - on-media-uploaded - (mf/use-callback - (mf/deps file-id) - (fn [data] - (st/emit! (dwl/add-media data)))) - on-selected (mf/use-callback (mf/deps file-id) (fn [blobs] (let [params {:file-id file-id - :data (seq blobs)}] + :blobs (seq blobs)}] (st/emit! (dw/upload-media-asset params))))) on-delete diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 2bcf54cac..1d94d938d 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -389,8 +389,8 @@ (defn on-image-uploaded [] (mf/use-callback - (fn [image {:keys [x y]}] - (st/emit! (dw/image-uploaded image x y))))) + (fn [image position] + (st/emit! (dw/image-uploaded image position))))) (defn on-drop [file viewport-ref zoom] (let [on-image-uploaded (on-image-uploaded)] @@ -427,21 +427,23 @@ (dnd/has-type? event "text/uri-list") (let [data (dnd/get-data event "text/uri-list") lines (str/lines data) - urls (filter #(and (not (str/blank? %)) + uris (filter #(and (not (str/blank? %)) (not (str/starts-with? % "#"))) lines) params {:file-id (:id file) - :uris urls}] - (st/emit! (dw/upload-media-workspace params viewport-coord))) + :position viewport-coord + :uris uris}] + (st/emit! (dw/upload-media-workspace params))) ;; Will trigger when the user drags an SVG asset from the assets panel (and (dnd/has-type? event "text/asset-id") (= asset-type "image/svg+xml")) (let [path (cfg/resolve-file-media {:id asset-id}) params {:file-id (:id file) + :position viewport-coord :uris [path] :name asset-name :mtype asset-type}] - (st/emit! (dw/upload-media-workspace params viewport-coord))) + (st/emit! (dw/upload-media-workspace params))) ;; Will trigger when the user drags an image from the assets SVG (dnd/has-type? event "text/asset-id") @@ -458,8 +460,9 @@ :else (let [files (dnd/get-files event) params {:file-id (:id file) - :data (seq files)}] - (st/emit! (dw/upload-media-workspace params viewport-coord))))))))) + :position viewport-coord + :blobs (seq files)}] + (st/emit! (dw/upload-media-workspace params))))))))) (defn on-paste [disable-paste in-viewport?] (mf/use-callback From 7cc4873dd4d61393e151dd6bb5c2a66fc91da1a4 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Apr 2021 16:29:38 +0200 Subject: [PATCH 038/155] :recycle: Move svg parsing into query rpc methods. --- backend/src/app/http.clj | 6 +- backend/src/app/main.clj | 10 --- backend/src/app/media.clj | 2 +- backend/src/app/rpc.clj | 10 ++- backend/src/app/rpc/queries/comments.clj | 1 - backend/src/app/rpc/queries/svg.clj | 48 +++++++++++++ backend/src/app/svgparse.clj | 70 ------------------- .../app/main/data/workspace/persistence.cljs | 2 +- frontend/src/app/main/repo.cljs | 10 ++- 9 files changed, 65 insertions(+), 94 deletions(-) create mode 100644 backend/src/app/rpc/queries/svg.clj delete mode 100644 backend/src/app/svgparse.clj diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 63263c4f0..3e3504cda 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -119,7 +119,7 @@ (s/keys :req-un [::rpc ::session ::mtx/metrics ::oauth ::storage ::assets ::feedback])) (defmethod ig/init-key ::router - [_ {:keys [session rpc oauth metrics svgparse assets feedback] :as cfg}] + [_ {:keys [session rpc oauth metrics assets feedback] :as cfg}] (rr/router [["/metrics" {:get (:handler metrics)}] ["/assets" {:middleware [[middleware/format-response-body] @@ -146,7 +146,6 @@ [middleware/errors errors/handle] [middleware/cookies]]} - ["/svg/parse" {:post svgparse}] ["/feedback" {:middleware [(:middleware session)] :post feedback}] @@ -162,5 +161,6 @@ ["/rpc" {:middleware [(:middleware session) middleware/activity-logger]} - ["/query/:type" {:get (:query-handler rpc)}] + ["/query/:type" {:get (:query-handler rpc) + :post (:query-handler rpc)}] ["/mutation/:type" {:post (:mutation-handler rpc)}]]]])) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index f39d85083..fcf889b94 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -94,7 +94,6 @@ :metrics (ig/ref :app.metrics/metrics) :oauth (ig/ref :app.http.oauth/all) :assets (ig/ref :app.http.assets/handlers) - :svgparse (ig/ref :app.svgparse/handler) :storage (ig/ref :app.storage/storage) :sns-webhook (ig/ref :app.http.awsns/handler) :feedback (ig/ref :app.http.feedback/handler) @@ -140,14 +139,6 @@ :client-id (cf/get :gitlab-client-id) :client-secret (cf/get :gitlab-client-secret)} - :app.svgparse/svgc - {:metrics (ig/ref :app.metrics/metrics)} - - ;; HTTP Handler for SVG parsing - :app.svgparse/handler - {:metrics (ig/ref :app.metrics/metrics) - :svgc (ig/ref :app.svgparse/svgc)} - ;; RLimit definition for password hashing :app.rlimits/password (cf/get :rlimits-password) @@ -169,7 +160,6 @@ :storage (ig/ref :app.storage/storage) :msgbus (ig/ref :app.msgbus/msgbus) :rlimits (ig/ref :app.rlimits/all) - :svgc (ig/ref :app.svgparse/svgc) :public-uri (cf/get :public-uri)} :app.notifications/handler diff --git a/backend/src/app/media.clj b/backend/src/app/media.clj index c811aed66..6d035a9c8 100644 --- a/backend/src/app/media.clj +++ b/backend/src/app/media.clj @@ -12,7 +12,7 @@ [app.common.media :as cm] [app.common.spec :as us] [app.rlimits :as rlm] - [app.svgparse :as svg] + [app.rpc.queries.svg :as svg] [clojure.spec.alpha :as s] [cuerdas.core :as str] [datoteka.core :as fs]) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 2a29749b8..a0c1a4bda 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -30,10 +30,15 @@ (defn- rpc-query-handler [methods {:keys [profile-id] :as request}] (let [type (keyword (get-in request [:path-params :type])) - data (assoc (:params request) ::type type) + + data (d/merge (:params request) + (:body-params request) + (:uploads request)) + data (if profile-id (assoc data :profile-id profile-id) (dissoc data :profile-id)) + result ((get methods type default-handler) data) mdata (meta result)] @@ -114,7 +119,8 @@ 'app.rpc.queries.comments 'app.rpc.queries.profile 'app.rpc.queries.recent-files - 'app.rpc.queries.viewer) + 'app.rpc.queries.viewer + 'app.rpc.queries.svg) (map (partial process-method cfg)) (into {})))) diff --git a/backend/src/app/rpc/queries/comments.clj b/backend/src/app/rpc/queries/comments.clj index 2a5191043..1894022f9 100644 --- a/backend/src/app/rpc/queries/comments.clj +++ b/backend/src/app/rpc/queries/comments.clj @@ -128,7 +128,6 @@ (-> (db/exec-one! conn [sql profile-id file-id id]) (decode-row))))) - ;; --- Query: Comments (declare retrieve-comments) diff --git a/backend/src/app/rpc/queries/svg.clj b/backend/src/app/rpc/queries/svg.clj new file mode 100644 index 000000000..fdcac5438 --- /dev/null +++ b/backend/src/app/rpc/queries/svg.clj @@ -0,0 +1,48 @@ +;; 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.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] + [clojure.xml :as xml] + [integrant.core :as ig]) + (:import + javax.xml.XMLConstants + javax.xml.parsers.SAXParserFactory + org.apache.commons.io.IOUtils)) + +(defn- secure-parser-factory + [s ch] + (.. (doto (SAXParserFactory/newInstance) + (.setFeature javax.xml.XMLConstants/FEATURE_SECURE_PROCESSING true) + (.setFeature "http://apache.org/xml/features/disallow-doctype-decl" true)) + (newSAXParser) + (parse s ch))) + +(defn parse + [data] + (try + (with-open [istream (IOUtils/toInputStream data "UTF-8")] + (xml/parse istream secure-parser-factory)) + (catch Exception e + (l/warn :hint "error on processing svg" + :message (ex-message e)) + (ex/raise :type :validation + :code :invalid-svg-file + :cause e)))) + +(s/def ::data ::us/string) +(s/def ::parsed-svg (s/keys :req-un [::data])) + +(sv/defmethod ::parsed-svg + [_ {:keys [data] :as params}] + (parse data)) + + diff --git a/backend/src/app/svgparse.clj b/backend/src/app/svgparse.clj deleted file mode 100644 index ad7847f59..000000000 --- a/backend/src/app/svgparse.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 - -(ns app.svgparse - (:require - [app.common.exceptions :as ex] - [app.metrics :as mtx] - [app.util.logging :as l] - [clojure.spec.alpha :as s] - [clojure.xml :as xml] - [integrant.core :as ig]) - (:import - org.apache.commons.io.IOUtils)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Handler -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(declare handler) -(declare process-request) - -(defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [::mtx/metrics])) - -(defmethod ig/init-key ::handler - [_ {:keys [metrics] :as cfg}] - (let [handler #(handler cfg %)] - (->> {:registry (:registry metrics) - :type :summary - :name "http_handler_svgparse_timing" - :help "svg parse timings"} - (mtx/instrument handler)))) - -(defn- handler - [_ {:keys [headers body] :as request}] - (when (not= "image/svg+xml" (get headers "content-type")) - (ex/raise :type :validation - :code :unsupported-mime-type - :mime (get headers "content-type"))) - {:status 200 - :body (process-request body)}) - -(defn secure-factory - [s ch] - (.. (doto (javax.xml.parsers.SAXParserFactory/newInstance) - (.setFeature javax.xml.XMLConstants/FEATURE_SECURE_PROCESSING true) - (.setFeature "http://apache.org/xml/features/disallow-doctype-decl" true)) - (newSAXParser) - (parse s ch))) - -(defn parse - [data] - (try - (with-open [istream (IOUtils/toInputStream data "UTF-8")] - (xml/parse istream secure-factory)) - (catch Exception e - (l/warn :hint "error on processing svg" - :message (ex-message e)) - (ex/raise :type :validation - :code :invalid-svg-file - :cause e)))) - -(defn process-request - [body] - (let [data (slurp body)] - (parse data))) - diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 37b0cfb2a..5d4add00b 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -378,7 +378,7 @@ (defn parse-svg [[name text]] - (->> (rp/query! :parse-svg {:data text}) + (->> (rp/query! :parsed-svg {:data text}) (rx/map #(assoc % :name name)))) (defn fetch-svg [name uri] diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 51abce3cc..b4ec717bd 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -121,13 +121,11 @@ :response-type :blob}) (rx/mapcat handle-response))) -(defmethod query :parse-svg - [id {:keys [data] :as params}] +(defmethod query :parsed-svg + [id params] (->> (http/send! {:method :post - :uri (u/join base-uri "api/svg/parse") - :headers {"content-type" "image/svg+xml"} - :body data - :response-type :text}) + :uri (u/join base-uri "api/rpc/query/" (name id)) + :body (http/transit-data params)}) (rx/map http/conditional-decode-transit) (rx/mapcat handle-response))) From 9f5c19244d0ff81951a7b89541a489e0d8da8776 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Apr 2021 17:09:59 +0200 Subject: [PATCH 039/155] :tada: Add preprocessing to svg parse method. --- backend/src/app/rpc/queries/svg.clj | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/backend/src/app/rpc/queries/svg.clj b/backend/src/app/rpc/queries/svg.clj index fdcac5438..cd244aafd 100644 --- a/backend/src/app/rpc/queries/svg.clj +++ b/backend/src/app/rpc/queries/svg.clj @@ -12,7 +12,7 @@ [app.util.services :as sv] [clojure.spec.alpha :as s] [clojure.xml :as xml] - [integrant.core :as ig]) + [cuerdas.core :as str]) (:import javax.xml.XMLConstants javax.xml.parsers.SAXParserFactory @@ -21,7 +21,7 @@ (defn- secure-parser-factory [s ch] (.. (doto (SAXParserFactory/newInstance) - (.setFeature javax.xml.XMLConstants/FEATURE_SECURE_PROCESSING true) + (.setFeature XMLConstants/FEATURE_SECURE_PROCESSING true) (.setFeature "http://apache.org/xml/features/disallow-doctype-decl" true)) (newSAXParser) (parse s ch))) @@ -38,11 +38,21 @@ :code :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}] - (parse data)) + (->> data pre-process parse)) +;; --- PROCESSORS +(defn strip-doctype + [data] + (cond-> data + (str/includes? data "]+>" ""))) + +(def pre-process strip-doctype) From 3613e6f3d35c3d9f631f2a0e9a7ace4b2f8872b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 6 Apr 2021 09:22:30 +0200 Subject: [PATCH 040/155] :tada: Group components and graphics in assets sidebar --- CHANGES.md | 1 + common/app/common/pages.cljc | 3 + common/app/common/pages/changes.cljc | 16 +- common/app/common/pages/helpers.cljc | 45 +- common/app/common/pages/spec.cljc | 4 +- .../styles/main/partials/color-bullet.scss | 2 +- .../styles/main/partials/sidebar-assets.scss | 73 ++- .../partials/sidebar-element-options.scss | 2 +- .../app/main/data/workspace/libraries.cljs | 18 +- .../main/ui/components/editable_label.cljs | 7 +- .../src/app/main/ui/components/forms.cljs | 2 + .../app/main/ui/workspace/sidebar/assets.cljs | 415 +++++++++++++++--- frontend/translations/en.po | 22 +- frontend/translations/es.po | 22 +- 14 files changed, 525 insertions(+), 107 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e8effdbd0..7efcc5474 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ### :sparkles: New features - Add integration with gitpod.io (an online IDE) [#807](https://github.com/penpot/penpot/pull/807) +- Allow to group assets (components and graphics) [Taiga #1289](https://tree.taiga.io/project/penpot/us/1289) - Internal: refactor of http client, replace internal xhr usage with more modern Fetch API. diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 185a4fcd0..2b34a602a 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -62,6 +62,9 @@ (d/export helpers/get-index-in-parent) (d/export helpers/calculate-z-index) (d/export helpers/generate-child-all-parents-index) +(d/export helpers/parse-path-name) +(d/export helpers/merge-path-item) +(d/export helpers/compact-path) ;; Process changes (d/export changes/process-changes) diff --git a/common/app/common/pages/changes.cljc b/common/app/common/pages/changes.cljc index 458b02eaa..713ab73c9 100644 --- a/common/app/common/pages/changes.cljc +++ b/common/app/common/pages/changes.cljc @@ -364,21 +364,25 @@ ;; -- Components (defmethod process-change :add-component - [data {:keys [id name shapes]}] + [data {:keys [id name path shapes]}] (assoc-in data [:components id] {:id id :name name + :path path :objects (d/index-by :id shapes)})) (defmethod process-change :mod-component - [data {:keys [id name objects]}] + [data {:keys [id name path objects]}] (update-in data [:components id] #(cond-> % - (some? name) - (assoc :name name) + (some? name) + (assoc :name name) - (some? objects) - (assoc :objects objects)))) + (some? path) + (assoc :path path) + + (some? objects) + (assoc :objects objects)))) (defmethod process-change :del-component [data {:keys [id]}] diff --git a/common/app/common/pages/helpers.cljc b/common/app/common/pages/helpers.cljc index abaae925e..79030e8df 100644 --- a/common/app/common/pages/helpers.cljc +++ b/common/app/common/pages/helpers.cljc @@ -9,7 +9,8 @@ [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.spec :as us] - [app.common.uuid :as uuid])) + [app.common.uuid :as uuid] + [cuerdas.core :as str])) (defn walk-pages "Go through all pages of a file and apply a function to each one" @@ -332,7 +333,6 @@ (d/concat new-children new-child-objects) (d/concat updated-children updated-child-objects)))))))) - (defn indexed-shapes "Retrieves a list with the indexes for each element in the layer tree. This will be used for shift+selection." @@ -459,3 +459,44 @@ [parent-idx _] (d/seek (fn [[idx child-id]] (= child-id shape-id)) (d/enumerate (:shapes parent)))] parent-idx)) + +(defn parse-path-name + "Parse a string in the form 'group / subgroup / name'. + Retrieve the path and the name in separated values, normalizing spaces." + [path-name] + (let [path-name-split (->> (str/split path-name "/") + (map str/trim) + (remove str/empty?)) + path (str/join " / " (butlast path-name-split)) + name (last path-name-split)] + [path name])) + +(defn merge-path-item + "Put the item at the end of the path." + [path name] + (if-not (empty? path) + (str path " / " name) + name)) + +(defn compact-path + "Separate last component of the path, and truncate the others if too long: + 'one' -> ['' 'one' false] + 'one / two / three' -> ['one / two' 'three' false] + 'one / two / three / four' -> ['one / two / ...' 'four' true] + 'one-item-but-very-long / two' -> ['...' 'two' true] " + [path max-length] + (let [path-split (->> (str/split path "/") + (map str/trim)) + last-item (last path-split)] + (loop [other-items (seq (butlast path-split)) + other-path ""] + (if-let [item (first other-items)] + (let [full-path (-> other-path + (merge-path-item item) + (merge-path-item last-item))] + (if (> (count full-path) max-length) + [(merge-path-item other-path "...") last-item true] + (recur (next other-items) + (merge-path-item other-path item)))) + [other-path last-item false])))) + diff --git a/common/app/common/pages/spec.cljc b/common/app/common/pages/spec.cljc index 274d6f32e..6f7be0765 100644 --- a/common/app/common/pages/spec.cljc +++ b/common/app/common/pages/spec.cljc @@ -16,6 +16,7 @@ (s/def ::frame-id uuid?) (s/def ::id uuid?) (s/def ::name string?) +(s/def ::path string?) (s/def ::page-id uuid?) (s/def ::parent-id uuid?) (s/def ::string string?) @@ -547,7 +548,8 @@ (s/coll-of ::shape)) (defmethod change-spec :add-component [_] - (s/keys :req-un [::id ::name :internal.changes.add-component/shapes])) + (s/keys :req-un [::id ::name :internal.changes.add-component/shapes] + :opt-un [::path])) (defmethod change-spec :mod-component [_] (s/keys :req-un [::id] diff --git a/frontend/resources/styles/main/partials/color-bullet.scss b/frontend/resources/styles/main/partials/color-bullet.scss index 4c300a4e7..eb4882a51 100644 --- a/frontend/resources/styles/main/partials/color-bullet.scss +++ b/frontend/resources/styles/main/partials/color-bullet.scss @@ -74,7 +74,7 @@ ul.palette-menu .color-bullet { background-size: 8px; } -.asset-group .group-list-item .color-bullet { +.asset-section .asset-list-item .color-bullet { border: 1px solid $color-gray-20; border-radius: 10px; height: 20px; diff --git a/frontend/resources/styles/main/partials/sidebar-assets.scss b/frontend/resources/styles/main/partials/sidebar-assets.scss index b2915448e..ebbe3afff 100644 --- a/frontend/resources/styles/main/partials/sidebar-assets.scss +++ b/frontend/resources/styles/main/partials/sidebar-assets.scss @@ -118,7 +118,7 @@ cursor: pointer; } - .asset-group { + .asset-section { background-color: $color-gray-60; border-top: 1px solid $color-gray-50; padding: $small; @@ -126,33 +126,58 @@ color: $color-gray-20; /* TODO: see if this is useful, or is better to leave only one scroll bar in the whole sidebar - (also see .group-list) */ + (also see .asset-list) */ // max-height: 30rem; // overflow-y: scroll; - .group-title { - display: flex; - cursor: pointer; + .asset-title { + display: flex; + cursor: pointer; & .num-assets { color: $color-gray-30; } & svg { - height: 8px; - width: 8px; - fill: $color-gray-30; - margin-right: 4px; - transform: rotate(90deg); + height: 8px; + width: 8px; + fill: $color-gray-30; + margin-right: 4px; + transform: rotate(90deg); } &.closed svg { - transform: rotate(0deg); - transition: transform 0.3s; + transform: rotate(0deg); + transition: transform 0.3s; } } - .group-button { + .group-title { + display: flex; + cursor: pointer; + margin-top: $small; + margin-bottom: $x-small; + color: $color-white; + + & svg { + height: 8px; + width: 8px; + fill: $color-white; + margin-right: 4px; + transform: rotate(90deg); + } + + &.closed svg { + transform: rotate(0deg); + transition: transform 0.3s; + } + + & .dim { + color: $color-gray-40; + } + } + + .assets-button { margin-left: auto; cursor: pointer; @@ -167,8 +192,11 @@ } } - .group-grid { - margin-top: $medium; + .asset-title + .asset-grid { + margin-top: $small; + } + + .asset-grid { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; grid-auto-rows: 6vh; @@ -192,6 +220,7 @@ .grid-cell { background-color: $color-canvas; border-radius: 4px; + border: 2px solid transparent; overflow: hidden; display: flex; align-items: center; @@ -242,26 +271,30 @@ } .grid-cell:hover { - border: 1px solid $color-primary; + border: 2px solid $color-primary; & .cell-name { display: block; } } + .grid-cell.selected { + border: 2px solid $color-primary; + } + /* TODO: see if this is useful, or is better to leave only one scroll bar in the whole sidebar - (also see .asset-group) */ - // .group-list { + (also see .asset-section) */ + // .asset-list { // max-height: 30rem; // overflow-y: scroll; // } - .group-list { + .asset-list { margin-top: $medium; } - .group-list-item { + .asset-list-item { display: flex; align-items: center; margin-top: $small; diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index e0770692d..9ef30810d 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -983,7 +983,7 @@ display: flex; } -.asset-group { +.asset-section { .typography-entry { margin: 0.25rem 0; } diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 675e50a1f..1dbfdf2bf 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -157,14 +157,17 @@ ptk/WatchEvent (watch [_ state stream] (let [object (get-in state [:workspace-data :media id]) + [path name] (cp/parse-path-name new-name) rchanges [{:type :mod-media :object {:id id - :name new-name}}] + :name name + :path path}}] uchanges [{:type :mod-media :object {:id id - :name (:name object)}}]] + :name (:name object) + :path (:path object)}}]] (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) @@ -250,20 +253,24 @@ (ptk/reify ::rename-component ptk/WatchEvent (watch [_ state stream] - (let [component (get-in state [:workspace-data :components id]) + (let [[path name] (cp/parse-path-name new-name) + component (get-in state [:workspace-data :components id]) objects (get component :objects) + ; Give the same name to the root shape new-objects (assoc-in objects [(:id component) :name] - new-name) + name) rchanges [{:type :mod-component :id id - :name new-name + :name name + :path path :objects new-objects}] uchanges [{:type :mod-component :id id :name (:name component) + :path (:path component) :objects objects}]] (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) @@ -288,6 +295,7 @@ rchanges [{:type :add-component :id (:id new-shape) :name new-name + :path (:path component) :shapes new-shapes}] uchanges [{:type :del-component diff --git a/frontend/src/app/main/ui/components/editable_label.cljs b/frontend/src/app/main/ui/components/editable_label.cljs index d5d7188ed..d2c1f6ff9 100644 --- a/frontend/src/app/main/ui/components/editable_label.cljs +++ b/frontend/src/app/main/ui/components/editable_label.cljs @@ -14,8 +14,9 @@ [rumext.alpha :as mf])) (mf/defc editable-label - [{:keys [value on-change on-cancel editing? disable-dbl-click? class-name]}] - (let [input (mf/use-ref nil) + [{:keys [value on-change on-cancel editing? disable-dbl-click? class-name] :as props}] + (let [display-value (get props :display-value value) + input (mf/use-ref nil) state (mf/use-state (:editing false)) is-editing (:editing @state) start-editing (fn [] @@ -53,4 +54,4 @@ :on-blur cancel-editing}] [:span.editable-label-close {:on-click cancel-editing} i/close]] [:span.editable-label {:class class-name - :on-double-click on-dbl-click} value]))) + :on-double-click on-dbl-click} display-value]))) diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs index 230f521b1..2827f5a6e 100644 --- a/frontend/src/app/main/ui/components/forms.cljs +++ b/frontend/src/app/main/ui/components/forms.cljs @@ -23,6 +23,7 @@ (let [input-type (get props :type "text") input-name (get props :name) more-classes (get props :class) + auto-focus? (get props :auto-focus? false) form (or form (mf/use-ctx form-ctx)) @@ -84,6 +85,7 @@ (dissoc :help-icon :form :trim) (assoc :id (name input-name) :value value + :auto-focus auto-focus? :on-focus on-focus :on-blur on-blur :placeholder label diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 6a2abb5b0..5e97dee67 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.assets (:require [app.common.data :as d] + [app.common.spec :as us] [app.common.geom.point :as gpt] [app.common.geom.shapes :as geom] [app.common.media :as cm] @@ -17,6 +18,7 @@ [app.main.data.colors :as dc] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] + [app.main.data.workspace.common :as dwc] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.texts :as dwt] [app.main.exports :as exports] @@ -26,6 +28,7 @@ [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.components.editable-label :refer [editable-label]] [app.main.ui.components.file-uploader :refer [file-uploader]] + [app.main.ui.components.forms :as fm] [app.main.ui.components.tab-container :refer [tab-container tab-element]] [app.main.ui.context :as ctx] [app.main.ui.icons :as i] @@ -37,17 +40,127 @@ [app.util.keyboard :as kbd] [app.util.router :as rt] [app.util.timers :as timers] + [cljs.spec.alpha :as s] [cuerdas.core :as str] [okulary.core :as l] [rumext.alpha :as mf])) + +;; ---- Assets selection management + +(def empty-selection #{}) + +(defn toggle-select + [selected asset-id] + (if (contains? selected asset-id) + (disj selected asset-id) + (conj selected asset-id))) + +(defn replace-select + [selected asset-id] + #{asset-id}) + +(defn extend-select + [selected asset-id groups] + (let [assets (->> groups vals flatten) + clicked-idx (d/index-of-pred assets #(= (:id %) asset-id)) + selected-idx (->> selected + (map (fn [id] (d/index-of-pred assets + #(= (:id %) id))))) + min-idx (apply min (conj selected-idx clicked-idx)) + max-idx (apply max (conj selected-idx clicked-idx))] + + (->> assets + d/enumerate + (filter #(<= min-idx (first %) max-idx)) + (map #(-> % second :id)) + set))) + + +;; ---- Group assets management ---- + +(s/def ::asset-name ::us/not-empty-string) +(s/def ::create-group-form + (s/keys :req-un [::asset-name])) + +(defn group-assets + [assets] + (reduce (fn [groups asset] + (update groups (or (:path asset) "") + #(conj (or % []) asset))) + (sorted-map) + assets)) + +(def empty-folded-groups #{}) + +(defn toggle-folded-group + [folded-groups path] + (if (contains? folded-groups path) + (disj folded-groups path) + (conj folded-groups path))) + +(mf/defc create-group-dialog + {::mf/register modal/components + ::mf/register-as :create-group-dialog} + [{:keys [create] :as ctx}] + (let [form (fm/use-form :spec ::create-group-form + :initial {}) + + close #(modal/hide!) + + on-accept + (mf/use-callback + (mf/deps form) + (fn [event] + (let [asset-name (get-in @form [:clean-data :asset-name])] + (create asset-name) + (modal/hide!))))] + + [:div.modal-overlay + [:div.modal-container.confirm-dialog + [:div.modal-header + [:div.modal-header-title + [:h2 (tr "workspace.assets.create-group")]] + [:div.modal-close-button + {:on-click close} i/close]] + + [:div.modal-content.generic-form + [:& fm/form {:form form} + [:& fm/input {:name :asset-name + :auto-focus? true + :label (tr "workspace.assets.group-name") + :hint (tr "workspace.assets.create-group-hint")}]]] + + [:div.modal-footer + [:div.action-buttons + [:input.cancel-button + {:type "button" + :value (tr "labels.cancel") + :on-click close}] + + [:input.accept-button.primary + {:type "button" + :class (when-not (:valid @form) "btn-disabled") + :disabled (not (:valid @form)) + :value (tr "labels.create") + :on-click on-accept}]]]]])) + + +;; ---- Components box ---- + (mf/defc components-box [{:keys [file-id local? components open?] :as props}] (let [state (mf/use-state {:menu-open false :renaming nil :top nil :left nil - :component-id nil}) + :component-id nil + :selected empty-selection + :folded-groups empty-folded-groups}) + + groups (group-assets components) + selected (:selected @state) + folded-groups (:folded-groups @state) on-duplicate (mf/use-callback @@ -94,6 +207,60 @@ :left left :component-id component-id)))))) + unselect-all + (mf/use-callback + (fn [event] + (swap! state assoc :selected empty-selection))) + + on-select + (mf/use-callback + (mf/deps state) + (fn [component-id] + (fn [event] + (dom/stop-propagation event) + (swap! state update :selected + (fn [selected] + (cond + (kbd/ctrl? event) + (toggle-select selected component-id) + + (kbd/shift? event) + (extend-select selected component-id groups) + + :default + (replace-select selected component-id))))))) + + create-group + (mf/use-callback + (mf/deps components selected) + (fn [name] + (swap! state assoc :selected empty-selection) + (st/emit! (dwc/start-undo-transaction)) + (apply st/emit! + (->> components + (filter #(contains? selected (:id %))) + (map #(dwl/rename-component + (:id %) + (str name " / " + (cp/merge-path-item (:path %) (:name %))))))) + (st/emit! (dwc/commit-undo-transaction)))) + + on-fold-group + (mf/use-callback + (mf/deps groups folded-groups) + (fn [path] + (fn [event] + (dom/stop-propagation event) + (swap! state update :folded-groups + toggle-folded-group path)))) + + on-group + (mf/use-callback + (mf/deps components selected) + (fn [event] + (dom/stop-propagation event) + (modal/show! :create-group-dialog {:create create-group}))) + on-drag-start (mf/use-callback (fn [component event] @@ -101,30 +268,50 @@ :component component}) (dnd/set-allowed-effect! event "move")))] - [:div.asset-group - [:div.group-title {:class (when (not open?) "closed")} + [:div.asset-section {:on-click unselect-all} + [:div.asset-title {:class (when (not open?) "closed")} [:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :components (not open?)))} i/arrow-slide (tr "workspace.assets.components")] [:span (str "\u00A0(") (count components) ")"]] ;; Unicode 00A0 is non-breaking space (when open? - [:div.group-grid.big - (for [component components] - (let [renaming? (= (:renaming @state)(:id component))] - [:div.grid-cell {:key (:id component) - :draggable true - :on-context-menu (on-context-menu (:id component)) - :on-drag-start (partial on-drag-start component)} - [:& exports/component-svg {:group (get-in component [:objects (:id component)]) - :objects (:objects component)}] - [:& editable-label - {:class-name (dom/classnames - :cell-name true - :editing renaming?) - :value (:name component) - :editing? renaming? - :disable-dbl-click? true - :on-change do-rename - :on-cancel cancel-rename}]]))]) + (for [group groups] + (let [path (first group) + components (second group) + group-open? (not (contains? folded-groups path))] + [:* + (when-not (empty? path) + (let [[other-path last-path truncated] (cp/compact-path path 35)] + [:div.group-title {:class (when-not group-open? "closed") + :on-click (on-fold-group path)} + [:span i/arrow-slide] + (when-not (empty? other-path) + [:span.dim {:title (when truncated path)} + other-path "\u00A0/\u00A0"]) + [:span {:title (when truncated path)} + last-path]])) + (when group-open? + [:div.asset-grid.big + (for [component components] + (let [renaming? (= (:renaming @state)(:id component))] + [:div.grid-cell {:key (:id component) + :class-name (dom/classnames + :selected (contains? selected (:id component))) + :draggable true + :on-click (on-select (:id component)) + :on-context-menu (on-context-menu (:id component)) + :on-drag-start (partial on-drag-start component)} + [:& exports/component-svg {:group (get-in component [:objects (:id component)]) + :objects (:objects component)}] + [:& editable-label + {:class-name (dom/classnames + :cell-name true + :editing renaming?) + :value (cp/merge-path-item (:path component) (:name component)) + :display-value (:name component) + :editing? renaming? + :disable-dbl-click? true + :on-change do-rename + :on-cancel cancel-rename}]]))])]))) (when local? [:& context-menu @@ -133,9 +320,14 @@ :on-close #(swap! state assoc :menu-open false) :top (:top @state) :left (:left @state) - :options [[(tr "workspace.assets.rename") on-rename] + :options [(when (<= (count selected) 1) + [(tr "workspace.assets.rename") on-rename]) [(tr "workspace.assets.duplicate") on-duplicate] - [(tr "workspace.assets.delete") on-delete]]}])])) + [(tr "workspace.assets.delete") on-delete] + [(tr "workspace.assets.group") on-group]]}])])) + + +;; ---- Graphics box ---- (mf/defc graphics-box [{:keys [file-id local? objects open?] :as props}] @@ -144,7 +336,13 @@ :renaming nil :top nil :left nil - :object-id nil}) + :object-id nil + :selected empty-selection + :folded-groups empty-folded-groups}) + + groups (group-assets objects) + selected (:selected @state) + folded-groups (:folded-groups @state) add-graphic (mf/use-callback @@ -200,6 +398,60 @@ :left left :object-id object-id)))))) + unselect-all + (mf/use-callback + (fn [event] + (swap! state assoc :selected empty-selection))) + + on-select + (mf/use-callback + (mf/deps state) + (fn [object-id] + (fn [event] + (dom/stop-propagation event) + (swap! state update :selected + (fn [selected] + (cond + (kbd/ctrl? event) + (toggle-select selected object-id) + + (kbd/shift? event) + (extend-select selected object-id groups) + + :default + (replace-select selected object-id))))))) + + create-group + (mf/use-callback + (mf/deps objects selected) + (fn [name] + (swap! state assoc :selected empty-selection) + (st/emit! (dwc/start-undo-transaction)) + (apply st/emit! + (->> objects + (filter #(contains? selected (:id %))) + (map #(dwl/rename-media + (:id %) + (str name " / " + (cp/merge-path-item (:path %) (:name %))))))) + (st/emit! (dwc/commit-undo-transaction)))) + + on-fold-group + (mf/use-callback + (mf/deps groups folded-groups) + (fn [path] + (fn [event] + (dom/stop-propagation event) + (swap! state update :folded-groups + toggle-folded-group path)))) + + on-group + (mf/use-callback + (mf/deps objects selected) + (fn [event] + (dom/stop-propagation event) + (modal/show! :create-group-dialog {:create create-group}))) + on-drag-start (mf/use-callback (fn [{:keys [name id mtype]} event] @@ -208,49 +460,74 @@ (dnd/set-data! event "text/asset-type" mtype) (dnd/set-allowed-effect! event "move")))] - [:div.asset-group - [:div.group-title {:class (when (not open?) "closed")} + [:div.asset-section {:on-click unselect-all} + [:div.asset-title {:class (when (not open?) "closed")} [:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :graphics (not open?)))} i/arrow-slide (tr "workspace.assets.graphics")] [:span.num-assets (str "\u00A0(") (count objects) ")"] ;; Unicode 00A0 is non-breaking space (when local? - [:div.group-button {:on-click add-graphic} + [:div.assets-button {:on-click add-graphic} i/plus [:& file-uploader {:accept cm/str-media-types :multi true :input-ref input-ref :on-selected on-selected}]])] (when open? - [:div.group-grid - (for [object objects] - [:div.grid-cell {:key (:id object) - :draggable true - :on-context-menu (on-context-menu (:id object)) - :on-drag-start (partial on-drag-start object)} - [:img {:src (cfg/resolve-file-media object true) - :draggable false}] ;; Also need to add css pointer-events: none + (for [group groups] + (let [path (first group) + objects (second group) + group-open? (not (contains? folded-groups path))] + [:* + (when-not (empty? path) + (let [[other-path last-path truncated] (cp/compact-path path 35)] + [:div.group-title {:class (when-not group-open? "closed") + :on-click (on-fold-group path)} + [:span i/arrow-slide] + (when-not (empty? other-path) + [:span.dim {:title (when truncated path)} + other-path "\u00A0/\u00A0"]) + [:span {:title (when truncated path)} + last-path]])) + (when group-open? + [:div.asset-grid + (for [object objects] + [:div.grid-cell {:key (:id object) + :class-name (dom/classnames + :selected (contains? selected (:id object))) + :draggable true + :on-click (on-select (:id object)) + :on-context-menu (on-context-menu (:id object)) + :on-drag-start (partial on-drag-start object)} + [:img {:src (cfg/resolve-file-media object true) + :draggable false}] ;; Also need to add css pointer-events: none - #_[:div.cell-name (:name object)] - (let [renaming? (= (:renaming @state) (:id object))] - [:& editable-label - {:class-name (dom/classnames - :cell-name true - :editing renaming?) - :value (:name object) - :editing? renaming? - :disable-dbl-click? true - :on-change do-rename - :on-cancel cancel-rename}])]) + #_[:div.cell-name (:name object)] + (let [renaming? (= (:renaming @state) (:id object))] + [:& editable-label + {:class-name (dom/classnames + :cell-name true + :editing renaming?) + :value (cp/merge-path-item (:path object) (:name object)) + :display-value (:name object) + :editing? renaming? + :disable-dbl-click? true + :on-change do-rename + :on-cancel cancel-rename}])])])]))) - (when local? - [:& context-menu - {:selectable false - :show (:menu-open @state) - :on-close #(swap! state assoc :menu-open false) - :top (:top @state) - :left (:left @state) - :options [[(tr "workspace.assets.rename") on-rename] - [(tr "workspace.assets.delete") on-delete]]}])])])) + (when local? + [:& context-menu + {:selectable false + :show (:menu-open @state) + :on-close #(swap! state assoc :menu-open false) + :top (:top @state) + :left (:left @state) + :options [(when (<= (count selected) 1) + [(tr "workspace.assets.rename") on-rename]) + [(tr "workspace.assets.delete") on-delete] + [(tr "workspace.assets.group") on-group]]}])])) + + +;; ---- Colors box ---- (mf/defc color-item [{:keys [color local? file-id locale] :as props}] @@ -337,7 +614,7 @@ (dom/select-text! input)) nil)) - [:div.group-list-item {:on-context-menu on-context-menu} + [:div.asset-list-item {:on-context-menu on-context-menu} [:& bc/color-bullet {:color color :on-click click-color}] @@ -387,15 +664,15 @@ :data {:color "#406280" :opacity 1} :position :right})))] - [:div.asset-group - [:div.group-title {:class (when (not open?) "closed")} + [:div.asset-section + [:div.asset-title {:class (when (not open?) "closed")} [:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :colors (not open?)))} i/arrow-slide (t locale "workspace.assets.colors")] [:span.num-assets (str "\u00A0(") (count colors) ")"] ;; Unicode 00A0 is non-breaking space (when local? - [:div.group-button {:on-click add-color-clicked} i/plus])] + [:div.assets-button {:on-click add-color-clicked} i/plus])] (when open? - [:div.group-list + [:div.asset-list (for [color colors] (let [color (cond-> color (:value color) (assoc :color (:value color) :opacity 1) @@ -407,6 +684,9 @@ :local? local? :locale locale}]))])])) + +;; ---- Typography box ---- + (mf/defc typography-box [{:keys [file file-id local? typographies locale open?] :as props}] @@ -480,13 +760,13 @@ (when (:edit-typography local) (st/emit! #(update % :workspace-local dissoc :edit-typography))))) - [:div.asset-group - [:div.group-title {:class (when (not open?) "closed")} + [:div.asset-section + [:div.asset-title {:class (when (not open?) "closed")} [:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :typographies (not open?)))} i/arrow-slide (t locale "workspace.assets.typography")] [:span.num-assets (str "\u00A0(") (count typographies) ")"] ;; Unicode 00A0 is non-breaking space (when local? - [:div.group-button {:on-click add-typography} i/plus])] + [:div.assets-button {:on-click add-typography} i/plus])] [:& context-menu {:selectable false @@ -498,7 +778,7 @@ [(t locale "workspace.assets.edit") handle-edit-typography-clicked] [(t locale "workspace.assets.delete") handle-delete-typography]]}] (when open? - [:div.group-list + [:div.asset-list (for [typography (sort-by :ts typographies)] [:& typography-entry {:key (:id typography) @@ -511,6 +791,9 @@ :editting? (= editting-id (:id typography)) :focus-name? (= (:rename-typography local) (:id typography))}])])])) + +;; --- Assets toolbox ---- + (defn file-colors-ref [id] (l/derived (fn [state] @@ -656,8 +939,8 @@ :open? (open-box? :typographies)}]) (when (and (not show-components?) (not show-graphics?) (not show-colors?)) - [:div.asset-group - [:div.group-title (t locale "workspace.assets.not-found")]])]))])) + [:div.asset-section + [:div.asset-title (t locale "workspace.assets.not-found")]])]))])) (mf/defc assets-toolbox diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 06bd26481..7067eb619 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -780,6 +780,10 @@ msgstr "Confirm password" msgid "labels.content" msgstr "Content" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "labels.create" +msgstr "Create" + #: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs msgid "labels.create-team" msgstr "Create new team" @@ -1439,6 +1443,22 @@ msgstr "File library" msgid "workspace.assets.graphics" msgstr "Graphics" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Group" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group" +msgstr "Create a group" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group-name" +msgstr "Group name" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group-hint" +msgstr "Your items are going to be named automatically as \"group name / item name\"" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.libraries" msgstr "Libraries" @@ -2437,4 +2457,4 @@ msgid "workspace.updates.update" msgstr "Update" msgid "workspace.viewport.click-to-close-path" -msgstr "Click to close the path" \ No newline at end of file +msgstr "Click to close the path" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 52d06a8b1..a20c36d5b 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -776,6 +776,10 @@ msgstr "Confirmar contraseña" msgid "labels.content" msgstr "Contenido" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "labels.create" +msgstr "Crear" + #: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs msgid "labels.create-team" msgstr "Crea un nuevo equipo" @@ -1419,6 +1423,22 @@ msgstr "Biblioteca del archivo" msgid "workspace.assets.graphics" msgstr "Gráficos" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Agrupar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group" +msgstr "Crear un grupo" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group-name" +msgstr "Nombre del grupo" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group-hint" +msgstr "Tus elementos se renombrarán automáticamente a \"nombre grupo / nombre elemento\"" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.libraries" msgstr "Bibliotecas" @@ -2419,4 +2439,4 @@ msgid "workspace.updates.update" msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Pulsar para cerrar la ruta" \ No newline at end of file +msgstr "Pulsar para cerrar la ruta" From 77a2fd6e361e783149a039ab7705b5fdfed649d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 9 Apr 2021 14:54:58 +0200 Subject: [PATCH 041/155] :tada: Bulk duplicate and delete assets --- .../app/main/data/workspace/libraries.cljs | 1 + .../app/main/ui/workspace/sidebar/assets.cljs | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 1dbfdf2bf..59ab84956 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -318,6 +318,7 @@ uchanges [{:type :add-component :id id :name (:name component) + :path (:path component) :shapes (vals (:objects component))}]] (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 5e97dee67..38f86df09 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -165,13 +165,24 @@ on-duplicate (mf/use-callback (mf/deps state) - (st/emitf (dwl/duplicate-component {:id (:component-id @state)}))) + (fn [] + (if (empty? selected) + (st/emit! (dwl/duplicate-component {:id (:component-id @state)})) + (do + (st/emit! (dwc/start-undo-transaction)) + (apply st/emit! (map #(dwl/duplicate-component {:id %}) selected)) + (st/emit! (dwc/commit-undo-transaction)))))) on-delete (mf/use-callback (mf/deps state) (fn [] - (st/emit! (dwl/delete-component {:id (:component-id @state)})) + (if (empty? selected) + (st/emit! (dwl/delete-component {:id (:component-id @state)})) + (do + (st/emit! (dwc/start-undo-transaction)) + (apply st/emit! (map #(dwl/delete-component {:id %}) selected)) + (st/emit! (dwc/commit-undo-transaction)))) (st/emit! (dwl/sync-file file-id file-id)))) on-rename @@ -362,8 +373,12 @@ (mf/use-callback (mf/deps state) (fn [] - (let [params {:id (:object-id @state)}] - (st/emit! (dwl/delete-media params))))) + (if (empty? selected) + (st/emit! (dwl/delete-media {:id (:object-id @state)})) + (do + (st/emit! (dwc/start-undo-transaction)) + (apply st/emit! (map #(dwl/delete-media {:id %}) selected)) + (st/emit! (dwc/commit-undo-transaction)))))) on-rename (mf/use-callback From 112e656f40a5e3993cabcc5dd687e8134a3ac462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 12 Apr 2021 12:27:10 +0200 Subject: [PATCH 042/155] :tada: Sort assets ascending / descending --- .../resources/images/icons/listing-list.svg | 3 ++ .../resources/images/icons/listing-thumbs.svg | 3 ++ .../resources/images/icons/sort-ascending.svg | 3 ++ .../images/icons/sort-descending.svg | 3 ++ .../styles/main/partials/sidebar-assets.scss | 25 ++++++++++++- .../app/main/data/workspace/libraries.cljs | 2 +- frontend/src/app/main/ui/icons.cljs | 4 ++ .../app/main/ui/workspace/sidebar/assets.cljs | 37 +++++++++++++------ 8 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 frontend/resources/images/icons/listing-list.svg create mode 100644 frontend/resources/images/icons/listing-thumbs.svg create mode 100644 frontend/resources/images/icons/sort-ascending.svg create mode 100644 frontend/resources/images/icons/sort-descending.svg diff --git a/frontend/resources/images/icons/listing-list.svg b/frontend/resources/images/icons/listing-list.svg new file mode 100644 index 000000000..d979e5010 --- /dev/null +++ b/frontend/resources/images/icons/listing-list.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/listing-thumbs.svg b/frontend/resources/images/icons/listing-thumbs.svg new file mode 100644 index 000000000..ac5d98e47 --- /dev/null +++ b/frontend/resources/images/icons/listing-thumbs.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/sort-ascending.svg b/frontend/resources/images/icons/sort-ascending.svg new file mode 100644 index 000000000..a8907d170 --- /dev/null +++ b/frontend/resources/images/icons/sort-ascending.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/sort-descending.svg b/frontend/resources/images/icons/sort-descending.svg new file mode 100644 index 000000000..4ba6c45c0 --- /dev/null +++ b/frontend/resources/images/icons/sort-descending.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/styles/main/partials/sidebar-assets.scss b/frontend/resources/styles/main/partials/sidebar-assets.scss index ebbe3afff..78059a3a2 100644 --- a/frontend/resources/styles/main/partials/sidebar-assets.scss +++ b/frontend/resources/styles/main/partials/sidebar-assets.scss @@ -118,9 +118,27 @@ cursor: pointer; } + .listing-options { + background-color: $color-gray-60; + display: flex; + justify-content: flex-end; + align-items: center; + padding: $medium $small 0 $small; + + .listing-option-btn { + cursor: pointer; + margin-left: $small; + + svg { + fill: $color-gray-20; + height: 16px; + width: 16px; + } + } + } + .asset-section { background-color: $color-gray-60; - border-top: 1px solid $color-gray-50; padding: $small; font-size: $fs12; color: $color-gray-20; @@ -130,6 +148,11 @@ // max-height: 30rem; // overflow-y: scroll; + // First child is the listing options buttons + &:not(:nth-child(2)) { + border-top: 1px solid $color-gray-50; + } + .asset-title { display: flex; cursor: pointer; diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 59ab84956..fa9d3b279 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -193,7 +193,7 @@ ptk/WatchEvent (watch [_ state s] (let [rchg {:type :add-typography - :typography (assoc typography :ts (.now js/Date))} + :typography typography} uchg {:type :del-typography :id (:id typography)}] (rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 5c41f28ef..408f2825a 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -60,6 +60,8 @@ (def libraries (icon-xref :libraries)) (def library (icon-xref :library)) (def line (icon-xref :line)) +(def listing-list (icon-xref :listing-list)) +(def listing-thumbs (icon-xref :listing-thumbs)) (def line-height (icon-xref :line-height)) (def loader (icon-xref :loader)) (def lock (icon-xref :lock)) @@ -117,6 +119,8 @@ (def shape-vdistribute (icon-xref :shape-vdistribute)) (def size-horiz (icon-xref :size-horiz)) (def size-vert (icon-xref :size-vert)) +(def sort-ascending (icon-xref :sort-ascending)) +(def sort-descending (icon-xref :sort-descending)) (def strikethrough (icon-xref :strikethrough)) (def stroke (icon-xref :stroke)) (def sublevel (icon-xref :sublevel)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 38f86df09..cad955034 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -794,7 +794,7 @@ [(t locale "workspace.assets.delete") handle-delete-typography]]}] (when open? [:div.asset-list - (for [typography (sort-by :ts typographies)] + (for [typography typographies] [:& typography-entry {:key (:id typography) :typography typography @@ -852,12 +852,13 @@ (l/derived refs/workspace-local))) (defn apply-filters - [coll filters] - (->> coll - (filter (fn [item] - (or (matches-search (:name item "!$!") (:term filters)) - (matches-search (:value item "!$!") (:term filters))))) - (sort-by #(str/lower (:name %))))) + [coll filters reverse-sort?] + (let [comp-fn (if reverse-sort? > <)] + (->> coll + (filter (fn [item] + (or (matches-search (:name item "!$!") (:term filters)) + (matches-search (:value item "!$!") (:term filters))))) + (sort-by #(str/lower (:name %)) comp-fn)))) (mf/defc file-library [{:keys [file local? default-open? filters locale] :as props}] @@ -871,6 +872,9 @@ (d/nilv true))) shared? (:is-shared file) router (mf/deref refs/router) + + reverse-sort? (mf/use-state false) + toggle-open (st/emitf (dwl/set-assets-box-open (:id file) :library (not open?))) url (rt/resolve router :workspace @@ -879,16 +883,21 @@ {:page-id (get-in file [:data :pages 0])}) colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file))) - colors (apply-filters (mf/deref colors-ref) filters) + colors (apply-filters (mf/deref colors-ref) filters @reverse-sort?) typography-ref (mf/use-memo (mf/deps (:id file)) #(file-typography-ref (:id file))) - typographies (apply-filters (mf/deref typography-ref) filters) + typographies (apply-filters (mf/deref typography-ref) filters @reverse-sort?) media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file))) - media (apply-filters (mf/deref media-ref) filters) + media (apply-filters (mf/deref media-ref) filters @reverse-sort?) components-ref (mf/use-memo (mf/deps (:id file)) #(file-components-ref (:id file))) - components (apply-filters (mf/deref components-ref) filters)] + components (apply-filters (mf/deref components-ref) filters @reverse-sort?) + + toggle-sort + (mf/use-callback + (fn [event] + (swap! reverse-sort? not)))] [:div.tool-window [:div.tool-window-bar.library-bar @@ -928,6 +937,12 @@ (or (> (count typographies) 0) (str/empty? (:term filters))))] [:div.tool-window-content + [:div.listing-options + [:div.listing-option-btn {:on-click toggle-sort} + (if @reverse-sort? + i/sort-descending + i/sort-ascending)] + [:div.listing-option-btn i/listing-thumbs]] (when show-components? [:& components-box {:file-id (:id file) :local? local? From e8da04d4ab0b67bf29d47f5bb8aba5758045afb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 12 Apr 2021 16:47:30 +0200 Subject: [PATCH 043/155] :tada: Show assets as a list --- common/app/common/pages.cljc | 1 + common/app/common/pages/helpers.cljc | 23 +++- .../{listing-list.svg => listing-enum.svg} | 0 .../styles/main/partials/sidebar-assets.scss | 50 +++++++ .../main/ui/components/editable_label.cljs | 2 + frontend/src/app/main/ui/icons.cljs | 2 +- .../app/main/ui/workspace/sidebar/assets.cljs | 129 +++++++++++------- 7 files changed, 150 insertions(+), 57 deletions(-) rename frontend/resources/images/icons/{listing-list.svg => listing-enum.svg} (100%) diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 2b34a602a..2776e8168 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -65,6 +65,7 @@ (d/export helpers/parse-path-name) (d/export helpers/merge-path-item) (d/export helpers/compact-path) +(d/export helpers/compact-name) ;; Process changes (d/export changes/process-changes) diff --git a/common/app/common/pages/helpers.cljc b/common/app/common/pages/helpers.cljc index 79030e8df..39597788d 100644 --- a/common/app/common/pages/helpers.cljc +++ b/common/app/common/pages/helpers.cljc @@ -460,13 +460,19 @@ (d/enumerate (:shapes parent)))] parent-idx)) +(defn split-path + [path] + "Decompose a string in the form 'one / two / three' into + an array of strings, normalizing spaces." + (->> (str/split path "/") + (map str/trim) + (remove str/empty?))) + (defn parse-path-name "Parse a string in the form 'group / subgroup / name'. Retrieve the path and the name in separated values, normalizing spaces." [path-name] - (let [path-name-split (->> (str/split path-name "/") - (map str/trim) - (remove str/empty?)) + (let [path-name-split (split-path path-name) path (str/join " / " (butlast path-name-split)) name (last path-name-split)] [path name])) @@ -479,14 +485,13 @@ name)) (defn compact-path - "Separate last component of the path, and truncate the others if too long: + "Separate last item of the path, and truncate the others if too long: 'one' -> ['' 'one' false] 'one / two / three' -> ['one / two' 'three' false] 'one / two / three / four' -> ['one / two / ...' 'four' true] 'one-item-but-very-long / two' -> ['...' 'two' true] " [path max-length] - (let [path-split (->> (str/split path "/") - (map str/trim)) + (let [path-split (split-path path) last-item (last path-split)] (loop [other-items (seq (butlast path-split)) other-path ""] @@ -500,3 +505,9 @@ (merge-path-item other-path item)))) [other-path last-item false])))) +(defn compact-name + "Append the first item of the path and the name." + [path name] + (let [path-split (split-path path)] + (merge-path-item (first path-split) name))) + diff --git a/frontend/resources/images/icons/listing-list.svg b/frontend/resources/images/icons/listing-enum.svg similarity index 100% rename from frontend/resources/images/icons/listing-list.svg rename to frontend/resources/images/icons/listing-enum.svg diff --git a/frontend/resources/styles/main/partials/sidebar-assets.scss b/frontend/resources/styles/main/partials/sidebar-assets.scss index 78059a3a2..9ca1327d7 100644 --- a/frontend/resources/styles/main/partials/sidebar-assets.scss +++ b/frontend/resources/styles/main/partials/sidebar-assets.scss @@ -305,6 +305,56 @@ border: 2px solid $color-primary; } + .asset-title + .asset-enum { + margin-top: $small; + } + + .asset-enum { + .enum-item { + display: flex; + align-items: center; + margin-bottom: $small; + cursor: pointer; + + & > svg, + & > img { + background-color: $color-canvas; + border-radius: 4px; + border: 2px solid transparent; + height: 24px; + width: 24px; + margin-right: $small; + } + + .item-name { + width: calc(100% - 24px - #{$small}); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: block; + + &.editing { + display: flex; + align-items: center; + + .editable-label-input { + height: 24px; + } + + .editable-label-close { + display: none; + } + } + } + } + + .enum-item:hover, + .enum-item.selected, + { + color: $color-primary; + } + } + /* TODO: see if this is useful, or is better to leave only one scroll bar in the whole sidebar (also see .asset-section) */ diff --git a/frontend/src/app/main/ui/components/editable_label.cljs b/frontend/src/app/main/ui/components/editable_label.cljs index d2c1f6ff9..60e5feea4 100644 --- a/frontend/src/app/main/ui/components/editable_label.cljs +++ b/frontend/src/app/main/ui/components/editable_label.cljs @@ -16,6 +16,7 @@ (mf/defc editable-label [{:keys [value on-change on-cancel editing? disable-dbl-click? class-name] :as props}] (let [display-value (get props :display-value value) + tooltip (get props :tooltip) input (mf/use-ref nil) state (mf/use-state (:editing false)) is-editing (:editing @state) @@ -54,4 +55,5 @@ :on-blur cancel-editing}] [:span.editable-label-close {:on-click cancel-editing} i/close]] [:span.editable-label {:class class-name + :title tooltip :on-double-click on-dbl-click} display-value]))) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 408f2825a..0003704c0 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -60,7 +60,7 @@ (def libraries (icon-xref :libraries)) (def library (icon-xref :library)) (def line (icon-xref :line)) -(def listing-list (icon-xref :listing-list)) +(def listing-enum (icon-xref :listing-enum)) (def listing-thumbs (icon-xref :listing-thumbs)) (def line-height (icon-xref :line-height)) (def loader (icon-xref :loader)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index cad955034..24cb1d1fc 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -149,7 +149,7 @@ ;; ---- Components box ---- (mf/defc components-box - [{:keys [file-id local? components open?] :as props}] + [{:keys [file-id local? components listing-thumbs? open?] :as props}] (let [state (mf/use-state {:menu-open false :renaming nil :top nil @@ -301,24 +301,34 @@ [:span {:title (when truncated path)} last-path]])) (when group-open? - [:div.asset-grid.big + [:div {:class-name (dom/classnames + :asset-grid @listing-thumbs? + :big @listing-thumbs? + :asset-enum (not @listing-thumbs?))} (for [component components] (let [renaming? (= (:renaming @state)(:id component))] - [:div.grid-cell {:key (:id component) - :class-name (dom/classnames - :selected (contains? selected (:id component))) - :draggable true - :on-click (on-select (:id component)) - :on-context-menu (on-context-menu (:id component)) - :on-drag-start (partial on-drag-start component)} + [:div {:key (:id component) + :class-name (dom/classnames + :selected (contains? selected (:id component)) + :grid-cell @listing-thumbs? + :enum-item (not @listing-thumbs?)) + :draggable true + :on-click (on-select (:id component)) + :on-context-menu (on-context-menu (:id component)) + :on-drag-start (partial on-drag-start component)} [:& exports/component-svg {:group (get-in component [:objects (:id component)]) :objects (:objects component)}] [:& editable-label {:class-name (dom/classnames - :cell-name true + :cell-name @listing-thumbs? + :item-name (not @listing-thumbs?) :editing renaming?) :value (cp/merge-path-item (:path component) (:name component)) - :display-value (:name component) + :tooltip (cp/merge-path-item (:path component) (:name component)) + :display-value (if @listing-thumbs? + (:name component) + (cp/compact-name (:path component) + (:name component))) :editing? renaming? :disable-dbl-click? true :on-change do-rename @@ -341,7 +351,7 @@ ;; ---- Graphics box ---- (mf/defc graphics-box - [{:keys [file-id local? objects open?] :as props}] + [{:keys [file-id local? objects listing-thumbs? open?] :as props}] (let [input-ref (mf/use-ref nil) state (mf/use-state {:menu-open false :renaming nil @@ -504,26 +514,34 @@ [:span {:title (when truncated path)} last-path]])) (when group-open? - [:div.asset-grid + [:div {:class-name (dom/classnames + :asset-grid @listing-thumbs? + :asset-enum (not @listing-thumbs?))} (for [object objects] - [:div.grid-cell {:key (:id object) - :class-name (dom/classnames - :selected (contains? selected (:id object))) - :draggable true - :on-click (on-select (:id object)) - :on-context-menu (on-context-menu (:id object)) - :on-drag-start (partial on-drag-start object)} + [:div {:key (:id object) + :class-name (dom/classnames + :selected (contains? selected (:id object)) + :grid-cell @listing-thumbs? + :enum-item (not @listing-thumbs?)) + :draggable true + :on-click (on-select (:id object)) + :on-context-menu (on-context-menu (:id object)) + :on-drag-start (partial on-drag-start object)} [:img {:src (cfg/resolve-file-media object true) :draggable false}] ;; Also need to add css pointer-events: none - #_[:div.cell-name (:name object)] (let [renaming? (= (:renaming @state) (:id object))] [:& editable-label {:class-name (dom/classnames - :cell-name true + :cell-name @listing-thumbs? + :item-name (not @listing-thumbs?) :editing renaming?) :value (cp/merge-path-item (:path object) (:name object)) - :display-value (:name object) + :tooltip (cp/merge-path-item (:path object) (:name object)) + :display-value (if @listing-thumbs? + (:name object) + (cp/compact-name (:path object) + (:name object))) :editing? renaming? :disable-dbl-click? true :on-change do-rename @@ -861,43 +879,49 @@ (sort-by #(str/lower (:name %)) comp-fn)))) (mf/defc file-library - [{:keys [file local? default-open? filters locale] :as props}] - (let [open-file (mf/deref (open-file-ref (:id file))) - open? (-> open-file - :library - (d/nilv default-open?)) - open-box? (fn [box] - (-> open-file - box - (d/nilv true))) - shared? (:is-shared file) - router (mf/deref refs/router) + [{:keys [file local? default-open? filters locale] :as props}] + (let [open-file (mf/deref (open-file-ref (:id file))) + open? (-> open-file + :library + (d/nilv default-open?)) + open-box? (fn [box] + (-> open-file + box + (d/nilv true))) + shared? (:is-shared file) + router (mf/deref refs/router) - reverse-sort? (mf/use-state false) + reverse-sort? (mf/use-state false) + listing-thumbs? (mf/use-state true) - toggle-open (st/emitf (dwl/set-assets-box-open (:id file) :library (not open?))) + toggle-open (st/emitf (dwl/set-assets-box-open (:id file) :library (not open?))) - url (rt/resolve router :workspace - {:project-id (:project-id file) - :file-id (:id file)} - {:page-id (get-in file [:data :pages 0])}) + url (rt/resolve router :workspace + {:project-id (:project-id file) + :file-id (:id file)} + {:page-id (get-in file [:data :pages 0])}) - colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file))) - colors (apply-filters (mf/deref colors-ref) filters @reverse-sort?) + colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file))) + colors (apply-filters (mf/deref colors-ref) filters @reverse-sort?) - typography-ref (mf/use-memo (mf/deps (:id file)) #(file-typography-ref (:id file))) - typographies (apply-filters (mf/deref typography-ref) filters @reverse-sort?) + typography-ref (mf/use-memo (mf/deps (:id file)) #(file-typography-ref (:id file))) + typographies (apply-filters (mf/deref typography-ref) filters @reverse-sort?) - media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file))) - media (apply-filters (mf/deref media-ref) filters @reverse-sort?) + media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file))) + media (apply-filters (mf/deref media-ref) filters @reverse-sort?) - components-ref (mf/use-memo (mf/deps (:id file)) #(file-components-ref (:id file))) - components (apply-filters (mf/deref components-ref) filters @reverse-sort?) + components-ref (mf/use-memo (mf/deps (:id file)) #(file-components-ref (:id file))) + components (apply-filters (mf/deref components-ref) filters @reverse-sort?) toggle-sort (mf/use-callback (fn [event] - (swap! reverse-sort? not)))] + (swap! reverse-sort? not))) + + toggle-listing + (mf/use-callback + (fn [event] + (swap! listing-thumbs? not)))] [:div.tool-window [:div.tool-window-bar.library-bar @@ -942,16 +966,21 @@ (if @reverse-sort? i/sort-descending i/sort-ascending)] - [:div.listing-option-btn i/listing-thumbs]] + [:div.listing-option-btn {:on-click toggle-listing} + (if @listing-thumbs? + i/listing-thumbs + i/listing-thumbs)]] (when show-components? [:& components-box {:file-id (:id file) :local? local? :components components + :listing-thumbs? listing-thumbs? :open? (open-box? :components)}]) (when show-graphics? [:& graphics-box {:file-id (:id file) :local? local? :objects media + :listing-thumbs? listing-thumbs? :open? (open-box? :graphics)}]) (when show-colors? [:& colors-box {:file-id (:id file) From bb719d621147d5c61fee800d4f77f01fec0f7523 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 14 Apr 2021 15:43:17 +0200 Subject: [PATCH 044/155] :paperclip: Minor improvements to translations scripts. --- frontend/locales.clj | 151 ---------------------- frontend/package.json | 2 +- frontend/scripts/validate-translations.js | 31 +++++ frontend/translations/en.po | 18 +-- frontend/translations/es.po | 20 +-- 5 files changed, 52 insertions(+), 170 deletions(-) delete mode 100644 frontend/locales.clj create mode 100644 frontend/scripts/validate-translations.js diff --git a/frontend/locales.clj b/frontend/locales.clj deleted file mode 100644 index a64761248..000000000 --- a/frontend/locales.clj +++ /dev/null @@ -1,151 +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 - -(require '[clojure.pprint :as pp :refer [pprint]]) - -(require '[clojure.edn :as edn] - '[clojure.set :as set] - '[clojure.java.io :as io]) - -(require '[datoteka.core :as fs] - '[jsonista.core :as json] - '[parcera.core :as pa]) - -(import 'java.nio.file.Paths - 'java.nio.file.Path - 'java.nio.file.Files - 'java.nio.file.SimpleFileVisitor - 'java.nio.file.FileVisitResult - 'com.fasterxml.jackson.databind.ObjectMapper - 'com.fasterxml.jackson.databind.SerializationFeature) - -(defmulti task first) - -(defn- find-translations-in-form - [form] - (reduce (fn [messages node] - (let [found (->> node - (filter #(and (seq? %) (= :string (first %)))) - (map (fn [item] - (let [mdata (meta item)] - {:code (edn/read-string (second item)) - :line (get-in mdata [::pa/start :row])}))))] - (into messages found))) - [] - (->> (tree-seq seq? seq form) - (filter #(and (seq? %) - (seq? (second %)) - (= :list (first %)) - (= :symbol (first (second %))) - (or (= "t" (second (second %))) - (= "tr" (second (second %))))))))) - -(defn- find-translations - [path] - (let [forms (pa/ast (slurp path)) - spath (str path)] - (->> forms - (filter #(and (seq? %) (= :list (first %)))) - (reduce (fn [messages form] - (->> (find-translations-in-form form) - (map #(assoc % :file spath)) - (into messages))) [])))) - -(defn- collect-translations - [path] - (let [messages (atom [])] - (->> (proxy [SimpleFileVisitor] [] - (visitFile [path attrs] - (when (= (fs/ext path) "cljs") - (swap! messages into (find-translations path))) - FileVisitResult/CONTINUE) - (postVisitDirectory [dir exc] - FileVisitResult/CONTINUE)) - (Files/walkFileTree (fs/path path))) - @messages)) - -(defn- read-json-file - [path] - (when (fs/regular-file? path) - (let [content (json/read-value (io/as-file path))] - (reduce-kv (fn [res k v] - (let [v (into (sorted-map) v) - v (update v "translations" #(into (sorted-map) %))] - (assoc res k v))) - (sorted-map) - content)))) - -(defn- add-translation - [data {:keys [code file line] :as translation}] - (let [rpath (str file)] - (if (contains? data code) - (update data code (fn [state] - (if (get state "permanent") - state - (-> state - (dissoc "unused") - (update "used-in" conj rpath))))) - (assoc data code {"translations" (sorted-map "en" nil "es" nil) - "used-in" [rpath]})))) - -(defn- clean-removed-translations - [data imported] - (let [existing (into #{} (keys data)) - toremove (set/difference existing imported)] - (reduce (fn [data code] - (if (get-in data [code "permanent"]) - data - (-> data - (update code dissoc "used-in") - (update code assoc "unused" true)))) - data - toremove))) - -(defn- initial-cleanup - [data] - (reduce-kv (fn [data k v] - (if (string? v) - (assoc data k {"used-in" [] - "translations" {:en v}}) - (update data k assoc "used-in" []))) - data - data)) - -(defn- synchronize-translations - [data translations] - (loop [data (initial-cleanup data) - imported #{} - c (first translations) - r (rest translations)] - (if (nil? c) - (clean-removed-translations data imported) - (recur (add-translation data c) - (conj imported (:code c)) - (first r) - (rest r))))) - -(defn- write-result! - [data output-path] - (binding [*out* (io/writer (fs/path output-path))] - (let [mapper (doto (ObjectMapper.) - (.enable SerializationFeature/ORDER_MAP_ENTRIES_BY_KEYS)) - mapper (json/object-mapper {:pretty true :mapper mapper})] - (println (json/write-value-as-string data mapper)) - (flush)))) - -(defn- update-translations - [{:keys [find-directory output-path] :as props}] - (let [data (read-json-file output-path) - translations (collect-translations find-directory) - data (synchronize-translations data translations)] - (write-result! data output-path))) - -(defmethod task "collect" - [[_ in-path out-path]] - (update-translations {:find-directory in-path - :output-path out-path})) - -(task *command-line-args*) diff --git a/frontend/package.json b/frontend/package.json index 3b8b95580..31728cfb9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,7 +12,7 @@ "defaults" ], "scripts": { - "collect-locales": "clojure -M:dev locales.clj collect src/app/main/ resources/locales.json" + "validate-translations": "node ./scripts/validate-translations.js" }, "devDependencies": { "autoprefixer": "^10.2.4", diff --git a/frontend/scripts/validate-translations.js b/frontend/scripts/validate-translations.js new file mode 100644 index 000000000..52674054f --- /dev/null +++ b/frontend/scripts/validate-translations.js @@ -0,0 +1,31 @@ +const fs = require('fs').promises; +const gt = require("gettext-parser"); +const l = require("lodash"); +const path = require('path'); + +async function* getFiles(dir) { + const dirents = await fs.readdir(dir, { withFileTypes: true }); + for (const dirent of dirents) { + const res = path.resolve(dir, dirent.name); + if (dirent.isDirectory()) { + yield* getFiles(res); + } else { + yield res; + } + } +} + +;(async () => { + const fileRe = /.+\.po$/; + const target = path.normalize("./translations/"); + const parent = path.join(target, ".."); + for await (const f of getFiles(target)) { + if (!fileRe.test(f)) continue; + const entry = path.relative(parent, f); + console.log(`=> processing: ${entry}`); + const content = await fs.readFile(f); + const data = gt.po.parse(content, "utf-8") + const buff = gt.po.compile(data, {sort: true}); + await fs.writeFile(f, buff); + } +})() diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 7067eb619..26cab1a7f 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1423,6 +1423,14 @@ msgstr "Colors" msgid "workspace.assets.components" msgstr "Components" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group" +msgstr "Create a group" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group-hint" +msgstr "Your items are going to be named automatically as \"group name / item name\"" + #: 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 "Delete" @@ -1447,18 +1455,10 @@ msgstr "Graphics" msgid "workspace.assets.group" msgstr "Group" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.create-group" -msgstr "Create a group" - #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.group-name" msgstr "Group name" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.create-group-hint" -msgstr "Your items are going to be named automatically as \"group name / item name\"" - #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.libraries" msgstr "Libraries" @@ -2457,4 +2457,4 @@ msgid "workspace.updates.update" msgstr "Update" msgid "workspace.viewport.click-to-close-path" -msgstr "Click to close the path" +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 a20c36d5b..31911b1f0 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1403,6 +1403,16 @@ msgstr "Colores" msgid "workspace.assets.components" msgstr "Componentes" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group" +msgstr "Crear un grupo" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group-hint" +msgstr "" +"Tus elementos se renombrarán automáticamente a \"nombre grupo / nombre " +"elemento\"" + #: 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 "Borrar" @@ -1427,18 +1437,10 @@ msgstr "Gráficos" msgid "workspace.assets.group" msgstr "Agrupar" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.create-group" -msgstr "Crear un grupo" - #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.group-name" msgstr "Nombre del grupo" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.create-group-hint" -msgstr "Tus elementos se renombrarán automáticamente a \"nombre grupo / nombre elemento\"" - #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.libraries" msgstr "Bibliotecas" @@ -2439,4 +2441,4 @@ msgid "workspace.updates.update" msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Pulsar para cerrar la ruta" +msgstr "Pulsar para cerrar la ruta" \ No newline at end of file From a06a8c648ecffcff32969a5fdeb5c09be895e1b7 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 30 Mar 2021 17:13:05 +0200 Subject: [PATCH 045/155] :sparkles: Select path nodes in area --- common/app/common/geom/shapes.cljc | 1 + common/app/common/geom/shapes/intersect.cljc | 5 ++ common/app/common/geom/shapes/rect.cljc | 6 ++ .../app/main/data/workspace/drawing/path.cljs | 71 ++++++++++++++++++- .../app/main/data/workspace/selection.cljs | 5 +- .../main/ui/workspace/shapes/path/editor.cljs | 34 +++++---- .../src/app/main/ui/workspace/viewport.cljs | 2 +- .../main/ui/workspace/viewport/actions.cljs | 14 ++-- 8 files changed, 112 insertions(+), 26 deletions(-) diff --git a/common/app/common/geom/shapes.cljc b/common/app/common/geom/shapes.cljc index de6826249..28e110b73 100644 --- a/common/app/common/geom/shapes.cljc +++ b/common/app/common/geom/shapes.cljc @@ -253,3 +253,4 @@ ;; Intersection (d/export gin/overlaps?) (d/export gin/has-point?) +(d/export gin/has-point-rect?) diff --git a/common/app/common/geom/shapes/intersect.cljc b/common/app/common/geom/shapes/intersect.cljc index 6b9e3f5be..0b6fbcd6f 100644 --- a/common/app/common/geom/shapes/intersect.cljc +++ b/common/app/common/geom/shapes/intersect.cljc @@ -285,6 +285,11 @@ (or (not path?) (overlaps-path? shape rect)) (or (not circle?) (overlaps-ellipse? shape rect)))))) +(defn has-point-rect? + [rect point] + (let [lines (gpr/rect->lines rect)] + (is-point-inside-evenodd? point lines))) + (defn has-point? "Check if the shape contains a point" [shape point] diff --git a/common/app/common/geom/shapes/rect.cljc b/common/app/common/geom/shapes/rect.cljc index be221b1d9..91e7d18a9 100644 --- a/common/app/common/geom/shapes/rect.cljc +++ b/common/app/common/geom/shapes/rect.cljc @@ -19,6 +19,12 @@ (gpt/point (+ x width) (+ y height)) (gpt/point x (+ y height))]) +(defn rect->lines [{:keys [x y width height]}] + [[(gpt/point x y) (gpt/point (+ x width) y)] + [(gpt/point (+ x width) y) (gpt/point (+ x width) (+ y height))] + [(gpt/point (+ x width) (+ y height)) (gpt/point x (+ y height))] + [(gpt/point x (+ y height)) (gpt/point x y)]]) + (defn points->rect [points] (let [minx (transduce gco/map-x-xf min ##Inf points) diff --git a/frontend/src/app/main/data/workspace/drawing/path.cljs b/frontend/src/app/main/data/workspace/drawing/path.cljs index 494d796af..b0ed2dcac 100644 --- a/frontend/src/app/main/data/workspace/drawing/path.cljs +++ b/frontend/src/app/main/data/workspace/drawing/path.cljs @@ -732,7 +732,23 @@ (-> state (update-in [:workspace-local :edit-path id :selected-handlers] (fnil conj #{}) [index type])))))) -(defn select-node [position] +(defn select-node-area [shift?] + (ptk/reify ::select-node-area + ptk/UpdateEvent + (update [_ state] + (let [selrect (get-in state [:workspace-local :selrect]) + id (get-in state [:workspace-local :edition]) + content (get-in state (get-path state :content)) + selected-point? (fn [point] + (gsh/has-point-rect? selrect point)) + positions (into #{} + (comp (map (comp gpt/point :params)) + (filter selected-point?)) + content)] + (-> state + (assoc-in [:workspace-local :edit-path id :selected-points] positions)))))) + +(defn select-node [position shift?] (ptk/reify ::select-node ptk/UpdateEvent (update [_ state] @@ -740,7 +756,7 @@ (-> state (assoc-in [:workspace-local :edit-path id :selected-points] #{position})))))) -(defn deselect-node [position] +(defn deselect-node [position shift?] (ptk/reify ::deselect-node ptk/UpdateEvent (update [_ state] @@ -858,3 +874,54 @@ (rx/filter #(= % :interrupt)) (rx/take 1) (rx/map #(stop-path-edit)))))))) + + +(defn update-area-selection + [selrect] + (ptk/reify ::update-area-selection + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :selrect] selrect)))) + +(defn clear-area-selection + [] + (ptk/reify ::clear-area-selection + ptk/UpdateEvent + (update [_ state] + (update state :workspace-local dissoc :selrect)))) + +(defn handle-selection + [shift?] + (letfn [(data->selrect [data] + (let [start (:start data) + stop (:stop data) + start-x (min (:x start) (:x stop)) + start-y (min (:y start) (:y stop)) + end-x (max (:x start) (:x stop)) + end-y (max (:y start) (:y stop))] + {:x start-x + :y start-y + :width (mth/abs (- end-x start-x)) + :height (mth/abs (- end-y start-y))}))] + (ptk/reify ::handle-selection + ptk/WatchEvent + (watch [_ state stream] + (let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) + stoper (->> stream (rx/filter stop?))] + (rx/concat + #_(when-not preserve? + (rx/of (deselect-all))) + (->> ms/mouse-position + (rx/scan (fn [data pos] + (if data + (assoc data :stop pos) + {:start pos :stop pos})) + nil) + (rx/map data->selrect) + (rx/filter #(or (> (:width %) 10) + (> (:height %) 10))) + (rx/map update-area-selection) + (rx/take-until stoper)) + (rx/of (select-node-area shift?) + (clear-area-selection)) + #_(rx/of (select-shapes-by-current-selrect preserve?)))))))) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 8761f0a67..6812a5a9f 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -60,9 +60,8 @@ (ptk/reify ::handle-selection ptk/WatchEvent (watch [_ state stream] - (let [stoper (rx/filter #(or (dwc/interrupt? %) - (ms/mouse-up? %)) - stream)] + (let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) + stoper (->> stream (rx/filter stop?))] (rx/concat (when-not preserve? (rx/of (deselect-all))) 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 494761da8..b743e4270 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -15,7 +15,9 @@ [app.util.dom :as dom] [app.util.geom.path :as ugp] [goog.events :as events] - [rumext.alpha :as mf]) + [rumext.alpha :as mf] + + [app.util.keyboard :as kbd]) (:import goog.events.EventType)) (mf/defc path-point [{:keys [position zoom edit-mode hover? selected? preview? start-path? last-p?]}] @@ -35,12 +37,13 @@ (dom/stop-propagation event) (dom/prevent-default event) - (cond - (and (= edit-mode :move) (not selected?)) - (st/emit! (drp/select-node position)) + (let [shift? (kbd/shift? event)] + (cond + (and (= edit-mode :move) (not selected?)) + (st/emit! (drp/select-node position shift?)) - (and (= edit-mode :move) selected?) - (st/emit! (drp/deselect-node position))))) + (and (= edit-mode :move) selected?) + (st/emit! (drp/deselect-node position shift?)))))) on-mouse-down @@ -177,23 +180,24 @@ last-p (->> content last ugp/command->point) handlers (ugp/content->handlers content) - handle-click-outside - (fn [event] - (let [current (dom/get-target event) - editor-dom (mf/ref-val editor-ref)] - (when-not (or (.contains editor-dom current) - (dom/class? current "viewport-actions-entry")) - (st/emit! (drp/deselect-all))))) + ;;handle-click-outside + ;;(fn [event] + ;; (let [current (dom/get-target event) + ;; editor-dom (mf/ref-val editor-ref)] + ;; (when-not (or (.contains editor-dom current) + ;; (dom/class? current "viewport-actions-entry")) + ;; (st/emit! (drp/deselect-all))))) handle-double-click-outside (fn [event] (when (= edit-mode :move) - (st/emit! :interrupt)))] + (st/emit! :interrupt))) + ] (mf/use-layout-effect (mf/deps edit-mode) (fn [] - (let [keys [(events/listen (dom/get-root) EventType.CLICK handle-click-outside) + (let [keys [;;(events/listen (dom/get-root) EventType.CLICK handle-click-outside) (events/listen (dom/get-root) EventType.DBLCLICK handle-double-click-outside)]] #(doseq [key keys] (events/unlistenByKey key))))) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index cff98f588..bd2ccb43c 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -101,7 +101,7 @@ on-click (actions/on-click hover selected edition drawing-path? drawing-tool) on-context-menu (actions/on-context-menu hover) - on-double-click (actions/on-double-click hover hover-ids drawing-path? objects) + on-double-click (actions/on-double-click hover hover-ids drawing-path? objects edition) on-drag-enter (actions/on-drag-enter) on-drag-over (actions/on-drag-over) on-drop (actions/on-drop file viewport-ref zoom) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 1d94d938d..f2d2d203b 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -15,6 +15,7 @@ [app.main.store :as st] [app.main.streams :as ms] [app.main.ui.workspace.viewport.utils :as utils] + [app.main.data.workspace.drawing.path :as dwdp] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] [app.util.keyboard :as kbd] @@ -57,13 +58,16 @@ (st/emit! dw/clear-edition-mode)) (when (and (or (not edition) (not= edition id)) (not blocked) (not hidden) (not (#{:comments :path} drawing-tool))) + (not= edition id)) + (not blocked) + (not hidden)) (cond drawing-tool (st/emit! (dd/start-drawing drawing-tool)) (and edit-path (contains? edit-path edition)) - ;; Handle node select-drawing. NOP at the moment - nil + ;; Handle path node area selection + (st/emit! (dwdp/handle-selection shift?)) (or (not id) (and frame? (not selected?))) (st/emit! (dw/handle-selection shift?)) @@ -142,9 +146,9 @@ (st/emit! (dw/select-shape (:id @hover))))))))) (defn on-double-click - [hover hover-ids drawing-path? objects] + [hover hover-ids drawing-path? objects edition] (mf/use-callback - (mf/deps @hover @hover-ids drawing-path?) + (mf/deps @hover @hover-ids drawing-path? edition) (fn [event] (dom/stop-propagation event) (let [ctrl? (kbd/ctrl? event) @@ -170,7 +174,7 @@ (reset! hover-ids (into [] (rest @hover-ids))) (st/emit! (dw/select-shape (:id selected)))) - (or text? path?) + (and (not= id edition) (or text? path?)) (st/emit! (dw/select-shape id) (dw/start-editing-selected)) From c22b4a1de2c4c2563ba53552aa6cedde9726cd84 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 31 Mar 2021 09:12:00 +0200 Subject: [PATCH 046/155] :sparkles: Allows multiple selection and move --- .../app/main/data/workspace/drawing/path.cljs | 61 +++++++++---------- .../ui/workspace/shapes/path/actions.cljs | 6 +- .../main/ui/workspace/shapes/path/editor.cljs | 41 ++++++------- frontend/src/app/util/geom/path.cljs | 20 +++++- 4 files changed, 72 insertions(+), 56 deletions(-) diff --git a/frontend/src/app/main/data/workspace/drawing/path.cljs b/frontend/src/app/main/data/workspace/drawing/path.cljs index b0ed2dcac..94ff198d7 100644 --- a/frontend/src/app/main/data/workspace/drawing/path.cljs +++ b/frontend/src/app/main/data/workspace/drawing/path.cljs @@ -589,44 +589,43 @@ (= mode :draw) (rx/of :interrupt) :else (rx/of (finish-path "changed-content"))))))) -(defn move-path-point [start-point end-point] - (ptk/reify ::move-point - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state) - content (get-in state (get-path state :content)) +(defn move-selected-path-point [from-point to-point] + (letfn [(modify-content-point [content {dx :x dy :y} modifiers point] + (let [point-indices (ugp/point-indices content point) ;; [indices] + handler-indices (ugp/handler-indices content point) ;; [[index prefix]] - {dx :x dy :y} (gpt/subtract end-point start-point) + modify-point + (fn [modifiers index] + (-> modifiers + (update index assoc :x dx :y dy))) - handler-indices (-> (ugp/content->handlers content) - (get start-point)) + modify-handler + (fn [modifiers [index prefix]] + (let [cx (d/prefix-keyword prefix :x) + cy (d/prefix-keyword prefix :y)] + (-> modifiers + (update index assoc cx dx cy dy))))] - command-for-point (fn [[index command]] - (let [point (ugp/command->point command)] - (= point start-point))) + (as-> modifiers $ + (reduce modify-point $ point-indices) + (reduce modify-handler $ handler-indices))))] - point-indices (->> (d/enumerate content) - (filter command-for-point) - (map first)) + (ptk/reify ::move-point + ptk/UpdateEvent + (update [_ state] + (let [id (get-path-id state) + content (get-in state (get-path state :content)) + delta (gpt/subtract to-point from-point) + modifiers-reducer (partial modify-content-point content delta) - point-reducer (fn [modifiers index] - (-> modifiers - (assoc-in [index :x] dx) - (assoc-in [index :y] dy))) + points (get-in state [:workspace-local :edit-path id :selected-points] #{}) - handler-reducer (fn [modifiers [index prefix]] - (let [cx (d/prefix-keyword prefix :x) - cy (d/prefix-keyword prefix :y)] - (-> modifiers - (assoc-in [index cx] dx) - (assoc-in [index cy] dy)))) + modifiers (get-in state [:workspace-local :edit-path id :content-modifiers] {}) + modifiers (->> points + (reduce modifiers-reducer {}))] - modifiers (as-> (get-in state [:workspace-local :edit-path id :content-modifiers] {}) $ - (reduce point-reducer $ point-indices) - (reduce handler-reducer $ handler-indices))] - - (assoc-in state [:workspace-local :edit-path id :content-modifiers] modifiers))))) + (assoc-in state [:workspace-local :edit-path id :content-modifiers] modifiers)))))) (defn start-move-path-point [position] @@ -641,7 +640,7 @@ (rx/concat (->> ms/mouse-position (rx/take-until stopper) - (rx/map #(move-path-point position %))) + (rx/map #(move-selected-path-point start-position %))) (rx/of (apply-content-modifiers)))))))) (defn start-move-handler diff --git a/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs b/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs index 94b98119c..9a030ddeb 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs @@ -23,11 +23,11 @@ [:div.viewport-actions-entry {:class (when (= edit-mode :move) "is-toggled") :on-click #(st/emit! (drp/change-edit-mode :move))} i/pointer-inner]] - #_[:div.viewport-actions-group + [:div.viewport-actions-group [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-add] [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-remove]] - #_[:div.viewport-actions-group + [:div.viewport-actions-group [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-merge] [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-join] [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-separate]] @@ -40,5 +40,5 @@ :on-click #(when-not (empty? selected-points) (st/emit! (drp/make-curve)))} i/nodes-curve]] - #_[:div.viewport-actions-group + [:div.viewport-actions-group [:div.viewport-actions-entry {:class (when snap-toggled "is-toggled")} i/nodes-snap]]])) 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 b743e4270..a30d29f05 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -33,34 +33,32 @@ on-click (fn [event] - (when-not last-p? - (dom/stop-propagation event) - (dom/prevent-default event) + (dom/stop-propagation event) + (dom/prevent-default event) - (let [shift? (kbd/shift? event)] - (cond - (and (= edit-mode :move) (not selected?)) - (st/emit! (drp/select-node position shift?)) + (let [shift? (kbd/shift? event)] + (cond + (and (= edit-mode :move) (not selected?)) + (st/emit! (drp/select-node position shift?)) - (and (= edit-mode :move) selected?) - (st/emit! (drp/deselect-node position shift?)))))) + (and (= edit-mode :move) selected?) + (st/emit! (drp/deselect-node position shift?))))) on-mouse-down (fn [event] - (when-not last-p? - (dom/stop-propagation event) - (dom/prevent-default event) + (dom/stop-propagation event) + (dom/prevent-default event) - (cond - (= edit-mode :move) - (st/emit! (drp/start-move-path-point position)) + (cond + (= edit-mode :move) + (st/emit! (drp/start-move-path-point position)) - (and (= edit-mode :draw) start-path?) - (st/emit! (drp/start-path-from-point position)) + (and (= edit-mode :draw) start-path?) + (st/emit! (drp/start-path-from-point position)) - (and (= edit-mode :draw) (not start-path?)) - (st/emit! (drp/close-path-drag-start position)))))] + (and (= edit-mode :draw) (not start-path?)) + (st/emit! (drp/close-path-drag-start position))))] [:g.path-point [:circle.path-point @@ -80,8 +78,9 @@ :on-mouse-down on-mouse-down :on-mouse-enter on-enter :on-mouse-leave on-leave - :style {:cursor (cond - (and (not last-p?) (= edit-mode :draw)) cur/pen-node + :style {:pointer-events (when last-p? "none") + :cursor (cond + (= edit-mode :draw) cur/pen-node (= edit-mode :move) cur/pointer-node) :fill "transparent"}}]])) diff --git a/frontend/src/app/util/geom/path.cljs b/frontend/src/app/util/geom/path.cljs index 454b2da20..55f594fb1 100644 --- a/frontend/src/app/util/geom/path.cljs +++ b/frontend/src/app/util/geom/path.cljs @@ -468,7 +468,6 @@ [content] (->> (d/with-prev content) (d/enumerate) - (mapcat (fn [[index [cur-cmd pre-cmd]]] (if (and pre-cmd (= :curve-to (:command cur-cmd))) (let [cur-pos (command->point cur-cmd) @@ -480,6 +479,25 @@ (group-by first) (d/mapm #(mapv second %2)))) +(defn point-indices + [content point] + (->> (d/enumerate content) + (filter (fn [[_ cmd]] (= point (command->point cmd)))) + (mapv (fn [[index _]] index)))) + +(defn handler-indices + [content point] + (->> (d/with-prev content) + (d/enumerate) + (mapcat (fn [[index [cur-cmd pre-cmd]]] + (if (and (some? pre-cmd) (= :curve-to (:command cur-cmd))) + (let [cur-pos (command->point cur-cmd) + pre-pos (command->point pre-cmd)] + (cond-> [] + (= pre-pos point) (conj [index :c1]) + (= cur-pos point) (conj [index :c2]))) + []))))) + (defn opposite-index "Calculate sthe opposite index given a prefix and an index" [content index prefix] From 2e6dacf539c2870916466dbe783eccb8e414bafb Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 31 Mar 2021 20:14:51 +0200 Subject: [PATCH 047/155] :recycle: Refactor path into modules --- frontend/src/app/main/data/workspace.cljs | 2 +- .../src/app/main/data/workspace/common.cljs | 33 +- .../src/app/main/data/workspace/drawing.cljs | 2 +- .../app/main/data/workspace/drawing/path.cljs | 926 ------------------ .../src/app/main/data/workspace/path.cljs | 39 + .../app/main/data/workspace/path/changes.cljs | 71 ++ .../app/main/data/workspace/path/common.cljs | 28 + .../app/main/data/workspace/path/drawing.cljs | 337 +++++++ .../app/main/data/workspace/path/edition.cljs | 216 ++++ .../app/main/data/workspace/path/helpers.cljs | 123 +++ .../main/data/workspace/path/selection.cljs | 157 +++ .../app/main/data/workspace/path/spec.cljs | 52 + .../app/main/data/workspace/path/state.cljs | 32 + .../app/main/data/workspace/path/streams.cljs | 54 + .../app/main/data/workspace/path/tools.cljs | 42 + .../ui/workspace/shapes/path/actions.cljs | 2 +- .../main/ui/workspace/shapes/path/editor.cljs | 96 +- .../main/ui/workspace/viewport/actions.cljs | 2 +- 18 files changed, 1213 insertions(+), 1001 deletions(-) delete mode 100644 frontend/src/app/main/data/workspace/drawing/path.cljs create mode 100644 frontend/src/app/main/data/workspace/path.cljs create mode 100644 frontend/src/app/main/data/workspace/path/changes.cljs create mode 100644 frontend/src/app/main/data/workspace/path/common.cljs create mode 100644 frontend/src/app/main/data/workspace/path/drawing.cljs create mode 100644 frontend/src/app/main/data/workspace/path/edition.cljs create mode 100644 frontend/src/app/main/data/workspace/path/helpers.cljs create mode 100644 frontend/src/app/main/data/workspace/path/selection.cljs create mode 100644 frontend/src/app/main/data/workspace/path/spec.cljs create mode 100644 frontend/src/app/main/data/workspace/path/state.cljs create mode 100644 frontend/src/app/main/data/workspace/path/streams.cljs create mode 100644 frontend/src/app/main/data/workspace/path/tools.cljs diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index b5ce81ae2..5b629986d 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -24,7 +24,7 @@ [app.main.data.messages :as dm] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.drawing :as dwd] - [app.main.data.workspace.drawing.path :as dwdp] + [app.main.data.workspace.path :as dwdp] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.notifications :as dwn] diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index afec9744c..e993573bf 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -360,25 +360,30 @@ (ptk/reify ::undo ptk/WatchEvent (watch [_ state stream] - (let [undo (:workspace-undo state) - items (:items undo) - index (or (:index undo) (dec (count items)))] - (when-not (or (empty? items) (= index -1)) - (let [changes (get-in items [index :undo-changes])] - (rx/of (materialize-undo changes (dec index)) - (commit-changes changes [] {:save-undo? false})))))))) + (let [edition (get-in state [:workspace-local :edition])] + ;; Editors handle their own undo's + (when-not (some? edition) + (let [undo (:workspace-undo state) + items (:items undo) + index (or (:index undo) (dec (count items)))] + (when-not (or (empty? items) (= index -1)) + (let [changes (get-in items [index :undo-changes])] + (rx/of (materialize-undo changes (dec index)) + (commit-changes changes [] {:save-undo? false})))))))))) (def redo (ptk/reify ::redo ptk/WatchEvent (watch [_ state stream] - (let [undo (:workspace-undo state) - items (:items undo) - index (or (:index undo) (dec (count items)))] - (when-not (or (empty? items) (= index (dec (count items)))) - (let [changes (get-in items [(inc index) :redo-changes])] - (rx/of (materialize-undo changes (inc index)) - (commit-changes changes [] {:save-undo? false})))))))) + (let [edition (get-in state [:workspace-local :edition])] + (when-not (some? edition) + (let [undo (:workspace-undo state) + items (:items undo) + index (or (:index undo) (dec (count items)))] + (when-not (or (empty? items) (= index (dec (count items)))) + (let [changes (get-in items [(inc index) :redo-changes])] + (rx/of (materialize-undo changes (inc index)) + (commit-changes changes [] {:save-undo? false})))))))))) (def reinitialize-undo (ptk/reify ::reset-undo diff --git a/frontend/src/app/main/data/workspace/drawing.cljs b/frontend/src/app/main/data/workspace/drawing.cljs index 9c011c373..553b31038 100644 --- a/frontend/src/app/main/data/workspace/drawing.cljs +++ b/frontend/src/app/main/data/workspace/drawing.cljs @@ -14,8 +14,8 @@ [app.common.uuid :as uuid] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.path :as path] [app.main.data.workspace.drawing.common :as common] - [app.main.data.workspace.drawing.path :as path] [app.main.data.workspace.drawing.curve :as curve] [app.main.data.workspace.drawing.box :as box])) diff --git a/frontend/src/app/main/data/workspace/drawing/path.cljs b/frontend/src/app/main/data/workspace/drawing/path.cljs deleted file mode 100644 index 94ff198d7..000000000 --- a/frontend/src/app/main/data/workspace/drawing/path.cljs +++ /dev/null @@ -1,926 +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.main.data.workspace.drawing.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.common.math :as mth] - [app.common.pages :as cp] - [app.common.spec :as us] - [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.drawing.common :as common] - [app.main.store :as st] - [app.main.streams :as ms] - [app.util.geom.path :as ugp] - [beicon.core :as rx] - [clojure.spec.alpha :as s] - [potok.core :as ptk])) - -;; SCHEMAS - -(s/def ::command #{:move-to - :line-to - :line-to-horizontal - :line-to-vertical - :curve-to - :smooth-curve-to - :quadratic-bezier-curve-to - :smooth-quadratic-bezier-curve-to - :elliptical-arc - :close-path}) - -(s/def :paths.params/x number?) -(s/def :paths.params/y number?) -(s/def :paths.params/c1x number?) -(s/def :paths.params/c1y number?) -(s/def :paths.params/c2x number?) -(s/def :paths.params/c2y number?) - -(s/def ::relative? boolean?) - -(s/def ::params - (s/keys :req-un [:path.params/x - :path.params/y] - :opt-un [:path.params/c1x - :path.params/c1y - :path.params/c2x - :path.params/c2y])) - -(s/def ::content-entry - (s/keys :req-un [::command] - :req-opt [::params - ::relative?])) -(s/def ::content - (s/coll-of ::content-entry :kind vector?)) - - -;; CONSTANTS -(defonce enter-keycode 13) -(defonce drag-threshold 5) - -;; PRIVATE METHODS - -(defn get-path-id - "Retrieves the currently editing path id" - [state] - (or (get-in state [:workspace-local :edition]) - (get-in state [:workspace-drawing :object :id]))) - -(defn get-path - "Retrieves the location of the path object and additionaly can pass - the arguments. This location can be used in get-in, assoc-in... functions" - [state & path] - (let [edit-id (get-in state [:workspace-local :edition]) - page-id (:current-page-id state)] - (d/concat - (if edit-id - [:workspace-data :pages-index page-id :objects edit-id] - [:workspace-drawing :object]) - path))) - -(defn- points->components [shape content] - (let [transform (:transform shape (gmt/matrix)) - transform-inverse (:transform-inverse shape (gmt/matrix)) - center (gsh/center-shape shape) - base-content (gsh/transform-content - content - (gmt/transform-in center transform-inverse)) - - ;; Calculates the new selrect with points given the old center - points (-> (gsh/content->selrect base-content) - (gsh/rect->points) - (gsh/transform-points center (:transform shape (gmt/matrix)))) - - points-center (gsh/center-points points) - - ;; Points is now the selrect but the center is different so we can create the selrect - ;; through points - selrect (-> points - (gsh/transform-points points-center (:transform-inverse shape (gmt/matrix))) - (gsh/points->selrect))] - [points selrect])) - -(defn update-selrect - "Updates the selrect and points for a path" - [shape] - (if (= (:rotation shape 0) 0) - (let [content (:content shape) - selrect (gsh/content->selrect content) - points (gsh/rect->points selrect)] - (assoc shape :points points :selrect selrect)) - - (let [content (:content shape) - [points selrect] (points->components shape content)] - (assoc shape :points points :selrect selrect)))) - -(defn closest-angle [angle] - (cond - (or (> angle 337.5) (<= angle 22.5)) 0 - (and (> angle 22.5) (<= angle 67.5)) 45 - (and (> angle 67.5) (<= angle 112.5)) 90 - (and (> angle 112.5) (<= angle 157.5)) 135 - (and (> angle 157.5) (<= angle 202.5)) 180 - (and (> angle 202.5) (<= angle 247.5)) 225 - (and (> angle 247.5) (<= angle 292.5)) 270 - (and (> angle 292.5) (<= angle 337.5)) 315)) - -(defn position-fixed-angle [point from-point] - (if (and from-point point) - (let [angle (mod (+ 360 (- (gpt/angle point from-point))) 360) - to-angle (closest-angle angle) - distance (gpt/distance point from-point)] - (gpt/angle->point from-point (mth/radians to-angle) distance)) - point)) - -(defn next-node - "Calculates the next-node to be inserted." - [shape position prev-point prev-handler] - (let [last-command (-> shape :content last :command) - add-line? (and prev-point (not prev-handler) (not= last-command :close-path)) - add-curve? (and prev-point prev-handler (not= last-command :close-path))] - (cond - add-line? {:command :line-to - :params position} - add-curve? {:command :curve-to - :params (ugp/make-curve-params position prev-handler)} - :else {:command :move-to - :params position}))) - -(defn append-node - "Creates a new node in the path. Usualy used when drawing." - [shape position prev-point prev-handler] - (let [command (next-node shape position prev-point prev-handler)] - (-> shape - (update :content (fnil conj []) command) - (update-selrect)))) - -(defn move-handler-modifiers [content index prefix match-opposite? dx dy] - (let [[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y]) - [ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y]) - opposite-index (ugp/opposite-index content index prefix)] - - (cond-> {} - :always - (update index assoc cx dx cy dy) - - (and match-opposite? opposite-index) - (update opposite-index assoc ocx (- dx) ocy (- dy))))) - -(defn end-path-event? [{:keys [type shift] :as event}] - (or (= (ptk/type event) ::finish-path) - (= (ptk/type event) :esc-pressed) - (= event :interrupt) ;; ESC - (and (ms/mouse-double-click? event)))) - -(defn generate-path-changes [page-id shape old-content new-content] - (us/verify ::content old-content) - (us/verify ::content new-content) - (let [shape-id (:id shape) - [old-points old-selrect] (points->components shape old-content) - [new-points new-selrect] (points->components shape new-content) - - rch [{:type :mod-obj - :id shape-id - :page-id page-id - :operations [{:type :set :attr :content :val new-content} - {:type :set :attr :selrect :val new-selrect} - {:type :set :attr :points :val new-points}]} - {:type :reg-objects - :page-id page-id - :shapes [shape-id]}] - - uch [{:type :mod-obj - :id shape-id - :page-id page-id - :operations [{:type :set :attr :content :val old-content} - {:type :set :attr :selrect :val old-selrect} - {:type :set :attr :points :val old-points}]} - {:type :reg-objects - :page-id page-id - :shapes [shape-id]}]] - [rch uch])) - -(defn clean-edit-state - [state] - (dissoc state :last-point :prev-handler :drag-handler :preview)) - -(defn dragging? [start zoom] - (fn [current] - (>= (gpt/distance start current) (/ drag-threshold zoom)))) - -(defn drag-stream [to-stream] - (let [start @ms/mouse-position - zoom (get-in @st/state [:workspace-local :zoom] 1) - mouse-up (->> st/stream (rx/filter #(ms/mouse-up? %)))] - (->> ms/mouse-position - (rx/take-until mouse-up) - (rx/filter (dragging? start zoom)) - (rx/take 1) - (rx/merge-map (fn [] to-stream))))) - -(defn position-stream [] - (->> ms/mouse-position - (rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %)))) - (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %)))))) - -;; EVENTS - -(defn init-path [] - (ptk/reify ::init-path)) - -(defn finish-path [source] - (ptk/reify ::finish-path - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state)] - (-> state - (update-in [:workspace-local :edit-path id] clean-edit-state)))))) - -(defn preview-next-point [{:keys [x y shift?]}] - (ptk/reify ::preview-next-point - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state) - fix-angle? shift? - last-point (get-in state [:workspace-local :edit-path id :last-point]) - position (cond-> (gpt/point x y) - fix-angle? (position-fixed-angle last-point)) - shape (get-in state (get-path state)) - {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id]) - command (next-node shape position last-point prev-handler)] - (assoc-in state [:workspace-local :edit-path id :preview] command))))) - -(defn add-node [{:keys [x y shift?]}] - (ptk/reify ::add-node - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state) - fix-angle? shift? - {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id]) - position (cond-> (gpt/point x y) - fix-angle? (position-fixed-angle last-point))] - (if-not (= last-point position) - (-> state - (assoc-in [:workspace-local :edit-path id :last-point] position) - (update-in [:workspace-local :edit-path id] dissoc :prev-handler) - (update-in [:workspace-local :edit-path id] dissoc :preview) - (update-in (get-path state) append-node position last-point prev-handler)) - state))))) - -(defn start-drag-handler [] - (ptk/reify ::start-drag-handler - ptk/UpdateEvent - (update [_ state] - (let [content (get-in state (get-path state :content)) - index (dec (count content)) - command (get-in state (get-path state :content index :command)) - - make-curve - (fn [command] - (let [params (ugp/make-curve-params - (get-in content [index :params]) - (get-in content [(dec index) :params]))] - (-> command - (assoc :command :curve-to :params params))))] - - (cond-> state - (= command :line-to) - (update-in (get-path state :content index) make-curve)))))) - -(defn drag-handler [{:keys [x y alt? shift?]}] - (ptk/reify ::drag-handler - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state) - shape (get-in state (get-path state)) - content (:content shape) - index (dec (count content)) - node-position (ugp/command->point (nth content index)) - handler-position (cond-> (gpt/point x y) - shift? (position-fixed-angle node-position)) - {dx :x dy :y} (gpt/subtract handler-position node-position) - match-opposite? (not alt?) - modifiers (move-handler-modifiers content (inc index) :c1 match-opposite? dx dy)] - (-> state - (update-in [:workspace-local :edit-path id :content-modifiers] merge modifiers) - (assoc-in [:workspace-local :edit-path id :prev-handler] handler-position) - (assoc-in [:workspace-local :edit-path id :drag-handler] handler-position)))))) - -(defn finish-drag [] - (ptk/reify ::finish-drag - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state) - modifiers (get-in state [:workspace-local :edit-path id :content-modifiers]) - handler (get-in state [:workspace-local :edit-path id :drag-handler])] - (-> state - (update-in (get-path state :content) ugp/apply-content-modifiers modifiers) - (update-in [:workspace-local :edit-path id] dissoc :drag-handler) - (update-in [:workspace-local :edit-path id] dissoc :content-modifiers) - (assoc-in [:workspace-local :edit-path id :prev-handler] handler) - (update-in (get-path state) update-selrect)))) - - ptk/WatchEvent - (watch [_ state stream] - (let [id (get-path-id state) - handler (get-in state [:workspace-local :edit-path id :prev-handler])] - ;; Update the preview because can be outdated after the dragging - (rx/of (preview-next-point handler)))))) - -(declare close-path-drag-end) - -(defn close-path-drag-start [position] - (ptk/reify ::close-path-drag-start - ptk/WatchEvent - (watch [_ state stream] - (let [id (get-path-id state) - zoom (get-in state [:workspace-local :zoom]) - start-position @ms/mouse-position - - stop-stream - (->> stream (rx/filter #(or (end-path-event? %) - (ms/mouse-up? %)))) - - drag-events-stream - (->> (position-stream) - (rx/take-until stop-stream) - (rx/map #(drag-handler %)))] - - (rx/concat - (rx/of (add-node position)) - (drag-stream - (rx/concat - (rx/of (start-drag-handler)) - drag-events-stream - (rx/of (finish-drag)) - (rx/of (close-path-drag-end)))) - (rx/of (finish-path "close-path"))))))) - -(defn close-path-drag-end [] - (ptk/reify ::close-path-drag-end - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state)] - (update-in state [:workspace-local :edit-path id] dissoc :prev-handler))))) - -(defn path-pointer-enter [position] - (ptk/reify ::path-pointer-enter - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state)] - (update-in state [:workspace-local :edit-path id :hover-points] (fnil conj #{}) position))))) - -(defn path-pointer-leave [position] - (ptk/reify ::path-pointer-leave - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state)] - (update-in state [:workspace-local :edit-path id :hover-points] disj position))))) - -(defn start-path-from-point [position] - (ptk/reify ::start-path-from-point - ptk/WatchEvent - (watch [_ state stream] - (let [start-point @ms/mouse-position - zoom (get-in state [:workspace-local :zoom]) - mouse-up (->> stream (rx/filter #(or (end-path-event? %) - (ms/mouse-up? %)))) - drag-events (->> ms/mouse-position - (rx/take-until mouse-up) - (rx/map #(drag-handler %)))] - - (rx/concat - (rx/of (add-node position)) - (drag-stream - (rx/concat - (rx/of (start-drag-handler)) - drag-events - (rx/of (finish-drag))))))))) - -(defn make-corner [] - (ptk/reify ::make-corner - ptk/WatchEvent - (watch [_ state stream] - (let [id (get-path-id state) - page-id (:current-page-id state) - shape (get-in state (get-path state)) - selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) - new-content (reduce ugp/make-corner-point (:content shape) selected-points) - [rch uch] (generate-path-changes page-id shape (:content shape) new-content)] - (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) - -(defn make-curve [] - (ptk/reify ::make-curve - ptk/WatchEvent - (watch [_ state stream] - (let [id (get-path-id state) - page-id (:current-page-id state) - shape (get-in state (get-path state)) - selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) - new-content (reduce ugp/make-curve-point (:content shape) selected-points) - [rch uch] (generate-path-changes page-id shape (:content shape) new-content)] - (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) - -(defn path-handler-enter [index prefix] - (ptk/reify ::path-handler-enter - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state)] - (update-in state [:workspace-local :edit-path id :hover-handlers] (fnil conj #{}) [index prefix]))))) - -(defn path-handler-leave [index prefix] - (ptk/reify ::path-handler-leave - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state)] - (update-in state [:workspace-local :edit-path id :hover-handlers] disj [index prefix]))))) - -;; EVENT STREAMS - -(defn make-drag-stream - [stream down-event zoom] - (let [mouse-up (->> stream (rx/filter #(or (end-path-event? %) - (ms/mouse-up? %)))) - drag-events (->> (position-stream) - (rx/take-until mouse-up) - (rx/map #(drag-handler %)))] - - (rx/concat - (rx/of (add-node down-event)) - (drag-stream - (rx/concat - (rx/of (start-drag-handler)) - drag-events - (rx/of (finish-drag))))))) - -(defn make-node-events-stream - [stream] - (->> stream - (rx/filter (ptk/type? ::close-path-drag-start)) - (rx/take 1) - (rx/merge-map #(rx/empty)))) - -;; MAIN ENTRIES - -(defn handle-drawing-path - [id] - (ptk/reify ::handle-drawing-path - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state)] - (-> state - (assoc-in [:workspace-local :edit-path id :edit-mode] :draw)))) - - ptk/WatchEvent - (watch [_ state stream] - (let [zoom (get-in state [:workspace-local :zoom]) - mouse-down (->> stream (rx/filter ms/mouse-down?)) - end-path-events (->> stream (rx/filter end-path-event?)) - - ;; Mouse move preview - mousemove-events - (->> (position-stream) - (rx/take-until end-path-events) - (rx/map #(preview-next-point %))) - - ;; From mouse down we can have: click, drag and double click - mousedown-events - (->> mouse-down - (rx/take-until end-path-events) - (rx/with-latest merge (position-stream)) - - ;; We change to the stream that emits the first event - (rx/switch-map - #(rx/race (make-node-events-stream stream) - (make-drag-stream stream % zoom))))] - - (rx/concat - (rx/of (init-path)) - (rx/merge mousemove-events - mousedown-events) - (rx/of (finish-path "after-events"))))))) - - - -(defn modify-point [index prefix dx dy] - (ptk/reify ::modify-point - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition]) - [cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])] - (-> state - (update-in [:workspace-local :edit-path id :content-modifiers (inc index)] assoc - :c1x dx :c1y dy) - (update-in [:workspace-local :edit-path id :content-modifiers index] assoc - :x dx :y dy :c2x dx :c2y dy)))))) - -(defn modify-handler [id index prefix dx dy match-opposite?] - (ptk/reify ::modify-point - ptk/UpdateEvent - (update [_ state] - (let [content (get-in state (get-path state :content)) - [cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y]) - [ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y]) - opposite-index (ugp/opposite-index content index prefix)] - (cond-> state - :always - (update-in [:workspace-local :edit-path id :content-modifiers index] assoc - cx dx cy dy) - - (and match-opposite? opposite-index) - (update-in [:workspace-local :edit-path id :content-modifiers opposite-index] assoc - ocx (- dx) ocy (- dy))))))) - -(defn apply-content-modifiers [] - (ptk/reify ::apply-content-modifiers - ptk/WatchEvent - (watch [_ state stream] - (let [id (get-path-id state) - page-id (:current-page-id state) - shape (get-in state (get-path state)) - content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers]) - new-content (ugp/apply-content-modifiers (:content shape) content-modifiers) - [rch uch] (generate-path-changes page-id shape (:content shape) new-content)] - - (rx/of (dwc/commit-changes rch uch {:commit-local? true}) - (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers))))))) - -(defn save-path-content [] - (ptk/reify ::save-path-content - ptk/UpdateEvent - (update [_ state] - (let [content (get-in state (get-path state :content)) - content (if (= (-> content last :command) :move-to) - (into [] (take (dec (count content)) content)) - content)] - (assoc-in state (get-path state :content) content))) - - ptk/WatchEvent - (watch [_ state stream] - (let [id (get-in state [:workspace-local :edition]) - old-content (get-in state [:workspace-local :edit-path id :old-content])] - (if (some? old-content) - (let [shape (get-in state (get-path state)) - page-id (:current-page-id state) - [rch uch] (generate-path-changes page-id shape old-content (:content shape))] - (rx/of (dwc/commit-changes rch uch {:commit-local? true}))) - (rx/empty)))))) - -(declare start-draw-mode) -(defn check-changed-content [] - (ptk/reify ::check-changed-content - ptk/WatchEvent - (watch [_ state stream] - (let [id (get-path-id state) - content (get-in state (get-path state :content)) - old-content (get-in state [:workspace-local :edit-path id :old-content]) - mode (get-in state [:workspace-local :edit-path id :edit-mode])] - - (cond - (not= content old-content) (rx/of (save-path-content) - (start-draw-mode)) - (= mode :draw) (rx/of :interrupt) - :else (rx/of (finish-path "changed-content"))))))) - -(defn move-selected-path-point [from-point to-point] - (letfn [(modify-content-point [content {dx :x dy :y} modifiers point] - (let [point-indices (ugp/point-indices content point) ;; [indices] - handler-indices (ugp/handler-indices content point) ;; [[index prefix]] - - modify-point - (fn [modifiers index] - (-> modifiers - (update index assoc :x dx :y dy))) - - modify-handler - (fn [modifiers [index prefix]] - (let [cx (d/prefix-keyword prefix :x) - cy (d/prefix-keyword prefix :y)] - (-> modifiers - (update index assoc cx dx cy dy))))] - - (as-> modifiers $ - (reduce modify-point $ point-indices) - (reduce modify-handler $ handler-indices))))] - - (ptk/reify ::move-point - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state) - content (get-in state (get-path state :content)) - delta (gpt/subtract to-point from-point) - - modifiers-reducer (partial modify-content-point content delta) - - points (get-in state [:workspace-local :edit-path id :selected-points] #{}) - - modifiers (get-in state [:workspace-local :edit-path id :content-modifiers] {}) - modifiers (->> points - (reduce modifiers-reducer {}))] - - (assoc-in state [:workspace-local :edit-path id :content-modifiers] modifiers)))))) - -(defn start-move-path-point - [position] - (ptk/reify ::start-move-path-point - ptk/WatchEvent - (watch [_ state stream] - (let [start-position @ms/mouse-position - stopper (->> stream (rx/filter ms/mouse-up?)) - zoom (get-in state [:workspace-local :zoom])] - - (drag-stream - (rx/concat - (->> ms/mouse-position - (rx/take-until stopper) - (rx/map #(move-selected-path-point start-position %))) - (rx/of (apply-content-modifiers)))))))) - -(defn start-move-handler - [index prefix] - (ptk/reify ::start-move-handler - ptk/WatchEvent - (watch [_ state stream] - (let [id (get-in state [:workspace-local :edition]) - cx (d/prefix-keyword prefix :x) - cy (d/prefix-keyword prefix :y) - start-point @ms/mouse-position - modifiers (get-in state [:workspace-local :edit-path id :content-modifiers]) - start-delta-x (get-in modifiers [index cx] 0) - start-delta-y (get-in modifiers [index cy] 0) - - content (get-in state (get-path state :content)) - opposite-index (ugp/opposite-index content index prefix) - opposite-prefix (if (= prefix :c1) :c2 :c1) - opposite-handler (-> content (get opposite-index) (ugp/get-handler opposite-prefix)) - - point (-> content (get (if (= prefix :c1) (dec index) index)) (ugp/command->point)) - handler (-> content (get index) (ugp/get-handler prefix)) - - current-distance (when opposite-handler (gpt/distance (ugp/opposite-handler point handler) opposite-handler)) - match-opposite? (and opposite-handler (mth/almost-zero? current-distance))] - - (drag-stream - (rx/concat - (->> (position-stream) - (rx/take-until (->> stream (rx/filter ms/mouse-up?))) - (rx/map - (fn [{:keys [x y alt? shift?]}] - (let [pos (cond-> (gpt/point x y) - shift? (position-fixed-angle point))] - (modify-handler - id - index - prefix - (+ start-delta-x (- (:x pos) (:x start-point))) - (+ start-delta-y (- (:y pos) (:y start-point))) - (and (not alt?) match-opposite?)))))) - (rx/concat (rx/of (apply-content-modifiers))))))))) - -(defn start-draw-mode [] - (ptk/reify ::start-draw-mode - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition]) - page-id (:current-page-id state) - old-content (get-in state [:workspace-data :pages-index page-id :objects id :content])] - (-> state - (assoc-in [:workspace-local :edit-path id :old-content] old-content)))) - - ptk/WatchEvent - (watch [_ state stream] - (let [id (get-in state [:workspace-local :edition]) - edit-mode (get-in state [:workspace-local :edit-path id :edit-mode])] - (if (= :draw edit-mode) - (rx/concat - (rx/of (handle-drawing-path id)) - (->> stream - (rx/filter (ptk/type? ::finish-path)) - (rx/take 1) - (rx/merge-map #(rx/of (check-changed-content))))) - (rx/empty)))))) - -(defn change-edit-mode [mode] - (ptk/reify ::change-edit-mode - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition])] - (cond-> state - id (assoc-in [:workspace-local :edit-path id :edit-mode] mode)))) - - ptk/WatchEvent - (watch [_ state stream] - (let [id (get-path-id state)] - (cond - (and id (= :move mode)) (rx/of (finish-path "change-edit-mode")) - (and id (= :draw mode)) (rx/of (start-draw-mode)) - :else (rx/empty)))))) - -(defn select-handler [index type] - (ptk/reify ::select-handler - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition])] - (-> state - (update-in [:workspace-local :edit-path id :selected-handlers] (fnil conj #{}) [index type])))))) - -(defn select-node-area [shift?] - (ptk/reify ::select-node-area - ptk/UpdateEvent - (update [_ state] - (let [selrect (get-in state [:workspace-local :selrect]) - id (get-in state [:workspace-local :edition]) - content (get-in state (get-path state :content)) - selected-point? (fn [point] - (gsh/has-point-rect? selrect point)) - positions (into #{} - (comp (map (comp gpt/point :params)) - (filter selected-point?)) - content)] - (-> state - (assoc-in [:workspace-local :edit-path id :selected-points] positions)))))) - -(defn select-node [position shift?] - (ptk/reify ::select-node - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition])] - (-> state - (assoc-in [:workspace-local :edit-path id :selected-points] #{position})))))) - -(defn deselect-node [position shift?] - (ptk/reify ::deselect-node - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition])] - (-> state - (update-in [:workspace-local :edit-path id :selected-points] (fnil disj #{}) position)))))) - -(defn add-to-selection-handler [index type] - (ptk/reify ::add-to-selection-handler - ptk/UpdateEvent - (update [_ state] - state))) - -(defn add-to-selection-node [index] - (ptk/reify ::add-to-selection-node - ptk/UpdateEvent - (update [_ state] - state))) - -(defn remove-from-selection-handler [index] - (ptk/reify ::remove-from-selection-handler - ptk/UpdateEvent - (update [_ state] - state))) - -(defn remove-from-selection-node [index] - (ptk/reify ::remove-from-selection-handler - ptk/UpdateEvent - (update [_ state] - state))) - -(defn deselect-all [] - (ptk/reify ::deselect-all - ptk/UpdateEvent - (update [_ state] - (let [id (get-path-id state)] - (-> state - (assoc-in [:workspace-local :edit-path id :selected-handlers] #{}) - (assoc-in [:workspace-local :edit-path id :selected-points] #{})))))) - -(defn setup-frame-path [] - (ptk/reify ::setup-frame-path - ptk/UpdateEvent - (update [_ state] - - (let [objects (dwc/lookup-page-objects state) - content (get-in state [:workspace-drawing :object :content] []) - position (get-in content [0 :params] nil) - frame-id (cp/frame-id-by-position objects position)] - (-> state - (assoc-in [:workspace-drawing :object :frame-id] frame-id)))))) - -(defn handle-new-shape-result [shape-id] - (ptk/reify ::handle-new-shape-result - ptk/UpdateEvent - (update [_ state] - (let [content (get-in state [:workspace-drawing :object :content] [])] - (us/verify ::content content) - (if (> (count content) 1) - (assoc-in state [:workspace-drawing :object :initialized?] true) - state))) - - ptk/WatchEvent - (watch [_ state stream] - (->> (rx/of (setup-frame-path) - common/handle-finish-drawing - (dwc/start-edition-mode shape-id) - (change-edit-mode :draw)))))) - -(defn handle-new-shape - "Creates a new path shape" - [] - (ptk/reify ::handle-new-shape - ptk/WatchEvent - (watch [_ state stream] - (let [shape-id (get-in state [:workspace-drawing :object :id])] - (rx/concat - (rx/of (handle-drawing-path shape-id)) - (->> stream - (rx/filter (ptk/type? ::finish-path)) - (rx/take 1) - (rx/observe-on :async) - (rx/map #(handle-new-shape-result shape-id)))))))) - -(defn stop-path-edit [] - (ptk/reify ::stop-path-edit - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition])] - (update state :workspace-local dissoc :edit-path id))))) - -(defn start-path-edit - [id] - (ptk/reify ::start-path-edit - ptk/UpdateEvent - (update [_ state] - (let [edit-path (get-in state [:workspace-local :edit-path id])] - - (cond-> state - (or (not edit-path) (= :draw (:edit-mode edit-path))) - (assoc-in [:workspace-local :edit-path id] {:edit-mode :move - :selected #{} - :snap-toggled true}) - - (and (some? edit-path) (= :move (:edit-mode edit-path))) - (assoc-in [:workspace-local :edit-path id :edit-mode] :draw)))) - - ptk/WatchEvent - (watch [_ state stream] - (let [mode (get-in state [:workspace-local :edit-path id :edit-mode])] - (rx/concat - (rx/of (change-edit-mode mode)) - (->> stream - (rx/take-until (->> stream (rx/filter (ptk/type? ::start-path-edit)))) - (rx/filter #(= % :interrupt)) - (rx/take 1) - (rx/map #(stop-path-edit)))))))) - - -(defn update-area-selection - [selrect] - (ptk/reify ::update-area-selection - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-local :selrect] selrect)))) - -(defn clear-area-selection - [] - (ptk/reify ::clear-area-selection - ptk/UpdateEvent - (update [_ state] - (update state :workspace-local dissoc :selrect)))) - -(defn handle-selection - [shift?] - (letfn [(data->selrect [data] - (let [start (:start data) - stop (:stop data) - start-x (min (:x start) (:x stop)) - start-y (min (:y start) (:y stop)) - end-x (max (:x start) (:x stop)) - end-y (max (:y start) (:y stop))] - {:x start-x - :y start-y - :width (mth/abs (- end-x start-x)) - :height (mth/abs (- end-y start-y))}))] - (ptk/reify ::handle-selection - ptk/WatchEvent - (watch [_ state stream] - (let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) - stoper (->> stream (rx/filter stop?))] - (rx/concat - #_(when-not preserve? - (rx/of (deselect-all))) - (->> ms/mouse-position - (rx/scan (fn [data pos] - (if data - (assoc data :stop pos) - {:start pos :stop pos})) - nil) - (rx/map data->selrect) - (rx/filter #(or (> (:width %) 10) - (> (:height %) 10))) - (rx/map update-area-selection) - (rx/take-until stoper)) - (rx/of (select-node-area shift?) - (clear-area-selection)) - #_(rx/of (select-shapes-by-current-selrect preserve?)))))))) diff --git a/frontend/src/app/main/data/workspace/path.cljs b/frontend/src/app/main/data/workspace/path.cljs new file mode 100644 index 000000000..714775131 --- /dev/null +++ b/frontend/src/app/main/data/workspace/path.cljs @@ -0,0 +1,39 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.path + (:require + [app.common.data :as d] + [app.main.data.workspace.path.drawing :as drawing] + [app.main.data.workspace.path.edition :as edition] + [app.main.data.workspace.path.selection :as selection] + [app.main.data.workspace.path.tools :as tools])) + +;; Drawing +(d/export drawing/handle-new-shape) +(d/export drawing/start-path-from-point) +(d/export drawing/close-path-drag-start) +(d/export drawing/change-edit-mode) + +;; Edition +(d/export edition/start-move-handler) +(d/export edition/start-move-path-point) +(d/export edition/start-path-edit) + +;; Selection +(d/export selection/select-handler) +(d/export selection/handle-selection) +(d/export selection/path-handler-enter) +(d/export selection/path-handler-leave) +(d/export selection/path-pointer-enter) +(d/export selection/path-pointer-leave) + +;; Path tools +(d/export tools/make-curve) +(d/export tools/make-corner) diff --git a/frontend/src/app/main/data/workspace/path/changes.cljs b/frontend/src/app/main/data/workspace/path/changes.cljs new file mode 100644 index 000000000..53fdb6c9f --- /dev/null +++ b/frontend/src/app/main/data/workspace/path/changes.cljs @@ -0,0 +1,71 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.path.changes + (:require + [app.common.spec :as us] + [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.path.helpers :as helpers] + [app.main.data.workspace.path.spec :as spec] + [app.main.data.workspace.path.state :as st] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defn generate-path-changes + "Generates content changes and the undos for the content given" + [page-id shape old-content new-content] + (us/verify ::spec/content old-content) + (us/verify ::spec/content new-content) + (let [shape-id (:id shape) + [old-points old-selrect] (helpers/content->points+selrect shape old-content) + [new-points new-selrect] (helpers/content->points+selrect shape new-content) + + rch [{:type :mod-obj + :id shape-id + :page-id page-id + :operations [{:type :set :attr :content :val new-content} + {:type :set :attr :selrect :val new-selrect} + {:type :set :attr :points :val new-points}]} + {:type :reg-objects + :page-id page-id + :shapes [shape-id]}] + + uch [{:type :mod-obj + :id shape-id + :page-id page-id + :operations [{:type :set :attr :content :val old-content} + {:type :set :attr :selrect :val old-selrect} + {:type :set :attr :points :val old-points}]} + {:type :reg-objects + :page-id page-id + :shapes [shape-id]}]] + [rch uch])) + +(defn save-path-content [] + (ptk/reify ::save-path-content + ptk/UpdateEvent + (update [_ state] + (let [content (get-in state (st/get-path state :content)) + content (if (= (-> content last :command) :move-to) + (into [] (take (dec (count content)) content)) + content)] + (assoc-in state (st/get-path state :content) content))) + + ptk/WatchEvent + (watch [_ state stream] + (let [id (get-in state [:workspace-local :edition]) + old-content (get-in state [:workspace-local :edit-path id :old-content])] + (if (some? old-content) + (let [shape (get-in state (st/get-path state)) + page-id (:current-page-id state) + [rch uch] (generate-path-changes page-id shape old-content (:content shape))] + (rx/of (dwc/commit-changes rch uch {:commit-local? true}))) + (rx/empty)))))) + + diff --git a/frontend/src/app/main/data/workspace/path/common.cljs b/frontend/src/app/main/data/workspace/path/common.cljs new file mode 100644 index 000000000..0d28bf984 --- /dev/null +++ b/frontend/src/app/main/data/workspace/path/common.cljs @@ -0,0 +1,28 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.path.common + (:require + [app.main.data.workspace.path.state :as st] + [potok.core :as ptk])) + +(defn init-path [] + (ptk/reify ::init-path)) + +(defn clean-edit-state + [state] + (dissoc state :last-point :prev-handler :drag-handler :preview)) + +(defn finish-path [source] + (ptk/reify ::finish-path + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state)] + (-> state + (update-in [:workspace-local :edit-path id] clean-edit-state)))))) diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs new file mode 100644 index 000000000..6f9318ebf --- /dev/null +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -0,0 +1,337 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.path.drawing + (:require + [app.common.geom.point :as gpt] + [app.common.pages :as cp] + [app.common.spec :as us] + [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.drawing.common :as dwdc] + [app.main.data.workspace.path.changes :as changes] + [app.main.data.workspace.path.common :as common] + [app.main.data.workspace.path.helpers :as helpers] + [app.main.data.workspace.path.spec :as spec] + [app.main.data.workspace.path.state :as st] + [app.main.data.workspace.path.streams :as streams] + [app.main.data.workspace.path.tools :as tools] + [app.main.streams :as ms] + [app.util.geom.path :as ugp] + [beicon.core :as rx] + [potok.core :as ptk])) + +(declare change-edit-mode) + +(defn preview-next-point [{:keys [x y shift?]}] + (ptk/reify ::preview-next-point + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state) + fix-angle? shift? + last-point (get-in state [:workspace-local :edit-path id :last-point]) + position (cond-> (gpt/point x y) + fix-angle? (helpers/position-fixed-angle last-point)) + shape (get-in state (st/get-path state)) + {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id]) + command (helpers/next-node shape position last-point prev-handler)] + (assoc-in state [:workspace-local :edit-path id :preview] command))))) + +(defn add-node [{:keys [x y shift?]}] + (ptk/reify ::add-node + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state) + fix-angle? shift? + {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id]) + position (cond-> (gpt/point x y) + fix-angle? (helpers/position-fixed-angle last-point))] + (if-not (= last-point position) + (-> state + (assoc-in [:workspace-local :edit-path id :last-point] position) + (update-in [:workspace-local :edit-path id] dissoc :prev-handler) + (update-in [:workspace-local :edit-path id] dissoc :preview) + (update-in (st/get-path state) helpers/append-node position last-point prev-handler)) + state))))) + +(defn start-drag-handler [] + (ptk/reify ::start-drag-handler + ptk/UpdateEvent + (update [_ state] + (let [content (get-in state (st/get-path state :content)) + index (dec (count content)) + command (get-in state (st/get-path state :content index :command)) + + make-curve + (fn [command] + (let [params (ugp/make-curve-params + (get-in content [index :params]) + (get-in content [(dec index) :params]))] + (-> command + (assoc :command :curve-to :params params))))] + + (cond-> state + (= command :line-to) + (update-in (st/get-path state :content index) make-curve)))))) + +(defn drag-handler [{:keys [x y alt? shift?]}] + (ptk/reify ::drag-handler + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state) + shape (get-in state (st/get-path state)) + content (:content shape) + index (dec (count content)) + node-position (ugp/command->point (nth content index)) + handler-position (cond-> (gpt/point x y) + shift? (helpers/position-fixed-angle node-position)) + {dx :x dy :y} (gpt/subtract handler-position node-position) + match-opposite? (not alt?) + modifiers (helpers/move-handler-modifiers content (inc index) :c1 match-opposite? dx dy)] + (-> state + (update-in [:workspace-local :edit-path id :content-modifiers] merge modifiers) + (assoc-in [:workspace-local :edit-path id :prev-handler] handler-position) + (assoc-in [:workspace-local :edit-path id :drag-handler] handler-position)))))) + +(defn finish-drag [] + (ptk/reify ::finish-drag + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state) + modifiers (get-in state [:workspace-local :edit-path id :content-modifiers]) + handler (get-in state [:workspace-local :edit-path id :drag-handler])] + (-> state + (update-in (st/get-path state :content) ugp/apply-content-modifiers modifiers) + (update-in [:workspace-local :edit-path id] dissoc :drag-handler) + (update-in [:workspace-local :edit-path id] dissoc :content-modifiers) + (assoc-in [:workspace-local :edit-path id :prev-handler] handler) + (update-in (st/get-path state) helpers/update-selrect)))) + + ptk/WatchEvent + (watch [_ state stream] + (let [id (st/get-path-id state) + handler (get-in state [:workspace-local :edit-path id :prev-handler])] + ;; Update the preview because can be outdated after the dragging + (rx/of (preview-next-point handler)))))) + +(declare close-path-drag-end) + +(defn close-path-drag-start [position] + (ptk/reify ::close-path-drag-start + ptk/WatchEvent + (watch [_ state stream] + (let [id (st/get-path-id state) + zoom (get-in state [:workspace-local :zoom]) + start-position @ms/mouse-position + + stop-stream + (->> stream (rx/filter #(or (helpers/end-path-event? %) + (ms/mouse-up? %)))) + + drag-events-stream + (->> (streams/position-stream) + (rx/take-until stop-stream) + (rx/map #(drag-handler %)))] + + (rx/concat + (rx/of (add-node position)) + (streams/drag-stream + (rx/concat + (rx/of (start-drag-handler)) + drag-events-stream + (rx/of (finish-drag)) + (rx/of (close-path-drag-end)))) + (rx/of (common/finish-path "close-path"))))))) + +(defn close-path-drag-end [] + (ptk/reify ::close-path-drag-end + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state)] + (update-in state [:workspace-local :edit-path id] dissoc :prev-handler))))) + +(defn start-path-from-point [position] + (ptk/reify ::start-path-from-point + ptk/WatchEvent + (watch [_ state stream] + (let [start-point @ms/mouse-position + zoom (get-in state [:workspace-local :zoom]) + mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %) + (ms/mouse-up? %)))) + drag-events (->> ms/mouse-position + (rx/take-until mouse-up) + (rx/map #(drag-handler %)))] + + (rx/concat + (rx/of (add-node position)) + (streams/drag-stream + (rx/concat + (rx/of (start-drag-handler)) + drag-events + (rx/of (finish-drag))))))))) + +(defn make-node-events-stream + [stream] + (->> stream + (rx/filter (ptk/type? ::close-path-drag-start)) + (rx/take 1) + (rx/merge-map #(rx/empty)))) + +(defn make-drag-stream + [stream down-event zoom] + (let [mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %) + (ms/mouse-up? %)))) + drag-events (->> (streams/position-stream) + (rx/take-until mouse-up) + (rx/map #(drag-handler %)))] + + (rx/concat + (rx/of (add-node down-event)) + (streams/drag-stream + (rx/concat + (rx/of (start-drag-handler)) + drag-events + (rx/of (finish-drag))))))) + +(defn handle-drawing-path + [id] + (ptk/reify ::handle-drawing-path + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state)] + (-> state + (assoc-in [:workspace-local :edit-path id :edit-mode] :draw)))) + + ptk/WatchEvent + (watch [_ state stream] + (let [zoom (get-in state [:workspace-local :zoom]) + mouse-down (->> stream (rx/filter ms/mouse-down?)) + end-path-events (->> stream (rx/filter helpers/end-path-event?)) + + ;; Mouse move preview + mousemove-events + (->> (streams/position-stream) + (rx/take-until end-path-events) + (rx/map #(preview-next-point %))) + + ;; From mouse down we can have: click, drag and double click + mousedown-events + (->> mouse-down + (rx/take-until end-path-events) + (rx/with-latest merge (streams/position-stream)) + + ;; We change to the stream that emits the first event + (rx/switch-map + #(rx/race (make-node-events-stream stream) + (make-drag-stream stream % zoom))))] + + (rx/concat + (rx/of (common/init-path)) + (rx/merge mousemove-events + mousedown-events) + (rx/of (common/finish-path "after-events"))))))) + + +(defn setup-frame-path [] + (ptk/reify ::setup-frame-path + ptk/UpdateEvent + (update [_ state] + (let [objects (dwc/lookup-page-objects state) + content (get-in state [:workspace-drawing :object :content] []) + position (get-in content [0 :params] nil) + frame-id (cp/frame-id-by-position objects position)] + (-> state + (assoc-in [:workspace-drawing :object :frame-id] frame-id)))))) + +(defn handle-new-shape-result [shape-id] + (ptk/reify ::handle-new-shape-result + ptk/UpdateEvent + (update [_ state] + (let [content (get-in state [:workspace-drawing :object :content] [])] + (us/verify ::spec/content content) + (if (> (count content) 1) + (assoc-in state [:workspace-drawing :object :initialized?] true) + state))) + + ptk/WatchEvent + (watch [_ state stream] + (->> (rx/of (setup-frame-path) + dwdc/handle-finish-drawing + (dwc/start-edition-mode shape-id) + (change-edit-mode :draw)))))) + +(defn handle-new-shape + "Creates a new path shape" + [] + (ptk/reify ::handle-new-shape + ptk/WatchEvent + (watch [_ state stream] + (let [shape-id (get-in state [:workspace-drawing :object :id])] + (rx/concat + (rx/of (handle-drawing-path shape-id)) + (->> stream + (rx/filter (ptk/type? ::common/finish-path)) + (rx/take 1) + (rx/observe-on :async) + (rx/map #(handle-new-shape-result shape-id)))))))) + +(declare check-changed-content) + +(defn start-draw-mode [] + (ptk/reify ::start-draw-mode + ptk/UpdateEvent + (update [_ state] + (let [id (get-in state [:workspace-local :edition]) + page-id (:current-page-id state) + old-content (get-in state [:workspace-data :pages-index page-id :objects id :content])] + (-> state + (assoc-in [:workspace-local :edit-path id :old-content] old-content)))) + + ptk/WatchEvent + (watch [_ state stream] + (let [id (get-in state [:workspace-local :edition]) + edit-mode (get-in state [:workspace-local :edit-path id :edit-mode])] + (if (= :draw edit-mode) + (rx/concat + (rx/of (handle-drawing-path id)) + (->> stream + (rx/filter (ptk/type? ::common/finish-path)) + (rx/take 1) + (rx/merge-map #(rx/of (check-changed-content))))) + (rx/empty)))))) + +(defn check-changed-content [] + (ptk/reify ::check-changed-content + ptk/WatchEvent + (watch [_ state stream] + (let [id (st/get-path-id state) + content (get-in state (st/get-path state :content)) + old-content (get-in state [:workspace-local :edit-path id :old-content]) + mode (get-in state [:workspace-local :edit-path id :edit-mode])] + + (cond + (not= content old-content) (rx/of (changes/save-path-content) + (start-draw-mode)) + (= mode :draw) (rx/of :interrupt) + :else (rx/of (common/finish-path "changed-content"))))))) + +(defn change-edit-mode [mode] + (ptk/reify ::change-edit-mode + ptk/UpdateEvent + (update [_ state] + (let [id (get-in state [:workspace-local :edition])] + (cond-> state + id (assoc-in [:workspace-local :edit-path id :edit-mode] mode)))) + + ptk/WatchEvent + (watch [_ state stream] + (let [id (st/get-path-id state)] + (cond + (and id (= :move mode)) (rx/of (common/finish-path "change-edit-mode")) + (and id (= :draw mode)) (rx/of (start-draw-mode)) + :else (rx/empty)))))) diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs new file mode 100644 index 000000000..df71229e6 --- /dev/null +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -0,0 +1,216 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.path.edition + (:require + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.math :as mth] + [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.path.changes :as changes] + [app.main.data.workspace.path.common :as common] + [app.main.data.workspace.path.helpers :as helpers] + [app.main.data.workspace.path.selection :as selection] + [app.main.data.workspace.path.state :as st] + [app.main.data.workspace.path.streams :as streams] + [app.main.data.workspace.path.drawing :as drawing] + [app.main.streams :as ms] + [app.util.geom.path :as ugp] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defn modify-point [index prefix dx dy] + (ptk/reify ::modify-point + ptk/UpdateEvent + (update [_ state] + (let [id (get-in state [:workspace-local :edition]) + [cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])] + (-> state + (update-in [:workspace-local :edit-path id :content-modifiers (inc index)] assoc + :c1x dx :c1y dy) + (update-in [:workspace-local :edit-path id :content-modifiers index] assoc + :x dx :y dy :c2x dx :c2y dy)))))) + +(defn modify-handler [id index prefix dx dy match-opposite?] + (ptk/reify ::modify-point + ptk/UpdateEvent + (update [_ state] + (let [content (get-in state (st/get-path state :content)) + [cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y]) + [ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y]) + opposite-index (ugp/opposite-index content index prefix)] + (cond-> state + :always + (update-in [:workspace-local :edit-path id :content-modifiers index] assoc + cx dx cy dy) + + (and match-opposite? opposite-index) + (update-in [:workspace-local :edit-path id :content-modifiers opposite-index] assoc + ocx (- dx) ocy (- dy))))))) + +(defn apply-content-modifiers [] + (ptk/reify ::apply-content-modifiers + ptk/WatchEvent + (watch [_ state stream] + (let [id (st/get-path-id state) + page-id (:current-page-id state) + shape (get-in state (st/get-path state)) + content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers]) + new-content (ugp/apply-content-modifiers (:content shape) content-modifiers) + [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] + + (rx/of (dwc/commit-changes rch uch {:commit-local? true}) + (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers))))))) + +(defn move-selected-path-point [from-point to-point] + (letfn [(modify-content-point [content {dx :x dy :y} modifiers point] + (let [point-indices (ugp/point-indices content point) ;; [indices] + handler-indices (ugp/handler-indices content point) ;; [[index prefix]] + + modify-point + (fn [modifiers index] + (-> modifiers + (update index assoc :x dx :y dy))) + + modify-handler + (fn [modifiers [index prefix]] + (let [cx (d/prefix-keyword prefix :x) + cy (d/prefix-keyword prefix :y)] + (-> modifiers + (update index assoc cx dx cy dy))))] + + (as-> modifiers $ + (reduce modify-point $ point-indices) + (reduce modify-handler $ handler-indices))))] + + (ptk/reify ::move-point + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state) + content (get-in state (st/get-path state :content)) + delta (gpt/subtract to-point from-point) + + modifiers-reducer (partial modify-content-point content delta) + + points (get-in state [:workspace-local :edit-path id :selected-points] #{}) + + modifiers (get-in state [:workspace-local :edit-path id :content-modifiers] {}) + modifiers (->> points + (reduce modifiers-reducer {}))] + + (assoc-in state [:workspace-local :edit-path id :content-modifiers] modifiers)))))) + +(defn start-move-path-point + [position shift?] + (ptk/reify ::start-move-path-point + ptk/WatchEvent + (watch [_ state stream] + (let [start-position @ms/mouse-position + stopper (->> stream (rx/filter ms/mouse-up?)) + zoom (get-in state [:workspace-local :zoom]) + id (get-in state [:workspace-local :edition]) + selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) + selected? (contains? selected-points position) + + mouse-drag-stream + (rx/concat + ;; If we're dragging a selected item we don't change the selection + (if selected? + (rx/empty) + (rx/of (selection/select-node position shift?))) + + ;; This stream checks the consecutive mouse positions to do the draging + (->> ms/mouse-position + (rx/take-until stopper) + (rx/map #(move-selected-path-point start-position %))) + (rx/of (apply-content-modifiers))) + + ;; When there is not drag we select the node + mouse-click-stream + (rx/of (selection/select-node position shift?))] + + (streams/drag-stream mouse-drag-stream + mouse-click-stream))))) + +(defn start-move-handler + [index prefix] + (ptk/reify ::start-move-handler + ptk/WatchEvent + (watch [_ state stream] + (let [id (get-in state [:workspace-local :edition]) + cx (d/prefix-keyword prefix :x) + cy (d/prefix-keyword prefix :y) + start-point @ms/mouse-position + modifiers (get-in state [:workspace-local :edit-path id :content-modifiers]) + start-delta-x (get-in modifiers [index cx] 0) + start-delta-y (get-in modifiers [index cy] 0) + + content (get-in state (st/get-path state :content)) + opposite-index (ugp/opposite-index content index prefix) + opposite-prefix (if (= prefix :c1) :c2 :c1) + opposite-handler (-> content (get opposite-index) (ugp/get-handler opposite-prefix)) + + point (-> content (get (if (= prefix :c1) (dec index) index)) (ugp/command->point)) + handler (-> content (get index) (ugp/get-handler prefix)) + + current-distance (when opposite-handler (gpt/distance (ugp/opposite-handler point handler) opposite-handler)) + match-opposite? (and opposite-handler (mth/almost-zero? current-distance))] + + (streams/drag-stream + (rx/concat + (->> (streams/position-stream) + (rx/take-until (->> stream (rx/filter ms/mouse-up?))) + (rx/map + (fn [{:keys [x y alt? shift?]}] + (let [pos (cond-> (gpt/point x y) + shift? (helpers/position-fixed-angle point))] + (modify-handler + id + index + prefix + (+ start-delta-x (- (:x pos) (:x start-point))) + (+ start-delta-y (- (:y pos) (:y start-point))) + (and (not alt?) match-opposite?)))))) + (rx/concat (rx/of (apply-content-modifiers))))))))) + +(declare stop-path-edit) + +(defn start-path-edit + [id] + (ptk/reify ::start-path-edit + ptk/UpdateEvent + (update [_ state] + (let [edit-path (get-in state [:workspace-local :edit-path id])] + + (cond-> state + (or (not edit-path) (= :draw (:edit-mode edit-path))) + (assoc-in [:workspace-local :edit-path id] {:edit-mode :move + :selected #{} + :snap-toggled true}) + + (and (some? edit-path) (= :move (:edit-mode edit-path))) + (assoc-in [:workspace-local :edit-path id :edit-mode] :draw)))) + + ptk/WatchEvent + (watch [_ state stream] + (let [mode (get-in state [:workspace-local :edit-path id :edit-mode])] + (rx/concat + (rx/of (drawing/change-edit-mode mode)) + (->> stream + (rx/take-until (->> stream (rx/filter (ptk/type? ::start-path-edit)))) + (rx/filter #(= % :interrupt)) + (rx/take 1) + (rx/map #(stop-path-edit)))))))) + +(defn stop-path-edit [] + (ptk/reify ::stop-path-edit + ptk/UpdateEvent + (update [_ state] + (let [id (get-in state [:workspace-local :edition])] + (update state :workspace-local dissoc :edit-path id))))) diff --git a/frontend/src/app/main/data/workspace/path/helpers.cljs b/frontend/src/app/main/data/workspace/path/helpers.cljs new file mode 100644 index 000000000..f70e89ad9 --- /dev/null +++ b/frontend/src/app/main/data/workspace/path/helpers.cljs @@ -0,0 +1,123 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.path.helpers + (: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.math :as mth] + [app.main.data.workspace.path.state :refer [get-path]] + [app.main.data.workspace.path.common :as common] + [app.main.streams :as ms] + [app.util.geom.path :as ugp] + [potok.core :as ptk])) + +;; CONSTANTS +(defonce enter-keycode 13) + +(defn end-path-event? [{:keys [type shift] :as event}] + (or (= (ptk/type event) ::common/finish-path) + (= (ptk/type event) :esc-pressed) + (= event :interrupt) ;; ESC + (and (ms/mouse-double-click? event)))) + +(defn content->points+selrect + "Given the content of a shape, calculate its points and selrect" + [shape content] + (let [transform (:transform shape (gmt/matrix)) + transform-inverse (:transform-inverse shape (gmt/matrix)) + center (gsh/center-shape shape) + base-content (gsh/transform-content + content + (gmt/transform-in center transform-inverse)) + + ;; Calculates the new selrect with points given the old center + points (-> (gsh/content->selrect base-content) + (gsh/rect->points) + (gsh/transform-points center (:transform shape (gmt/matrix)))) + + points-center (gsh/center-points points) + + ;; Points is now the selrect but the center is different so we can create the selrect + ;; through points + selrect (-> points + (gsh/transform-points points-center (:transform-inverse shape (gmt/matrix))) + (gsh/points->selrect))] + [points selrect])) + +(defn update-selrect + "Updates the selrect and points for a path" + [shape] + (if (= (:rotation shape 0) 0) + (let [content (:content shape) + selrect (gsh/content->selrect content) + points (gsh/rect->points selrect)] + (assoc shape :points points :selrect selrect)) + + (let [content (:content shape) + [points selrect] (content->points+selrect shape content)] + (assoc shape :points points :selrect selrect)))) + + +(defn closest-angle + [angle] + (cond + (or (> angle 337.5) (<= angle 22.5)) 0 + (and (> angle 22.5) (<= angle 67.5)) 45 + (and (> angle 67.5) (<= angle 112.5)) 90 + (and (> angle 112.5) (<= angle 157.5)) 135 + (and (> angle 157.5) (<= angle 202.5)) 180 + (and (> angle 202.5) (<= angle 247.5)) 225 + (and (> angle 247.5) (<= angle 292.5)) 270 + (and (> angle 292.5) (<= angle 337.5)) 315)) + +(defn position-fixed-angle [point from-point] + (if (and from-point point) + (let [angle (mod (+ 360 (- (gpt/angle point from-point))) 360) + to-angle (closest-angle angle) + distance (gpt/distance point from-point)] + (gpt/angle->point from-point (mth/radians to-angle) distance)) + point)) + +(defn next-node + "Calculates the next-node to be inserted." + [shape position prev-point prev-handler] + (let [last-command (-> shape :content last :command) + add-line? (and prev-point (not prev-handler) (not= last-command :close-path)) + add-curve? (and prev-point prev-handler (not= last-command :close-path))] + (cond + add-line? {:command :line-to + :params position} + add-curve? {:command :curve-to + :params (ugp/make-curve-params position prev-handler)} + :else {:command :move-to + :params position}))) + +(defn append-node + "Creates a new node in the path. Usualy used when drawing." + [shape position prev-point prev-handler] + (let [command (next-node shape position prev-point prev-handler)] + (-> shape + (update :content (fnil conj []) command) + (update-selrect)))) + +(defn move-handler-modifiers + [content index prefix match-opposite? dx dy] + (let [[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y]) + [ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y]) + opposite-index (ugp/opposite-index content index prefix)] + + (cond-> {} + :always + (update index assoc cx dx cy dy) + + (and match-opposite? opposite-index) + (update opposite-index assoc ocx (- dx) ocy (- dy))))) diff --git a/frontend/src/app/main/data/workspace/path/selection.cljs b/frontend/src/app/main/data/workspace/path/selection.cljs new file mode 100644 index 000000000..120845fc8 --- /dev/null +++ b/frontend/src/app/main/data/workspace/path/selection.cljs @@ -0,0 +1,157 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.path.selection + (:require + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.path.state :as st] + [app.main.streams :as ms] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defn path-pointer-enter [position] + (ptk/reify ::path-pointer-enter + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state)] + (update-in state [:workspace-local :edit-path id :hover-points] (fnil conj #{}) position))))) + +(defn path-pointer-leave [position] + (ptk/reify ::path-pointer-leave + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state)] + (update-in state [:workspace-local :edit-path id :hover-points] disj position))))) + +(defn select-handler [index type] + (ptk/reify ::select-handler + ptk/UpdateEvent + (update [_ state] + (let [id (get-in state [:workspace-local :edition])] + (-> state + (update-in [:workspace-local :edit-path id :selected-handlers] (fnil conj #{}) [index type])))))) + + +(defn path-handler-enter [index prefix] + (ptk/reify ::path-handler-enter + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state)] + (update-in state [:workspace-local :edit-path id :hover-handlers] (fnil conj #{}) [index prefix]))))) + +(defn path-handler-leave [index prefix] + (ptk/reify ::path-handler-leave + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state)] + (update-in state [:workspace-local :edit-path id :hover-handlers] disj [index prefix]))))) + +(defn select-node-area [shift?] + (ptk/reify ::select-node-area + ptk/UpdateEvent + (update [_ state] + (let [selrect (get-in state [:workspace-local :selrect]) + id (get-in state [:workspace-local :edition]) + content (get-in state (st/get-path state :content)) + selected-point? (fn [point] + (gsh/has-point-rect? selrect point)) + positions (into #{} + (comp (map (comp gpt/point :params)) + (filter selected-point?)) + content)] + (cond-> state + (some? id) + (assoc-in [:workspace-local :edit-path id :selected-points] positions)))))) + +(defn select-node [position shift?] + (ptk/reify ::select-node + ptk/UpdateEvent + (update [_ state] + (let [id (get-in state [:workspace-local :edition])] + (cond-> state + (some? id) + (assoc-in [:workspace-local :edit-path id :selected-points] #{position})))))) + +(defn deselect-node [position shift?] + (ptk/reify ::deselect-node + ptk/UpdateEvent + (update [_ state] + (let [id (get-in state [:workspace-local :edition])] + (-> state + (update-in [:workspace-local :edit-path id :selected-points] (fnil disj #{}) position)))))) + +(defn add-to-selection-handler [index type] + (ptk/reify ::add-to-selection-handler + ptk/UpdateEvent + (update [_ state] + state))) + +(defn add-to-selection-node [index] + (ptk/reify ::add-to-selection-node + ptk/UpdateEvent + (update [_ state] + state))) + +(defn remove-from-selection-handler [index] + (ptk/reify ::remove-from-selection-handler + ptk/UpdateEvent + (update [_ state] + state))) + +(defn remove-from-selection-node [index] + (ptk/reify ::remove-from-selection-handler + ptk/UpdateEvent + (update [_ state] + state))) + +(defn deselect-all [] + (ptk/reify ::deselect-all + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state)] + (-> state + (assoc-in [:workspace-local :edit-path id :selected-handlers] #{}) + (assoc-in [:workspace-local :edit-path id :selected-points] #{})))))) + +(defn update-area-selection + [rect] + (ptk/reify ::update-area-selection + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :selrect] rect)))) + +(defn clear-area-selection + [] + (ptk/reify ::clear-area-selection + ptk/UpdateEvent + (update [_ state] + (update state :workspace-local dissoc :selrect)))) + +(defn handle-selection + [shift?] + (letfn [(valid-rect? [{width :width height :height}] + (or (> width 10) (> height 10)))] + + (ptk/reify ::handle-selection + ptk/WatchEvent + (watch [_ state stream] + (let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) + stoper (->> stream (rx/filter stop?)) + from-p @ms/mouse-position] + (rx/concat + (->> ms/mouse-position + (rx/take-until stoper) + (rx/map #(gsh/points->rect [from-p %])) + (rx/filter valid-rect?) + (rx/map update-area-selection)) + + (rx/of (select-node-area shift?) + (clear-area-selection)))))))) diff --git a/frontend/src/app/main/data/workspace/path/spec.cljs b/frontend/src/app/main/data/workspace/path/spec.cljs new file mode 100644 index 000000000..759f43dc0 --- /dev/null +++ b/frontend/src/app/main/data/workspace/path/spec.cljs @@ -0,0 +1,52 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.path.spec + (:require + [clojure.spec.alpha :as s])) + +;; SCHEMAS + +(s/def ::command #{:move-to + :line-to + :line-to-horizontal + :line-to-vertical + :curve-to + :smooth-curve-to + :quadratic-bezier-curve-to + :smooth-quadratic-bezier-curve-to + :elliptical-arc + :close-path}) + +(s/def :paths.params/x number?) +(s/def :paths.params/y number?) +(s/def :paths.params/c1x number?) +(s/def :paths.params/c1y number?) +(s/def :paths.params/c2x number?) +(s/def :paths.params/c2y number?) + +(s/def ::relative? boolean?) + +(s/def ::params + (s/keys :req-un [:path.params/x + :path.params/y] + :opt-un [:path.params/c1x + :path.params/c1y + :path.params/c2x + :path.params/c2y])) + +(s/def ::content-entry + (s/keys :req-un [::command] + :req-opt [::params + ::relative?])) +(s/def ::content + (s/coll-of ::content-entry :kind vector?)) + + + diff --git a/frontend/src/app/main/data/workspace/path/state.cljs b/frontend/src/app/main/data/workspace/path/state.cljs new file mode 100644 index 000000000..96c4c7221 --- /dev/null +++ b/frontend/src/app/main/data/workspace/path/state.cljs @@ -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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.path.state + (:require + [app.common.data :as d])) + +(defn get-path-id + "Retrieves the currently editing path id" + [state] + (or (get-in state [:workspace-local :edition]) + (get-in state [:workspace-drawing :object :id]))) + +(defn get-path + "Retrieves the location of the path object and additionaly can pass + the arguments. This location can be used in get-in, assoc-in... functions" + [state & path] + (let [edit-id (get-in state [:workspace-local :edition]) + page-id (:current-page-id state)] + (d/concat + (if edit-id + [:workspace-data :pages-index page-id :objects edit-id] + [:workspace-drawing :object]) + path))) + + diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs new file mode 100644 index 000000000..5040b0c34 --- /dev/null +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -0,0 +1,54 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.path.streams + (:require + [app.main.data.workspace.path.helpers :as helpers] + [app.common.geom.point :as gpt] + [app.main.store :as st] + [app.main.streams :as ms] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defonce drag-threshold 5) + +(defn dragging? [start zoom] + (fn [current] + (>= (gpt/distance start current) (/ drag-threshold zoom)))) + +(defn drag-stream + ([to-stream] + (drag-stream to-stream (rx/empty))) + + ([to-stream not-drag-stream] + (let [start @ms/mouse-position + zoom (get-in @st/state [:workspace-local :zoom] 1) + mouse-up (->> st/stream (rx/filter #(ms/mouse-up? %))) + + position-stream + (->> ms/mouse-position + (rx/take-until mouse-up) + (rx/filter (dragging? start zoom)) + (rx/take 1))] + + (rx/merge + (->> position-stream + (rx/if-empty ::empty) + (rx/merge-map (fn [value] + (if (= value ::empty) + not-drag-stream + (rx/empty))))) + + (->> position-stream + (rx/merge-map (fn [] to-stream))))))) + +(defn position-stream [] + (->> ms/mouse-position + (rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %)))) + (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %)))))) diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs new file mode 100644 index 000000000..2085ddf79 --- /dev/null +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -0,0 +1,42 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.path.tools + (:require + [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.path.changes :as changes] + [app.main.data.workspace.path.common :as common] + [app.main.data.workspace.path.state :as st] + [app.util.geom.path :as ugp] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defn make-corner [] + (ptk/reify ::make-corner + ptk/WatchEvent + (watch [_ state stream] + (let [id (st/get-path-id state) + page-id (:current-page-id state) + shape (get-in state (st/get-path state)) + selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) + new-content (reduce ugp/make-corner-point (:content shape) selected-points) + [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] + (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) + +(defn make-curve [] + (ptk/reify ::make-curve + ptk/WatchEvent + (watch [_ state stream] + (let [id (st/get-path-id state) + page-id (:current-page-id state) + shape (get-in state (st/get-path state)) + selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) + new-content (reduce ugp/make-curve-point (:content shape) selected-points) + [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] + (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs b/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs index 9a030ddeb..e71df0316 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs @@ -6,7 +6,7 @@ (ns app.main.ui.workspace.shapes.path.actions (:require - [app.main.data.workspace.drawing.path :as drp] + [app.main.data.workspace.path :as drp] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.icons :as i] 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 a30d29f05..a459dc836 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,7 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] - [app.main.data.workspace.drawing.path :as drp] + [app.main.data.workspace.path :as drp] [app.main.store :as st] [app.main.ui.cursors :as cur] [app.main.ui.workspace.shapes.path.common :as pc] @@ -31,34 +31,21 @@ (fn [event] (st/emit! (drp/path-pointer-leave position))) - on-click + on-mouse-down (fn [event] (dom/stop-propagation event) (dom/prevent-default event) (let [shift? (kbd/shift? event)] (cond - (and (= edit-mode :move) (not selected?)) - (st/emit! (drp/select-node position shift?)) + (= edit-mode :move) + (st/emit! (drp/start-move-path-point position shift?)) - (and (= edit-mode :move) selected?) - (st/emit! (drp/deselect-node position shift?))))) + (and (= edit-mode :draw) start-path?) + (st/emit! (drp/start-path-from-point position)) - - on-mouse-down - (fn [event] - (dom/stop-propagation event) - (dom/prevent-default event) - - (cond - (= edit-mode :move) - (st/emit! (drp/start-move-path-point position)) - - (and (= edit-mode :draw) start-path?) - (st/emit! (drp/start-path-from-point position)) - - (and (= edit-mode :draw) (not start-path?)) - (st/emit! (drp/close-path-drag-start position))))] + (and (= edit-mode :draw) (not start-path?)) + (st/emit! (drp/close-path-drag-start position)))))] [:g.path-point [:circle.path-point @@ -74,7 +61,6 @@ [:circle {:cx x :cy y :r (/ 10 zoom) - :on-click on-click :on-mouse-down on-mouse-down :on-mouse-enter on-enter :on-mouse-leave on-leave @@ -179,25 +165,15 @@ last-p (->> content last ugp/command->point) handlers (ugp/content->handlers content) - ;;handle-click-outside - ;;(fn [event] - ;; (let [current (dom/get-target event) - ;; editor-dom (mf/ref-val editor-ref)] - ;; (when-not (or (.contains editor-dom current) - ;; (dom/class? current "viewport-actions-entry")) - ;; (st/emit! (drp/deselect-all))))) - handle-double-click-outside (fn [event] (when (= edit-mode :move) - (st/emit! :interrupt))) - ] + (st/emit! :interrupt)))] (mf/use-layout-effect (mf/deps edit-mode) (fn [] - (let [keys [;;(events/listen (dom/get-root) EventType.CLICK handle-click-outside) - (events/listen (dom/get-root) EventType.DBLCLICK handle-double-click-outside)]] + (let [keys [(events/listen (dom/get-root) EventType.DBLCLICK handle-double-click-outside)]] #(doseq [key keys] (events/unlistenByKey key))))) @@ -208,29 +184,35 @@ :zoom zoom}]) (for [position points] - [:g.path-node - [:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")} - (for [[index prefix] (get handlers position)] - (let [command (get content index) - x (get-in command [:params (d/prefix-keyword prefix :x)]) - y (get-in command [:params (d/prefix-keyword prefix :y)]) - handler-position (gpt/point x y)] - (when (not= position handler-position) - [:& path-handler {:point position - :handler handler-position - :index index - :prefix prefix - :zoom zoom - :selected? (contains? selected-handlers [index prefix]) - :hover? (contains? hover-handlers [index prefix]) - :edit-mode edit-mode}])))] - [:& path-point {:position position - :zoom zoom - :edit-mode edit-mode - :selected? (contains? selected-points position) - :hover? (contains? hover-points position) - :last-p? (= last-point position) - :start-path? (nil? last-point)}]]) + (let [point-selected? (contains? selected-points position) + point-hover? (contains? hover-points position) + last-p? (= last-point position) + start-p? (some? last-point)] + [:g.path-node + [:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")} + (for [[index prefix] (get handlers position)] + (let [command (get content index) + x (get-in command [:params (d/prefix-keyword prefix :x)]) + y (get-in command [:params (d/prefix-keyword prefix :y)]) + handler-position (gpt/point x y) + handler-selected? (contains? selected-handlers [index prefix]) + handler-hover? (contains? hover-handlers [index prefix])] + (when (not= position handler-position) + [:& path-handler {:point position + :handler handler-position + :index index + :prefix prefix + :zoom zoom + :selected? handler-selected? + :hover? handler-hover? + :edit-mode edit-mode}])))] + [:& path-point {:position position + :zoom zoom + :edit-mode edit-mode + :selected? point-selected? + :hover? point-hover? + :last-p? last-p? + :start-path? start-p?}]])) (when prev-handler [:g.prev-handler {:pointer-events "none"} diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index f2d2d203b..82b427730 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -15,7 +15,7 @@ [app.main.store :as st] [app.main.streams :as ms] [app.main.ui.workspace.viewport.utils :as utils] - [app.main.data.workspace.drawing.path :as dwdp] + [app.main.data.workspace.path :as dwdp] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] [app.util.keyboard :as kbd] From 421b30c1d86b6ef3c53fb757986573b81763da54 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 5 Apr 2021 19:09:42 +0200 Subject: [PATCH 048/155] :sparkles: Snapping on path elements --- .../src/app/main/data/workspace/path.cljs | 7 + .../app/main/data/workspace/path/drawing.cljs | 2 +- .../app/main/data/workspace/path/edition.cljs | 5 +- .../app/main/data/workspace/path/streams.cljs | 11 +- .../app/main/data/workspace/path/tools.cljs | 22 ++ .../ui/workspace/shapes/path/actions.cljs | 44 ---- .../main/ui/workspace/shapes/path/editor.cljs | 2 +- .../src/app/main/ui/workspace/viewport.cljs | 7 +- .../main/ui/workspace/viewport/actions.cljs | 7 +- .../ui/workspace/viewport/path_actions.cljs | 174 ++++++++++++++++ .../main/ui/workspace/viewport/snap_path.cljs | 191 ++++++++++++++++++ .../main/ui/workspace/viewport/widgets.cljs | 2 +- frontend/src/app/util/geom/path.cljs | 44 ++++ 13 files changed, 464 insertions(+), 54 deletions(-) delete mode 100644 frontend/src/app/main/ui/workspace/shapes/path/actions.cljs create mode 100644 frontend/src/app/main/ui/workspace/viewport/path_actions.cljs create mode 100644 frontend/src/app/main/ui/workspace/viewport/snap_path.cljs diff --git a/frontend/src/app/main/data/workspace/path.cljs b/frontend/src/app/main/data/workspace/path.cljs index 714775131..c6c1dd2df 100644 --- a/frontend/src/app/main/data/workspace/path.cljs +++ b/frontend/src/app/main/data/workspace/path.cljs @@ -37,3 +37,10 @@ ;; Path tools (d/export tools/make-curve) (d/export tools/make-corner) +(d/export tools/add-node) +(d/export tools/remove-node) +(d/export tools/merge-nodes) +(d/export tools/join-nodes) +(d/export tools/separate-nodes) +(d/export tools/toggle-snap) + diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index 6f9318ebf..feadd1a9e 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -163,7 +163,7 @@ zoom (get-in state [:workspace-local :zoom]) mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %) (ms/mouse-up? %)))) - drag-events (->> ms/mouse-position + drag-events (->> (streams/position-stream) (rx/take-until mouse-up) (rx/map #(drag-handler %)))] diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index df71229e6..c40c99259 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -126,7 +126,7 @@ (rx/of (selection/select-node position shift?))) ;; This stream checks the consecutive mouse positions to do the draging - (->> ms/mouse-position + (->> (streams/position-stream) (rx/take-until stopper) (rx/map #(move-selected-path-point start-position %))) (rx/of (apply-content-modifiers))) @@ -135,8 +135,7 @@ mouse-click-stream (rx/of (selection/select-node position shift?))] - (streams/drag-stream mouse-drag-stream - mouse-click-stream))))) + (streams/drag-stream mouse-drag-stream mouse-click-stream))))) (defn start-move-handler [index prefix] diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs index 5040b0c34..eb061f0b2 100644 --- a/frontend/src/app/main/data/workspace/path/streams.cljs +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -14,7 +14,8 @@ [app.main.store :as st] [app.main.streams :as ms] [beicon.core :as rx] - [potok.core :as ptk])) + [potok.core :as ptk] + [app.common.math :as mth])) (defonce drag-threshold 5) @@ -48,7 +49,15 @@ (->> position-stream (rx/merge-map (fn [] to-stream))))))) +(defn to-dec [num] + (let [k 50] + (* (mth/floor (/ num k)) k))) + (defn position-stream [] (->> ms/mouse-position + ;; TODO: Prueba para el snap + #_(rx/map #(-> % + (update :x to-dec) + (update :y to-dec))) (rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %)))) (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %)))))) diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs index 2085ddf79..445da96cb 100644 --- a/frontend/src/app/main/data/workspace/path/tools.cljs +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -40,3 +40,25 @@ new-content (reduce ugp/make-curve-point (:content shape) selected-points) [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) + +(defn add-node [] + (ptk/reify ::add-node)) + +(defn remove-node [] + (ptk/reify ::remove-node)) + +(defn merge-nodes [] + (ptk/reify ::merge-nodes)) + +(defn join-nodes [] + (ptk/reify ::join-nodes)) + +(defn separate-nodes [] + (ptk/reify ::separate-nodes)) + +(defn toggle-snap [] + (ptk/reify ::toggle-snap + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state)] + (update-in state [:workspace-local :edit-path id :snap-toggled] not))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs b/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs deleted file mode 100644 index e71df0316..000000000 --- a/frontend/src/app/main/ui/workspace/shapes/path/actions.cljs +++ /dev/null @@ -1,44 +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.main.ui.workspace.shapes.path.actions - (:require - [app.main.data.workspace.path :as drp] - [app.main.refs :as refs] - [app.main.store :as st] - [app.main.ui.icons :as i] - [app.main.ui.workspace.shapes.path.common :as pc] - [rumext.alpha :as mf])) - -(mf/defc path-actions [{:keys [shape]}] - (let [id (mf/deref refs/selected-edition) - {:keys [edit-mode selected-points snap-toggled] :as all} (mf/deref pc/current-edit-path-ref)] - [:div.path-actions - [:div.viewport-actions-group - [:div.viewport-actions-entry {:class (when (= edit-mode :draw) "is-toggled") - :on-click #(st/emit! (drp/change-edit-mode :draw))} i/pen] - [:div.viewport-actions-entry {:class (when (= edit-mode :move) "is-toggled") - :on-click #(st/emit! (drp/change-edit-mode :move))} i/pointer-inner]] - - [:div.viewport-actions-group - [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-add] - [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-remove]] - - [:div.viewport-actions-group - [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-merge] - [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-join] - [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-separate]] - - [:div.viewport-actions-group - [:div.viewport-actions-entry {:class (when (empty? selected-points) "is-disabled") - :on-click #(when-not (empty? selected-points) - (st/emit! (drp/make-corner)))} i/nodes-corner] - [:div.viewport-actions-entry {:class (when (empty? selected-points) "is-disabled") - :on-click #(when-not (empty? selected-points) - (st/emit! (drp/make-curve)))} i/nodes-curve]] - - [:div.viewport-actions-group - [:div.viewport-actions-entry {:class (when snap-toggled "is-toggled")} i/nodes-snap]]])) 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 a459dc836..85fb3ce86 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -187,7 +187,7 @@ (let [point-selected? (contains? selected-points position) point-hover? (contains? hover-points position) last-p? (= last-point position) - start-p? (some? last-point)] + start-p? (not (some? last-point))] [:g.path-node [:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")} (for [[index prefix] (get handlers position)] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index bd2ccb43c..c9f3779ab 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -27,6 +27,7 @@ [app.main.ui.workspace.viewport.selection :as selection] [app.main.ui.workspace.viewport.snap-distances :as snap-distances] [app.main.ui.workspace.viewport.snap-points :as snap-points] + [app.main.ui.workspace.viewport.snap-path :as snap-path] [app.main.ui.workspace.viewport.utils :as utils] [app.main.ui.workspace.viewport.widgets :as widgets] [beicon.core :as rx] @@ -282,12 +283,16 @@ :selected selected :page-id page-id}]) + [:& snap-path/snap-path + {:zoom zoom + :edition edition + :edit-path edit-path}] + (when show-cursor-tooltip? [:& widgets/cursor-tooltip {:zoom zoom :tooltip tooltip}]) - (when show-presence? [:& presence/active-cursors {:page-id page-id}]) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 82b427730..21c7606c7 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -45,7 +45,9 @@ middle-click? (= 2 (.-which event)) frame? (= :frame type) - selected? (contains? selected id)] + selected? (contains? selected id) + + drawing-path? (= :draw (get-in edit-path [edition :edit-mode]))] (when middle-click? (dom/prevent-default bevent) @@ -60,7 +62,8 @@ (when (and (or (not edition) (not= edition id)) (not blocked) (not hidden) (not (#{:comments :path} drawing-tool))) (not= edition id)) (not blocked) - (not hidden)) + (not hidden) + (not drawing-path?)) (cond drawing-tool (st/emit! (dd/start-drawing drawing-tool)) diff --git a/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs b/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs new file mode 100644 index 000000000..cc0caf131 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs @@ -0,0 +1,174 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.ui.workspace.viewport.path-actions + (:require + [app.main.data.workspace.path :as drp] + [app.main.data.workspace.path.helpers :as wph] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.icons :as i] + [app.main.ui.workspace.shapes.path.common :as pc] + [app.util.geom.path :as ugp] + [rumext.alpha :as mf])) + +(defn check-enabled [content selected-points] + (let [segments (ugp/get-segments content selected-points) + + points-selected? (not (empty? selected-points)) + segments-selected? (not (empty? segments))] + {:make-corner points-selected? + :make-curve points-selected? + :add-node segments-selected? + :remove-node points-selected? + :merge-nodes segments-selected? + :join-nodes segments-selected? + :separate-nodes segments-selected?})) + +(mf/defc path-actions [{:keys [shape]}] + (let [id (mf/deref refs/selected-edition) + {:keys [edit-mode selected-points snap-toggled] :as all} (mf/deref pc/current-edit-path-ref) + content (:content shape) + + enabled-buttons + (mf/use-memo + (mf/deps content selected-points) + #(check-enabled content selected-points)) + + on-select-draw-mode + (mf/use-callback + (fn [event] + (st/emit! (drp/change-edit-mode :draw)))) + + on-select-edit-mode + (mf/use-callback + (fn [event] + (st/emit! (drp/change-edit-mode :move)))) + + on-add-node + (mf/use-callback + (mf/deps (:add-node enabled-buttons)) + (fn [event] + (when (:add-node enabled-buttons) + (st/emit! (drp/add-node))))) + + on-remove-node + (mf/use-callback + (mf/deps (:remove-node enabled-buttons)) + (fn [event] + (when (:remove-node enabled-buttons) + (st/emit! (drp/remove-node))))) + + on-merge-nodes + (mf/use-callback + (mf/deps (:merge-nodes enabled-buttons)) + (fn [event] + (when (:merge-nodes enabled-buttons) + (st/emit! (drp/merge-nodes))))) + + on-join-nodes + (mf/use-callback + (mf/deps (:join-nodes enabled-buttons)) + (fn [event] + (when (:join-nodes enabled-buttons) + (st/emit! (drp/join-nodes))))) + + on-separate-nodes + (mf/use-callback + (mf/deps (:separate-nodes enabled-buttons)) + (fn [event] + (when (:separate-nodes enabled-buttons) + (st/emit! (drp/separate-nodes))))) + + on-make-corner + (mf/use-callback + (mf/deps (:make-corner enabled-buttons)) + (fn [event] + (when (:make-corner enabled-buttons) + (st/emit! (drp/make-corner))))) + + on-make-curve + (mf/use-callback + (mf/deps (:make-curve enabled-buttons)) + (fn [event] + (when (:make-curve enabled-buttons) + (st/emit! (drp/make-curve))))) + + on-toggle-snap + (mf/use-callback + (fn [event] + (st/emit! (drp/toggle-snap)))) + + ] + [:div.path-actions + [:div.viewport-actions-group + + ;; Draw Mode + [:div.viewport-actions-entry + {:class (when (= edit-mode :draw) "is-toggled") + :on-click on-select-draw-mode} + i/pen] + + ;; Edit mode + [:div.viewport-actions-entry + {:class (when (= edit-mode :move) "is-toggled") + :on-click on-select-edit-mode} + i/pointer-inner]] + + [:div.viewport-actions-group + ;; Add Node + [:div.viewport-actions-entry + {:class (when-not (:add-node enabled-buttons) "is-disabled") + :on-click on-add-node} + i/nodes-add] + + ;; Remove node + [:div.viewport-actions-entry + {:class (when-not (:remove-node enabled-buttons) "is-disabled") + :on-click on-remove-node} + i/nodes-remove]] + + [:div.viewport-actions-group + ;; Merge Nodes + [:div.viewport-actions-entry + {:class (when-not (:merge-nodes enabled-buttons) "is-disabled") + :on-click on-merge-nodes} + i/nodes-merge] + + ;; Join Nodes + [:div.viewport-actions-entry + {:class (when-not (:join-nodes enabled-buttons) "is-disabled") + :on-click on-join-nodes} + i/nodes-join] + + ;; Separate Nodes + [:div.viewport-actions-entry + {:class (when-not (:separate-nodes enabled-buttons) "is-disabled") + :on-click on-separate-nodes} + i/nodes-separate]] + + ;; Make Corner + [:div.viewport-actions-group + [:div.viewport-actions-entry + {:class (when-not (:make-corner enabled-buttons) "is-disabled") + :on-click on-make-corner} + i/nodes-corner] + + ;; Make Curve + [:div.viewport-actions-entry + {:class (when-not (:make-curve enabled-buttons) "is-disabled") + :on-click on-make-curve} + i/nodes-curve]] + + ;; Toggle snap + [:div.viewport-actions-group + [:div.viewport-actions-entry + {:class (when snap-toggled "is-toggled") + :on-click on-toggle-snap} + i/nodes-snap]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_path.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_path.cljs new file mode 100644 index 000000000..191756430 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/snap_path.cljs @@ -0,0 +1,191 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.viewport.snap-path + (:require + #_[app.common.math :as mth] + #_[app.common.data :as d] + #_[app.common.geom.point :as gpt] + #_[app.common.geom.shapes :as gsh] + [app.main.refs :as refs] + #_[app.main.snap :as snap] + #_[app.util.geom.snap-points :as sp] + [app.util.geom.path :as ugp] + #_[beicon.core :as rx] + [rumext.alpha :as mf])) + +#_(def ^:private line-color "#D383DA") +#_(def ^:private line-opacity 0.6) +#_(def ^:private line-width 1) + +;; Configuration for debug +;; (def ^:private line-color "red") +;; (def ^:private line-opacity 1 ) +;; (def ^:private line-width 2) + +#_(mf/defc snap-point + [{:keys [point zoom]}] + (let [{:keys [x y]} point + x (mth/round x) + y (mth/round y) + cross-width (/ 3 zoom)] + [:g + [:line {:x1 (- x cross-width) + :y1 (- y cross-width) + :x2 (+ x cross-width) + :y2 (+ y cross-width) + :style {:stroke line-color :stroke-width (str (/ line-width zoom))}}] + [:line {:x1 (- x cross-width) + :y1 (+ y cross-width) + :x2 (+ x cross-width) + :y2 (- y cross-width) + :style {:stroke line-color :stroke-width (str (/ line-width zoom))}}]])) + +#_(mf/defc snap-line + [{:keys [snap point zoom]}] + [:line {:x1 (mth/round (:x snap)) + :y1 (mth/round (:y snap)) + :x2 (mth/round (:x point)) + :y2 (mth/round (:y point)) + :style {:stroke line-color :stroke-width (str (/ line-width zoom))} + :opacity line-opacity}]) + +#_(defn get-snap + [coord {:keys [shapes page-id filter-shapes modifiers]}] + (let [shape (if (> (count shapes) 1) + (->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect})) + (->> shapes (first))) + + shape (if modifiers + (-> shape (assoc :modifiers modifiers) gsh/transform-shape) + shape) + + frame-id (snap/snap-frame-id shapes)] + + (->> (rx/of shape) + (rx/flat-map (fn [shape] + (->> (sp/shape-snap-points shape) + (map #(vector frame-id %))))) + (rx/flat-map (fn [[frame-id point]] + (->> (snap/get-snap-points page-id frame-id filter-shapes point coord) + (rx/map #(vector point % coord))))) + (rx/reduce conj [])))) + +#_(defn- flip + "Function that reverses the x/y coordinates to their counterpart" + [coord] + (if (= coord :x) :y :x)) + +#_(defn add-point-to-snaps + [[point snaps coord]] + (let [normalize-coord #(assoc % coord (get point coord))] + (cons point (map normalize-coord snaps)))) + + +#_(defn- process-snap-lines + "Gets the snaps for a coordinate and creates lines with a fixed coordinate" + [snaps coord] + (->> snaps + ;; only snap on the `coord` coordinate + (filter #(= (nth % 2) coord)) + ;; we add the point so the line goes from the point to the snap + (mapcat add-point-to-snaps) + ;; We flatten because it's a list of from-to points + (flatten) + ;; Put together the points of the coordinate + (group-by coord) + ;; Keep only the other coordinate + (d/mapm #(map (flip coord) %2)) + ;; Finally get the max/min and this will define the line to draw + (d/mapm #(vector (apply min %2) (apply max %2))) + ;; Change the structure to retrieve a list of lines from/todo + (map (fn [[fixedv [minv maxv]]] [(hash-map coord fixedv (flip coord) minv) + (hash-map coord fixedv (flip coord) maxv)])))) + +#_(mf/defc snap-feedback + [{:keys [shapes page-id filter-shapes zoom modifiers] :as props}] + (let [state (mf/use-state []) + subject (mf/use-memo #(rx/subject)) + + ;; We use sets to store points/lines so there are no points/lines repeated + ;; can cause problems with react keys + snap-points (into #{} (mapcat add-point-to-snaps) @state) + + snap-lines (->> (into (process-snap-lines @state :x) + (process-snap-lines @state :y)) + (into #{}))] + + (mf/use-effect + (fn [] + (let [sub (->> subject + (rx/switch-map #(rx/combine-latest + d/concat + (get-snap :y %) + (get-snap :x %))) + (rx/subs #(let [rs (filter (fn [[_ snaps _]] (> (count snaps) 0)) %)] + (reset! state rs))))] + + ;; On unmount callback + #(rx/dispose! sub)))) + + (mf/use-effect + (mf/deps shapes modifiers) + (fn [] + (rx/push! subject props))) + + [:g.snap-feedback + (for [[from-point to-point] snap-lines] + [:& snap-line {:key (str "line-" (:x from-point) + "-" (:y from-point) + "-" (:x to-point) + "-" (:y to-point) "-") + :snap from-point + :point to-point + :zoom zoom}]) + (for [point snap-points] + [:& snap-point {:key (str "point-" (:x point) + "-" (:y point)) + :point point + :zoom zoom}])])) + +#_(mf/defc snap-points + {::mf/wrap [mf/memo]} + [{:keys [layout zoom selected page-id drawing transform modifiers] :as props}] + (let [shapes (mf/deref (refs/objects-by-id selected)) + filter-shapes (mf/deref refs/selected-shapes-with-children) + filter-shapes (fn [id] + (if (= id :layout) + (or (not (contains? layout :display-grid)) + (not (contains? layout :snap-grid))) + (or (filter-shapes id) + (not (contains? layout :dynamic-alignment))))) + shapes (if drawing [drawing] shapes)] + (when (or drawing transform) + [:& snap-feedback {:shapes shapes + :page-id page-id + :filter-shapes filter-shapes + :zoom zoom + :modifiers modifiers}]))) + +(mf/defc snap-feedback []) + + +(mf/defc snap-path + {::mf/wrap [mf/memo]} + [{:keys [edition edit-path zoom]}] + (let [{:keys [content]} (mf/deref (refs/object-by-id edition)) + {:keys [drag-handler preview snap-toggled]} (get edit-path edition) + + position (or drag-handler + (ugp/command->point preview))] + + (when snap-toggled + [:& snap-feedback {:content content + :position position + :zoom zoom}]))) diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index d810b8a06..6bd73feb8 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -14,7 +14,7 @@ [app.main.store :as st] [app.main.streams :as ms] [app.main.ui.hooks :as hooks] - [app.main.ui.workspace.shapes.path.actions :refer [path-actions]] + [app.main.ui.workspace.viewport.path-actions :refer [path-actions]] [app.util.dom :as dom] [app.util.object :as obj] [rumext.alpha :as mf])) diff --git a/frontend/src/app/util/geom/path.cljs b/frontend/src/app/util/geom/path.cljs index 55f594fb1..452c2d329 100644 --- a/frontend/src/app/util/geom/path.cljs +++ b/frontend/src/app/util/geom/path.cljs @@ -604,3 +604,47 @@ (as-> content $ (reduce redfn $ content-next) (remove-line-curves $)))) + +(defn get-segments + "Given a content and a set of points return all the segments in the path + that uses the points" + [content points] + (let [point-set (set points)] + + (loop [segments [] + prev-point nil + start-point nil + cur-cmd (first content) + content (rest content)] + + (let [;; Close-path makes a segment from the last point to the initial path point + cur-point (if (= :close-path (:command cur-cmd)) + start-point + (command->point cur-cmd)) + + ;; If there is a move-to we don't have a segment + prev-point (if (= :move-to (:command cur-cmd)) + nil + prev-point) + + ;; We update the start point + start-point (if (= :move-to (:command cur-cmd)) + cur-point + start-point) + + is-segment? (and (some? prev-point) + (contains? point-set prev-point) + (contains? point-set cur-point)) + + segments (cond-> segments + is-segment? + (conj [prev-point cur-point]))] + + (if (some? cur-cmd) + (recur segments + cur-point + start-point + (first content) + (rest content)) + + segments))))) From e81b1b81157fb2fc79d062c0c1730b5128b98f26 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 6 Apr 2021 12:51:03 +0200 Subject: [PATCH 049/155] :sparkles: Adds point to curves tool --- common/app/common/geom/point.cljc | 9 +++++ common/app/common/geom/shapes/path.cljc | 14 +++++++ .../app/main/data/workspace/path/tools.cljs | 35 ++++++++++++++++- frontend/src/app/util/geom/path.cljs | 39 ++++++++++++++++--- 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/common/app/common/geom/point.cljc b/common/app/common/geom/point.cljc index 0124b142d..7a7dfeee6 100644 --- a/common/app/common/geom/point.cljc +++ b/common/app/common/geom/point.cljc @@ -253,7 +253,16 @@ (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))) ;; --- Debug (defmethod pp/simple-dispatch Point [obj] (pr obj)) + diff --git a/common/app/common/geom/shapes/path.cljc b/common/app/common/geom/shapes/path.cljc index 2072ade31..5ab3a340a 100644 --- a/common/app/common/geom/shapes/path.cljc +++ b/common/app/common/geom/shapes/path.cljc @@ -41,6 +41,20 @@ (gpt/point (coord-v :x) (coord-v :y)))) +(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] + + (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]])) + ;; https://pomax.github.io/bezierinfo/#extremities (defn curve-extremities "Given a cubic bezier cube finds its roots in t. This are the extremities diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs index 445da96cb..3fb92be7a 100644 --- a/frontend/src/app/main/data/workspace/path/tools.cljs +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -14,6 +14,7 @@ [app.main.data.workspace.path.common :as common] [app.main.data.workspace.path.state :as st] [app.util.geom.path :as ugp] + [app.common.geom.point :as gpt] [beicon.core :as rx] [potok.core :as ptk])) @@ -41,8 +42,40 @@ [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) +(defn split-segments [[start end cmd]] + (case (:command cmd) + :line-to [cmd (ugp/split-line-to start cmd 0.5)] + :curve-to [cmd (ugp/split-curve-to start cmd 0.5)] + :close-path [cmd [(ugp/make-line-to (gpt/line-val start end 0.5)) + cmd]] + nil)) + (defn add-node [] - (ptk/reify ::add-node)) + (ptk/reify ::add-node + ptk/WatchEvent + (watch [_ state stream] + + (let [id (st/get-path-id state) + page-id (:current-page-id state) + shape (get-in state (st/get-path state)) + selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) + content (:content shape) + + + cmd-changes (->> (ugp/get-segments content selected-points) + (into {} + (comp (map split-segments) + (filter (comp not nil?))))) + + process-segments (fn [command] + (if (contains? cmd-changes command) + (get cmd-changes command) + [command])) + + new-content (into [] (mapcat process-segments) content) + + [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] + (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) (defn remove-node [] (ptk/reify ::remove-node)) diff --git a/frontend/src/app/util/geom/path.cljs b/frontend/src/app/util/geom/path.cljs index 452c2d329..d573b582c 100644 --- a/frontend/src/app/util/geom/path.cljs +++ b/frontend/src/app/util/geom/path.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] + [app.common.geom.shapes.path :as gshp] [app.util.a2c :refer [a2c]] [app.util.geom.path-impl-simplify :as impl-simplify] [app.util.svg :as usvg] @@ -64,6 +65,11 @@ (cond-> result (not (empty? current)) (conj current)))))) +(defn command->point [command] + (when-not (nil? command) + (let [{{:keys [x y]} :params} command] + (gpt/point x y)))) + (defn command->param-list [command] (let [params (:params command)] (case (:command command) @@ -387,6 +393,12 @@ (mapv command->string) (str/join ""))) +(defn make-line-to [to] + {:command :line-to + :relative false + :params {:x (:x to) + :y (:y to)}}) + (defn make-curve-params ([point] (make-curve-params point point point)) @@ -401,6 +413,26 @@ :c2x (:x h2) :c2y (:y h2)})) +(defn make-curve-to [to h1 h2] + {:command :curve-to + :relative false + :params (make-curve-params to h1 h2)}) + +(defn split-line-to [from-p cmd val] + (let [to-p (command->point cmd) + sp (gpt/line-val from-p to-p val)] + [(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)] + [(make-curve-to to1 h11 h21) + (make-curve-to to2 h12 h22)])) + (defn opposite-handler "Calculates the coordinates of the opposite handler" [point handler] @@ -441,11 +473,6 @@ (let [content (if (vector? content) content (into [] content))] (reduce apply-to-index content modifiers)))) -(defn command->point [command] - (when-not (nil? command) - (let [{{:keys [x y]} :params} command] - (gpt/point x y)))) - (defn content->points [content] (->> content (map #(when (-> % :params :x) (gpt/point (-> % :params :x) (-> % :params :y)))) @@ -638,7 +665,7 @@ segments (cond-> segments is-segment? - (conj [prev-point cur-point]))] + (conj [prev-point cur-point cur-cmd]))] (if (some? cur-cmd) (recur segments From 5361e429760b2f1222516e7d0a60579087d7b88f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 6 Apr 2021 14:27:58 +0200 Subject: [PATCH 050/155] :sparkles: Path split segments --- .../app/main/data/workspace/path/tools.cljs | 38 +++++++++---------- .../main/ui/workspace/viewport/actions.cljs | 4 +- frontend/src/app/util/geom/path.cljs | 22 +++++++++++ 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs index 3fb92be7a..ad2764dda 100644 --- a/frontend/src/app/main/data/workspace/path/tools.cljs +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -42,19 +42,22 @@ [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) -(defn split-segments [[start end cmd]] - (case (:command cmd) - :line-to [cmd (ugp/split-line-to start cmd 0.5)] - :curve-to [cmd (ugp/split-curve-to start cmd 0.5)] - :close-path [cmd [(ugp/make-line-to (gpt/line-val start end 0.5)) - cmd]] - nil)) - (defn add-node [] (ptk/reify ::add-node ptk/WatchEvent (watch [_ state stream] + (let [id (st/get-path-id state) + page-id (:current-page-id state) + shape (get-in state (st/get-path state)) + selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) + new-content (ugp/split-segments (:content shape) selected-points 0.5) + [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] + (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) +(defn remove-node [] + (ptk/reify ::remove-node + ptk/WatchEvent + (watch [_ state stream] (let [id (st/get-path-id state) page-id (:current-page-id state) shape (get-in state (st/get-path state)) @@ -62,23 +65,16 @@ content (:content shape) - cmd-changes (->> (ugp/get-segments content selected-points) - (into {} - (comp (map split-segments) - (filter (comp not nil?))))) + + - process-segments (fn [command] - (if (contains? cmd-changes command) - (get cmd-changes command) - [command])) - - new-content (into [] (mapcat process-segments) content) + new-content (->> content + (filterv #(not (contains? selected-points (ugp/command->point %))))) [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] - (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) + (rx/of (dwc/commit-changes rch uch {:commit-local? true})))) -(defn remove-node [] - (ptk/reify ::remove-node)) + )) (defn merge-nodes [] (ptk/reify ::merge-nodes)) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 21c7606c7..2cea72660 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -59,10 +59,10 @@ (when (and (not= edition id) text-editing?) (st/emit! dw/clear-edition-mode)) - (when (and (or (not edition) (not= edition id)) (not blocked) (not hidden) (not (#{:comments :path} drawing-tool))) - (not= edition id)) + (when (and (or (not edition) (not= edition id)) (not blocked) (not hidden) + (not (#{:comments :path} drawing-tool)) (not drawing-path?)) (cond drawing-tool diff --git a/frontend/src/app/util/geom/path.cljs b/frontend/src/app/util/geom/path.cljs index d573b582c..7ae7bbce5 100644 --- a/frontend/src/app/util/geom/path.cljs +++ b/frontend/src/app/util/geom/path.cljs @@ -675,3 +675,25 @@ (rest content)) segments))))) + +(defn split-segments [content points value] + (let [split-command + (fn [[start end cmd]] + (case (:command cmd) + :line-to [cmd (split-line-to start cmd value)] + :curve-to [cmd (split-curve-to start cmd value)] + :close-path [cmd [(make-line-to (gpt/line-val start end value)) cmd]] + nil)) + + cmd-changes + (->> (get-segments content points) + (into {} (comp (map split-command) + (filter (comp not nil?))))) + + process-segments + (fn [command] + (if (contains? cmd-changes command) + (get cmd-changes command) + [command]))] + + (into [] (mapcat process-segments) content))) From bc3640893c2d64cf76c1344961352505ae171079 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Apr 2021 13:21:05 +0200 Subject: [PATCH 051/155] :sparkles: Remove nodes --- .../app/main/data/workspace/path/tools.cljs | 9 +-- frontend/src/app/util/geom/path.cljs | 75 ++++++++++++++++++- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs index ad2764dda..8fc961b7a 100644 --- a/frontend/src/app/main/data/workspace/path/tools.cljs +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -63,14 +63,7 @@ shape (get-in state (st/get-path state)) selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) content (:content shape) - - - - - - new-content (->> content - (filterv #(not (contains? selected-points (ugp/command->point %))))) - + new-content (ugp/remove-nodes content selected-points) [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] (rx/of (dwc/commit-changes rch uch {:commit-local? true})))) diff --git a/frontend/src/app/util/geom/path.cljs b/frontend/src/app/util/geom/path.cljs index 7ae7bbce5..aa825e1a4 100644 --- a/frontend/src/app/util/geom/path.cljs +++ b/frontend/src/app/util/geom/path.cljs @@ -676,7 +676,9 @@ segments))))) -(defn split-segments [content points value] +(defn split-segments + "Given a content creates splits commands between points with new segments" + [content points value] (let [split-command (fn [[start end cmd]] (case (:command cmd) @@ -697,3 +699,74 @@ [command]))] (into [] (mapcat process-segments) content))) + +(defn remove-nodes + "Removes from content the points given. Will try to reconstruct the paths + to keep everything consistent" + [content points] + + (let [content (d/with-prev content)] + + (loop [result [] + last-handler nil + [cur-cmd prev-cmd] (first content) + content (rest content)] + + (if (nil? cur-cmd) + ;; The result with be an array of arrays were every entry is a subpath + (->> result + ;; remove empty and only 1 node subpaths + (filter #(> (count %) 1)) + ;; flatten array-of-arrays plain array + (flatten) + (into [])) + + (let [move? (= :move-to (:command cur-cmd)) + curve? (= :curve-to (:command cur-cmd)) + + ;; When the old command was a move we start a subpath + result (if move? (conj result []) result) + + subpath (peek result) + + point (command->point cur-cmd) + + old-prev-point (command->point prev-cmd) + new-prev-point (command->point (peek subpath)) + + remove? (contains? points point) + + + ;; We store the first handler for the first curve to be removed to + ;; use it for the first handler of the regenerated path + cur-handler (cond + (and (not last-handler) remove? curve?) + (select-keys (:params cur-cmd) [:c1x :c1y]) + + (not remove?) + nil + + :else + last-handler) + + cur-cmd (cond-> cur-cmd + ;; If we're starting a subpath and it's not a move make it a move + (and (not move?) (empty? subpath)) + (assoc :command :move-to + :params (select-keys (:params cur-cmd) [:x :y])) + + ;; If have a curve the first handler will be relative to the previous + ;; point. We change the handler to the new previous point + (and curve? (not (empty? subpath)) (not= old-prev-point new-prev-point)) + (update :params merge last-handler)) + + head-idx (dec (count result)) + + result (cond-> result + (not remove?) + (update head-idx conj cur-cmd))] + (recur result + cur-handler + (first content) + (rest content))))))) + From fc383664c7f07f957dd80c93debde23c5b240187 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Apr 2021 19:00:29 +0200 Subject: [PATCH 052/155] :sparkles: Adds join, merge, separate nodes --- common/app/common/geom/point.cljc | 4 +- .../app/main/data/workspace/path/tools.cljs | 57 +++---- .../ui/workspace/viewport/path_actions.cljs | 2 +- frontend/src/app/util/geom/path.cljs | 147 +++++++++++++++++- 4 files changed, 168 insertions(+), 42 deletions(-) diff --git a/common/app/common/geom/point.cljc b/common/app/common/geom/point.cljc index 7a7dfeee6..6e656d888 100644 --- a/common/app/common/geom/point.cljc +++ b/common/app/common/geom/point.cljc @@ -220,7 +220,9 @@ v2-unit (point scalar-projection scalar-projection)))) -(defn center-points [points] +(defn center-points + "Centroid of a group of points" + [points] (let [k (point (count points))] (reduce #(add %1 (divide %2 k)) (point) points))) diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs index 8fc961b7a..850e925e2 100644 --- a/frontend/src/app/main/data/workspace/path/tools.cljs +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -18,65 +18,44 @@ [beicon.core :as rx] [potok.core :as ptk])) -(defn make-corner [] - (ptk/reify ::make-corner +(defn process-path-tool + "Generic function that executes path transformations with the content and selected nodes" + [tool-fn] + (ptk/reify ::process-path-tool ptk/WatchEvent (watch [_ state stream] (let [id (st/get-path-id state) page-id (:current-page-id state) shape (get-in state (st/get-path state)) selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) - new-content (reduce ugp/make-corner-point (:content shape) selected-points) + new-content (tool-fn (:content shape) selected-points) [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) +(defn make-corner [] + (process-path-tool + (fn [content points] + (reduce ugp/make-corner-point content points)))) + (defn make-curve [] - (ptk/reify ::make-curve - ptk/WatchEvent - (watch [_ state stream] - (let [id (st/get-path-id state) - page-id (:current-page-id state) - shape (get-in state (st/get-path state)) - selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) - new-content (reduce ugp/make-curve-point (:content shape) selected-points) - [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] - (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) + (process-path-tool + (fn [content points] + (reduce ugp/make-curve-point content points)))) (defn add-node [] - (ptk/reify ::add-node - ptk/WatchEvent - (watch [_ state stream] - (let [id (st/get-path-id state) - page-id (:current-page-id state) - shape (get-in state (st/get-path state)) - selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) - new-content (ugp/split-segments (:content shape) selected-points 0.5) - [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] - (rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) + (process-path-tool (fn [content points] (ugp/split-segments content points 0.5)))) (defn remove-node [] - (ptk/reify ::remove-node - ptk/WatchEvent - (watch [_ state stream] - (let [id (st/get-path-id state) - page-id (:current-page-id state) - shape (get-in state (st/get-path state)) - selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) - content (:content shape) - new-content (ugp/remove-nodes content selected-points) - [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] - (rx/of (dwc/commit-changes rch uch {:commit-local? true})))) - - )) + (process-path-tool ugp/remove-nodes)) (defn merge-nodes [] - (ptk/reify ::merge-nodes)) + (process-path-tool ugp/merge-nodes)) (defn join-nodes [] - (ptk/reify ::join-nodes)) + (process-path-tool ugp/join-nodes)) (defn separate-nodes [] - (ptk/reify ::separate-nodes)) + (process-path-tool ugp/separate-nodes)) (defn toggle-snap [] (ptk/reify ::toggle-snap diff --git a/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs b/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs index cc0caf131..8eb346a93 100644 --- a/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs @@ -28,7 +28,7 @@ :add-node segments-selected? :remove-node points-selected? :merge-nodes segments-selected? - :join-nodes segments-selected? + :join-nodes points-selected? :separate-nodes segments-selected?})) (mf/defc path-actions [{:keys [shape]}] diff --git a/frontend/src/app/util/geom/path.cljs b/frontend/src/app/util/geom/path.cljs index aa825e1a4..c4a9a7249 100644 --- a/frontend/src/app/util/geom/path.cljs +++ b/frontend/src/app/util/geom/path.cljs @@ -12,7 +12,9 @@ [app.util.a2c :refer [a2c]] [app.util.geom.path-impl-simplify :as impl-simplify] [app.util.svg :as usvg] - [cuerdas.core :as str])) + [cuerdas.core :as str] + [clojure.set :as set] + [app.common.math :as mth])) (defn calculate-opposite-handler "Given a point and its handler, gives the symetric handler" @@ -393,6 +395,12 @@ (mapv command->string) (str/join ""))) +(defn make-move-to [to] + {:command :move-to + :relative false + :params {:x (:x to) + :y (:y to)}}) + (defn make-line-to [to] {:command :line-to :relative false @@ -770,3 +778,140 @@ (first content) (rest content))))))) +(defn join-nodes + "Creates new segments between points that weren't previously" + [content points] + + (let [segments-set (into #{} + (map (fn [[p1 p2 _]] [p1 p2])) + (get-segments content points)) + + create-line-command (fn [point other] + [(make-move-to point) + (make-line-to other)]) + + not-segment? (fn [point other] (and (not (contains? segments-set [point other])) + (not (contains? segments-set [other point])))) + + new-content (->> (d/map-perm create-line-command not-segment? points) + (flatten) + (into []))] + + (d/concat content new-content))) + + +(defn separate-nodes + "Removes the segments between the points given" + [content points] + + (let [content (d/with-prev content)] + (loop [result [] + [cur-cmd prev-cmd] (first content) + content (rest content)] + + (if (nil? cur-cmd) + (->> result + (filter #(> (count %) 1)) + (flatten) + (into [])) + + (let [prev-point (command->point prev-cmd) + cur-point (command->point cur-cmd) + + cur-cmd (cond-> cur-cmd + (and (contains? points prev-point) + (contains? points cur-point)) + + (assoc :command :move-to + :params (select-keys (:params cur-cmd) [:x :y]))) + + move? (= :move-to (:command cur-cmd)) + + result (if move? (conj result []) result) + head-idx (dec (count result)) + + result (-> result + (update head-idx conj cur-cmd))] + (recur result + (first content) + (rest content))))))) + + +(defn- add-to-set + "Given a list of sets adds the value to the target set" + [set-list target value] + (->> set-list + (mapv (fn [it] + (cond-> it + (= it target) (conj value)))))) + +(defn- join-sets + "Given a list of sets join two sets in the list into a new one" + [set-list target other] + (conj (->> set-list + (filterv #(and (not= % target) + (not= % other)))) + (set/union target other))) + +(defn group-segments [segments] + (loop [result [] + [point-a point-b :as segment] (first segments) + segments (rest segments)] + + (if (nil? segment) + result + + (let [set-a (d/seek #(contains? % point-a) result) + set-b (d/seek #(contains? % point-b) result) + + result (cond-> result + (and (nil? set-a) (nil? set-b)) + (conj #{point-a point-b}) + + (and (some? set-a) (nil? set-b)) + (add-to-set set-a point-b) + + (and (nil? set-a) (some? set-b)) + (add-to-set set-b point-a) + + (and (some? set-a) (some? set-b) (not= set-a set-b)) + (join-sets set-a set-b))] + (recur result + (first segments) + (rest segments)))))) + +(defn calculate-merge-points [group-segments points] + (let [index-merge-point (fn [group] (vector group (-> (gpt/center-points group) + (update :x mth/round) + (update :y mth/round)))) + index-group (fn [point] (vector point (d/seek #(contains? % point) group-segments))) + + group->merge-point (into {} (map index-merge-point) group-segments) + point->group (into {} (map index-group) points)] + (d/mapm #(group->merge-point %2) point->group))) + +;; TODO: Improve the replace for curves +(defn replace-points + "Replaces the points in a path for its merge-point" + [content point->merge-point] + (let [replace-command + (fn [cmd] + (let [point (command->point cmd)] + (if (contains? point->merge-point point) + (let [merge-point (get point->merge-point point)] + (-> cmd (update :params assoc :x (:x merge-point) :y (:y merge-point)))) + cmd)))] + (->> content + (mapv replace-command)))) + +(defn merge-nodes + "Reduces the continguous segments in points to a single point" + [content points] + (let [point->merge-point (-> content + (get-segments points) + (group-segments) + (calculate-merge-points points))] + (-> content + (separate-nodes points) + (replace-points point->merge-point)))) + From 6db144e5edd5f56f4db66444b27168f9ea508e68 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 8 Apr 2021 17:48:59 +0200 Subject: [PATCH 053/155] :sparkles: Path-point calculation --- common/app/common/geom/shapes/path.cljc | 89 +++++++++++++++++++ .../app/main/data/workspace/path/drawing.cljs | 29 ++++-- .../app/main/data/workspace/path/edition.cljs | 9 +- .../app/main/data/workspace/path/streams.cljs | 25 ++++-- frontend/src/app/main/snap.cljs | 30 +++++++ .../main/ui/workspace/shapes/path/editor.cljs | 54 +++++++++-- .../src/app/main/ui/workspace/viewport.cljs | 3 +- 7 files changed, 212 insertions(+), 27 deletions(-) diff --git a/common/app/common/geom/shapes/path.cljc b/common/app/common/geom/shapes/path.cljc index 5ab3a340a..eaa177254 100644 --- a/common/app/common/geom/shapes/path.cljc +++ b/common/app/common/geom/shapes/path.cljc @@ -225,3 +225,92 @@ point)) (conj result [prev-point last-start])))) + +(defonce path-closest-point-accuracy 0.01) +(defn curve-closest-point + [position start end h1 h2] + (let [d (memoize (fn [t] (gpt/distance position (curve-values start end h1 h2 t))))] + (loop [t1 0 + t2 1] + (if (<= (mth/abs (- t1 t2)) path-closest-point-accuracy) + (curve-values start end h1 h2 t1) + + (let [ht (+ t1 (/ (- t2 t1) 2)) + ht1 (+ t1 (/ (- t2 t1) 4)) + ht2 (+ t1 (/ (* 3 (- t2 t1)) 4)) + + [t1 t2] (cond + (< (d ht1) (d ht2)) + [t1 ht] + + (< (d ht2) (d ht1)) + [ht t2] + + (and (< (d ht) (d t1)) (< (d ht) (d t2))) + [ht1 ht2] + + (< (d t1) (d t2)) + [t1 ht] + + :else + [ht t2])] + (recur t1 t2)))))) + +(defn line-closest-point + "Point on line" + [position from-p to-p] + + (let [{v1x :x v1y :y} from-p + {v2x :x v2y :y} to-p + {px :x py :y} position + + e1 (gpt/point (- v2x v1x) (- v2y v1y)) + e2 (gpt/point (- px v1x) (- py v1y)) + + len2 (+ (mth/sq (:x e1)) (mth/sq (:y e1))) + val-dp (/ (gpt/dot e1 e2) len2)] + + (if (and (>= val-dp 0) + (<= val-dp 1) + (not (mth/almost-zero? len2))) + (gpt/point (+ v1x (* val-dp (:x e1))) + (+ v1y (* val-dp (:y e1)))) + ;; There is no perpendicular projection in the line so the closest + ;; point will be one of the extremes + (if (<= (gpt/distance position from-p) (gpt/distance position to-p)) + from-p + to-p)))) + +(defn path-closest-point + "Given a path and a position" + [shape position] + + (let [point+distance (fn [[cur-cmd prev-cmd]] + (let [point + (case (:command cur-cmd) + :line-to (line-closest-point + position + (command->point prev-cmd) + (command->point cur-cmd)) + :curve-to (curve-closest-point + position + (command->point prev-cmd) + (command->point cur-cmd) + (gpt/point (get-in cur-cmd [:params :c1x]) + (get-in cur-cmd [:params :c1y])) + (gpt/point (get-in cur-cmd [:params :c2x]) + (get-in cur-cmd [:params :c2y]))) + 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))) + [min-p min-dist] + [cur-p cur-dist]))] + + (->> (:content shape) + (d/with-prev) + (map point+distance) + (reduce find-min-point) + (first)))) diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index feadd1a9e..09633ec17 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -133,8 +133,11 @@ (->> stream (rx/filter #(or (helpers/end-path-event? %) (ms/mouse-up? %)))) + content (get-in state (st/get-path state :content)) + points (ugp/content->points content) + drag-events-stream - (->> (streams/position-stream) + (->> (streams/position-stream points) (rx/take-until stop-stream) (rx/map #(drag-handler %)))] @@ -163,7 +166,10 @@ zoom (get-in state [:workspace-local :zoom]) mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %) (ms/mouse-up? %)))) - drag-events (->> (streams/position-stream) + content (get-in state (st/get-path state :content)) + points (ugp/content->points content) + + drag-events (->> (streams/position-stream points) (rx/take-until mouse-up) (rx/map #(drag-handler %)))] @@ -183,10 +189,10 @@ (rx/merge-map #(rx/empty)))) (defn make-drag-stream - [stream down-event zoom] + [stream down-event zoom points] (let [mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %) (ms/mouse-up? %)))) - drag-events (->> (streams/position-stream) + drag-events (->> (streams/position-stream points) (rx/take-until mouse-up) (rx/map #(drag-handler %)))] @@ -213,9 +219,12 @@ mouse-down (->> stream (rx/filter ms/mouse-down?)) end-path-events (->> stream (rx/filter helpers/end-path-event?)) + content (get-in state (st/get-path state :content)) + points (ugp/content->points content) + ;; Mouse move preview mousemove-events - (->> (streams/position-stream) + (->> (streams/position-stream points) (rx/take-until end-path-events) (rx/map #(preview-next-point %))) @@ -223,12 +232,12 @@ mousedown-events (->> mouse-down (rx/take-until end-path-events) - (rx/with-latest merge (streams/position-stream)) + (rx/with-latest merge (streams/position-stream points)) ;; We change to the stream that emits the first event (rx/switch-map #(rx/race (make-node-events-stream stream) - (make-drag-stream stream % zoom))))] + (make-drag-stream stream % zoom points))))] (rx/concat (rx/of (common/init-path)) @@ -269,6 +278,12 @@ "Creates a new path shape" [] (ptk/reify ::handle-new-shape + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state)] + (-> state + (assoc-in [:workspace-local :edit-path id :snap-toggled] true)))) + ptk/WatchEvent (watch [_ state stream] (let [shape-id (get-in state [:workspace-drawing :object :id])] diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index c40c99259..4f723b8fb 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -118,6 +118,9 @@ selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) selected? (contains? selected-points position) + content (get-in state (st/get-path state :content)) + points (ugp/content->points content) + mouse-drag-stream (rx/concat ;; If we're dragging a selected item we don't change the selection @@ -126,7 +129,7 @@ (rx/of (selection/select-node position shift?))) ;; This stream checks the consecutive mouse positions to do the draging - (->> (streams/position-stream) + (->> (streams/position-stream points) (rx/take-until stopper) (rx/map #(move-selected-path-point start-position %))) (rx/of (apply-content-modifiers))) @@ -151,6 +154,8 @@ start-delta-y (get-in modifiers [index cy] 0) content (get-in state (st/get-path state :content)) + points (ugp/content->points content) + opposite-index (ugp/opposite-index content index prefix) opposite-prefix (if (= prefix :c1) :c2 :c1) opposite-handler (-> content (get opposite-index) (ugp/get-handler opposite-prefix)) @@ -163,7 +168,7 @@ (streams/drag-stream (rx/concat - (->> (streams/position-stream) + (->> (streams/position-stream points) (rx/take-until (->> stream (rx/filter ms/mouse-up?))) (rx/map (fn [{:keys [x y alt? shift?]}] diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs index eb061f0b2..2f5c27a85 100644 --- a/frontend/src/app/main/data/workspace/path/streams.cljs +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -15,7 +15,8 @@ [app.main.streams :as ms] [beicon.core :as rx] [potok.core :as ptk] - [app.common.math :as mth])) + [app.common.math :as mth] + [app.main.snap :as snap])) (defonce drag-threshold 5) @@ -53,11 +54,17 @@ (let [k 50] (* (mth/floor (/ num k)) k))) -(defn position-stream [] - (->> ms/mouse-position - ;; TODO: Prueba para el snap - #_(rx/map #(-> % - (update :x to-dec) - (update :y to-dec))) - (rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %)))) - (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %)))))) +(defn position-stream + ([points] + (position-stream points #{})) + + ([points selected-points] + (let [zoom (get-in @st/state [:workspace-local :zoom] 1)] + (->> (snap/path-snap ms/mouse-position points selected-points zoom) + (rx/with-latest vector ms/mouse-position) + (rx/map (fn [[{[x] :x [y] :y} position]] + (cond-> position + (some? x) (assoc :x x) + (some? y) (assoc :y y)))) + (rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %)))) + (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %)))))))) diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index e82c67f00..36570576e 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -15,6 +15,7 @@ [app.main.refs :as refs] [app.main.worker :as uw] [app.util.geom.snap-points :as sp] + [app.util.range-tree :as rt] [beicon.core :as rx] [clojure.set :as set])) @@ -240,3 +241,32 @@ (rx/reduce gpt/min) (rx/map #(or % (gpt/point 0 0)))))) +(defn path-snap [position-stream points selected-points zoom] + (let [selected-points (or selected-points #{}) + into-tree (fn [coord] + (fn [tree point] + (rt/insert tree (get point coord) point))) + + ranges-x (->> points + (filter (comp not selected-points)) + (reduce (into-tree :x) (rt/make-tree))) + + ranges-y (->> points + (filter (comp not selected-points)) + (reduce (into-tree :y) (rt/make-tree))) + + min-match (fn [matches] + (->> matches + (reduce (fn [[cur-val :as current] [other-val :as other]] + (if (< cur-val other-val) + current + other)))))] + + (->> position-stream + (rx/map + (fn [{:keys [x y]}] + (let [d-pos (/ snap-accuracy zoom) + x-match (rt/range-query ranges-x (- x d-pos) (+ x d-pos)) + y-match (rt/range-query ranges-y (- y d-pos) (+ y d-pos))] + {:x (min-match x-match) + :y (min-match y-match)})))))) 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 85fb3ce86..66fcdf63f 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -8,16 +8,19 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] + [app.common.geom.shapes.path :as gshp] [app.main.data.workspace.path :as drp] + [app.main.snap :as snap] [app.main.store :as st] + [app.main.streams :as ms] [app.main.ui.cursors :as cur] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.shapes.path.common :as pc] [app.util.dom :as dom] [app.util.geom.path :as ugp] + [app.util.keyboard :as kbd] [goog.events :as events] - [rumext.alpha :as mf] - - [app.util.keyboard :as kbd]) + [rumext.alpha :as mf]) (:import goog.events.EventType)) (mf/defc path-point [{:keys [position zoom edit-mode hover? selected? preview? start-path? last-p?]}] @@ -131,8 +134,9 @@ [:g.preview {:style {:pointer-events "none"}} (when (not= :move-to (:command command)) [:path {:style {:fill "transparent" - :stroke pc/secondary-color - :stroke-width (/ 1 zoom)} + :stroke pc/black-color + :stroke-width (/ 1 zoom) + :stroke-dasharray (/ 4 zoom)} :d (ugp/content->path [{:command :move-to :params {:x (:x from) :y (:y from)}} @@ -141,11 +145,23 @@ :preview? true :zoom zoom}]]) +(mf/defc snap-path-points [{:keys [snaps zoom]}] + [:g.snap-paths + (for [[from to] snaps] + [:line {:x1 (:x from) + :y1 (:y from) + :x2 (:x to) + :y2 (:y to) + :style {:stroke pc/secondary-color + :stroke-width (/ 1 zoom)}}])]) + (mf/defc path-editor [{:keys [shape zoom]}] (let [editor-ref (mf/use-ref nil) edit-path-ref (pc/make-edit-path-ref (:id shape)) + hover-point (mf/use-state nil) + {:keys [edit-mode drag-handler prev-handler @@ -158,9 +174,9 @@ hover-points] :as edit-path} (mf/deref edit-path-ref) - {:keys [content]} shape - content (ugp/apply-content-modifiers content content-modifiers) - points (->> content ugp/content->points (into #{})) + {base-content :content} shape + content (ugp/apply-content-modifiers base-content content-modifiers) + points (mf/use-memo (mf/deps content) #(->> content ugp/content->points (into #{}))) last-command (last content) last-p (->> content last ugp/command->point) handlers (ugp/content->handlers content) @@ -177,12 +193,34 @@ #(doseq [key keys] (events/unlistenByKey key))))) + #_(hooks/use-stream + ms/mouse-position + (mf/deps shape) + (fn [position] + (reset! hover-point (gshp/path-closest-point shape position)))) + + (hooks/use-stream + (mf/use-memo + (mf/deps base-content selected-points zoom) + #(snap/path-snap ms/mouse-position points selected-points zoom)) + + (fn [result] + (prn "??" result))) + [:g.path-editor {:ref editor-ref} + #_[:& snap-points {}] + + (when (and preview (not drag-handler)) [:& path-preview {:command preview :from last-p :zoom zoom}]) + (when @hover-point + [:g.hover-point + [:& path-point {:position @hover-point + :zoom zoom}]]) + (for [position points] (let [point-selected? (contains? selected-points position) point-hover? (contains? hover-points position) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index c9f3779ab..fda4afe53 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -171,7 +171,8 @@ :width (:width vport 0) :height (:height vport 0) :view-box (utils/format-viewbox vbox) - :style {:background-color (get options :background "#E8E9EA")}} + :style {:background-color (get options :background "#E8E9EA") + :pointer-events "none"}} [:& (mf/provider muc/embed-ctx) {:value true} ;; Render root shape From 5ce2bc862c5e1e424c59a8f9e49eb0588dd1b7d8 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 12 Apr 2021 16:02:51 +0200 Subject: [PATCH 054/155] :recycle: Move streams refactor --- frontend/deps.edn | 2 +- .../app/main/data/workspace/path/edition.cljs | 5 +- .../app/main/data/workspace/path/streams.cljs | 69 ++++++++--- frontend/src/app/main/snap.cljs | 113 +++++++++++++++++- .../main/ui/workspace/shapes/path/editor.cljs | 2 +- 5 files changed, 172 insertions(+), 19 deletions(-) diff --git a/frontend/deps.edn b/frontend/deps.edn index 51d43a0f6..313e8db1a 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -11,7 +11,7 @@ danlentz/clj-uuid {:mvn/version "0.1.9"} frankiesardo/linked {:mvn/version "1.3.0"} - funcool/beicon {:mvn/version "2021.04.09-1"} + funcool/beicon {:mvn/version "2021.04.12-1"} funcool/cuerdas {:mvn/version "2020.03.26-3"} funcool/okulary {:mvn/version "2020.04.14-0"} funcool/potok {:mvn/version "3.2.0"} diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 4f723b8fb..afc98af60 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -129,7 +129,8 @@ (rx/of (selection/select-node position shift?))) ;; This stream checks the consecutive mouse positions to do the draging - (->> (streams/position-stream points) + (->> points + (streams/move-points-stream start-position selected-points) (rx/take-until stopper) (rx/map #(move-selected-path-point start-position %))) (rx/of (apply-content-modifiers))) @@ -168,7 +169,7 @@ (streams/drag-stream (rx/concat - (->> (streams/position-stream points) + (->> (streams/move-handler-stream start-point handler points) (rx/take-until (->> stream (rx/filter ms/mouse-up?))) (rx/map (fn [{:keys [x y alt? shift?]}] diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs index 2f5c27a85..9dfd011f4 100644 --- a/frontend/src/app/main/data/workspace/path/streams.cljs +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -10,13 +10,16 @@ (ns app.main.data.workspace.path.streams (:require [app.main.data.workspace.path.helpers :as helpers] + [app.main.data.workspace.path.state :as state] [app.common.geom.point :as gpt] [app.main.store :as st] [app.main.streams :as ms] [beicon.core :as rx] [potok.core :as ptk] [app.common.math :as mth] - [app.main.snap :as snap])) + [app.main.snap :as snap] + [okulary.core :as l] + [app.util.geom.path :as ugp])) (defonce drag-threshold 5) @@ -54,17 +57,55 @@ (let [k 50] (* (mth/floor (/ num k)) k))) -(defn position-stream - ([points] - (position-stream points #{})) +(defn move-points-stream + [start-point selected-points points] - ([points selected-points] - (let [zoom (get-in @st/state [:workspace-local :zoom] 1)] - (->> (snap/path-snap ms/mouse-position points selected-points zoom) - (rx/with-latest vector ms/mouse-position) - (rx/map (fn [[{[x] :x [y] :y} position]] - (cond-> position - (some? x) (assoc :x x) - (some? y) (assoc :y y)))) - (rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %)))) - (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %)))))))) + (let [zoom (get-in @st/state [:workspace-local :zoom] 1) + ranges (snap/create-ranges selected-points points) + d-pos (/ snap/snap-accuracy zoom)] + (->> ms/mouse-position + (rx/map (fn [position] + (let [delta (gpt/subtract position start-point) + moved-points (->> selected-points (mapv #(gpt/add % delta)))] + (gpt/add + position + (snap/get-snap-delta moved-points ranges d-pos))))))) + ) + +(defn move-handler-stream + [start-point handler points] + + (let [zoom (get-in @st/state [:workspace-local :zoom] 1) + ranges (snap/create-ranges points) + d-pos (/ snap/snap-accuracy zoom)] + (->> ms/mouse-position + (rx/map (fn [position] + (let [delta (gpt/subtract position start-point) + handler-position (gpt/add handler delta)] + (gpt/add + position + (snap/get-snap-delta [handler-position] ranges d-pos)))))))) + +(defn position-stream + [points] + (let [zoom (get-in @st/state [:workspace-local :zoom] 1) + ranges (snap/create-ranges points) + d-pos (/ snap/snap-accuracy zoom) + get-content (fn [state] (get-in state (state/get-path state :content))) + + content-stream (-> (l/derived get-content st/state) + (rx/from-atom)) + ] + + (->> content-stream + (rx/map ugp/content->points) + (rx/subs #(prn "????" %))) + + + (->> ms/mouse-position + (rx/tap #(prn "pos" %)) + (rx/map #(let [snap (snap/get-snap-delta [%] ranges d-pos)] + (prn ">>>" snap) + (gpt/add % snap))) + (rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %)))) + (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %))))))) diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index 36570576e..3831fb4f6 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -241,7 +241,118 @@ (rx/reduce gpt/min) (rx/map #(or % (gpt/point 0 0)))))) -(defn path-snap [position-stream points selected-points zoom] + +;;; PATH SNAP + +(defn create-ranges + ([points] + (create-ranges points #{})) + + ([points selected-points] + (let [selected-points (or selected-points #{}) + + into-tree + (fn [coord] + (fn [tree point] + (rt/insert tree (get point coord) point))) + + make-ranges + (fn [coord] + (->> points + (filter (comp not selected-points)) + (reduce (into-tree coord) (rt/make-tree))))] + + {:x (make-ranges :x) + :y (make-ranges :y)}))) + +(defn query-delta-point [ranges point precision] + (let [query-coord + (fn [coord] + (let [pval (get point coord)] + #_(prn "..." (rt/range-query (get ranges coord) (- pval precision) (+ pval precision))) + + (->> (rt/range-query (get ranges coord) (- pval precision) (+ pval precision)) + + ;; We save the distance to the point and add the matching point to the points + (mapv (fn [[value points]] + #_(prn "!! " value [(mth/abs (- value pval)) + (->> points (mapv #(vector point %)))]) + [(mth/abs (- value pval)) + (->> points (mapv #(vector point %)))])))))] + + {:x (query-coord :x) + :y (query-coord :y)})) + +(defn merge-matches [matches other] + (let [merge-coord + (fn [matches other] + (prn "merge-coord" matches other) + (into {} + (map (fn [key] [key (d/concat [] (get matches key) (get other key))])) + (set/union (keys matches) (keys other))))] + + (-> matches + (update :x merge-matches (:x other)) + (update :y merge-matches (:y other))))) + +(defn min-match + [default matches] + (let [get-min + (fn [[cur-val :as current] [other-val :as other]] + (if (< cur-val other-val) + current + other)) + + min-match-coord + (fn [matches] + (if (and (seq matches) (not (empty? matches))) + (->> matches (reduce get-min)) + default))] + + (-> matches + (update :x min-match-coord) + (update :y min-match-coord)))) + +(defn get-snap-delta-match + [points ranges accuracy] + (assert vector? points) + + (->> points + (mapv #(query-delta-point ranges % accuracy)) + (reduce merge-matches) + (min-match [0 nil]))) + +(defn get-snap-delta + [points ranges accuracy] + (-> (get-snap-delta-match points ranges accuracy) + (update :x first) + (update :y first) + (gpt/point))) + +#_(defn path-snap-points-delta [points-stream selected-points points zoom] + + (let [ranges (create-ranges points selected-points) + d-pos (/ snap-accuracy zoom)] + + (->> points-stream + (rx/map (fn [points] + (get-snap-delta points ranges d-pos))))) + + + ) + +#_(defn path-snap [position-stream points selected-points zoom] + (let [ranges (create-ranges points selected-points) + d-pos (/ snap-accuracy zoom)] + + (->> position-stream + (rx/map (fn [position] + (gpt/add + position + (get-snap-delta position ranges d-pos))))))) + + +#_(defn path-snap [position-stream points selected-points zoom] (let [selected-points (or selected-points #{}) into-tree (fn [coord] (fn [tree point] 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 66fcdf63f..4299e681b 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -199,7 +199,7 @@ (fn [position] (reset! hover-point (gshp/path-closest-point shape position)))) - (hooks/use-stream + #_(hooks/use-stream (mf/use-memo (mf/deps base-content selected-points zoom) #(snap/path-snap ms/mouse-position points selected-points zoom)) From 5f114163dc737a11f6abcb928970bf5d90497be2 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 12 Apr 2021 16:21:03 +0200 Subject: [PATCH 055/155] :sparkles: Fixes licenses headers --- frontend/src/app/main/data/workspace/path.cljs | 3 --- frontend/src/app/main/data/workspace/path/changes.cljs | 3 --- frontend/src/app/main/data/workspace/path/common.cljs | 3 --- frontend/src/app/main/data/workspace/path/drawing.cljs | 3 --- frontend/src/app/main/data/workspace/path/edition.cljs | 3 --- frontend/src/app/main/data/workspace/path/helpers.cljs | 3 --- frontend/src/app/main/data/workspace/path/selection.cljs | 3 --- frontend/src/app/main/data/workspace/path/spec.cljs | 3 --- frontend/src/app/main/data/workspace/path/state.cljs | 3 --- frontend/src/app/main/data/workspace/path/streams.cljs | 3 --- frontend/src/app/main/data/workspace/path/tools.cljs | 3 --- .../src/app/main/ui/workspace/viewport/path_actions.cljs | 3 --- frontend/src/app/main/ui/workspace/viewport/snap_path.cljs | 5 +---- 13 files changed, 1 insertion(+), 40 deletions(-) diff --git a/frontend/src/app/main/data/workspace/path.cljs b/frontend/src/app/main/data/workspace/path.cljs index c6c1dd2df..2a02ebe7f 100644 --- a/frontend/src/app/main/data/workspace/path.cljs +++ b/frontend/src/app/main/data/workspace/path.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.path diff --git a/frontend/src/app/main/data/workspace/path/changes.cljs b/frontend/src/app/main/data/workspace/path/changes.cljs index 53fdb6c9f..84e2de574 100644 --- a/frontend/src/app/main/data/workspace/path/changes.cljs +++ b/frontend/src/app/main/data/workspace/path/changes.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.path.changes diff --git a/frontend/src/app/main/data/workspace/path/common.cljs b/frontend/src/app/main/data/workspace/path/common.cljs index 0d28bf984..d8e6e19cb 100644 --- a/frontend/src/app/main/data/workspace/path/common.cljs +++ b/frontend/src/app/main/data/workspace/path/common.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.path.common diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index 09633ec17..781e3fffa 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.path.drawing diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index afc98af60..ff14d5d00 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.path.edition diff --git a/frontend/src/app/main/data/workspace/path/helpers.cljs b/frontend/src/app/main/data/workspace/path/helpers.cljs index f70e89ad9..0fe040d42 100644 --- a/frontend/src/app/main/data/workspace/path/helpers.cljs +++ b/frontend/src/app/main/data/workspace/path/helpers.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.path.helpers diff --git a/frontend/src/app/main/data/workspace/path/selection.cljs b/frontend/src/app/main/data/workspace/path/selection.cljs index 120845fc8..99ea74b17 100644 --- a/frontend/src/app/main/data/workspace/path/selection.cljs +++ b/frontend/src/app/main/data/workspace/path/selection.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.path.selection diff --git a/frontend/src/app/main/data/workspace/path/spec.cljs b/frontend/src/app/main/data/workspace/path/spec.cljs index 759f43dc0..96ad24fa0 100644 --- a/frontend/src/app/main/data/workspace/path/spec.cljs +++ b/frontend/src/app/main/data/workspace/path/spec.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.path.spec diff --git a/frontend/src/app/main/data/workspace/path/state.cljs b/frontend/src/app/main/data/workspace/path/state.cljs index 96c4c7221..6bb59c4c6 100644 --- a/frontend/src/app/main/data/workspace/path/state.cljs +++ b/frontend/src/app/main/data/workspace/path/state.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.path.state diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs index 9dfd011f4..99cf0f298 100644 --- a/frontend/src/app/main/data/workspace/path/streams.cljs +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.path.streams diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs index 850e925e2..90ea6a191 100644 --- a/frontend/src/app/main/data/workspace/path/tools.cljs +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.data.workspace.path.tools diff --git a/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs b/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs index 8eb346a93..85e4ff220 100644 --- a/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs @@ -2,9 +2,6 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; ;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.path-actions diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_path.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_path.cljs index 191756430..22f724c9f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_path.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_path.cljs @@ -2,10 +2,7 @@ ;; 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/. ;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.workspace.viewport.snap-path (:require From de8207c5a63ce91cd3d7d1674e389d1bfb57dd52 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 12 Apr 2021 18:22:47 +0200 Subject: [PATCH 056/155] :sparkles: Snap on paths --- .../app/main/data/workspace/path/streams.cljs | 27 +++--- frontend/src/app/main/snap.cljs | 90 +++++-------------- .../main/ui/workspace/shapes/path/editor.cljs | 50 +++++------ 3 files changed, 56 insertions(+), 111 deletions(-) diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs index 99cf0f298..4f0b2a5ef 100644 --- a/frontend/src/app/main/data/workspace/path/streams.cljs +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -86,23 +86,26 @@ (defn position-stream [points] (let [zoom (get-in @st/state [:workspace-local :zoom] 1) - ranges (snap/create-ranges points) + ;; ranges (snap/create-ranges points) d-pos (/ snap/snap-accuracy zoom) get-content (fn [state] (get-in state (state/get-path state :content))) - content-stream (-> (l/derived get-content st/state) - (rx/from-atom)) - ] - - (->> content-stream - (rx/map ugp/content->points) - (rx/subs #(prn "????" %))) + content-stream + (-> (l/derived get-content st/state) + (rx/from-atom {:emit-current-value? true})) + ranges-stream + (->> content-stream + (rx/map ugp/content->points) + (rx/map snap/create-ranges))] (->> ms/mouse-position - (rx/tap #(prn "pos" %)) - (rx/map #(let [snap (snap/get-snap-delta [%] ranges d-pos)] - (prn ">>>" snap) - (gpt/add % snap))) + (rx/with-latest vector ranges-stream) + (rx/map (fn [[position ranges]] + (let [snap (snap/get-snap-delta [position] ranges d-pos)] + #_(prn ">>>" snap) + (gpt/add position snap)) + )) + (rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %)))) (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %))))))) diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index 3831fb4f6..414cc0a6a 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -267,33 +267,35 @@ (defn query-delta-point [ranges point precision] (let [query-coord - (fn [coord] + (fn [point coord] (let [pval (get point coord)] - #_(prn "..." (rt/range-query (get ranges coord) (- pval precision) (+ pval precision))) - (->> (rt/range-query (get ranges coord) (- pval precision) (+ pval precision)) - ;; We save the distance to the point and add the matching point to the points (mapv (fn [[value points]] - #_(prn "!! " value [(mth/abs (- value pval)) - (->> points (mapv #(vector point %)))]) [(mth/abs (- value pval)) (->> points (mapv #(vector point %)))])))))] - {:x (query-coord :x) - :y (query-coord :y)})) + {:x (query-coord point :x) + :y (query-coord point :y)})) -(defn merge-matches [matches other] - (let [merge-coord - (fn [matches other] - (prn "merge-coord" matches other) - (into {} - (map (fn [key] [key (d/concat [] (get matches key) (get other key))])) - (set/union (keys matches) (keys other))))] +(defn merge-matches + ([] {:x nil :y nil}) + ([matches other] + (let [merge-coord + (fn [matches other] + + (let [matches (into {} matches) + other (into {} other) + keys (set/union (keys matches) (keys other))] + (into {} + (map (fn [key] + [key + (d/concat [] (get matches key []) (get other key []))])) + keys)))] - (-> matches - (update :x merge-matches (:x other)) - (update :y merge-matches (:y other))))) + (-> matches + (update :x merge-coord (:x other)) + (update :y merge-coord (:y other)))))) (defn min-match [default matches] @@ -329,55 +331,3 @@ (update :y first) (gpt/point))) -#_(defn path-snap-points-delta [points-stream selected-points points zoom] - - (let [ranges (create-ranges points selected-points) - d-pos (/ snap-accuracy zoom)] - - (->> points-stream - (rx/map (fn [points] - (get-snap-delta points ranges d-pos))))) - - - ) - -#_(defn path-snap [position-stream points selected-points zoom] - (let [ranges (create-ranges points selected-points) - d-pos (/ snap-accuracy zoom)] - - (->> position-stream - (rx/map (fn [position] - (gpt/add - position - (get-snap-delta position ranges d-pos))))))) - - -#_(defn path-snap [position-stream points selected-points zoom] - (let [selected-points (or selected-points #{}) - into-tree (fn [coord] - (fn [tree point] - (rt/insert tree (get point coord) point))) - - ranges-x (->> points - (filter (comp not selected-points)) - (reduce (into-tree :x) (rt/make-tree))) - - ranges-y (->> points - (filter (comp not selected-points)) - (reduce (into-tree :y) (rt/make-tree))) - - min-match (fn [matches] - (->> matches - (reduce (fn [[cur-val :as current] [other-val :as other]] - (if (< cur-val other-val) - current - other)))))] - - (->> position-stream - (rx/map - (fn [{:keys [x y]}] - (let [d-pos (/ snap-accuracy zoom) - x-match (rt/range-query ranges-x (- x d-pos) (+ x d-pos)) - y-match (rt/range-query ranges-y (- y d-pos) (+ y d-pos))] - {:x (min-match x-match) - :y (min-match y-match)})))))) 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 4299e681b..90a754787 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -145,15 +145,19 @@ :preview? true :zoom zoom}]]) -(mf/defc snap-path-points [{:keys [snaps zoom]}] - [:g.snap-paths - (for [[from to] snaps] - [:line {:x1 (:x from) - :y1 (:y from) - :x2 (:x to) - :y2 (:y to) - :style {:stroke pc/secondary-color - :stroke-width (/ 1 zoom)}}])]) +(mf/defc snap-points [{:keys [selected points zoom]}] + (let [ranges (mf/use-memo (mf/deps selected points) #(snap/create-ranges points selected)) + snap-matches (snap/get-snap-delta-match selected ranges (/ 1 zoom)) + matches (d/concat [] (second (:x snap-matches)) (second (:y snap-matches)))] + + [:g.snap-paths + (for [[from to] matches] + [:line {:x1 (:x from) + :y1 (:y from) + :x2 (:x to) + :y2 (:y to) + :style {:stroke pc/secondary-color + :stroke-width (/ 1 zoom)}}])])) (mf/defc path-editor [{:keys [shape zoom]}] @@ -193,28 +197,16 @@ #(doseq [key keys] (events/unlistenByKey key))))) - #_(hooks/use-stream - ms/mouse-position - (mf/deps shape) - (fn [position] - (reset! hover-point (gshp/path-closest-point shape position)))) - - #_(hooks/use-stream - (mf/use-memo - (mf/deps base-content selected-points zoom) - #(snap/path-snap ms/mouse-position points selected-points zoom)) - - (fn [result] - (prn "??" result))) - [:g.path-editor {:ref editor-ref} - #_[:& snap-points {}] - - (when (and preview (not drag-handler)) - [:& path-preview {:command preview - :from last-p - :zoom zoom}]) + [:* + [:& snap-points {:selected #{(ugp/command->point preview)} + :points points + :zoom zoom}] + + [:& path-preview {:command preview + :from last-p + :zoom zoom}]]) (when @hover-point [:g.hover-point From f396ef4fa0c86f2b790d2bd7b96503fb9117ee72 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 13 Apr 2021 17:44:26 +0200 Subject: [PATCH 057/155] :sparkles: Snap for moving path nodes and handlers --- .../src/app/main/data/workspace/path.cljs | 2 +- .../app/main/data/workspace/path/edition.cljs | 20 ++--- .../main/data/workspace/path/selection.cljs | 10 --- .../app/main/data/workspace/path/streams.cljs | 41 +++++----- frontend/src/app/main/snap.cljs | 6 +- .../main/ui/workspace/shapes/path/editor.cljs | 77 +++++++++++-------- .../main/ui/workspace/viewport/actions.cljs | 2 +- 7 files changed, 83 insertions(+), 75 deletions(-) diff --git a/frontend/src/app/main/data/workspace/path.cljs b/frontend/src/app/main/data/workspace/path.cljs index 2a02ebe7f..e4c04a5a7 100644 --- a/frontend/src/app/main/data/workspace/path.cljs +++ b/frontend/src/app/main/data/workspace/path.cljs @@ -24,8 +24,8 @@ (d/export edition/start-path-edit) ;; Selection -(d/export selection/select-handler) (d/export selection/handle-selection) +(d/export selection/select-node) (d/export selection/path-handler-enter) (d/export selection/path-handler-leave) (d/export selection/path-pointer-enter) diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index ff14d5d00..e5add8011 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -35,17 +35,20 @@ :x dx :y dy :c2x dx :c2y dy)))))) (defn modify-handler [id index prefix dx dy match-opposite?] - (ptk/reify ::modify-point + (ptk/reify ::modify-handler ptk/UpdateEvent (update [_ state] (let [content (get-in state (st/get-path state :content)) [cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y]) [ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y]) + point (gpt/point (+ (get-in content [index :params cx]) dx) + (+ (get-in content [index :params cy]) dy)) opposite-index (ugp/opposite-index content index prefix)] (cond-> state :always - (update-in [:workspace-local :edit-path id :content-modifiers index] assoc - cx dx cy dy) + (-> (update-in [:workspace-local :edit-path id :content-modifiers index] assoc + cx dx cy dy) + (assoc-in [:workspace-local :edit-path id :moving-handler] point)) (and match-opposite? opposite-index) (update-in [:workspace-local :edit-path id :content-modifiers opposite-index] assoc @@ -63,7 +66,7 @@ [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] (rx/of (dwc/commit-changes rch uch {:commit-local? true}) - (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers))))))) + (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler))))))) (defn move-selected-path-point [from-point to-point] (letfn [(modify-content-point [content {dx :x dy :y} modifiers point] @@ -101,7 +104,9 @@ modifiers (->> points (reduce modifiers-reducer {}))] - (assoc-in state [:workspace-local :edit-path id :content-modifiers] modifiers)))))) + (-> state + (assoc-in [:workspace-local :edit-path id :moving-nodes] true) + (assoc-in [:workspace-local :edit-path id :content-modifiers] modifiers))))))) (defn start-move-path-point [position shift?] @@ -120,11 +125,6 @@ mouse-drag-stream (rx/concat - ;; If we're dragging a selected item we don't change the selection - (if selected? - (rx/empty) - (rx/of (selection/select-node position shift?))) - ;; This stream checks the consecutive mouse positions to do the draging (->> points (streams/move-points-stream start-position selected-points) diff --git a/frontend/src/app/main/data/workspace/path/selection.cljs b/frontend/src/app/main/data/workspace/path/selection.cljs index 99ea74b17..c9ef72394 100644 --- a/frontend/src/app/main/data/workspace/path/selection.cljs +++ b/frontend/src/app/main/data/workspace/path/selection.cljs @@ -28,15 +28,6 @@ (let [id (st/get-path-id state)] (update-in state [:workspace-local :edit-path id :hover-points] disj position))))) -(defn select-handler [index type] - (ptk/reify ::select-handler - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition])] - (-> state - (update-in [:workspace-local :edit-path id :selected-handlers] (fnil conj #{}) [index type])))))) - - (defn path-handler-enter [index prefix] (ptk/reify ::path-handler-enter ptk/UpdateEvent @@ -115,7 +106,6 @@ (update [_ state] (let [id (st/get-path-id state)] (-> state - (assoc-in [:workspace-local :edit-path id :selected-handlers] #{}) (assoc-in [:workspace-local :edit-path id :selected-points] #{})))))) (defn update-area-selection diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs index 4f0b2a5ef..7251096e6 100644 --- a/frontend/src/app/main/data/workspace/path/streams.cljs +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -58,36 +58,39 @@ [start-point selected-points points] (let [zoom (get-in @st/state [:workspace-local :zoom] 1) - ranges (snap/create-ranges selected-points points) - d-pos (/ snap/snap-accuracy zoom)] + ranges (snap/create-ranges points selected-points) + d-pos (/ snap/snap-path-accuracy zoom) + + check-path-snap + (fn [position] + (let [delta (gpt/subtract position start-point) + moved-points (->> selected-points (mapv #(gpt/add % delta))) + snap (snap/get-snap-delta moved-points ranges d-pos)] + (gpt/add position snap)))] (->> ms/mouse-position - (rx/map (fn [position] - (let [delta (gpt/subtract position start-point) - moved-points (->> selected-points (mapv #(gpt/add % delta)))] - (gpt/add - position - (snap/get-snap-delta moved-points ranges d-pos))))))) - ) + (rx/map check-path-snap)))) (defn move-handler-stream [start-point handler points] (let [zoom (get-in @st/state [:workspace-local :zoom] 1) ranges (snap/create-ranges points) - d-pos (/ snap/snap-accuracy zoom)] + d-pos (/ snap/snap-path-accuracy zoom) + + check-path-snap + (fn [position] + (let [delta (gpt/subtract position start-point) + handler-position (gpt/add handler delta) + snap (snap/get-snap-delta [handler-position] ranges d-pos)] + (gpt/add position snap)))] (->> ms/mouse-position - (rx/map (fn [position] - (let [delta (gpt/subtract position start-point) - handler-position (gpt/add handler delta)] - (gpt/add - position - (snap/get-snap-delta [handler-position] ranges d-pos)))))))) + (rx/map check-path-snap)))) (defn position-stream [points] (let [zoom (get-in @st/state [:workspace-local :zoom] 1) ;; ranges (snap/create-ranges points) - d-pos (/ snap/snap-accuracy zoom) + d-pos (/ snap/snap-path-accuracy zoom) get-content (fn [state] (get-in state (state/get-path state :content))) content-stream @@ -103,9 +106,7 @@ (rx/with-latest vector ranges-stream) (rx/map (fn [[position ranges]] (let [snap (snap/get-snap-delta [position] ranges d-pos)] - #_(prn ">>>" snap) - (gpt/add position snap)) - )) + (gpt/add position snap)))) (rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %)))) (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %))))))) diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index 414cc0a6a..3e7ebd870 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -20,6 +20,7 @@ [clojure.set :as set])) (defonce ^:private snap-accuracy 5) +(defonce ^:private snap-path-accuracy 10) (defonce ^:private snap-distance-accuracy 10) (defn- remove-from-snap-points @@ -272,9 +273,8 @@ (->> (rt/range-query (get ranges coord) (- pval precision) (+ pval precision)) ;; We save the distance to the point and add the matching point to the points (mapv (fn [[value points]] - [(mth/abs (- value pval)) + [(- value pval) (->> points (mapv #(vector point %)))])))))] - {:x (query-coord point :x) :y (query-coord point :y)})) @@ -301,7 +301,7 @@ [default matches] (let [get-min (fn [[cur-val :as current] [other-val :as other]] - (if (< cur-val other-val) + (if (< (mth/abs cur-val) (mth/abs other-val)) current other)) 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 90a754787..406d4bd58 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -19,6 +19,7 @@ [app.util.dom :as dom] [app.util.geom.path :as ugp] [app.util.keyboard :as kbd] + [clojure.set :refer [map-invert]] [goog.events :as events] [rumext.alpha :as mf]) (:import goog.events.EventType)) @@ -42,7 +43,10 @@ (let [shift? (kbd/shift? event)] (cond (= edit-mode :move) - (st/emit! (drp/start-move-path-point position shift?)) + ;; If we're dragging a selected item we don't change the selection + (do (when (not selected?) + (st/emit! (drp/select-node position shift?))) + (st/emit! (drp/start-move-path-point position shift?))) (and (= edit-mode :draw) start-path?) (st/emit! (drp/start-path-from-point position)) @@ -84,14 +88,6 @@ (fn [event] (st/emit! (drp/path-handler-leave index prefix))) - on-click - (fn [event] - (dom/stop-propagation event) - (dom/prevent-default event) - (cond - (= edit-mode :move) - (drp/select-handler index prefix))) - on-mouse-down (fn [event] (dom/stop-propagation event) @@ -123,7 +119,6 @@ [:circle {:cx x :cy y :r (/ 10 zoom) - :on-click on-click :on-mouse-down on-mouse-down :on-mouse-enter on-enter :on-mouse-leave on-leave @@ -145,7 +140,7 @@ :preview? true :zoom zoom}]]) -(mf/defc snap-points [{:keys [selected points zoom]}] +(mf/defc path-snap [{:keys [selected points zoom]}] (let [ranges (mf/use-memo (mf/deps selected points) #(snap/create-ranges points selected)) snap-matches (snap/get-snap-delta-match selected ranges (/ 1 zoom)) matches (d/concat [] (second (:x snap-matches)) (second (:y snap-matches)))] @@ -172,19 +167,41 @@ preview content-modifiers last-point - selected-handlers selected-points + moving-nodes + moving-handler hover-handlers hover-points] :as edit-path} (mf/deref edit-path-ref) - {base-content :content} shape + selected-points (or selected-points #{}) + + base-content (:content shape) + base-points (mf/use-memo (mf/deps base-content) #(->> base-content ugp/content->points)) + content (ugp/apply-content-modifiers base-content content-modifiers) - points (mf/use-memo (mf/deps content) #(->> content ugp/content->points (into #{}))) + content-points (mf/use-memo (mf/deps content) #(->> content ugp/content->points)) + + point->base (->> (map hash-map content-points base-points) (reduce merge)) + base->point (map-invert point->base) + + points (into #{} content-points) + last-command (last content) last-p (->> content last ugp/command->point) handlers (ugp/content->handlers content) + [snap-selected snap-points] + (cond + (some? drag-handler) [#{drag-handler} points] + (some? preview) [#{(ugp/command->point preview)} points] + (some? moving-handler) [#{moving-handler} points] + :else + [(->> selected-points (map base->point) (into #{})) + (->> points (remove selected-points) (into #{}))]) + + show-snap? (or (some? drag-handler) (some? preview) (some? moving-handler) moving-nodes) + handle-double-click-outside (fn [event] (when (= edit-mode :move) @@ -199,13 +216,14 @@ [:g.path-editor {:ref editor-ref} (when (and preview (not drag-handler)) - [:* - [:& snap-points {:selected #{(ugp/command->point preview)} - :points points - :zoom zoom}] + [:& path-preview {:command preview + :from last-p + :zoom zoom}]) - [:& path-preview {:command preview - :from last-p + (when drag-handler + [:g.drag-handler {:pointer-events "none"} + [:& path-handler {:point last-p + :handler drag-handler :zoom zoom}]]) (when @hover-point @@ -214,10 +232,11 @@ :zoom zoom}]]) (for [position points] - (let [point-selected? (contains? selected-points position) - point-hover? (contains? hover-points position) - last-p? (= last-point position) + (let [point-selected? (contains? selected-points (get point->base position)) + point-hover? (contains? hover-points (get point->base position)) + last-p? (= last-point (get point->base position)) start-p? (not (some? last-point))] + [:g.path-node [:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")} (for [[index prefix] (get handlers position)] @@ -225,7 +244,6 @@ x (get-in command [:params (d/prefix-keyword prefix :x)]) y (get-in command [:params (d/prefix-keyword prefix :y)]) handler-position (gpt/point x y) - handler-selected? (contains? selected-handlers [index prefix]) handler-hover? (contains? hover-handlers [index prefix])] (when (not= position handler-position) [:& path-handler {:point position @@ -233,7 +251,6 @@ :index index :prefix prefix :zoom zoom - :selected? handler-selected? :hover? handler-hover? :edit-mode edit-mode}])))] [:& path-point {:position position @@ -250,9 +267,9 @@ :handler prev-handler :zoom zoom}]]) - (when drag-handler - [:g.drag-handler {:pointer-events "none"} - [:& path-handler {:point last-p - :handler drag-handler - :zoom zoom}]])])) + (when show-snap? + [:g.path-snap {:pointer-events "none"} + [:& path-snap {:selected snap-selected + :points snap-points + :zoom zoom}]])])) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 2cea72660..6b886dbe9 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -59,7 +59,7 @@ (when (and (not= edition id) text-editing?) (st/emit! dw/clear-edition-mode)) - (when (and (or (not edition) (not= edition id)) + (when (and (not text-editing?) (not blocked) (not hidden) (not (#{:comments :path} drawing-tool)) From 74f99f0d48e3cca99829627a3ca9db22d6ed5c6f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 14 Apr 2021 10:50:30 +0200 Subject: [PATCH 058/155] :sparkles: Toggle snap on button --- .../app/main/data/workspace/path/drawing.cljs | 22 +++++++++---- .../app/main/data/workspace/path/edition.cljs | 8 +++-- .../app/main/data/workspace/path/streams.cljs | 32 +++++++++++-------- .../main/ui/workspace/shapes/path/editor.cljs | 5 +-- 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index 781e3fffa..f4621fbc8 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -131,10 +131,11 @@ (ms/mouse-up? %)))) content (get-in state (st/get-path state :content)) + snap-toggled (get-in state [:workspace-local :edit-path id :snap-toggled]) points (ugp/content->points content) drag-events-stream - (->> (streams/position-stream points) + (->> (streams/position-stream snap-toggled points) (rx/take-until stop-stream) (rx/map #(drag-handler %)))] @@ -166,7 +167,10 @@ content (get-in state (st/get-path state :content)) points (ugp/content->points content) - drag-events (->> (streams/position-stream points) + id (st/get-path-id state) + snap-toggled (get-in state [:workspace-local :edit-path id :snap-toggled]) + + drag-events (->> (streams/position-stream snap-toggled points) (rx/take-until mouse-up) (rx/map #(drag-handler %)))] @@ -186,10 +190,11 @@ (rx/merge-map #(rx/empty)))) (defn make-drag-stream - [stream down-event zoom points] + [stream snap-toggled zoom points down-event] (let [mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %) (ms/mouse-up? %)))) - drag-events (->> (streams/position-stream points) + + drag-events (->> (streams/position-stream snap-toggled points) (rx/take-until mouse-up) (rx/map #(drag-handler %)))] @@ -219,9 +224,12 @@ content (get-in state (st/get-path state :content)) points (ugp/content->points content) + id (st/get-path-id state) + snap-toggled (get-in state [:workspace-local :edit-path id :snap-toggled]) + ;; Mouse move preview mousemove-events - (->> (streams/position-stream points) + (->> (streams/position-stream snap-toggled points) (rx/take-until end-path-events) (rx/map #(preview-next-point %))) @@ -229,12 +237,12 @@ mousedown-events (->> mouse-down (rx/take-until end-path-events) - (rx/with-latest merge (streams/position-stream points)) + (rx/with-latest merge (streams/position-stream snap-toggled points)) ;; We change to the stream that emits the first event (rx/switch-map #(rx/race (make-node-events-stream stream) - (make-drag-stream stream % zoom points))))] + (make-drag-stream stream snap-toggled zoom points %))))] (rx/concat (rx/of (common/init-path)) diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index e5add8011..0443d2b66 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -118,6 +118,7 @@ zoom (get-in state [:workspace-local :zoom]) id (get-in state [:workspace-local :edition]) selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) + snap-toggled (get-in state [:workspace-local :edit-path id :snap-toggled]) selected? (contains? selected-points position) content (get-in state (st/get-path state :content)) @@ -127,7 +128,7 @@ (rx/concat ;; This stream checks the consecutive mouse positions to do the draging (->> points - (streams/move-points-stream start-position selected-points) + (streams/move-points-stream snap-toggled start-position selected-points) (rx/take-until stopper) (rx/map #(move-selected-path-point start-position %))) (rx/of (apply-content-modifiers))) @@ -162,11 +163,12 @@ handler (-> content (get index) (ugp/get-handler prefix)) current-distance (when opposite-handler (gpt/distance (ugp/opposite-handler point handler) opposite-handler)) - match-opposite? (and opposite-handler (mth/almost-zero? current-distance))] + match-opposite? (and opposite-handler (mth/almost-zero? current-distance)) + snap-toggled (get-in state [:workspace-local :edit-path id :snap-toggled])] (streams/drag-stream (rx/concat - (->> (streams/move-handler-stream start-point handler points) + (->> (streams/move-handler-stream snap-toggled start-point handler points) (rx/take-until (->> stream (rx/filter ms/mouse-up?))) (rx/map (fn [{:keys [x y alt? shift?]}] diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs index 7251096e6..f7c7a83c2 100644 --- a/frontend/src/app/main/data/workspace/path/streams.cljs +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -55,7 +55,7 @@ (* (mth/floor (/ num k)) k))) (defn move-points-stream - [start-point selected-points points] + [snap-toggled start-point selected-points points] (let [zoom (get-in @st/state [:workspace-local :zoom] 1) ranges (snap/create-ranges points selected-points) @@ -63,15 +63,17 @@ check-path-snap (fn [position] - (let [delta (gpt/subtract position start-point) - moved-points (->> selected-points (mapv #(gpt/add % delta))) - snap (snap/get-snap-delta moved-points ranges d-pos)] - (gpt/add position snap)))] + (if snap-toggled + (let [delta (gpt/subtract position start-point) + moved-points (->> selected-points (mapv #(gpt/add % delta))) + snap (snap/get-snap-delta moved-points ranges d-pos)] + (gpt/add position snap)) + position))] (->> ms/mouse-position (rx/map check-path-snap)))) (defn move-handler-stream - [start-point handler points] + [snap-toggled start-point handler points] (let [zoom (get-in @st/state [:workspace-local :zoom] 1) ranges (snap/create-ranges points) @@ -79,15 +81,17 @@ check-path-snap (fn [position] - (let [delta (gpt/subtract position start-point) - handler-position (gpt/add handler delta) - snap (snap/get-snap-delta [handler-position] ranges d-pos)] - (gpt/add position snap)))] + (if snap-toggled + (let [delta (gpt/subtract position start-point) + handler-position (gpt/add handler delta) + snap (snap/get-snap-delta [handler-position] ranges d-pos)] + (gpt/add position snap)) + position))] (->> ms/mouse-position (rx/map check-path-snap)))) (defn position-stream - [points] + [snap-toggled points] (let [zoom (get-in @st/state [:workspace-local :zoom] 1) ;; ranges (snap/create-ranges points) d-pos (/ snap/snap-path-accuracy zoom) @@ -105,8 +109,10 @@ (->> ms/mouse-position (rx/with-latest vector ranges-stream) (rx/map (fn [[position ranges]] - (let [snap (snap/get-snap-delta [position] ranges d-pos)] - (gpt/add position snap)))) + (if snap-toggled + (let [snap (snap/get-snap-delta [position] ranges d-pos)] + (gpt/add position snap)) + position))) (rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %)))) (rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %))))))) 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 406d4bd58..3c8e37e9a 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -171,7 +171,8 @@ moving-nodes moving-handler hover-handlers - hover-points] + hover-points + snap-toggled] :as edit-path} (mf/deref edit-path-ref) selected-points (or selected-points #{}) @@ -200,7 +201,7 @@ [(->> selected-points (map base->point) (into #{})) (->> points (remove selected-points) (into #{}))]) - show-snap? (or (some? drag-handler) (some? preview) (some? moving-handler) moving-nodes) + show-snap? (and snap-toggled (or (some? drag-handler) (some? preview) (some? moving-handler) moving-nodes)) handle-double-click-outside (fn [event] From 48ba80c6e2948f699ed95588df66553f10c595e7 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 14 Apr 2021 10:57:13 +0200 Subject: [PATCH 059/155] :books: Updated changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 7efcc5474..3eb09081c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ - Add integration with gitpod.io (an online IDE) [#807](https://github.com/penpot/penpot/pull/807) - Allow to group assets (components and graphics) [Taiga #1289](https://tree.taiga.io/project/penpot/us/1289) - Internal: refactor of http client, replace internal xhr usage with more modern Fetch API. +- New features for paths: snap points on edition, add/remove nodes, merge/join/split nodes. [Taiga #907](https://tree.taiga.io/project/penpot/us/907) ### :bug: Bugs fixed From 07799d9b019a3825a37e541534ed83074d6760d1 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 14 Apr 2021 11:04:00 +0200 Subject: [PATCH 060/155] :sparkles: Path improvements --- .../src/app/main/ui/workspace/viewport.cljs | 6 - .../main/ui/workspace/viewport/snap_path.cljs | 188 ------------------ 2 files changed, 194 deletions(-) delete mode 100644 frontend/src/app/main/ui/workspace/viewport/snap_path.cljs diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index fda4afe53..3a0c42280 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -27,7 +27,6 @@ [app.main.ui.workspace.viewport.selection :as selection] [app.main.ui.workspace.viewport.snap-distances :as snap-distances] [app.main.ui.workspace.viewport.snap-points :as snap-points] - [app.main.ui.workspace.viewport.snap-path :as snap-path] [app.main.ui.workspace.viewport.utils :as utils] [app.main.ui.workspace.viewport.widgets :as widgets] [beicon.core :as rx] @@ -284,11 +283,6 @@ :selected selected :page-id page-id}]) - [:& snap-path/snap-path - {:zoom zoom - :edition edition - :edit-path edit-path}] - (when show-cursor-tooltip? [:& widgets/cursor-tooltip {:zoom zoom diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_path.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_path.cljs deleted file mode 100644 index 22f724c9f..000000000 --- a/frontend/src/app/main/ui/workspace/viewport/snap_path.cljs +++ /dev/null @@ -1,188 +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.main.ui.workspace.viewport.snap-path - (:require - #_[app.common.math :as mth] - #_[app.common.data :as d] - #_[app.common.geom.point :as gpt] - #_[app.common.geom.shapes :as gsh] - [app.main.refs :as refs] - #_[app.main.snap :as snap] - #_[app.util.geom.snap-points :as sp] - [app.util.geom.path :as ugp] - #_[beicon.core :as rx] - [rumext.alpha :as mf])) - -#_(def ^:private line-color "#D383DA") -#_(def ^:private line-opacity 0.6) -#_(def ^:private line-width 1) - -;; Configuration for debug -;; (def ^:private line-color "red") -;; (def ^:private line-opacity 1 ) -;; (def ^:private line-width 2) - -#_(mf/defc snap-point - [{:keys [point zoom]}] - (let [{:keys [x y]} point - x (mth/round x) - y (mth/round y) - cross-width (/ 3 zoom)] - [:g - [:line {:x1 (- x cross-width) - :y1 (- y cross-width) - :x2 (+ x cross-width) - :y2 (+ y cross-width) - :style {:stroke line-color :stroke-width (str (/ line-width zoom))}}] - [:line {:x1 (- x cross-width) - :y1 (+ y cross-width) - :x2 (+ x cross-width) - :y2 (- y cross-width) - :style {:stroke line-color :stroke-width (str (/ line-width zoom))}}]])) - -#_(mf/defc snap-line - [{:keys [snap point zoom]}] - [:line {:x1 (mth/round (:x snap)) - :y1 (mth/round (:y snap)) - :x2 (mth/round (:x point)) - :y2 (mth/round (:y point)) - :style {:stroke line-color :stroke-width (str (/ line-width zoom))} - :opacity line-opacity}]) - -#_(defn get-snap - [coord {:keys [shapes page-id filter-shapes modifiers]}] - (let [shape (if (> (count shapes) 1) - (->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect})) - (->> shapes (first))) - - shape (if modifiers - (-> shape (assoc :modifiers modifiers) gsh/transform-shape) - shape) - - frame-id (snap/snap-frame-id shapes)] - - (->> (rx/of shape) - (rx/flat-map (fn [shape] - (->> (sp/shape-snap-points shape) - (map #(vector frame-id %))))) - (rx/flat-map (fn [[frame-id point]] - (->> (snap/get-snap-points page-id frame-id filter-shapes point coord) - (rx/map #(vector point % coord))))) - (rx/reduce conj [])))) - -#_(defn- flip - "Function that reverses the x/y coordinates to their counterpart" - [coord] - (if (= coord :x) :y :x)) - -#_(defn add-point-to-snaps - [[point snaps coord]] - (let [normalize-coord #(assoc % coord (get point coord))] - (cons point (map normalize-coord snaps)))) - - -#_(defn- process-snap-lines - "Gets the snaps for a coordinate and creates lines with a fixed coordinate" - [snaps coord] - (->> snaps - ;; only snap on the `coord` coordinate - (filter #(= (nth % 2) coord)) - ;; we add the point so the line goes from the point to the snap - (mapcat add-point-to-snaps) - ;; We flatten because it's a list of from-to points - (flatten) - ;; Put together the points of the coordinate - (group-by coord) - ;; Keep only the other coordinate - (d/mapm #(map (flip coord) %2)) - ;; Finally get the max/min and this will define the line to draw - (d/mapm #(vector (apply min %2) (apply max %2))) - ;; Change the structure to retrieve a list of lines from/todo - (map (fn [[fixedv [minv maxv]]] [(hash-map coord fixedv (flip coord) minv) - (hash-map coord fixedv (flip coord) maxv)])))) - -#_(mf/defc snap-feedback - [{:keys [shapes page-id filter-shapes zoom modifiers] :as props}] - (let [state (mf/use-state []) - subject (mf/use-memo #(rx/subject)) - - ;; We use sets to store points/lines so there are no points/lines repeated - ;; can cause problems with react keys - snap-points (into #{} (mapcat add-point-to-snaps) @state) - - snap-lines (->> (into (process-snap-lines @state :x) - (process-snap-lines @state :y)) - (into #{}))] - - (mf/use-effect - (fn [] - (let [sub (->> subject - (rx/switch-map #(rx/combine-latest - d/concat - (get-snap :y %) - (get-snap :x %))) - (rx/subs #(let [rs (filter (fn [[_ snaps _]] (> (count snaps) 0)) %)] - (reset! state rs))))] - - ;; On unmount callback - #(rx/dispose! sub)))) - - (mf/use-effect - (mf/deps shapes modifiers) - (fn [] - (rx/push! subject props))) - - [:g.snap-feedback - (for [[from-point to-point] snap-lines] - [:& snap-line {:key (str "line-" (:x from-point) - "-" (:y from-point) - "-" (:x to-point) - "-" (:y to-point) "-") - :snap from-point - :point to-point - :zoom zoom}]) - (for [point snap-points] - [:& snap-point {:key (str "point-" (:x point) - "-" (:y point)) - :point point - :zoom zoom}])])) - -#_(mf/defc snap-points - {::mf/wrap [mf/memo]} - [{:keys [layout zoom selected page-id drawing transform modifiers] :as props}] - (let [shapes (mf/deref (refs/objects-by-id selected)) - filter-shapes (mf/deref refs/selected-shapes-with-children) - filter-shapes (fn [id] - (if (= id :layout) - (or (not (contains? layout :display-grid)) - (not (contains? layout :snap-grid))) - (or (filter-shapes id) - (not (contains? layout :dynamic-alignment))))) - shapes (if drawing [drawing] shapes)] - (when (or drawing transform) - [:& snap-feedback {:shapes shapes - :page-id page-id - :filter-shapes filter-shapes - :zoom zoom - :modifiers modifiers}]))) - -(mf/defc snap-feedback []) - - -(mf/defc snap-path - {::mf/wrap [mf/memo]} - [{:keys [edition edit-path zoom]}] - (let [{:keys [content]} (mf/deref (refs/object-by-id edition)) - {:keys [drag-handler preview snap-toggled]} (get edit-path edition) - - position (or drag-handler - (ugp/command->point preview))] - - (when snap-toggled - [:& snap-feedback {:content content - :position position - :zoom zoom}]))) From 74a09301a70fe661496eb997c788e259ae3a84e4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 14 Apr 2021 13:24:55 +0200 Subject: [PATCH 061/155] :sparkles: Shift+select path nodes --- .../app/main/data/workspace/path/edition.cljs | 56 ++++++++++++------- .../main/data/workspace/path/selection.cljs | 35 ++++++++++-- .../main/ui/workspace/shapes/path/editor.cljs | 4 +- 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 0443d2b66..0d07756ba 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -62,10 +62,18 @@ page-id (:current-page-id state) shape (get-in state (st/get-path state)) content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers]) - new-content (ugp/apply-content-modifiers (:content shape) content-modifiers) + + content (:content shape) + new-content (ugp/apply-content-modifiers content content-modifiers) + + old-points (->> content ugp/content->points) + new-points (->> new-content ugp/content->points) + point-change (->> (map hash-map old-points new-points) (reduce merge)) + [rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)] (rx/of (dwc/commit-changes rch uch {:commit-local? true}) + (selection/update-selection point-change) (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler))))))) (defn move-selected-path-point [from-point to-point] @@ -108,36 +116,44 @@ (assoc-in [:workspace-local :edit-path id :moving-nodes] true) (assoc-in [:workspace-local :edit-path id :content-modifiers] modifiers))))))) +(declare drag-selected-points) + (defn start-move-path-point [position shift?] (ptk/reify ::start-move-path-point ptk/WatchEvent (watch [_ state stream] - (let [start-position @ms/mouse-position - stopper (->> stream (rx/filter ms/mouse-up?)) + (let [id (get-in state [:workspace-local :edition]) + selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) + selected? (contains? selected-points position)] + (streams/drag-stream + (rx/of + (when-not selected? (selection/select-node position shift? "drag")) + (drag-selected-points @ms/mouse-position)) + (rx/of (selection/select-node position shift? "click"))))))) + +(defn drag-selected-points + [start-position] + (ptk/reify ::drag-selected-points + ptk/WatchEvent + (watch [_ state stream] + (let [stopper (->> stream (rx/filter ms/mouse-up?)) zoom (get-in state [:workspace-local :zoom]) id (get-in state [:workspace-local :edition]) - selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) snap-toggled (get-in state [:workspace-local :edit-path id :snap-toggled]) - selected? (contains? selected-points position) + + selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) content (get-in state (st/get-path state :content)) - points (ugp/content->points content) + points (ugp/content->points content)] - mouse-drag-stream - (rx/concat - ;; This stream checks the consecutive mouse positions to do the draging - (->> points - (streams/move-points-stream snap-toggled start-position selected-points) - (rx/take-until stopper) - (rx/map #(move-selected-path-point start-position %))) - (rx/of (apply-content-modifiers))) - - ;; When there is not drag we select the node - mouse-click-stream - (rx/of (selection/select-node position shift?))] - - (streams/drag-stream mouse-drag-stream mouse-click-stream))))) + (rx/concat + ;; This stream checks the consecutive mouse positions to do the draging + (->> points + (streams/move-points-stream snap-toggled start-position selected-points) + (rx/take-until stopper) + (rx/map #(move-selected-path-point start-position %))) + (rx/of (apply-content-modifiers))))))) (defn start-move-handler [index prefix] diff --git a/frontend/src/app/main/data/workspace/path/selection.cljs b/frontend/src/app/main/data/workspace/path/selection.cljs index c9ef72394..903954da3 100644 --- a/frontend/src/app/main/data/workspace/path/selection.cljs +++ b/frontend/src/app/main/data/workspace/path/selection.cljs @@ -49,9 +49,11 @@ (let [selrect (get-in state [:workspace-local :selrect]) id (get-in state [:workspace-local :edition]) content (get-in state (st/get-path state :content)) - selected-point? (fn [point] - (gsh/has-point-rect? selrect point)) - positions (into #{} + selected-point? #(gsh/has-point-rect? selrect %) + + selected-points (get-in state [:workspace-local :edit-path id :selected-points]) + + positions (into (if shift? selected-points #{}) (comp (map (comp gpt/point :params)) (filter selected-point?)) content)] @@ -59,14 +61,24 @@ (some? id) (assoc-in [:workspace-local :edit-path id :selected-points] positions)))))) -(defn select-node [position shift?] +(defn select-node [position shift? kk] (ptk/reify ::select-node ptk/UpdateEvent (update [_ state] - (let [id (get-in state [:workspace-local :edition])] + (let [id (get-in state [:workspace-local :edition]) + selected-points (or (get-in state [:workspace-local :edit-path id :selected-points]) #{}) + selected-points (cond + (and shift? (contains? selected-points position)) + (disj selected-points position) + + shift? + (conj selected-points position) + + :else + #{position})] (cond-> state (some? id) - (assoc-in [:workspace-local :edit-path id :selected-points] #{position})))))) + (assoc-in [:workspace-local :edit-path id :selected-points] selected-points)))))) (defn deselect-node [position shift?] (ptk/reify ::deselect-node @@ -142,3 +154,14 @@ (rx/of (select-node-area shift?) (clear-area-selection)))))))) + +(defn update-selection + [point-change] + (ptk/reify ::update-selection + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state) + selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) + selected-points (into #{} (map point-change) selected-points)] + (-> state + (assoc-in [:workspace-local :edit-path id :selected-points] selected-points)))))) 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 3c8e37e9a..150e66b31 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -44,9 +44,7 @@ (cond (= edit-mode :move) ;; If we're dragging a selected item we don't change the selection - (do (when (not selected?) - (st/emit! (drp/select-node position shift?))) - (st/emit! (drp/start-move-path-point position shift?))) + (st/emit! (drp/start-move-path-point position shift?)) (and (= edit-mode :draw) start-path?) (st/emit! (drp/start-path-from-point position)) From 63b95e71a705c0390d085ac5f90d4c4057c428bf Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Apr 2021 08:57:08 +0200 Subject: [PATCH 062/155] :tada: Add generic oauth2/openid-connect authentication subsystem. --- CHANGES.md | 3 +- backend/src/app/config.clj | 12 + backend/src/app/http.clj | 11 +- backend/src/app/http/oauth.clj | 278 ++++++++++++++++++ backend/src/app/http/oauth/github.clj | 157 ---------- backend/src/app/http/oauth/gitlab.clj | 166 ----------- backend/src/app/http/oauth/google.clj | 181 ------------ backend/src/app/main.clj | 33 +-- docker/images/files/config.js | 1 + docker/images/files/nginx-entrypoint.sh | 9 + .../resources/styles/main/layouts/login.scss | 2 +- frontend/src/app/config.cljs | 1 + frontend/src/app/main/repo.cljs | 22 +- frontend/src/app/main/ui/auth/login.cljs | 68 ++--- frontend/src/app/main/ui/auth/register.cljs | 20 +- frontend/translations/en.po | 12 +- frontend/translations/es.po | 12 +- 17 files changed, 368 insertions(+), 620 deletions(-) create mode 100644 backend/src/app/http/oauth.clj delete mode 100644 backend/src/app/http/oauth/github.clj delete mode 100644 backend/src/app/http/oauth/gitlab.clj delete mode 100644 backend/src/app/http/oauth/google.clj diff --git a/CHANGES.md b/CHANGES.md index 3eb09081c..d979bb597 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,8 @@ - Allow to group assets (components and graphics) [Taiga #1289](https://tree.taiga.io/project/penpot/us/1289) - Internal: refactor of http client, replace internal xhr usage with more modern Fetch API. - New features for paths: snap points on edition, add/remove nodes, merge/join/split nodes. [Taiga #907](https://tree.taiga.io/project/penpot/us/907) - +- Add OpenID-Connect support. +- Reimplement social auth providers on top of the generic openid impl. ### :bug: Bugs fixed diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 3d73599f0..3518d601e 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -105,6 +105,12 @@ (s/def ::gitlab-client-secret ::us/string) (s/def ::google-client-id ::us/string) (s/def ::google-client-secret ::us/string) +(s/def ::oidc-client-id ::us/string) +(s/def ::oidc-client-secret ::us/string) +(s/def ::oidc-base-uri ::us/string) +(s/def ::oidc-token-uri ::us/string) +(s/def ::oidc-auth-uri ::us/string) +(s/def ::oidc-user-uri ::us/string) (s/def ::host ::us/string) (s/def ::http-server-port ::us/integer) (s/def ::http-session-cookie-name ::us/string) @@ -178,6 +184,12 @@ ::gitlab-client-secret ::google-client-id ::google-client-secret + ::oidc-client-id + ::oidc-client-secret + ::oidc-base-uri + ::oidc-token-uri + ::oidc-auth-uri + ::oidc-user-uri ::host ::http-server-port ::http-session-idle-max-age diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 3e3504cda..4ad574102 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -149,15 +149,8 @@ ["/feedback" {:middleware [(:middleware session)] :post feedback}] - ["/oauth" - ["/google" {:post (get-in oauth [:google :handler])}] - ["/google/callback" {:get (get-in oauth [:google :callback-handler])}] - - ["/gitlab" {:post (get-in oauth [:gitlab :handler])}] - ["/gitlab/callback" {:get (get-in oauth [:gitlab :callback-handler])}] - - ["/github" {:post (get-in oauth [:github :handler])}] - ["/github/callback" {:get (get-in oauth [:github :callback-handler])}]] + ["/auth/oauth/:provider" {:post (:handler oauth)}] + ["/auth/oauth/:provider/callback" {:get (:callback-handler oauth)}] ["/rpc" {:middleware [(:middleware session) middleware/activity-logger]} diff --git a/backend/src/app/http/oauth.clj b/backend/src/app/http/oauth.clj new file mode 100644 index 000000000..20866091b --- /dev/null +++ b/backend/src/app/http/oauth.clj @@ -0,0 +1,278 @@ +;; 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.oauth + (:require + [app.common.exceptions :as ex] + [app.common.spec :as us] + [app.config :as cf] + [app.util.http :as http] + [app.util.logging :as l] + [app.util.time :as dt] + [clojure.data.json :as json] + [clojure.spec.alpha :as s] + [integrant.core :as ig] + [lambdaisland.uri :as u])) + +(defn redirect-response + [uri] + {:status 302 + :headers {"location" (str uri)} + :body ""}) + +(defn generate-error-redirect-uri + [cfg] + (-> (u/uri (:public-uri cfg)) + (assoc :path "/#/auth/login") + (assoc :query (u/map->query-string {:error "unable-to-auth"})))) + +(defn register-profile + [{:keys [rpc] :as cfg} info] + (let [method-fn (get-in rpc [:methods :mutation :login-or-register]) + profile (method-fn {:email (:email info) + :backend (:backend info) + :fullname (:fullname info)})] + (cond-> profile + (some? (:invitation-token info)) + (assoc :invitation-token (:invitation-token info))))) + +(defn generate-redirect-uri + [{:keys [tokens] :as cfg} profile] + (let [token (or (:invitation-token profile) + (tokens :generate {:iss :auth + :exp (dt/in-future "15m") + :profile-id (:id profile)}))] + (-> (u/uri (:public-uri cfg)) + (assoc :path "/#/auth/verify-token") + (assoc :query (u/map->query-string {:token token}))))) + +(defn- build-redirect-uri + [{:keys [provider] :as cfg}] + (let [public (u/uri (:public-uri cfg))] + (str (assoc public :path (str "/api/auth/oauth/" (:name provider) "/callback"))))) + +(defn- build-auth-uri + [{:keys [provider] :as cfg} state] + (let [params {:client_id (:client-id provider) + :redirect_uri (build-redirect-uri cfg) + :response_type "code" + :state state + :scope (:scope provider)} + query (u/map->query-string params)] + (-> (u/uri (:auth-uri provider)) + (assoc :query query) + (str)))) + +(defn retrieve-access-token + [{:keys [provider] :as cfg} code] + (try + (let [params {:client_id (:client-id provider) + :client_secret (:client-secret provider) + :code code + :grant_type "authorization_code" + :redirect_uri (build-redirect-uri cfg)} + req {:method :post + :headers {"content-type" "application/x-www-form-urlencoded"} + :uri (:token-uri provider) + :body (u/map->query-string params)} + res (http/send! req)] + (when (= 200 (:status res)) + (let [data (json/read-str (:body res))] + {:token (get data "access_token") + :type (get data "token_type")}))) + (catch Exception e + (l/error :hint "unexpected error on retrieve-access-token" + :cause e) + nil))) + +(defn- retrieve-user-info + [{:keys [provider] :as cfg} tdata] + (try + (let [req {:uri (:user-uri provider) + :headers {"Authorization" (str (:type tdata) " " (:token tdata))} + :timeout 6000 + :method :get} + res (http/send! req)] + + (when (= 200 (:status res)) + (let [data (json/read-str (:body res))] + {:email (get data "email") + :backend (:name provider) + :fullname (get data "name")}))) + + (catch Exception e + (l/error :hint "unexpected exception on retrieve-user-info" + :cause e) + nil))) + +(defn retrieve-info + [{:keys [tokens] :as cfg} request] + (let [state (get-in request [:params :state]) + state (tokens :verify {:token state :iss :oauth}) + info (some->> (get-in request [:params :code]) + (retrieve-access-token cfg) + (retrieve-user-info cfg))] + (when-not info + (ex/raise :type :internal + :code :unable-to-auth)) + + (cond-> info + (some? (:invitation-token state)) + (assoc :invitation-token (:invitation-token state))))) + +;; --- HTTP HANDLERS + +(defn- auth-handler + [{:keys [tokens] :as cfg} request] + (let [invitation (get-in request [:params :invitation-token]) + state (tokens :generate + {:iss :oauth + :invitation-token invitation + :exp (dt/in-future "15m")}) + uri (build-auth-uri cfg state)] + {:status 200 + :body {:redirect-uri uri}})) + +(defn- callback-handler + [{:keys [session] :as cfg} request] + (try + (let [info (retrieve-info cfg request) + profile (register-profile cfg info) + uri (generate-redirect-uri cfg profile) + sxf ((:create session) (:id profile))] + (->> (redirect-response uri) + (sxf request))) + (catch Exception _e + (-> (generate-error-redirect-uri cfg) + (redirect-response))))) + +;; --- INIT + +(declare initialize) + +(s/def ::public-uri ::us/not-empty-string) +(s/def ::session map?) +(s/def ::tokens fn?) +(s/def ::rpc map?) + +(defmethod ig/pre-init-spec :app.http.oauth/handlers [_] + (s/keys :req-un [::public-uri ::session ::tokens ::rpc])) + +(defn wrap-handler + [cfg handler] + (fn [request] + (let [provider (get-in request [:path-params :provider]) + provider (get-in @cfg [:providers provider])] + (when-not provider + (ex/raise :type :not-found + :context {:provider provider} + :hint "provider not configured")) + (-> (assoc @cfg :provider provider) + (handler request))))) + +(defmethod ig/init-key :app.http.oauth/handlers + [_ cfg] + (let [cfg (initialize cfg)] + {:handler (wrap-handler cfg auth-handler) + :callback-handler (wrap-handler cfg callback-handler)})) + +(defn- discover-oidc-config + [{:keys [base-uri] :as opts}] + (let [discovery-uri (u/join base-uri ".well-known/openid-configuration") + response (http/send! {:method :get :uri (str discovery-uri)})] + (when (= 200 (:status response)) + (let [data (json/read-str (:body response))] + (assoc opts + :token-uri (get data "token_endpoint") + :auth-uri (get data "authorization_endpoint") + :user-uri (get data "userinfo_endpoint")))))) + +(defn- initialize-oidc-provider + [cfg] + (let [opts {:base-uri (cf/get :oidc-base-uri) + :client-id (cf/get :oidc-client-id) + :client-secret (cf/get :oidc-client-secret) + :token-uri (cf/get :oidc-token-uri) + :auth-uri (cf/get :oidc-auth-uri) + :user-uri (cf/get :oidc-user-uri) + :scope "openid profile email name" + :name "oidc"}] + (if (and (string? (:base-uri opts)) + (string? (:client-id opts)) + (string? (:client-secret opts))) + (if (and (string? (:token-uri opts)) + (string? (:user-uri opts)) + (string? (:auth-uri opts))) + (do + (l/info :action "initialize" :provider "oid" :method "static") + (assoc-in cfg [:providers "oidc"] opts)) + (let [opts (discover-oidc-config opts)] + (l/info :action "initialize" :provider "oid" :method "discover") + (assoc-in cfg [:providers "oidc"] opts))) + cfg))) + +(defn- initialize-google-provider + [cfg] + (let [opts {:client-id (cf/get :google-client-id) + :client-secret (cf/get :google-client-secret) + :scope (str "email profile " + "https://www.googleapis.com/auth/userinfo.email " + "https://www.googleapis.com/auth/userinfo.profile " + "openid") + :auth-uri "https://accounts.google.com/o/oauth2/v2/auth" + :token-uri "https://oauth2.googleapis.com/token" + :user-uri "https://openidconnect.googleapis.com/v1/userinfo" + :name "google"}] + (if (and (string? (:client-id opts)) + (string? (:client-secret opts))) + (do + (l/info :action "initialize" :provider "google") + (assoc-in cfg [:providers "google"] opts)) + cfg))) + +(defn- initialize-github-provider + [cfg] + (let [opts {:client-id (cf/get :github-client-id) + :client-secret (cf/get :github-client-secret) + :scope "user:email" + :auth-uri "https://github.com/login/oauth/authorize" + :token-uri "https://github.com/login/oauth/access_token" + :user-uri "https://api.github.com/user" + :name "github"}] + (if (and (string? (:client-id opts)) + (string? (:client-secret opts))) + (do + (l/info :action "initialize" :provider "github") + (assoc-in cfg [:providers "github"] opts)) + cfg))) + + +(defn- initialize-gitlab-provider + [cfg] + (let [base (cf/get :gitlab-base-uri "https://gitlab.com") + opts {:base-uri base + :client-id (cf/get :gitlab-client-id) + :client-secret (cf/get :gitlab-client-secret) + :scope "read_user" + :auth-uri (str base "/oauth/authorize") + :token-uri (str base "/oauth/token") + :user-uri (str base "/api/v4/user") + :name "gitlab"}] + (if (and (string? (:client-id opts)) + (string? (:client-secret opts))) + (do + (l/info :action "initialize" :provider "gitlab") + (assoc-in cfg [:providers "gitlab"] opts)) + cfg))) + +(defn- initialize + [cfg] + (let [cfg (agent cfg :error-mode :continue)] + (send-off cfg initialize-google-provider) + (send-off cfg initialize-gitlab-provider) + (send-off cfg initialize-github-provider) + (send-off cfg initialize-oidc-provider) + cfg)) diff --git a/backend/src/app/http/oauth/github.clj b/backend/src/app/http/oauth/github.clj deleted file mode 100644 index c48d9b401..000000000 --- a/backend/src/app/http/oauth/github.clj +++ /dev/null @@ -1,157 +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.http.oauth.github - (:require - [app.common.exceptions :as ex] - [app.common.spec :as us] - [app.http.oauth.google :as gg] - [app.util.http :as http] - [app.util.logging :as l] - [app.util.time :as dt] - [clojure.data.json :as json] - [clojure.spec.alpha :as s] - [integrant.core :as ig] - [lambdaisland.uri :as u])) - -(def base-github-uri - (u/uri "https://github.com")) - -(def base-api-github-uri - (u/uri "https://api.github.com")) - -(def authorize-uri - (assoc base-github-uri :path "/login/oauth/authorize")) - -(def token-url - (assoc base-github-uri :path "/login/oauth/access_token")) - -(def user-info-url - (assoc base-api-github-uri :path "/user")) - -(def scope "user:email") - -(defn- build-redirect-url - [cfg] - (let [public (u/uri (:public-uri cfg))] - (str (assoc public :path "/api/oauth/github/callback")))) - -(defn- get-access-token - [cfg state code] - (try - (let [params {:client_id (:client-id cfg) - :client_secret (:client-secret cfg) - :code code - :state state - :redirect_uri (build-redirect-url cfg)} - req {:method :post - :headers {"content-type" "application/x-www-form-urlencoded" - "accept" "application/json"} - :uri (str token-url) - :timeout 6000 - :body (u/map->query-string params)} - res (http/send! req)] - - (when (= 200 (:status res)) - (-> (json/read-str (:body res)) - (get "access_token")))) - - (catch Exception e - (l/error :hint "unexpected error on get-access-token" - :cause e) - nil))) - -(defn- get-user-info - [_ token] - (try - (let [req {:uri (str user-info-url) - :headers {"authorization" (str "token " token)} - :timeout 6000 - :method :get} - res (http/send! req)] - (when (= 200 (:status res)) - (let [data (json/read-str (:body res))] - {:email (get data "email") - :backend "github" - :fullname (get data "name")}))) - (catch Exception e - (l/error :hint "unexpected exception on get-user-info" - :cause e) - nil))) - -(defn- retrieve-info - [{:keys [tokens] :as cfg} request] - (let [token (get-in request [:params :state]) - state (tokens :verify {:token token :iss :github-oauth}) - info (some->> (get-in request [:params :code]) - (get-access-token cfg state) - (get-user-info cfg))] - (when-not info - (ex/raise :type :internal - :code :unable-to-auth)) - - (cond-> info - (some? (:invitation-token state)) - (assoc :invitation-token (:invitation-token state))))) - -(defn auth-handler - [{:keys [tokens] :as cfg} request] - (let [invitation (get-in request [:params :invitation-token]) - state (tokens :generate {:iss :github-oauth - :invitation-token invitation - :exp (dt/in-future "15m")}) - params {:client_id (:client-id cfg) - :redirect_uri (build-redirect-url cfg) - :state state - :scope scope} - query (u/map->query-string params) - uri (-> authorize-uri - (assoc :query query))] - {:status 200 - :body {:redirect-uri (str uri)}})) - -(defn- callback-handler - [{:keys [session] :as cfg} request] - (try - (let [info (retrieve-info cfg request) - profile (gg/register-profile cfg info) - uri (gg/generate-redirect-uri cfg profile) - sxf ((:create session) (:id profile))] - (->> (gg/redirect-response uri) - (sxf request))) - (catch Exception _e - (-> (gg/generate-error-redirect-uri cfg) - (gg/redirect-response))))) - - -;; --- ENTRY POINT - -(s/def ::client-id ::us/not-empty-string) -(s/def ::client-secret ::us/not-empty-string) -(s/def ::public-uri ::us/not-empty-string) -(s/def ::session map?) -(s/def ::tokens fn?) - -(defmethod ig/pre-init-spec :app.http.oauth/github [_] - (s/keys :req-un [::public-uri - ::session - ::tokens] - :opt-un [::client-id - ::client-secret])) - -(defn- default-handler - [_] - (ex/raise :type :not-found)) - -(defmethod ig/init-key :app.http.oauth/github - [_ cfg] - (if (and (:client-id cfg) - (:client-secret cfg)) - {:handler #(auth-handler cfg %) - :callback-handler #(callback-handler cfg %)} - {:handler default-handler - :callback-handler default-handler})) - diff --git a/backend/src/app/http/oauth/gitlab.clj b/backend/src/app/http/oauth/gitlab.clj deleted file mode 100644 index 58c81396f..000000000 --- a/backend/src/app/http/oauth/gitlab.clj +++ /dev/null @@ -1,166 +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.http.oauth.gitlab - (:require - [app.common.data :as d] - [app.common.exceptions :as ex] - [app.common.spec :as us] - [app.http.oauth.google :as gg] - [app.util.http :as http] - [app.util.logging :as l] - [app.util.time :as dt] - [clojure.data.json :as json] - [clojure.spec.alpha :as s] - [integrant.core :as ig] - [lambdaisland.uri :as u])) - -(def scope "read_user") - -(defn- build-redirect-url - [cfg] - (let [public (u/uri (:public-uri cfg))] - (str (assoc public :path "/api/oauth/gitlab/callback")))) - -(defn- build-oauth-uri - [cfg] - (let [base-uri (u/uri (:base-uri cfg))] - (assoc base-uri :path "/oauth/authorize"))) - -(defn- build-token-url - [cfg] - (let [base-uri (u/uri (:base-uri cfg))] - (str (assoc base-uri :path "/oauth/token")))) - -(defn- build-user-info-url - [cfg] - (let [base-uri (u/uri (:base-uri cfg))] - (str (assoc base-uri :path "/api/v4/user")))) - -(defn- get-access-token - [cfg code] - (try - (let [params {:client_id (:client-id cfg) - :client_secret (:client-secret cfg) - :code code - :grant_type "authorization_code" - :redirect_uri (build-redirect-url cfg)} - req {:method :post - :headers {"content-type" "application/x-www-form-urlencoded"} - :uri (build-token-url cfg) - :body (u/map->query-string params)} - res (http/send! req)] - - (when (= 200 (:status res)) - (-> (json/read-str (:body res)) - (get "access_token")))) - - (catch Exception e - (l/error :hint "unexpected error on get-access-token" - :cause e) - nil))) - -(defn- get-user-info - [cfg token] - (try - (let [req {:uri (build-user-info-url cfg) - :headers {"Authorization" (str "Bearer " token)} - :timeout 6000 - :method :get} - res (http/send! req)] - - (when (= 200 (:status res)) - (let [data (json/read-str (:body res))] - {:email (get data "email") - :backend "gitlab" - :fullname (get data "name")}))) - - (catch Exception e - (l/error :hint "unexpected exception on get-user-info" - :cause e) - nil))) - - -(defn- retrieve-info - [{:keys [tokens] :as cfg} request] - (let [token (get-in request [:params :state]) - state (tokens :verify {:token token :iss :gitlab-oauth}) - info (some->> (get-in request [:params :code]) - (get-access-token cfg) - (get-user-info cfg))] - (when-not info - (ex/raise :type :internal - :code :unable-to-auth)) - - (cond-> info - (some? (:invitation-token state)) - (assoc :invitation-token (:invitation-token state))))) - - -(defn- auth-handler - [{:keys [tokens] :as cfg} request] - (let [invitation (get-in request [:params :invitation-token]) - state (tokens :generate - {:iss :gitlab-oauth - :invitation-token invitation - :exp (dt/in-future "15m")}) - - params {:client_id (:client-id cfg) - :redirect_uri (build-redirect-url cfg) - :response_type "code" - :state state - :scope scope} - query (u/map->query-string params) - uri (-> (build-oauth-uri cfg) - (assoc :query query))] - {:status 200 - :body {:redirect-uri (str uri)}})) - -(defn- callback-handler - [{:keys [session] :as cfg} request] - (try - (let [info (retrieve-info cfg request) - profile (gg/register-profile cfg info) - uri (gg/generate-redirect-uri cfg profile) - sxf ((:create session) (:id profile))] - (->> (gg/redirect-response uri) - (sxf request))) - (catch Exception _e - (-> (gg/generate-error-redirect-uri cfg) - (gg/redirect-response))))) - -(s/def ::client-id ::us/not-empty-string) -(s/def ::client-secret ::us/not-empty-string) -(s/def ::base-uri ::us/not-empty-string) -(s/def ::public-uri ::us/not-empty-string) -(s/def ::session map?) -(s/def ::tokens fn?) - -(defmethod ig/pre-init-spec :app.http.oauth/gitlab [_] - (s/keys :req-un [::public-uri - ::session - ::tokens] - :opt-un [::base-uri - ::client-id - ::client-secret])) - -(defmethod ig/prep-key :app.http.oauth/gitlab - [_ cfg] - (d/merge {:base-uri "https://gitlab.com"} - (d/without-nils cfg))) - -(defn- default-handler - [_] - (ex/raise :type :not-found)) - -(defmethod ig/init-key :app.http.oauth/gitlab - [_ cfg] - (if (and (:client-id cfg) - (:client-secret cfg)) - {:handler #(auth-handler cfg %) - :callback-handler #(callback-handler cfg %)} - {:handler default-handler - :callback-handler default-handler})) diff --git a/backend/src/app/http/oauth/google.clj b/backend/src/app/http/oauth/google.clj deleted file mode 100644 index b671079a6..000000000 --- a/backend/src/app/http/oauth/google.clj +++ /dev/null @@ -1,181 +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.http.oauth.google - (:require - [app.common.exceptions :as ex] - [app.common.spec :as us] - [app.util.http :as http] - [app.util.logging :as l] - [app.util.time :as dt] - [clojure.data.json :as json] - [clojure.spec.alpha :as s] - [integrant.core :as ig] - [lambdaisland.uri :as u])) - -(def base-goauth-uri "https://accounts.google.com/o/oauth2/v2/auth") - -(def scope - (str "email profile " - "https://www.googleapis.com/auth/userinfo.email " - "https://www.googleapis.com/auth/userinfo.profile " - "openid")) - -(defn- build-redirect-url - [cfg] - (let [public (u/uri (:public-uri cfg))] - (str (assoc public :path "/api/oauth/google/callback")))) - -(defn- get-access-token - [cfg code] - (try - (let [params {:code code - :client_id (:client-id cfg) - :client_secret (:client-secret cfg) - :redirect_uri (build-redirect-url cfg) - :grant_type "authorization_code"} - req {:method :post - :headers {"content-type" "application/x-www-form-urlencoded"} - :uri "https://oauth2.googleapis.com/token" - :timeout 6000 - :body (u/map->query-string params)} - res (http/send! req)] - - (when (= 200 (:status res)) - (-> (json/read-str (:body res)) - (get "access_token")))) - (catch Exception e - (l/error :hint "unexpected error on get-access-token" - :cause e) - nil))) - -(defn- get-user-info - [_ token] - (try - (let [req {:uri "https://openidconnect.googleapis.com/v1/userinfo" - :headers {"Authorization" (str "Bearer " token)} - :timeout 6000 - :method :get} - res (http/send! req)] - - (when (= 200 (:status res)) - (let [data (json/read-str (:body res))] - {:email (get data "email") - :backend "google" - :fullname (get data "name")}))) - (catch Exception e - (l/error :hint "unexpected exception on get-user-info" - :cause e) - nil))) - -(defn- retrieve-info - [{:keys [tokens] :as cfg} request] - (let [token (get-in request [:params :state]) - state (tokens :verify {:token token :iss :google-oauth}) - info (some->> (get-in request [:params :code]) - (get-access-token cfg) - (get-user-info cfg))] - - - (when-not info - (ex/raise :type :internal - :code :unable-to-auth)) - - (cond-> info - (some? (:invitation-token state)) - (assoc :invitation-token (:invitation-token state))))) - -(defn register-profile - [{:keys [rpc] :as cfg} info] - (let [method-fn (get-in rpc [:methods :mutation :login-or-register]) - profile (method-fn {:email (:email info) - :backend (:backend info) - :fullname (:fullname info)})] - (cond-> profile - (some? (:invitation-token info)) - (assoc :invitation-token (:invitation-token info))))) - -(defn generate-redirect-uri - [{:keys [tokens] :as cfg} profile] - (let [token (or (:invitation-token profile) - (tokens :generate {:iss :auth - :exp (dt/in-future "15m") - :profile-id (:id profile)}))] - (-> (u/uri (:public-uri cfg)) - (assoc :path "/#/auth/verify-token") - (assoc :query (u/map->query-string {:token token}))))) - -(defn generate-error-redirect-uri - [cfg] - (-> (u/uri (:public-uri cfg)) - (assoc :path "/#/auth/login") - (assoc :query (u/map->query-string {:error "unable-to-auth"})))) - -(defn redirect-response - [uri] - {:status 302 - :headers {"location" (str uri)} - :body ""}) - -(defn- auth-handler - [{:keys [tokens] :as cfg} request] - (let [invitation (get-in request [:params :invitation-token]) - state (tokens :generate - {:iss :google-oauth - :invitation-token invitation - :exp (dt/in-future "15m")}) - params {:scope scope - :access_type "offline" - :include_granted_scopes true - :state state - :response_type "code" - :redirect_uri (build-redirect-url cfg) - :client_id (:client-id cfg)} - query (u/map->query-string params) - uri (-> (u/uri base-goauth-uri) - (assoc :query query))] - - {:status 200 - :body {:redirect-uri (str uri)}})) - -(defn- callback-handler - [{:keys [session] :as cfg} request] - (try - (let [info (retrieve-info cfg request) - profile (register-profile cfg info) - uri (generate-redirect-uri cfg profile) - sxf ((:create session) (:id profile))] - (->> (redirect-response uri) - (sxf request))) - (catch Exception _e - (-> (generate-error-redirect-uri cfg) - (redirect-response))))) - -(s/def ::client-id ::us/not-empty-string) -(s/def ::client-secret ::us/not-empty-string) -(s/def ::public-uri ::us/not-empty-string) -(s/def ::session map?) -(s/def ::tokens fn?) - -(defmethod ig/pre-init-spec :app.http.oauth/google [_] - (s/keys :req-un [::public-uri - ::session - ::tokens] - :opt-un [::client-id - ::client-secret])) - -(defn- default-handler - [_] - (ex/raise :type :not-found)) - -(defmethod ig/init-key :app.http.oauth/google - [_ cfg] - (if (and (:client-id cfg) - (:client-secret cfg)) - {:handler #(auth-handler cfg %) - :callback-handler #(callback-handler cfg %)} - {:handler default-handler - :callback-handler default-handler})) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index fcf889b94..e40946e14 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -86,13 +86,12 @@ :ws {"/ws/notifications" (ig/ref :app.notifications/handler)}} :app.http/router - { - :rpc (ig/ref :app.rpc/rpc) + {:rpc (ig/ref :app.rpc/rpc) :session (ig/ref :app.http.session/session) :tokens (ig/ref :app.tokens/tokens) :public-uri (cf/get :public-uri) :metrics (ig/ref :app.metrics/metrics) - :oauth (ig/ref :app.http.oauth/all) + :oauth (ig/ref :app.http.oauth/handlers) :assets (ig/ref :app.http.assets/handlers) :storage (ig/ref :app.storage/storage) :sns-webhook (ig/ref :app.http.awsns/handler) @@ -109,35 +108,11 @@ :app.http.feedback/handler {:pool (ig/ref :app.db/pool)} - :app.http.oauth/all - {:google (ig/ref :app.http.oauth/google) - :gitlab (ig/ref :app.http.oauth/gitlab) - :github (ig/ref :app.http.oauth/github)} - - :app.http.oauth/google + :app.http.oauth/handlers {:rpc (ig/ref :app.rpc/rpc) :session (ig/ref :app.http.session/session) :tokens (ig/ref :app.tokens/tokens) - :public-uri (cf/get :public-uri) - :client-id (cf/get :google-client-id) - :client-secret (cf/get :google-client-secret)} - - :app.http.oauth/github - {:rpc (ig/ref :app.rpc/rpc) - :session (ig/ref :app.http.session/session) - :tokens (ig/ref :app.tokens/tokens) - :public-uri (cf/get :public-uri) - :client-id (cf/get :github-client-id) - :client-secret (cf/get :github-client-secret)} - - :app.http.oauth/gitlab - {:rpc (ig/ref :app.rpc/rpc) - :session (ig/ref :app.http.session/session) - :tokens (ig/ref :app.tokens/tokens) - :public-uri (cf/get :public-uri) - :base-uri (cf/get :gitlab-base-uri) - :client-id (cf/get :gitlab-client-id) - :client-secret (cf/get :gitlab-client-secret)} + :public-uri (cf/get :public-uri)} ;; RLimit definition for password hashing :app.rlimits/password diff --git a/docker/images/files/config.js b/docker/images/files/config.js index 0ac491bbb..aac4fb709 100644 --- a/docker/images/files/config.js +++ b/docker/images/files/config.js @@ -6,5 +6,6 @@ //var penpotGoogleClientID = ""; //var penpotGitlabClientID = ""; //var penpotGithubClientID = ""; +//var penpotOIDCClientID = ""; //var penpotLoginWithLDAP = ; //var penpotRegistrationEnabled = ; diff --git a/docker/images/files/nginx-entrypoint.sh b/docker/images/files/nginx-entrypoint.sh index e99e69787..b341649a7 100644 --- a/docker/images/files/nginx-entrypoint.sh +++ b/docker/images/files/nginx-entrypoint.sh @@ -69,6 +69,14 @@ update_github_client_id() { fi } +update_oidc_client_id() { + if [ -n "$PENPOT_OIDC_CLIENT_ID" ]; then + log "Updating Oidc Client Id: $PENPOT_OIDC_CLIENT_ID" + sed -i \ + -e "s|^//var penpotOIDCClientID = \".*\";|var penpotOIDCClientID = \"$PENPOT_OIDC_CLIENT_ID\";|g" \ + "$1" + fi +} update_login_with_ldap() { if [ -n "$PENPOT_LOGIN_WITH_LDAP" ]; then @@ -95,6 +103,7 @@ update_allow_demo_users /var/www/app/js/config.js update_google_client_id /var/www/app/js/config.js update_gitlab_client_id /var/www/app/js/config.js update_github_client_id /var/www/app/js/config.js +update_oidc_client_id /var/www/app/js/config.js update_login_with_ldap /var/www/app/js/config.js update_registration_enabled /var/www/app/js/config.js diff --git a/frontend/resources/styles/main/layouts/login.scss b/frontend/resources/styles/main/layouts/login.scss index 03de4ceaa..13be87951 100644 --- a/frontend/resources/styles/main/layouts/login.scss +++ b/frontend/resources/styles/main/layouts/login.scss @@ -57,7 +57,7 @@ .form-container { width: 412px; - .btn-ocean { + .auth-buttons { margin-top: $x-big; } diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 17f67993f..1542f45a7 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -69,6 +69,7 @@ (def google-client-id (obj/get global "penpotGoogleClientID" nil)) (def gitlab-client-id (obj/get global "penpotGitlabClientID" nil)) (def github-client-id (obj/get global "penpotGithubClientID" nil)) +(def oidc-client-id (obj/get global "penpotOIDCClientID" nil)) (def login-with-ldap (obj/get global "penpotLoginWithLDAP" false)) (def registration-enabled (obj/get global "penpotRegistrationEnabled" true)) (def worker-uri (obj/get global "penpotWorkerURI" "/js/worker.js")) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index b4ec717bd..ef9050268 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -6,6 +6,7 @@ (ns app.main.repo (:require + [app.common.data :as d] [beicon.core :as rx] [lambdaisland.uri :as u] [cuerdas.core :as str] @@ -84,23 +85,10 @@ ([id] (mutation id {})) ([id params] (mutation id params))) -(defmethod mutation :login-with-google - [id params] - (let [uri (u/join base-uri "api/oauth/google")] - (->> (http/send! {:method :post :uri uri :query params}) - (rx/map http/conditional-decode-transit) - (rx/mapcat handle-response)))) - -(defmethod mutation :login-with-gitlab - [id params] - (let [uri (u/join base-uri "api/oauth/gitlab")] - (->> (http/send! {:method :post :uri uri :query params}) - (rx/map http/conditional-decode-transit) - (rx/mapcat handle-response)))) - -(defmethod mutation :login-with-github - [id params] - (let [uri (u/join base-uri "api/oauth/github")] +(defmethod mutation :login-with-oauth + [id {: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}) (rx/map http/conditional-decode-transit) (rx/mapcat handle-response)))) diff --git a/frontend/src/app/main/ui/auth/login.cljs b/frontend/src/app/main/ui/auth/login.cljs index 973ef6c41..5dd40cecc 100644 --- a/frontend/src/app/main/ui/auth/login.cljs +++ b/frontend/src/app/main/ui/auth/login.cljs @@ -29,26 +29,10 @@ (s/def ::login-form (s/keys :req-un [::email ::password])) -(defn- login-with-google - [event params] +(defn- login-with-oauth + [event provider params] (dom/prevent-default event) - (->> (rp/mutation! :login-with-google params) - (rx/subs (fn [{:keys [redirect-uri] :as rsp}] - (.replace js/location redirect-uri)) - (fn [{:keys [type] :as error}] - (st/emit! (dm/error (tr "errors.google-auth-not-enabled"))))))) - -(defn- login-with-gitlab - [event params] - (dom/prevent-default event) - (->> (rp/mutation! :login-with-gitlab params) - (rx/subs (fn [{:keys [redirect-uri] :as rsp}] - (.replace js/location redirect-uri))))) - -(defn- login-with-github - [event params] - (dom/prevent-default event) - (->> (rp/mutation! :login-with-github params) + (->> (rp/mutation! :login-with-oauth (assoc params :provider provider)) (rx/subs (fn [{:keys [redirect-uri] :as rsp}] (.replace js/location redirect-uri))))) @@ -127,6 +111,33 @@ {:label (tr "auth.login-with-ldap-submit") :on-click on-submit-ldap}])]])) +(mf/defc login-buttons + [{:keys [params] :as props}] + [:div.auth-buttons + (when cfg/google-client-id + [:a.btn-ocean.btn-large.btn-google-auth + {:on-click #(login-with-oauth % :google params)} + (tr "auth.login-with-google-submit")]) + + (when cfg/gitlab-client-id + [:a.btn-ocean.btn-large.btn-gitlab-auth + {:on-click #(login-with-oauth % :gitlab params)} + [:img.logo + {:src "/images/icons/brand-gitlab.svg"}] + (tr "auth.login-with-gitlab-submit")]) + + (when cfg/github-client-id + [:a.btn-ocean.btn-large.btn-github-auth + {:on-click #(login-with-oauth % :github params)} + [:img.logo + {:src "/images/icons/brand-github.svg"}] + (tr "auth.login-with-github-submit")]) + + (when cfg/oidc-client-id + [:a.btn-ocean.btn-large.btn-github-auth + {:on-click #(login-with-oauth % :oidc params)} + (tr "auth.login-with-oidc-submit")])]) + (mf/defc login-page [{:keys [params] :as props}] [:div.generic-form.login-form @@ -149,24 +160,7 @@ :tab-index "6"} (tr "auth.register-submit")]])] - (when cfg/google-client-id - [:a.btn-ocean.btn-large.btn-google-auth - {:on-click #(login-with-google % params)} - "Login with Google"]) - - (when cfg/gitlab-client-id - [:a.btn-ocean.btn-large.btn-gitlab-auth - {:on-click #(login-with-gitlab % params)} - [:img.logo - {:src "/images/icons/brand-gitlab.svg"}] - (tr "auth.login-with-gitlab-submit")]) - - (when cfg/github-client-id - [:a.btn-ocean.btn-large.btn-github-auth - {:on-click #(login-with-github % params)} - [:img.logo - {:src "/images/icons/brand-github.svg"}] - (tr "auth.login-with-github-submit")]) + [:& login-buttons {:params params}] (when cfg/allow-demo-users [:div.links.demo diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 34cbfae1f..a8321a6a0 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -137,7 +137,6 @@ [:div.notification-text-email (:email params "")] [:div.notification-text (tr "auth.check-your-email")]]) - (mf/defc register-page [{:keys [params] :as props}] [:div.form-container @@ -161,24 +160,9 @@ [:span (tr "auth.create-demo-profile") " "] [:a {:on-click #(st/emit! da/create-demo-profile) :tab-index "5"} - (tr "auth.create-demo-account")]])] + (tr "auth.create-demo-account")]]) - (when cfg/google-client-id - [:a.btn-ocean.btn-large.btn-google-auth - {:on-click #(login/login-with-google % params)} - "Login with Google"]) + [:& login/login-buttons {:params params}]]]) - (when cfg/gitlab-client-id - [:a.btn-ocean.btn-large.btn-gitlab-auth - {:on-click #(login/login-with-gitlab % params)} - [:img.logo - {:src "/images/icons/brand-gitlab.svg"}] - (tr "auth.login-with-gitlab-submit")]) - (when cfg/github-client-id - [:a.btn-ocean.btn-large.btn-github-auth - {:on-click #(login/login-with-github % params)} - [:img.logo - {:src "/images/icons/brand-github.svg"}] - (tr "auth.login-with-github-submit")])]) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 26cab1a7f..a47d70664 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -64,18 +64,26 @@ msgstr "Enter your details below" msgid "auth.login-title" msgstr "Great to see you again!" -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +#: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" msgstr "Login with Github" -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +#: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" msgstr "Login with Gitlab" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Login with Google" + #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" msgstr "Sign in with LDAP" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "Login with OpenID (SSO)" + #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" msgstr "Type a new password" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 31911b1f0..b10462477 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -60,18 +60,26 @@ msgstr "Introduce tus datos aquí" msgid "auth.login-title" msgstr "Encantados de volverte a ver" -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +#: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" msgstr "Entrar con Github" -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +#: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" msgstr "Entrar con Gitlab" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Entrar con Google" + #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" msgstr "Entrar con LDAP" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "Entrar con OpenID (SSO)" + #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" msgstr "Introduce la nueva contraseña" From dd6bd6bbff7ad835d04592cb04bb5358633e22a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 16 Apr 2021 11:30:20 +0200 Subject: [PATCH 063/155] :bug: Fix frontend tests --- common/app/common/pages/spec.cljc | 2 +- .../src/app/main/data/workspace/common.cljs | 2 +- frontend/src/app/util/globals.js | 28 ++++++++++++++++--- frontend/src/app/util/http.cljs | 3 +- frontend/src/app/util/i18n.cljs | 5 ++-- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/common/app/common/pages/spec.cljc b/common/app/common/pages/spec.cljc index 6f7be0765..ffea43331 100644 --- a/common/app/common/pages/spec.cljc +++ b/common/app/common/pages/spec.cljc @@ -16,7 +16,7 @@ (s/def ::frame-id uuid?) (s/def ::id uuid?) (s/def ::name string?) -(s/def ::path string?) +(s/def ::path (s/nilable string?)) (s/def ::page-id uuid?) (s/def ::parent-id uuid?) (s/def ::string string?) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index e993573bf..d479b5b83 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -72,7 +72,7 @@ commit-local? false} :as opts}] (us/verify ::cp/changes changes) - ;; (us/verify ::cp/changes undo-changes) + (us/verify ::cp/changes undo-changes) (log/debug :msg "commit-changes" :js/changes changes :js/undo-changes undo-changes) diff --git a/frontend/src/app/util/globals.js b/frontend/src/app/util/globals.js index 3f132ccf5..2493e9d5b 100644 --- a/frontend/src/app/util/globals.js +++ b/frontend/src/app/util/globals.js @@ -20,11 +20,23 @@ goog.provide("app.util.globals"); goog.scope(function() { app.util.globals.global = goog.global; + function createGlobalEventEmiter(k) { + /* Allow mocked objects to be event emitters, so other modules + * may subscribe to them. + */ + return { + addListener(...args) { + }, + removeListener(...args) { + } + } + } + app.util.globals.window = (function() { if (typeof goog.global.window !== "undefined") { return goog.global.window; } else { - return {}; + return createGlobalEventEmiter(); } })(); @@ -32,7 +44,7 @@ goog.scope(function() { if (typeof goog.global.document !== "undefined") { return goog.global.document; } else { - return {}; + return createGlobalEventEmiter(); } })(); @@ -40,7 +52,7 @@ goog.scope(function() { if (typeof goog.global.location !== "undefined") { return goog.global.location; } else { - return {}; + return createGlobalEventEmiter(); } })(); @@ -48,7 +60,15 @@ goog.scope(function() { if (typeof goog.global.navigator !== "undefined") { return goog.global.navigator; } else { - return {}; + return createGlobalEventEmiter(); + } + })(); + + app.util.globals.FormData = (function() { + if (typeof goog.global.FormData !== "undefined") { + return goog.global.FormData; + } else { + return function() {}; } })(); }); diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs index f13acf6b3..713a0f269 100644 --- a/frontend/src/app/util/http.cljs +++ b/frontend/src/app/util/http.cljs @@ -9,6 +9,7 @@ (:require [app.common.data :as d] [app.config :as cfg] + [app.util.globals :as globals] [app.util.object :as obj] [app.util.transit :as t] [beicon.core :as rx] @@ -22,7 +23,7 @@ (-get-body-data [_])) (extend-protocol IBodyData - js/FormData + globals/FormData (-get-body-data [it] it) (-update-headers [it headers] (dissoc headers "content-type" "Content-Type")) diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 3bfdd80dc..ff93245f2 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -8,6 +8,7 @@ "A i18n foundation." (:require [app.config :as cfg] + [app.util.globals :as globals] [app.util.storage :refer [storage]] [app.util.transit :as t] [beicon.core :as rx] @@ -28,7 +29,7 @@ (defn- parse-locale [locale] - (let [locale (-> (.-language js/navigator) + (let [locale (-> (.-language globals/navigator) (str/lower) (str/replace "-" "_"))] (cond-> [locale] @@ -37,7 +38,7 @@ (def ^:private browser-locales (delay - (-> (.-language js/navigator) + (-> (.-language globals/navigator) (parse-locale)))) (defn- autodetect From 874378869d374110b1687b63a3e5579135ee7952 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 15 Apr 2021 11:12:52 +0200 Subject: [PATCH 064/155] :fire: Remove legacy system user and team. --- CHANGES.md | 5 +++-- backend/src/app/migrations.clj | 3 +++ .../src/app/migrations/sql/0052-del-legacy-user-and-team.sql | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 backend/src/app/migrations/sql/0052-del-legacy-user-and-team.sql diff --git a/CHANGES.md b/CHANGES.md index d979bb597..5b5050ea7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,8 +13,9 @@ ### :bug: Bugs fixed -- Fixes problem with pan and space [#811](https://github.com/penpot/penpot/issues/811) -- Fixes issue when parsing exponential numbers in paths +- Fix problem with pan and space [#811](https://github.com/penpot/penpot/issues/811) +- Fix issue when parsing exponential numbers in paths +- Remove legacy system user and team [#843](https://github.com/penpot/penpot/issues/843) ### :arrow_up: Deps updates diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index b3c579196..18a8917c4 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -163,6 +163,9 @@ {:name "0051-mod-file-library-rel-table" :fn (mg/resource "app/migrations/sql/0051-mod-file-library-rel-table.sql")} + + {:name "0052-del-legacy-user-and-team" + :fn (mg/resource "app/migrations/sql/0052-del-legacy-user-and-team.sql")} ]) diff --git a/backend/src/app/migrations/sql/0052-del-legacy-user-and-team.sql b/backend/src/app/migrations/sql/0052-del-legacy-user-and-team.sql new file mode 100644 index 000000000..6410b13c6 --- /dev/null +++ b/backend/src/app/migrations/sql/0052-del-legacy-user-and-team.sql @@ -0,0 +1,2 @@ +DELETE FROM team WHERE id = '00000000-0000-0000-0000-000000000000'; +DELETE FROM profile WHERE id = '00000000-0000-0000-0000-000000000000'; From e4d4245b6cfbaa2d039ce500018d004d4a45c837 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 15 Apr 2021 15:30:04 +0200 Subject: [PATCH 065/155] :sparkles: Move data.colors under workspace namespace. --- frontend/src/app/main/data/shortcuts.cljs | 2 +- .../src/app/main/data/viewer/shortcuts.cljs | 2 +- frontend/src/app/main/data/workspace.cljs | 2 +- .../app/main/data/{ => workspace}/colors.cljs | 117 ++++++++---------- .../app/main/data/workspace/shortcuts.cljs | 2 +- .../app/main/ui/workspace/colorpalette.cljs | 2 +- .../app/main/ui/workspace/colorpicker.cljs | 2 +- .../workspace/colorpicker/color_inputs.cljs | 2 +- .../ui/workspace/colorpicker/gradients.cljs | 2 +- .../ui/workspace/colorpicker/harmony.cljs | 2 +- .../main/ui/workspace/colorpicker/hsva.cljs | 2 +- .../ui/workspace/colorpicker/libraries.cljs | 2 +- .../main/ui/workspace/colorpicker/ramp.cljs | 2 +- .../colorpicker/slider_selector.cljs | 2 +- .../app/main/ui/workspace/sidebar/assets.cljs | 2 +- .../workspace/sidebar/options/menus/fill.cljs | 2 +- .../sidebar/options/menus/stroke.cljs | 2 +- .../main/ui/workspace/viewport/gradients.cljs | 2 +- .../ui/workspace/viewport/pixel_overlay.cljs | 2 +- 19 files changed, 72 insertions(+), 81 deletions(-) rename frontend/src/app/main/data/{ => workspace}/colors.cljs (61%) diff --git a/frontend/src/app/main/data/shortcuts.cljs b/frontend/src/app/main/data/shortcuts.cljs index 05a04e42f..00701f830 100644 --- a/frontend/src/app/main/data/shortcuts.cljs +++ b/frontend/src/app/main/data/shortcuts.cljs @@ -6,7 +6,7 @@ (ns app.main.data.shortcuts (:require - [app.main.data.colors :as mdc] + [app.main.data.workspace.colors :as mdc] [app.main.data.workspace.transforms :as dwt] [app.main.store :as st] [app.util.dom :as dom] diff --git a/frontend/src/app/main/data/viewer/shortcuts.cljs b/frontend/src/app/main/data/viewer/shortcuts.cljs index af1243753..91ea3b300 100644 --- a/frontend/src/app/main/data/viewer/shortcuts.cljs +++ b/frontend/src/app/main/data/viewer/shortcuts.cljs @@ -7,7 +7,7 @@ (ns app.main.data.viewer.shortcuts (:require [app.config :as cfg] - [app.main.data.colors :as mdc] + [app.main.data.workspace.colors :as mdc] [app.main.data.shortcuts :as ds] [app.main.data.shortcuts :refer [c-mod]] [app.main.data.viewer :as dv] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 5b629986d..dbd7eca00 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -20,7 +20,7 @@ [app.common.uuid :as uuid] [app.config :as cfg] [app.main.constants :as c] - [app.main.data.colors :as mdc] + [app.main.data.workspace.colors :as mdc] [app.main.data.messages :as dm] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.drawing :as dwd] diff --git a/frontend/src/app/main/data/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs similarity index 61% rename from frontend/src/app/main/data/colors.cljs rename to frontend/src/app/main/data/workspace/colors.cljs index a1b46b11c..6a27b8f92 100644 --- a/frontend/src/app/main/data/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.main.data.colors +(ns app.main.data.workspace.colors (:require [app.common.data :as d] [app.common.pages :as cp] @@ -38,8 +38,7 @@ (ptk/reify ::rename-color ptk/WatchEvent (watch [_ state stream] - (->> (rp/mutation! :rename-color {:id color-id - :name name}) + (->> (rp/mutation! :rename-color {:id color-id :name name}) (rx/map (partial rename-color-result file-id)))))) (defn rename-color-result @@ -101,8 +100,7 @@ ptk/UpdateEvent (update [_ state] (-> state - (update :workspace-local dissoc :picked-color-select) - (update :workspace-local dissoc :picked-shift?) + (update :workspace-local dissoc :picked-color-select :picked-shift?) (assoc-in [:workspace-local :picking-color?] false))))) (defn pick-color @@ -123,75 +121,68 @@ (assoc-in [:workspace-local :picked-shift?] shift?))))) (defn change-fill - ([ids color] - (ptk/reify ::change-fill - ptk/WatchEvent - (watch [_ state s] - (let [pid (:current-page-id state) - objects (get-in state [:workspace-data :pages-index pid :objects]) - not-frame (fn [shape-id] (not= (get-in objects [shape-id :type]) :frame)) - is-text? #(= :text (:type (get objects %))) - text-ids (filter is-text? ids) - shape-ids (filter (comp not is-text?) ids) + [ids color] + (ptk/reify ::change-fill + ptk/WatchEvent + (watch [_ state s] + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) - attrs (cond-> {} - (contains? color :color) - (assoc :fill-color (:color color)) + is-text? #(= :text (:type (get objects %))) + text-ids (filter is-text? ids) + shape-ids (filter (comp not is-text?) ids) - (contains? color :id) - (assoc :fill-color-ref-id (:id color)) + attrs (cond-> {} + (contains? color :color) + (assoc :fill-color (:color color)) - (contains? color :file-id) - (assoc :fill-color-ref-file (:file-id color)) + (contains? color :id) + (assoc :fill-color-ref-id (:id color)) - (contains? color :gradient) - (assoc :fill-color-gradient (:gradient color)) + (contains? color :file-id) + (assoc :fill-color-ref-file (:file-id color)) - (contains? color :opacity) - (assoc :fill-opacity (:opacity color))) + (contains? color :gradient) + (assoc :fill-color-gradient (:gradient color)) - update-fn (fn [shape] (merge shape attrs)) - editors (get-in state [:workspace-local :editors]) - reduce-fn (fn [state id] - (update-in state [:workspace-data :pages-index pid :objects id] update-fn))] + (contains? color :opacity) + (assoc :fill-opacity (:opacity color)))] - (rx/from (conj - (map #(dwt/update-text-attrs {:id % :editor (get editors %) :attrs attrs}) text-ids) - (dwc/update-shapes shape-ids update-fn)))))))) + (rx/concat + (rx/from (map #(dwt/update-text-attrs {:id % :attrs attrs}) text-ids)) + (rx/of (dwc/update-shapes shape-ids (fn [shape] (d/merge shape attrs))))))))) (defn change-stroke [ids color] (ptk/reify ::change-stroke ptk/WatchEvent (watch [_ state s] - (let [pid (:current-page-id state) - objects (get-in state [:workspace-data :pages-index pid :objects]) - not-frame (fn [shape-id] (not= (get-in objects [shape-id :type]) :frame)) + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) - color-attrs (cond-> {} - (contains? color :color) - (assoc :stroke-color (:color color)) + attrs (cond-> {} + (contains? color :color) + (assoc :stroke-color (:color color)) - (contains? color :id) - (assoc :stroke-color-ref-id (:id color)) + (contains? color :id) + (assoc :stroke-color-ref-id (:id color)) - (contains? color :file-id) - (assoc :stroke-color-ref-file (:file-id color)) + (contains? color :file-id) + (assoc :stroke-color-ref-file (:file-id color)) - (contains? color :gradient) - (assoc :stroke-color-gradient (:gradient color)) + (contains? color :gradient) + (assoc :stroke-color-gradient (:gradient color)) - (contains? color :opacity) - (assoc :stroke-opacity (:opacity color))) + (contains? color :opacity) + (assoc :stroke-opacity (:opacity color)))] + + (rx/of (dwc/update-shapes ids (fn [shape] + (cond-> (d/merge shape attrs) + (= (:stroke-style shape) :none) + (assoc :stroke-style :solid + :stroke-width 1 + :stroke-opacity 1))))))))) - update-fn (fn [shape] - (-> shape - (merge color-attrs) - (cond-> (= (:stroke-style s) :none) - (assoc :stroke-style :solid - :stroke-width 1 - :stroke-opacity 1))))] - (rx/of (dwc/update-shapes ids update-fn)))))) (defn picker-for-selected-shape [] @@ -199,15 +190,15 @@ (ptk/reify ::picker-for-selected-shape ptk/WatchEvent (watch [_ state stream] - (let [ids (get-in state [:workspace-local :selected]) - stop? (->> stream - (rx/filter (ptk/type? ::stop-picker))) + (let [ids (get-in state [:workspace-local :selected]) + stop? (rx/filter (ptk/type? ::stop-picker) stream) - update-events (fn [[color shift?]] - (rx/of (if shift? - (change-stroke ids color) - (change-fill ids color)) - (stop-picker)))] + update-events + (fn [[color shift?]] + (rx/of (if shift? + (change-stroke ids color) + (change-fill ids color)) + (stop-picker)))] (rx/merge ;; Stream that updates the stroke/width and stops if `esc` pressed (->> sub diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index f7dda4240..f87abcabd 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -7,7 +7,7 @@ (ns app.main.data.workspace.shortcuts (:require [app.config :as cfg] - [app.main.data.colors :as mdc] + [app.main.data.workspace.colors :as mdc] [app.main.data.shortcuts :as ds] [app.main.data.workspace :as dw] [app.main.data.workspace.common :as dwc] diff --git a/frontend/src/app/main/ui/workspace/colorpalette.cljs b/frontend/src/app/main/ui/workspace/colorpalette.cljs index dd0840608..d4a951f62 100644 --- a/frontend/src/app/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/app/main/ui/workspace/colorpalette.cljs @@ -7,7 +7,7 @@ (ns app.main.ui.workspace.colorpalette (:require [app.common.math :as mth] - [app.main.data.colors :as mdc] + [app.main.data.workspace.colors :as mdc] [app.main.data.workspace :as udw] [app.main.refs :as refs] [app.main.store :as st] diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index 2e66abd72..f1ca966e0 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -18,7 +18,7 @@ [app.main.store :as st] [app.main.refs :as refs] [app.main.data.workspace.libraries :as dwl] - [app.main.data.colors :as dc] + [app.main.data.workspace.colors :as dc] [app.main.data.modal :as modal] [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [t]] 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 c14778cf6..08a0ddd2d 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs @@ -18,7 +18,7 @@ [app.main.store :as st] [app.main.refs :as refs] [app.main.data.workspace.libraries :as dwl] - [app.main.data.colors :as dc] + [app.main.data.workspace.colors :as dc] [app.main.data.modal :as modal] [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [t]])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs index 27cf95e2f..447bfbbe7 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs @@ -18,7 +18,7 @@ [app.main.store :as st] [app.main.refs :as refs] [app.main.data.workspace.libraries :as dwl] - [app.main.data.colors :as dc] + [app.main.data.workspace.colors :as dc] [app.main.data.modal :as modal] [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [t]])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs index 3b33d9a22..d9b9f57a2 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs @@ -18,7 +18,7 @@ [app.main.store :as st] [app.main.refs :as refs] [app.main.data.workspace.libraries :as dwl] - [app.main.data.colors :as dc] + [app.main.data.workspace.colors :as dc] [app.main.data.modal :as modal] [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [t]] diff --git a/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs b/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs index 98271f0dd..0e5e9b8bb 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs @@ -18,7 +18,7 @@ [app.main.store :as st] [app.main.refs :as refs] [app.main.data.workspace.libraries :as dwl] - [app.main.data.colors :as dc] + [app.main.data.workspace.colors :as dc] [app.main.data.modal :as modal] [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [t]] diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs index 8dd45e186..c866a08e1 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs @@ -18,7 +18,7 @@ [app.main.store :as st] [app.main.refs :as refs] [app.main.data.workspace.libraries :as dwl] - [app.main.data.colors :as dc] + [app.main.data.workspace.colors :as dc] [app.main.data.modal :as modal] [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [t]] diff --git a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs index 7b7d28d55..23bb4f413 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs @@ -18,7 +18,7 @@ [app.main.store :as st] [app.main.refs :as refs] [app.main.data.workspace.libraries :as dwl] - [app.main.data.colors :as dc] + [app.main.data.workspace.colors :as dc] [app.main.data.modal :as modal] [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [t]] 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 c76fb8bdc..0c3a8e1da 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs @@ -18,7 +18,7 @@ [app.main.store :as st] [app.main.refs :as refs] [app.main.data.workspace.libraries :as dwl] - [app.main.data.colors :as dc] + [app.main.data.workspace.colors :as dc] [app.main.data.modal :as modal] [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [t]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 24cb1d1fc..279b66300 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -15,7 +15,7 @@ [app.common.text :as txt] [app.common.uuid :as uuid] [app.config :as cfg] - [app.main.data.colors :as dc] + [app.main.data.workspace.colors :as dc] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.common :as dwc] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs index e61035935..b70447603 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs @@ -7,7 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.menus.fill (:require [app.common.pages :as cp] - [app.main.data.colors :as dc] + [app.main.data.workspace.colors :as dc] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.texts :as dwt] [app.main.refs :as refs] 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 6a56ec363..002a6a7bc 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 @@ -11,7 +11,7 @@ [app.common.data :as d] [app.common.math :as math] [app.main.data.workspace.common :as dwc] - [app.main.data.colors :as dc] + [app.main.data.workspace.colors :as dc] [app.main.store :as st] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] diff --git a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs index 633679725..380c96f77 100644 --- a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs @@ -21,7 +21,7 @@ [app.main.streams :as ms] [app.main.data.modal :as modal] [app.main.data.workspace.common :as dwc] - [app.main.data.colors :as dc])) + [app.main.data.workspace.colors :as dc])) (def gradient-line-stroke-width 2) (def gradient-line-stroke-color "white") diff --git a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs index 95aba3048..ab46aeb73 100644 --- a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs @@ -7,7 +7,7 @@ (ns app.main.ui.workspace.viewport.pixel-overlay (:require [app.common.uuid :as uuid] - [app.main.data.colors :as dwc] + [app.main.data.workspace.colors :as dwc] [app.main.data.modal :as modal] [app.main.refs :as refs] [app.main.store :as st] From 88eb6abdd68d8fc7dcdaf9f13cebb55f4bfaaf84 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 15 Apr 2021 17:03:36 +0200 Subject: [PATCH 066/155] :sparkles: Improve time related functions (frontend). --- frontend/package.json | 4 +- frontend/src/app/util/time.cljs | 226 +++++++++++++++++++++++++---- frontend/src/app/util/transit.cljs | 21 +++ frontend/yarn.lock | 18 +-- 4 files changed, 227 insertions(+), 42 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 31728cfb9..efb975945 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,11 +35,11 @@ "shadow-cljs": "^2.11.20" }, "dependencies": { - "date-fns": "^2.19.0", + "date-fns": "^2.21.1", "draft-js": "^0.11.7", "highlight.js": "^10.6.0", - "humanize-duration": "~3.25.0", "js-beautify": "^1.13.5", + "luxon": "^1.26.0", "mousetrap": "^1.6.5", "randomcolor": "^0.6.2", "react": "~17.0.1", diff --git a/frontend/src/app/util/time.cljs b/frontend/src/app/util/time.cljs index 70668984c..968036558 100644 --- a/frontend/src/app/util/time.cljs +++ b/frontend/src/app/util/time.cljs @@ -6,47 +6,211 @@ (ns app.util.time (:require - ["date-fns/parseISO" :as dateFnsParseISO] - ["date-fns/formatISO" :as dateFnsFormatISO] - ["date-fns/format" :as dateFnsFormat] - ["date-fns/formatDistanceToNowStrict" :as dateFnsFormatDistanceToNowStrict] - ["date-fns/locale/fr" :as dateFnsLocalesFr] - ["date-fns/locale/en-US" :as dateFnsLocalesEnUs] - ["date-fns/locale/zh-CN" :as dateFnsLocalesZhCn] - ["date-fns/locale/es" :as dateFnsLocalesEs] - ["date-fns/locale/ru" :as dateFnsLocalesRu] + [cuerdas.core :as str] + ["luxon" :as lxn] + ["date-fns/formatDistanceToNowStrict" :default dateFnsFormatDistanceToNowStrict] + ["date-fns/locale/fr" :default dateFnsLocalesFr] + ["date-fns/locale/ca" :default dateFnsLocalesCa] + ["date-fns/locale/en-US" :default dateFnsLocalesEnUs] + ["date-fns/locale/zh-CN" :default dateFnsLocalesZhCn] + ["date-fns/locale/es" :default dateFnsLocalesEs] + ["date-fns/locale/tr" :default dateFnsLocalesTr] + ["date-fns/locale/ru" :default dateFnsLocalesRu] [app.util.object :as obj])) +(def DateTime lxn/DateTime) +(def Duration lxn/Duration) + +(defprotocol ITimeMath + (plus [_ o]) + (minus [_ o])) + +(defprotocol ITimeFormat + (format [_ fmt])) + +(defn duration? + [o] + (instance? Duration o)) + +(defn datetime? + [o] + (instance? DateTime o)) + +(defn duration + [o] + (cond + (integer? o) (.fromMillis Duration o) + (duration? o) o + (string? o) (.fromISO Duration o) + (map? o) (.fromObject Duration (clj->js o)) + :else (throw (js/Error. "unexpected arguments")))) + +(defn datetime + ([s] (datetime s nil)) + ([s {:keys [zone force-zone] :or {zone "local" force-zone false}}] + (cond + (integer? s) + (.fromMillis ^js DateTime s #js {:zone zone :setZone force-zone}) + + (map? s) + (.fromObject ^js DateTime (-> (clj->js s) + (obj/set! "zone" zone) + (obj/set! "setZone" force-zone))) + + :else + (throw (js/Error. "invalid arguments"))))) + +(defn epoch->datetime + ([seconds] (epoch->datetime seconds nil)) + ([seconds {:keys [zone force-zone] :or {zone "local" force-zone false}}] + (.fromSeconds ^js DateTime seconds #js {:zone zone :setZone force-zone}))) + +(defn iso->datetime + "A faster option for transit date parsing." + [s] + (.fromISO ^js DateTime s #js {:zone "local"})) + +(defn parse-datetime + ([s] (parse-datetime s :iso nil)) + ([s fmt] (parse-datetime s fmt nil)) + ([s fmt {:keys [zone force-zone] :or {zone "local" force-zone false}}] + (if (string? fmt) + (.fromFormat ^js DateTime s fmt #js {:zone zone :setZone force-zone}) + (case fmt + :iso (.fromISO ^js DateTime s #js {:zone zone :setZone force-zone}) + :rfc2822 (.fromRFC2822 ^js DateTime s #js {:zone zone :setZone force-zone}) + :http (.fromHTTP ^js DateTime s #js {:zone zone :setZone force-zone}))))) + +(defn now + [] + (.local ^js DateTime)) + +(defn utc-now + [] + (.utc ^js DateTime)) + +(defn ->utc + [dt] + (.toUTC ^js dt)) + +(defn diff + [dt1 dt2] + (.diff ^js dt1 dt2)) + +(extend-protocol IEquiv + DateTime + (-equiv [it other] + (.equals it other)) + + Duration + (-equiv [it other] + (.equals it other))) + +(extend-protocol Inst + DateTime + (inst-ms* [inst] (.toMillis ^js inst)) + + Duration + (inst-ms* [inst] (.toMillis ^js inst))) + +(extend-protocol IComparable + DateTime + (-compare [it other] + (if ^boolean (.equals it other) + 0 + (if (< (inst-ms it) (inst-ms other)) -1 1))) + + Duration + (-compare [it other] + (if ^boolean (.equals it other) + 0 + (if (< (inst-ms it) (inst-ms other)) -1 1)))) + +(extend-protocol ITimeMath + DateTime + (plus [it o] + (if (map? o) + (.plus ^js it (clj->js o)) + (.plus ^js it o))) + + (minus [it o] + (if (map? o) + (.minus ^js it (clj->js o)) + (.minus ^js it o))) + + Duration + (plus [it o] + (if (map? o) + (.plus ^js it (clj->js o)) + (.plus ^js it o))) + + (minus [it o] + (if (map? o) + (.minus ^js it (clj->js o)) + (.minus ^js it o)))) + +(extend-protocol IPrintWithWriter + DateTime + (-pr-writer [p writer opts] + (-write writer (str/fmt "#stks/datetime \"%s\"" (format p :iso)))) + + Duration + (-pr-writer [p writer opts] + (-write writer (str/fmt "#stks/duration \"%s\"" (format p :iso))))) + +(defn- resolve-format + [v] + (case v + :time-24-simple (.-TIME_24_SIMPLE ^js DateTime) + :datetime-short (.-DATETIME_SHORT ^js DateTime) + :datetime-med (.-DATETIME_MED ^js DateTime) + :datetime-full (.-DATETIME_FULL ^js DateTime) + :date-full (.-DATE_FULL ^js DateTime) + :date-med-with-weekday (.-DATE_MED_WITH_WEEKDAY ^js DateTime) + v)) + +(defn- format-datetime + [dt fmt] + (case fmt + :iso (.toISO ^js dt) + :rfc2822 (.toRFC2822 ^js dt) + :http (.toHTTP ^js dt) + :json (.toJSON ^js dt) + :date (.toJSDate ^js dt) + :epoch (js/Math.floor (.toSeconds ^js dt)) + :millis (.toMillis ^js dt) + (let [f (resolve-format fmt)] + (if (string? f) + (.toFormat ^js dt f) + (.toLocaleString ^js dt f))))) + +(extend-protocol ITimeFormat + DateTime + (format [it fmt] + (format-datetime it fmt)) + + Duration + (format [it fmt] + (case fmt + :iso (.toISO it) + :json (.toJSON it) + (.toFormat ^js it fmt)))) + (def ^:private locales #js {:en dateFnsLocalesEnUs :fr dateFnsLocalesFr + :tr dateFnsLocalesTr :es dateFnsLocalesEs + :ca dateFnsLocalesCa :ru dateFnsLocalesRu :zh_cn dateFnsLocalesZhCn}) -(defn now - "Return the current Instant." - [] - (js/Date.)) - -(defn parse - [v] - (^js dateFnsParseISO v)) - -(defn format-iso - [v] - (^js dateFnsFormatISO v)) - -(defn format - ([v fmt] (format v fmt nil)) - ([v fmt {:keys [locale] :or {locale "en"}}] - (dateFnsFormat v fmt #js {:locale (obj/get locales locale)}))) - (defn timeago ([v] (timeago v nil)) ([v {:keys [locale] :or {locale "en"}}] (when v - (->> #js {:includeSeconds true - :addSuffix true - :locale (obj/get locales locale)} - (dateFnsFormatDistanceToNowStrict v))))) + (let [v (if (datetime? v) (format v :date) v)] + (->> #js {:includeSeconds true + :addSuffix true + :locale (obj/get locales locale)} + (dateFnsFormatDistanceToNowStrict v)))))) diff --git a/frontend/src/app/util/transit.cljs b/frontend/src/app/util/transit.cljs index 49ebf8152..24d385176 100644 --- a/frontend/src/app/util/transit.cljs +++ b/frontend/src/app/util/transit.cljs @@ -10,6 +10,7 @@ [cognitect.transit :as t] [linked.core :as lk] [linked.set :as lks] + [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.matrix :as gmt] [app.util.time :as dt])) @@ -67,6 +68,22 @@ (def ordered-set-read-handler (t/read-handler #(into (lk/set) %))) +(def date-read-handler + (t/read-handler (fn [value] (-> value (js/parseInt 10) (dt/datetime))))) + +(def duration-read-handler + (t/read-handler (fn [value] (dt/duration value)))) + +(def date-write-handler + (t/write-handler + (constantly "m") + (fn [v] (str (inst-ms v))))) + +(def duration-write-handler + (t/write-handler + (constantly "duration") + (fn [v] (inst-ms v)))) + ;; --- Transit Handlers (def ^:privare +read-handlers+ @@ -75,11 +92,15 @@ "ordered-set" ordered-set-read-handler "jsonblob" blob-read-handler "matrix" matrix-read-handler + "m" date-read-handler + "duration" duration-read-handler "point" point-read-handler}) (def ^:privare +write-handlers+ {gmt/Matrix matrix-write-handler Blob blob-write-handler + dt/DateTime date-write-handler + dt/Duration duration-write-handler lks/LinkedSet ordered-set-write-handler gpt/Point point-write-handler}) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 119d530e1..1d05ca47f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1189,10 +1189,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -date-fns@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.19.0.tgz#65193348635a28d5d916c43ec7ce6fbd145059e1" - integrity sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg== +date-fns@^2.21.1: + version "2.21.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.21.1.tgz#679a4ccaa584c0706ea70b3fa92262ac3009d2b0" + integrity sha512-m1WR0xGiC6j6jNFAyW4Nvh4WxAi4JF4w9jRJwSI8nBmNcyZXPcP9VUQG+6gHQXAmqaGEKDKhOqAtENDC941UkA== dateformat@^3.0.3: version "3.0.3" @@ -2368,11 +2368,6 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -humanize-duration@~3.25.0: - version "3.25.1" - resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.25.1.tgz#50e12bf4b3f515ec91106107ee981e8cfe955d6f" - integrity sha512-P+dRo48gpLgc2R9tMRgiDRNULPKCmqFYgguwqOO2C0fjO35TgdURDQDANSR1Nt92iHlbHGMxOTnsB8H8xnMa2Q== - iconv-lite@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" @@ -3129,6 +3124,11 @@ lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" +luxon@^1.26.0: + version "1.26.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.26.0.tgz#d3692361fda51473948252061d0f8561df02b578" + integrity sha512-+V5QIQ5f6CDXQpWNICELwjwuHdqeJM1UenlZWx5ujcRMc9venvluCjFb4t5NYLhb6IhkbMVOxzVuOqkgMxee2A== + make-iterator@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" From a777e8e42a080ae33d0b633460f046b5e4e79ae6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 15 Apr 2021 17:33:10 +0200 Subject: [PATCH 067/155] :bug: Set correct locale code for Greek. --- frontend/gulpfile.js | 2 +- frontend/src/app/util/i18n.cljs | 2 +- frontend/src/app/util/time.cljs | 2 ++ frontend/translations/{gr.po => el.po} | 42 +++++++++++++------------- 4 files changed, 25 insertions(+), 23 deletions(-) rename frontend/translations/{gr.po => el.po} (99%) diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js index c0450faff..57dc9b164 100644 --- a/frontend/gulpfile.js +++ b/frontend/gulpfile.js @@ -32,7 +32,7 @@ paths.dist = "./target/dist/"; // Templates function readLocales() { - const langs = ["ca", "gr", "en", "es", "fr", "tr", "ru", "zh_cn"]; + const langs = ["ca", "el", "en", "es", "fr", "tr", "ru", "zh_cn"]; const result = {}; for (let lang of langs) { diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index ff93245f2..9c3e06939 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -24,7 +24,7 @@ {:label "Deutsch (community)" :value "de"} {:label "Русский (community)" :value "ru"} {:label "Türkçe (community)" :value "tr"} - {:label "Ελληνική γλώσσα (community)" :value "gr"} + {:label "Ελληνική γλώσσα (community)" :value "el"} {:label "简体中文 (community)" :value "zh_cn"}]) (defn- parse-locale diff --git a/frontend/src/app/util/time.cljs b/frontend/src/app/util/time.cljs index 968036558..67b75291c 100644 --- a/frontend/src/app/util/time.cljs +++ b/frontend/src/app/util/time.cljs @@ -9,6 +9,7 @@ [cuerdas.core :as str] ["luxon" :as lxn] ["date-fns/formatDistanceToNowStrict" :default dateFnsFormatDistanceToNowStrict] + ["date-fns/locale/el" :default dateFnsLocalesEl] ["date-fns/locale/fr" :default dateFnsLocalesFr] ["date-fns/locale/ca" :default dateFnsLocalesCa] ["date-fns/locale/en-US" :default dateFnsLocalesEnUs] @@ -202,6 +203,7 @@ :tr dateFnsLocalesTr :es dateFnsLocalesEs :ca dateFnsLocalesCa + :el dateFnsLocalesEl :ru dateFnsLocalesRu :zh_cn dateFnsLocalesZhCn}) diff --git a/frontend/translations/gr.po b/frontend/translations/el.po similarity index 99% rename from frontend/translations/gr.po rename to frontend/translations/el.po index 98eb07d7f..2aa22d3f1 100644 --- a/frontend/translations/gr.po +++ b/frontend/translations/el.po @@ -383,7 +383,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" @@ -715,7 +715,7 @@ msgstr "Εικόνα" #: src/app/main/ui/handoff/right_sidebar.cljs msgid "handoff.tabs.code.selected.multiple" -msgstr "% S Επιλεγμένα" +msgstr "%s Επιλεγμένα" msgid "handoff.tabs.code.selected.path" msgstr "Σχέδιο" @@ -734,7 +734,7 @@ msgid "handoff.tabs.info" msgstr "Πληροφορίες" msgid "history.alert-message" -msgstr "Βλέπετε την έκδοση% s" +msgstr "Βλέπετε την έκδοση %s" msgid "labels.accept" msgstr "Αποδέχομαι" @@ -885,13 +885,13 @@ msgstr "Ωχ" msgid "labels.num-of-files" msgid_plural "labels.num-of-files" msgstr[0] "1 αρχείο" -msgstr[1] "% s αρχεία" +msgstr[1] "%s αρχεία" #: src/app/main/ui/dashboard/team.cljs msgid "labels.num-of-projects" msgid_plural "labels.num-of-projects" msgstr[0] "1 έργο" -msgstr[1] "% s έργα" +msgstr[1] "%s έργα" #: src/app/main/ui/settings/password.cljs msgid "labels.old-password" @@ -1140,7 +1140,7 @@ msgstr "Προσκαλέστε να συμμετάσχετε στην ομάδα #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.hint1" -msgstr "Είστε ο ιδιοκτήτης του% s" +msgstr "Είστε ο ιδιοκτήτης του %s" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.hint2" @@ -1230,7 +1230,7 @@ msgstr "Το προφίλ αποθηκεύτηκε με επιτυχία!" #: src/app/main/ui/settings/change_email.cljs msgid "notifications.validation-email-sent" -msgstr "Το email επαλήθευσης εστάλη στο% s. Ελέγξτε το email σας!" +msgstr "Το email επαλήθευσης εστάλη στο %s. Ελέγξτε το email σας!" #: src/app/main/ui/auth/recovery.cljs msgid "profile.recovery.go-to-login" @@ -1509,7 +1509,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" @@ -1517,7 +1517,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" @@ -1541,7 +1541,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" @@ -1549,7 +1549,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" @@ -1569,7 +1569,7 @@ msgstr "Δεν υπάρχουν κοινόχρηστες βιβλιοθήκες #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.no-matches-for" -msgstr "Δεν βρίσκεται «% s»" +msgstr "Δεν βρίσκεται «%s»" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.no-shared-libraries-available" @@ -1593,7 +1593,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" @@ -2168,11 +2168,11 @@ msgstr "23 / 5000 Resultados de traducción Ενημέρωση κύριου στ #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.sidebar.history" -msgstr "Ιστορικό (% s)" +msgstr "Ιστορικό (%s)" #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.sidebar.layers" -msgstr "στρώσεις (% s)" +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" @@ -2188,15 +2188,15 @@ msgstr "Χάρτης ιστοτόπου" #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.assets" -msgstr "Στοιχεία (% s)" +msgstr "Στοιχεία (%s)" #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.color-palette" -msgstr "Παλέτα χρωμάτων (% s)" +msgstr "Παλέτα χρωμάτων (%s)" #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.comments" -msgstr "Σχόλια (% s)" +msgstr "Σχόλια (%s)" #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.curve" @@ -2236,7 +2236,7 @@ msgstr "Δεν υπάρχουν μέχρι στιγμής αλλαγές στο #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.delete" -msgstr "Διαγράφηκε% s" +msgstr "Διαγράφηκε %s" #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.modify" @@ -2244,7 +2244,7 @@ msgstr "" #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.move" -msgstr "Τροποποιήθηκε% s" +msgstr "Τροποποιήθηκε %s" msgid "workspace.undo.entry.multiple.circle" msgstr "κύκλους" @@ -2339,7 +2339,7 @@ msgstr "τυπογραφικό στοιχείο" #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.unknown" -msgstr "Λειτουργία άνω του% s" +msgstr "Λειτουργία άνω του %s" #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.title" From 2b35dce0372ea8eab4e034a00f38fe114a503566 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 15 Apr 2021 17:33:52 +0200 Subject: [PATCH 068/155] :tada: Add cache for font fetching on embedd ns. --- frontend/src/app/main/repo.cljs | 9 ++-- .../src/app/main/ui/shapes/text/embed.cljs | 46 ++++++++++++++----- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index ef9050268..3d2a167a7 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -7,12 +7,13 @@ (ns app.main.repo (:require [app.common.data :as d] - [beicon.core :as rx] - [lambdaisland.uri :as u] - [cuerdas.core :as str] [app.config :as cfg] + [app.util.http :as http] + [app.util.time :as dt] [app.util.transit :as t] - [app.util.http :as http])) + [beicon.core :as rx] + [cuerdas.core :as str] + [lambdaisland.uri :as u])) (defn- handle-response [{:keys [status body] :as response}] diff --git a/frontend/src/app/main/ui/shapes/text/embed.cljs b/frontend/src/app/main/ui/shapes/text/embed.cljs index 4e8030497..1b1475a60 100644 --- a/frontend/src/app/main/ui/shapes/text/embed.cljs +++ b/frontend/src/app/main/ui/shapes/text/embed.cljs @@ -11,6 +11,7 @@ [app.common.text :as txt] [app.main.fonts :as fonts] [app.util.http :as http] + [app.util.time :as dt] [app.util.webapi :as wapi] [app.util.object :as obj] [clojure.set :as set] @@ -19,6 +20,23 @@ [beicon.core :as rx] [rumext.alpha :as mf])) + +(defonce cache (atom {})) + +(defn with-cache + [{:keys [key max-age]} observable] + (let [entry (get @cache key) + age (when entry + (dt/diff (dt/now) + (:created-at entry)))] + (if (and (some? entry) + (< age max-age)) + (rx/of (:data entry)) + (->> observable + (rx/tap (fn [data] + (let [entry {:created-at (dt/now) :data data}] + (swap! cache assoc key entry)))))))) + (def font-face-template " /* latin */ @font-face { @@ -63,20 +81,26 @@ (->> (rx/take 1 observable) (rx/subs resolve reject))))) -(defn get-font-data +(defn fetch-font-data "Parses the CSS and retrieves the font data as DataURI." [^string css] (let [uris (->> (re-seq #"url\(([^)]+)\)" css) - (map second))] - (->> (rx/from (seq uris)) - (rx/mapcat (fn [uri] - (http/send! {:method :get - :uri uri - :response-type :blob}))) - (rx/map :body) - (rx/mapcat wapi/read-file-as-data-url) - (rx/reduce conj []) - (http/as-promise)))) + (mapv second))] + (with-cache {:key uris :max-age (dt/duration {:hours 4})} + (->> (rx/from (seq uris)) + (rx/mapcat (fn [uri] + (http/send! {:method :get + :uri uri + :response-type :blob}))) + (rx/map :body))))) + +(defn get-font-data + "Parses the CSS and retrieves the font data as DataURI." + [^string css] + (->> (fetch-font-data css) + (rx/mapcat wapi/read-file-as-data-url) + (rx/reduce conj []) + (http/as-promise))) (defn embed-font "Given a font-id and font-variant-id, retrieves the CSS for it and From dd92e5d773cbebbbd2c5e43517e330046b42fc0a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 15 Apr 2021 18:17:45 +0200 Subject: [PATCH 069/155] :bug: Fix bug in font embedding. --- frontend/src/app/main/ui/shapes/text.cljs | 2 +- frontend/src/app/main/ui/shapes/text/embed.cljs | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/text.cljs b/frontend/src/app/main/ui/shapes/text.cljs index 7f061eee6..6449c3e64 100644 --- a/frontend/src/app/main/ui/shapes/text.cljs +++ b/frontend/src/app/main/ui/shapes/text.cljs @@ -38,7 +38,7 @@ {:style style :xmlns "http://www.w3.org/1999/xhtml"} (when embed? - [ste/embed-fontfaces-style {:node node}]) + [:& ste/embed-fontfaces-style {:node node}]) children])) (mf/defc render-paragraph-set diff --git a/frontend/src/app/main/ui/shapes/text/embed.cljs b/frontend/src/app/main/ui/shapes/text/embed.cljs index 1b1475a60..b487bb25f 100644 --- a/frontend/src/app/main/ui/shapes/text/embed.cljs +++ b/frontend/src/app/main/ui/shapes/text/embed.cljs @@ -89,17 +89,16 @@ (with-cache {:key uris :max-age (dt/duration {:hours 4})} (->> (rx/from (seq uris)) (rx/mapcat (fn [uri] - (http/send! {:method :get - :uri uri - :response-type :blob}))) - (rx/map :body))))) + (->> (http/send! {:method :get :uri uri :response-type :blob}) + (rx/map :body) + (rx/mapcat wapi/read-file-as-data-url) + (rx/map #(vector uri %))))) + (rx/reduce conj []))))) (defn get-font-data "Parses the CSS and retrieves the font data as DataURI." [^string css] (->> (fetch-font-data css) - (rx/mapcat wapi/read-file-as-data-url) - (rx/reduce conj []) (http/as-promise))) (defn embed-font From fad9d2fd3a55c99d43953273db391e8ab97068a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 15 Apr 2021 15:50:13 +0200 Subject: [PATCH 070/155] :tada: Allow calculations in numeric fields --- frontend/deps.edn | 2 + .../app/main/ui/components/numeric_input.cljs | 160 +++++++++++------- frontend/src/app/util/simple_math.cljs | 104 ++++++++++++ frontend/tests/app/test_util_simple_math.cljs | 78 +++++++++ 4 files changed, 285 insertions(+), 59 deletions(-) create mode 100644 frontend/src/app/util/simple_math.cljs create mode 100644 frontend/tests/app/test_util_simple_math.cljs diff --git a/frontend/deps.edn b/frontend/deps.edn index 313e8db1a..5783b856b 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -21,6 +21,8 @@ lambdaisland/uri {:mvn/version "1.4.54" :exclusions [org.clojure/data.json]} + instaparse/instaparse {:mvn/version "1.4.10"} + } :aliases {:dev diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs index f8b4027b1..6088b9b8d 100644 --- a/frontend/src/app/main/ui/components/numeric_input.cljs +++ b/frontend/src/app/main/ui/components/numeric_input.cljs @@ -8,83 +8,122 @@ (:require [app.common.data :as d] [app.common.math :as math] + [app.common.spec :as us] [app.util.dom :as dom] [app.util.keyboard :as kbd] [app.util.object :as obj] + [app.util.simple-math :as sm] [rumext.alpha :as mf])) (mf/defc numeric-input {::mf/wrap-props false ::mf/forward-ref true} [props ref] - (let [value (obj/get props "value") - on-change (obj/get props "onChange") - min-val (obj/get props "min") - max-val (obj/get props "max") + (let [value-str (obj/get props "value") + min-val-str (obj/get props "min") + max-val-str (obj/get props "max") wrap-value? (obj/get props "data-wrap") + on-change (obj/get props "onChange") - stored-val (mf/use-var value) - local-ref (mf/use-ref nil) + local-ref (mf/use-ref) ref (or ref local-ref) - min-val (cond-> min-val - (string? min-val) (d/parse-integer nil)) + value (d/parse-integer value-str) - max-val (cond-> max-val - (string? max-val) (d/parse-integer nil)) + min-val (when (string? min-val-str) + (d/parse-integer min-val-str)) + max-val (when (string? max-val-str) + (d/parse-integer max-val-str)) + num? (fn [val] (and (number? val) + (not (math/nan? val)) + (math/finite? val))) - num? (fn [value] (and (number? value) - (not (math/nan? value)) - (math/finite? value))) - - parse-value (fn [event] - (let [value (-> (dom/get-target-val event) (d/parse-integer nil))] - (when (num? value) - (cond-> value - (num? min-val) (cljs.core/max min-val) - (num? max-val) (cljs.core/min max-val))))) - handle-change + parse-value (mf/use-callback - (mf/deps on-change) - (fn [event] - (let [value (parse-value event)] - (when (and on-change (num? value)) - (on-change value))))) + (mf/deps ref min-val max-val value) + (fn [] + (let [input-node (mf/ref-val ref) + new-value (-> (dom/get-value input-node) + (sm/expr-eval value))] + (when (num? new-value) + (cond-> new-value + true + (math/round) + + true + (cljs.core/max us/min-safe-int) + + true + (cljs.core/min us/max-safe-int) + + (num? min-val) + (cljs.core/max min-val) + + (num? max-val) + (cljs.core/min max-val)))))) + + update-input + (mf/use-callback + (mf/deps ref) + (fn [new-value] + (let [input-node (mf/ref-val ref)] + (dom/set-value! input-node (str new-value))))) + + apply-value + (mf/use-callback + (mf/deps on-change update-input) + (fn [new-value] + (when new-value + (when on-change + (on-change new-value)) + (update-input new-value)))) set-delta (mf/use-callback - (mf/deps on-change wrap-value? min-val max-val) + (mf/deps wrap-value? min-val max-val parse-value apply-value) (fn [event up? down?] - (let [value (parse-value event) - increment (if up? 9 -9)] - (when (and (or up? down?) (num? value)) - (cond - (kbd/shift? event) - (let [new-value (+ value increment) - new-value (cond - (and wrap-value? (num? max-val) (num? min-val) (> new-value max-val) up?) - (+ min-val (- max-val new-value)) + (let [current-value (parse-value)] + (when current-value + (let [increment (if (kbd/shift? event) + (if up? 10 -10) + (if up? 1 -1)) - (and wrap-value? (num? min-val) (num? max-val) (< new-value min-val) down?) - (- max-val (- new-value min-val)) + new-value (+ current-value increment) + new-value (cond + (and wrap-value? (num? max-val) (num? min-val) + (> new-value max-val) up?) + (-> new-value (- max-val) (+ min-val) (- 1)) - (and (num? min-val) (< new-value min-val)) min-val - (and (num? max-val) (> new-value max-val)) max-val - :else new-value)] - (dom/set-value! (dom/get-target event) new-value)) + (and wrap-value? (num? min-val) (num? max-val) + (< new-value min-val) down?) + (-> new-value (- min-val) (+ max-val) (+ 1)) - (and wrap-value? (num? max-val) (num? min-val) (= value max-val) up?) - (dom/set-value! (dom/get-target event) (dec min-val)) + (and (num? min-val) (< new-value min-val)) + min-val - (and wrap-value? (num? min-val) (num? max-val) (= value min-val) down?) - (dom/set-value! (dom/get-target event) (inc max-val))))))) + (and (num? max-val) (> new-value max-val)) + max-val + + :else new-value)] + + (apply-value new-value)))))) handle-key-down (mf/use-callback - (mf/deps set-delta) + (mf/deps set-delta apply-value update-input) (fn [event] - (set-delta event (kbd/up-arrow? event) (kbd/down-arrow? event)))) + (let [up? (kbd/up-arrow? event) + down? (kbd/down-arrow? event) + enter? (kbd/enter? event) + esc? (kbd/esc? event)] + (when (or up? down?) + (set-delta event up? down?)) + (when enter? + (let [new-value (parse-value)] + (apply-value new-value))) + (when esc? + (update-input value-str))))) handle-mouse-wheel (mf/use-callback @@ -93,27 +132,30 @@ (set-delta event (< (.-deltaY event) 0) (> (.-deltaY event) 0)))) handle-blur - (fn [event] - (when-let [input-node (and ref (mf/ref-val ref))] - (dom/set-value! input-node @stored-val))) + (mf/use-callback + (mf/deps parse-value apply-value update-input) + (fn [event] + (let [new-value (parse-value)] + (if new-value + (apply-value new-value) + (update-input value-str))))) props (-> props (obj/without ["value" "onChange"]) (obj/set! "className" "input-text") - (obj/set! "type" "number") + (obj/set! "type" "text") (obj/set! "ref" ref) - (obj/set! "defaultValue" value) + (obj/set! "defaultValue" value-str) (obj/set! "onWheel" handle-mouse-wheel) (obj/set! "onKeyDown" handle-key-down) - (obj/set! "onChange" handle-change) (obj/set! "onBlur" handle-blur))] (mf/use-effect - (mf/deps value) + (mf/deps value-str) (fn [] - (when-let [input-node (and ref (mf/ref-val ref))] - (if-not (dom/active? input-node) - (dom/set-value! input-node value) - (reset! stored-val value))))) + (when-let [input-node (mf/ref-val ref)] + (when-not (dom/active? input-node) + (dom/set-value! input-node value-str))))) + [:> :input props])) diff --git a/frontend/src/app/util/simple_math.cljs b/frontend/src/app/util/simple_math.cljs new file mode 100644 index 000000000..45cb01719 --- /dev/null +++ b/frontend/src/app/util/simple_math.cljs @@ -0,0 +1,104 @@ +;; 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.simple-math + (:require + [cljs.spec.alpha :as s] + [clojure.string :refer [index-of]] + [cuerdas.core :as str] + [instaparse.core :as insta] + [app.common.data :as d] + [app.common.exceptions :as ex])) + +(def parser + (insta/parser + "opt-expr = '' | expr + expr = term ( ('+'|'-') expr)* | + ('+'|'-'|'*'|'/') factor + term = factor ( ('*'|'/') term)* + factor = number | ('(' expr ')') + number = #'[0-9]*[.,]?[0-9]+%?' + spaces = ' '*")) + +(defn interpret + [tree init-value] + (let [token (first tree) + args (rest tree)] + (case token + + :opt-expr + (if (empty? args) 0 (interpret (first args) init-value)) + + :expr + (if (index-of "+-*/" (first args)) + (let [operator (first args) + second-value (interpret (second args) init-value)] + (case operator + "+" (+ init-value second-value) + "-" (- init-value second-value) + "*" (* init-value second-value) + "/" (/ init-value second-value))) + (let [value (interpret (first args) init-value)] + (loop [value value + rest-expr (rest args)] + (if (empty? rest-expr) + value + (let [operator (first rest-expr) + second-value (interpret (second rest-expr) init-value) + rest-expr (-> rest-expr rest rest)] + (case operator + "+" (recur (+ value second-value) rest-expr) + "-" (recur (- value second-value) rest-expr))))))) + + :term + (let [value (interpret (first args) init-value)] + (loop [value value + rest-expr (rest args)] + (if (empty? rest-expr) + value + (let [operator (first rest-expr) + second-value (interpret (second rest-expr) init-value) + rest-expr (-> rest-expr rest rest)] + (case operator + "*" (recur (* value second-value) rest-expr) + "/" (recur (/ value second-value) rest-expr)))))) + + :factor + (if (= (first args) "(") + (interpret (second args) init-value) + (interpret (first args) init-value)) + + :number + (let [value-str (str/replace (first args) "," ".")] + (if-not (str/ends-with? value-str "%") + (d/parse-double value-str) + (-> value-str + (str/replace "%" "") + (d/parse-double) + (/ 100) + (* init-value)))) + + (ex/raise :type :validation + :hint (str "Unknown token" token args))))) + +(defn expr-eval + [expr init-value] + (s/assert string? expr) + (s/assert number? init-value) + (let [result (parser expr)] + (if-not (insta/failure? result) + (interpret result init-value) + (let [text (:text result) + index (:index result) + expecting (->> result + :reason + (map :expecting) + (filter some?))] + (js/console.debug + (str "Invalid value '" text "' at index " index + ". Expected one of " expecting ".")) + nil)))) + diff --git a/frontend/tests/app/test_util_simple_math.cljs b/frontend/tests/app/test_util_simple_math.cljs new file mode 100644 index 000000000..98f73eb1f --- /dev/null +++ b/frontend/tests/app/test_util_simple_math.cljs @@ -0,0 +1,78 @@ +(ns app.test-util-simple-math + (:require [cljs.test :as t :include-macros true] + [cljs.pprint :refer [pprint]] + [app.common.math :as cm] + [app.util.simple-math :as sm])) + +(t/deftest test-parser-inst + (t/testing "Evaluate an empty string" + (let [result (sm/expr-eval "" 999)] + (t/is (= result 0)))) + + (t/testing "Evaluate a single number" + (let [result (sm/expr-eval "10" 999)] + (t/is (= result 10)))) + + (t/testing "Evaluate an addition" + (let [result (sm/expr-eval "10+3" 999)] + (t/is (= result 13)))) + + (t/testing "Evaluate an addition with spaces" + (let [result (sm/expr-eval "100 + 35" 999)] + (t/is (= result 135)))) + + (t/testing "Evaluate some operations" + (let [result (sm/expr-eval "100 + 35 - 10 * 2" 999)] + (t/is (= result 115)))) + + (t/testing "Evaluate some operations with parentheses" + (let [result (sm/expr-eval "(100 + 35 - 10) * 2" 999)] + (t/is (= result 250)))) + + (t/testing "Evaluate some operations with nested parentheses" + (let [result (sm/expr-eval "(100 + 35 - (20/2))*2" 999)] + (t/is (= result 250)))) + + (t/testing "Evaluate a relative addition" + (let [result (sm/expr-eval "+10" 20)] + (t/is (= result 30)))) + + (t/testing "Evaluate a relative multiplication" + (let [result (sm/expr-eval "*10" 20)] + (t/is (= result 200)))) + + (t/testing "Evaluate a relative complex operation" + (let [result (sm/expr-eval "+(10*2 - 5)" 20)] + (t/is (= result 35)))) + + (t/testing "Evaluate a percentual operation" + (let [result (sm/expr-eval "+50%" 20)] + (t/is (= result 30)))) + + (t/testing "Evaluate a complex operation with percents" + (let [result (sm/expr-eval "5 + (25% * 2)" 100)] + (t/is (= result 55)))) + + (t/testing "Evaluate a complex operation with percents and relative" + (let [result (sm/expr-eval "+ (25% * 2)" 100)] + (t/is (= result 150)))) + + (t/testing "Evaluate an addition with decimals" + (let [result1 (sm/expr-eval "10 + 2.5" 999) + result2 (sm/expr-eval "10 + 2,5" 999)] + (t/is (= result1 result2 12.5)))) + + (t/testing "Evaluate a relative operation with decimals" + (let [result (sm/expr-eval "*.5" 20)] + (t/is (= result 10)))) + + (t/testing "Evaluate a percentual operation with decimals" + (let [result (sm/expr-eval "+10.5%" 20)] + (t/is (= result 22.1)))) + + (t/testing "Evaluate a complex operation with decimals" + (let [result (sm/expr-eval "(20.333 + 10%) * (1 / 3)" 20)] + (t/is (cm/close? result 7.44433333)))) + + ) + From d64eaab0b9392e2efb47a48477c14ad3085c72e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 20 Apr 2021 15:14:24 +0200 Subject: [PATCH 071/155] :lipstick: Minor aesthetics --- .../app/main/ui/components/numeric_input.cljs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs index 6088b9b8d..729f07a69 100644 --- a/frontend/src/app/main/ui/components/numeric_input.cljs +++ b/frontend/src/app/main/ui/components/numeric_input.cljs @@ -47,21 +47,16 @@ new-value (-> (dom/get-value input-node) (sm/expr-eval value))] (when (num? new-value) - (cond-> new-value - true - (math/round) + (-> new-value + (math/round) + (cljs.core/max us/min-safe-int) + (cljs.core/min us/max-safe-int) + (cond-> + (num? min-val) + (cljs.core/max min-val) - true - (cljs.core/max us/min-safe-int) - - true - (cljs.core/min us/max-safe-int) - - (num? min-val) - (cljs.core/max min-val) - - (num? max-val) - (cljs.core/min max-val)))))) + (num? max-val) + (cljs.core/min max-val))))))) update-input (mf/use-callback From 2828ccda7f96ae87419f8f9a273fc5e3487ed0cf Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 16 Apr 2021 13:07:41 +0200 Subject: [PATCH 072/155] :sparkles: Add the ability to check roles to openid integration. --- backend/src/app/config.clj | 6 +++ backend/src/app/http/oauth.clj | 52 ++++++++++++++++------- backend/src/app/rpc/mutations/profile.clj | 29 +++++++++++-- common/app/common/spec.cljc | 14 ++++++ frontend/src/app/util/i18n.cljs | 19 ++++++--- 5 files changed, 93 insertions(+), 27 deletions(-) diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 3518d601e..260e00c5f 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -111,6 +111,9 @@ (s/def ::oidc-token-uri ::us/string) (s/def ::oidc-auth-uri ::us/string) (s/def ::oidc-user-uri ::us/string) +(s/def ::oidc-scopes ::us/set-of-str) +(s/def ::oidc-roles ::us/set-of-str) +(s/def ::oidc-roles-attr ::us/keyword) (s/def ::host ::us/string) (s/def ::http-server-port ::us/integer) (s/def ::http-session-cookie-name ::us/string) @@ -190,6 +193,9 @@ ::oidc-token-uri ::oidc-auth-uri ::oidc-user-uri + ::oidc-scopes + ::oidc-roles-attr + ::oidc-roles ::host ::http-server-port ::http-session-idle-max-age diff --git a/backend/src/app/http/oauth.clj b/backend/src/app/http/oauth.clj index 20866091b..fd1b3241e 100644 --- a/backend/src/app/http/oauth.clj +++ b/backend/src/app/http/oauth.clj @@ -13,7 +13,9 @@ [app.util.logging :as l] [app.util.time :as dt] [clojure.data.json :as json] + [clojure.set :as set] [clojure.spec.alpha :as s] + [cuerdas.core :as str] [integrant.core :as ig] [lambdaisland.uri :as u])) @@ -32,9 +34,7 @@ (defn register-profile [{:keys [rpc] :as cfg} info] (let [method-fn (get-in rpc [:methods :mutation :login-or-register]) - profile (method-fn {:email (:email info) - :backend (:backend info) - :fullname (:fullname info)})] + profile (method-fn info)] (cond-> profile (some? (:invitation-token info)) (assoc :invitation-token (:invitation-token info))))) @@ -60,7 +60,7 @@ :redirect_uri (build-redirect-uri cfg) :response_type "code" :state state - :scope (:scope provider)} + :scope (str/join " " (:scopes provider []))} query (u/map->query-string params)] (-> (u/uri (:auth-uri provider)) (assoc :query query) @@ -98,10 +98,10 @@ res (http/send! req)] (when (= 200 (:status res)) - (let [data (json/read-str (:body res))] - {:email (get data "email") - :backend (:name provider) - :fullname (get data "name")}))) + (let [{:keys [name] :as data} (json/read-str (:body res) :key-fn keyword)] + (-> data + (assoc :backend (:name provider)) + (assoc :fullname name))))) (catch Exception e (l/error :hint "unexpected exception on retrieve-user-info" @@ -109,7 +109,7 @@ nil))) (defn retrieve-info - [{:keys [tokens] :as cfg} request] + [{:keys [tokens provider] :as cfg} request] (let [state (get-in request [:params :state]) state (tokens :verify {:token state :iss :oauth}) info (some->> (get-in request [:params :code]) @@ -119,6 +119,23 @@ (ex/raise :type :internal :code :unable-to-auth)) + ;; If the provider is OIDC, we can proceed to check + ;; roles if they are defined. + (when (and (= "oidc" (:name provider)) + (seq (:roles provider))) + (let [provider-roles (into #{} (:roles provider)) + profile-roles (let [attr (cf/get :oidc-roles-attr :roles) + roles (get info attr)] + (cond + (string? roles) (into #{} (str/words roles)) + (vector? roles) (into #{} roles) + :else #{}))] + ;; check if profile has a configured set of roles + (when-not (set/subset? provider-roles profile-roles) + (ex/raise :type :internal + :code :unable-to-auth + :hint "not enought permissions")))) + (cond-> info (some? (:invitation-token state)) (assoc :invitation-token (:invitation-token state))))) @@ -198,7 +215,10 @@ :token-uri (cf/get :oidc-token-uri) :auth-uri (cf/get :oidc-auth-uri) :user-uri (cf/get :oidc-user-uri) - :scope "openid profile email name" + :scopes (into #{"openid" "profile" "email" "name"} + (cf/get :oidc-scopes #{})) + :roles-attr (cf/get :oidc-roles-attr) + :roles (cf/get :oidc-roles) :name "oidc"}] (if (and (string? (:base-uri opts)) (string? (:client-id opts)) @@ -218,10 +238,9 @@ [cfg] (let [opts {:client-id (cf/get :google-client-id) :client-secret (cf/get :google-client-secret) - :scope (str "email profile " - "https://www.googleapis.com/auth/userinfo.email " - "https://www.googleapis.com/auth/userinfo.profile " - "openid") + :scopes #{"email" "profile" "openid" + "https://www.googleapis.com/auth/userinfo.email" + "https://www.googleapis.com/auth/userinfo.profile"} :auth-uri "https://accounts.google.com/o/oauth2/v2/auth" :token-uri "https://oauth2.googleapis.com/token" :user-uri "https://openidconnect.googleapis.com/v1/userinfo" @@ -237,7 +256,8 @@ [cfg] (let [opts {:client-id (cf/get :github-client-id) :client-secret (cf/get :github-client-secret) - :scope "user:email" + :scopes #{"read:user" + "user:email"} :auth-uri "https://github.com/login/oauth/authorize" :token-uri "https://github.com/login/oauth/access_token" :user-uri "https://api.github.com/user" @@ -256,7 +276,7 @@ opts {:base-uri base :client-id (cf/get :gitlab-client-id) :client-secret (cf/get :gitlab-client-secret) - :scope "read_user" + :scopes #{"read_user"} :auth-uri (str base "/oauth/authorize") :token-uri (str base "/oauth/token") :user-uri (str base "/api/v4/user") diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index e056ef840..8095b3133 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -6,6 +6,7 @@ (ns app.rpc.mutations.profile (:require + [app.common.data :as d] [app.common.exceptions :as ex] [app.common.spec :as us] [app.common.uuid :as uuid] @@ -303,16 +304,35 @@ (defn login-or-register [{:keys [conn] :as cfg} {:keys [email backend] :as params}] - (letfn [(create-profile [conn {:keys [fullname email]}] + (letfn [(info->props [info] + (dissoc info :name :fullname :email :backend)) + + (info->lang [{:keys [locale] :as info}] + (when (and (string? locale) + (not (str/blank? locale))) + locale)) + + (create-profile [conn {:keys [email] :as info}] (db/insert! conn :profile {:id (uuid/next) - :fullname fullname + :fullname (:fullname info) :email (str/lower email) + :lang (info->lang info) :auth-backend backend :is-active true :password "!" + :props (db/tjson (info->props info)) :is-demo false})) + (update-profile [conn info profile] + (let [props (d/merge (:props profile) + (info->props info))] + (db/update! conn :profile + {:props (db/tjson props) + :modified-at (dt/now)} + {:id (:id profile)}) + (assoc profile :props props))) + (register-profile [conn params] (let [profile (->> (create-profile conn params) (create-profile-relations conn))] @@ -321,7 +341,9 @@ (let [profile (profile/retrieve-profile-data-by-email conn email) profile (if profile - (profile/populate-additional-data conn profile) + (->> profile + (update-profile conn params) + (profile/populate-additional-data conn)) (register-profile conn params))] (profile/strip-private-attrs profile)))) @@ -346,7 +368,6 @@ (update-profile conn params) nil)) - ;; --- Mutation: Update Password (declare validate-password!) diff --git a/common/app/common/spec.cljc b/common/app/common/spec.cljc index 4e968b6c1..eabce91ff 100644 --- a/common/app/common/spec.cljc +++ b/common/app/common/spec.cljc @@ -133,6 +133,20 @@ ::s/invalid))] (s/def ::email (s/conformer cfn str))) + +;; --- SPEC: set-of-str +(letfn [(conformer [s] + (cond + (string? s) (into #{} (str/split s #"\s*,\s*")) + (set? s) (if (every? string? s) + s + ::s/invalid) + :else ::s/invalid)) + + (unformer [s] + (str/join "," s))] + (s/def ::set-of-str (s/conformer conformer unformer))) + ;; --- Macros (defn spec-assert* diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 9c3e06939..ac66402b0 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -29,8 +29,7 @@ (defn- parse-locale [locale] - (let [locale (-> (.-language globals/navigator) - (str/lower) + (let [locale (-> (str/lower locale) (str/replace "-" "_"))] (cond-> [locale] (str/includes? locale "_") @@ -66,11 +65,17 @@ (set! translations data)) (defn set-locale! - [lang] - (if lang - (do - (swap! storage assoc ::locale lang) - (reset! locale lang)) + [lname] + (if lname + (let [supported (into #{} (map :value supported-locales)) + lname (loop [locales (seq (parse-locale lname))] + (if-let [locale (first locales)] + (if (contains? supported locale) + locale + (recur (rest locales))) + cfg/default-language))] + (swap! storage assoc ::locale lname) + (reset! locale lname)) (do (swap! storage dissoc ::locale) (reset! locale (autodetect))))) From 55ea84a056a7cc6ee5a712139845b31d99c4f97e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 19 Apr 2021 18:35:36 +0200 Subject: [PATCH 073/155] :sparkles: Add more adaptations to make app run in a prefixed path. --- common/app/common/uri.cljc | 34 ++++++++++ frontend/gulpfile.js | 12 ++-- frontend/resources/templates/index.mustache | 5 +- frontend/src/app/config.cljs | 29 ++++++--- .../main/data/workspace/notifications.cljs | 21 +++++-- frontend/src/app/main/repo.cljs | 8 +-- frontend/src/app/main/ui/cursors.clj | 4 +- frontend/src/app/main/ui/dashboard/grid.cljs | 1 - frontend/src/app/main/ui/viewer/header.cljs | 6 +- frontend/src/app/util/http.cljs | 2 +- frontend/src/app/util/logging.clj | 6 ++ frontend/src/app/util/router.cljs | 63 +++++-------------- frontend/src/app/util/websockets.cljs | 17 +---- frontend/src/app/worker.cljs | 5 +- 14 files changed, 115 insertions(+), 98 deletions(-) create mode 100644 common/app/common/uri.cljc diff --git a/common/app/common/uri.cljc b/common/app/common/uri.cljc new file mode 100644 index 000000000..45bb2cb61 --- /dev/null +++ b/common/app/common/uri.cljc @@ -0,0 +1,34 @@ +;; 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.uri + (:require + [app.common.data :as d] + [lambdaisland.uri :as u] + [lambdaisland.uri.normalize :as un])) + +(d/export u/uri) +(d/export u/join) +(d/export un/percent-encode) + +(defn query-string->map + [s] + (u/query-string->map s)) + +(defn default-encode-value + [v] + (if (keyword? v) (name v) v)) + +(defn map->query-string + ([params] (map->query-string params nil)) + ([params {:keys [value-fn key-fn] + :or {value-fn default-encode-value + key-fn identity}}] + (->> params + (into {} (comp + (remove #(nil? (second %))) + (map (fn [[k v]] [(key-fn k) (value-fn v)])))) + (u/map->query-string)))) diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js index 57dc9b164..2ac9d26ff 100644 --- a/frontend/gulpfile.js +++ b/frontend/gulpfile.js @@ -68,23 +68,23 @@ function readManifest() { const content = JSON.parse(fs.readFileSync(path, {encoding: "utf8"})); const index = { - "config": "/js/config.js?ts=" + Date.now(), + "config": "js/config.js?ts=" + Date.now(), "polyfills": "js/polyfills.js?ts=" + Date.now(), }; for (let item of content) { - index[item.name] = "/js/" + item["output-name"]; + index[item.name] = "js/" + item["output-name"]; }; return index; } catch (e) { console.error("Error on reading manifest, using default."); return { - "config": "/js/config.js", + "config": "js/config.js", "polyfills": "js/polyfills.js", - "main": "/js/main.js", - "shared": "/js/shared.js", - "worker": "/js/worker.js" + "main": "js/main.js", + "shared": "js/shared.js", + "worker": "js/worker.js" }; } } diff --git a/frontend/resources/templates/index.mustache b/frontend/resources/templates/index.mustache index dc6e7c179..0b0dfd279 100644 --- a/frontend/resources/templates/index.mustache +++ b/frontend/resources/templates/index.mustache @@ -15,10 +15,9 @@ - + - +