diff --git a/.clj-kondo/hooks/export.clj b/.clj-kondo/hooks/export.clj index 16ab4e76af..f59bd669e8 100644 --- a/.clj-kondo/hooks/export.clj +++ b/.clj-kondo/hooks/export.clj @@ -53,24 +53,37 @@ [{:keys [:node]}] (let [[rnode rtype ?meta & other] (:children node) rsym (gensym (name (:k rtype))) - result (api/list-node - [(api/token-node (symbol "do")) - (api/list-node - [(api/token-node (symbol "declare")) - (api/token-node rsym)]) - (if (= :map (:tag ?meta)) - (api/list-node - [(api/token-node (symbol "reset-meta!")) - (api/token-node rsym) - ?meta]) - (api/list-node - [(api/token-node (symbol "comment")) - (api/token-node rsym)])) - (api/list-node - (into [(api/token-node (symbol "defmethod")) - (api/token-node rsym) - rtype] - (cons ?meta other)))])] - ;; (prn "==============" rtype (into {} ?meta)) + + [?docs other] (if (api/string-node? ?meta) + [?meta other] + [nil (cons ?meta other)]) + + [?meta other] (let [?meta (first other)] + (if (api/map-node? ?meta) + [?meta (rest other)] + [nil other])) + + nodes [(api/token-node (symbol "do")) + (api/list-node + [(api/token-node (symbol "declare")) + (api/token-node rsym)]) + + (when ?docs + (api/list-node + [(api/token-node (symbol "comment")) ?docs])) + + (when ?meta + (api/list-node + [(api/token-node (symbol "reset-meta!")) + (api/token-node rsym) + ?meta])) + (api/list-node + (into [(api/token-node (symbol "defmethod")) + (api/token-node rsym) + rtype] + other))] + result (api/list-node (filterv some? nodes))] + + ;; (prn "=====>" rtype) ;; (prn (api/sexpr result)) {:node result})) diff --git a/.gitignore b/.gitignore index 22ae73c022..b7f152cd6b 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ /backend/resources/public/assets /backend/resources/public/media /backend/target/ +/backend/builtin-templates /bundle* /cd.md /clj-profiler/ diff --git a/CHANGES.md b/CHANGES.md index 15390b086c..c713129b54 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,66 @@ # CHANGELOG +## 1.15.0-beta + +### :boom: Breaking changes & Deprecations + +- The `PENPOT_LOGIN_WITH_LDAP` environment variable is finally removed (after + many version with deprecation). It is replaced with the + `enable-login-with-ldap` flag. +- The `PENPOT_LDAP_ATTRS_PHOTO` finally removed, it was unused for many + versions. +- If you are using social login (google, github, gitlab or generic OIDC) you + will need to ensure to add the following flags respectivelly to let them + enabled: `enable-login-with-google`, `enable-login-with-github`, + `enable-login-with-gitlab` and `enable-login-with-oidc`. If not, they will + remain disabled after application start independently if you set the client-id + and client-sectet options. +- The `PENPOT_REGISTRATION_ENABLED` is finally removed in favour of + `-registration` flag. +- The OIDC providers are now initialized synchronously, and if you are using the + discovery mechanism of the generic OIDC integration, the start time of the + application will depend on how fast the OIDC provider responds to the + discovery http request. + +### :sparkles: New features + +- Allow for nested and rotated boards inside other boards and groups [Taiga #2874](https://tree.taiga.io/project/penpot/us/2874?milestone=319982) +- View mode improvements to enable access and use in different conditions [Taiga #3023](https://tree.taiga.io/project/penpot/us/3023) +- Improved share link options. Now you can allow non-team members to comment and/or inspect [Taiga #3056] (https://tree.taiga.io/project/penpot/us/3056) +- Signin/Signup from shared link [Taiga #3472](https://tree.taiga.io/project/penpot/us/3472) +- Support for import/export binary format [Taiga #2991](https://tree.taiga.io/project/penpot/us/2991) +- Comments positioning [Taiga #2007](https://tree.taiga.io/project/penpot/us/2007) +- Select all inside a group select only the objects at this group level [Taiga #2382](https://tree.taiga.io/project/penpot/issue/2382) +- Make the media maximum upload size configurable + +### :bug: Bugs fixed + +- Fix hide html options on handoff [Taiga 3533](https://tree.taiga.io/project/penpot/issue/3533) +- Fix share prototypes overlay and stroke [Taiga #3994](https://tree.taiga.io/project/penpot/issue/3994) +- Fix border radious on boolean operations [Taiga #3959](https://tree.taiga.io/project/penpot/issue/3959) +- Fix inconsistent representation of rectangles [Taiga #3977](https://tree.taiga.io/project/penpot/issue/3977) +- Fix recent fonts info [Taiga #3953](https://tree.taiga.io/project/penpot/issue/3953) +- Fix clipped elements affect boards and centering [Taiga #3666](https://tree.taiga.io/project/penpot/issue/3666) +- Fix intro action in multi input [Taiga #3541](https://tree.taiga.io/project/penpot/issue/3541) +- Fix team default image [Taiga #3919](https://tree.taiga.io/project/penpot/issue/3919) +- Fix problem with group coordinates [#2008](https://github.com/penpot/penpot/issues/2008) +- Fix problem with line-height and texts [Taiga #3578](https://tree.taiga.io/project/penpot/issue/3578) +- Fix moving frame-guides outside frames [Taiga #3839](https://tree.taiga.io/project/penpot/issue/3839) +- Fix problem with 180 degree rotations [#2082](https://github.com/penpot/penpot/issues/2082) +- Fix font rendering on grid thumbnails [Taiga #3473](https://tree.taiga.io/project/penpot/issue/3473) +- Fix Drag and drop font assets in groups [Taiga #3763](https://tree.taiga.io/project/penpot/issue/3763) +- Fix copy and paste layers order [Taiga #1617](https://tree.taiga.io/project/penpot/issue/1617) +- Fix unexpected removal of guides on copy&paste frames [Taiga #3887](https://tree.taiga.io/project/penpot/issue/3887) by @andrewzhurov +- Fix props preserving on copy&paste texts [Taiga #3629](https://tree.taiga.io/project/penpot/issue/3629) by @andrewzhurov +- Fix unexpected layers ungrouping on moving it [Taiga #3932](https://tree.taiga.io/project/penpot/issue/3932) by @andrewzhurov +- Fix unexpected exception and behavior on colorpicker with gradients [Taiga #3448](https://tree.taiga.io/project/penpot/issue/3448) +- Fix multiselection with shift not working inside a library group [Taiga #3532](https://tree.taiga.io/project/penpot/issue/3532) + + + +### :arrow_up: Deps updates +### :heart: Community contributions by (Thank you!) + ## 1.14.2-beta ### :bug: Bugs fixed diff --git a/backend/deps.edn b/backend/deps.edn index 70e6717f00..b57ee55ec1 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -1,13 +1,13 @@ {:deps {penpot/common {:local/root "../common"} - org.clojure/clojure {:mvn/version "1.10.3"} + org.clojure/clojure {:mvn/version "1.11.1"} org.clojure/core.async {:mvn/version "1.5.648"} ;; Logging org.zeromq/jeromq {:mvn/version "0.5.2"} com.taoensso/nippy {:mvn/version "3.1.1"} - com.github.luben/zstd-jni {:mvn/version "1.5.2-2"} + com.github.luben/zstd-jni {:mvn/version "1.5.2-3"} org.clojure/data.fressian {:mvn/version "1.0.0"} io.prometheus/simpleclient {:mvn/version "0.15.0"} @@ -17,25 +17,27 @@ org.eclipse.jetty/jetty-servlet]} io.prometheus/simpleclient_httpserver {:mvn/version "0.15.0"} - io.lettuce/lettuce-core {:mvn/version "6.1.6.RELEASE"} + io.lettuce/lettuce-core {:mvn/version "6.1.8.RELEASE"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} - funcool/yetti {:git/tag "v9.1" :git/sha "63f35d9" + funcool/yetti {:git/tag "v9.8" :git/sha "fbe1d7d" :git/url "https://github.com/funcool/yetti.git" :exclusions [org.slf4j/slf4j-api]} - com.github.seancorfield/next.jdbc {:mvn/version "1.2.772"} - metosin/reitit-core {:mvn/version "0.5.16"} - org.postgresql/postgresql {:mvn/version "42.3.3"} + com.github.seancorfield/next.jdbc {:mvn/version "1.2.780"} + metosin/reitit-core {:mvn/version "0.5.18"} + org.postgresql/postgresql {:mvn/version "42.4.0"} com.zaxxer/HikariCP {:mvn/version "5.0.1"} - funcool/datoteka {:mvn/version "2.0.0"} + + funcool/datoteka {:mvn/version "3.0.64"} buddy/buddy-hashers {:mvn/version "1.8.158"} buddy/buddy-sign {:mvn/version "3.4.333"} - org.jsoup/jsoup {:mvn/version "1.14.3"} + org.jsoup/jsoup {:mvn/version "1.15.1"} org.im4java/im4java {:git/tag "1.4.0-penpot-2" :git/sha "e2b3e16" :git/url "https://github.com/penpot/im4java"} + org.lz4/lz4-java {:mvn/version "1.8.0"} org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"} @@ -44,11 +46,11 @@ io.sentry/sentry {:mvn/version "5.6.1"} dawran6/emoji {:mvn/version "0.1.5"} - markdown-clj/markdown-clj {:mvn/version "1.11.0"} + markdown-clj/markdown-clj {:mvn/version "1.11.1"} ;; Pretty Print specs pretty-spec/pretty-spec {:mvn/version "0.1.4"} - software.amazon.awssdk/s3 {:mvn/version "2.17.136"}} + software.amazon.awssdk/s3 {:mvn/version "2.17.209"}} :paths ["src" "resources" "target/classes"] :aliases @@ -65,7 +67,7 @@ :build {:extra-deps - {io.github.clojure/tools.build {:git/tag "v0.7.7" :git/sha "1474ad6"}} + {io.github.clojure/tools.build {:git/tag "v0.8.2" :git/sha "ba1a2bf"}} :ns-default build} :test diff --git a/backend/dev/script-fix-sobjects.clj b/backend/dev/script-fix-sobjects.clj new file mode 100644 index 0000000000..b198463d7e --- /dev/null +++ b/backend/dev/script-fix-sobjects.clj @@ -0,0 +1,114 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +;; This is an example on how it can be executed: +;; clojure -Scp $(cat classpath) -M dev/script-fix-sobjects.clj + +(require + '[app.common.logging :as l] + '[app.common.data :as d] + '[app.common.pprint] + '[app.db :as db] + '[app.storage :as sto] + '[app.storage.impl :as impl] + '[app.util.time :as dt] + '[integrant.core :as ig]) + +;; --- HELPERS + +(l/info :hint "initializing script" :args *command-line-args*) + +(def noop? (some #(= % "noop") *command-line-args*)) +(def chunk-size 10) + +(def sql:retrieve-sobjects-chunk + "SELECT * FROM storage_object + WHERE created_at < ? AND deleted_at is NULL + ORDER BY created_at desc LIMIT ?") + +(defn get-chunk + [conn cursor] + (let [rows (db/exec! conn [sql:retrieve-sobjects-chunk cursor chunk-size])] + [(some->> rows peek :created-at) (seq rows)])) + +(defn get-candidates + [conn] + (->> (d/iteration (partial get-chunk conn) + :vf second + :kf first + :initk (dt/now)) + (sequence cat))) + +(def modules + [:app.db/pool + :app.storage/storage + [:app.main/default :app.worker/executor] + [:app.main/assets :app.storage.s3/backend] + [:app.main/assets :app.storage.fs/backend]]) + +(def system + (let [config (select-keys app.main/system-config modules) + config (-> config + (assoc :app.migrations/all {}) + (assoc :app.metrics/metrics nil))] + (ig/load-namespaces config) + (-> config ig/prep ig/init))) + +(defn update-fn + [{:keys [conn] :as storage} {:keys [id backend] :as row}] + (cond + (= backend "s3") + (do + (l/info :hint "rename storage object backend" + :id id + :from-backend backend + :to-backend :assets-s3) + (assoc row :backend "assets-s3")) + + (= backend "assets-s3") + (do + (l/info :hint "ignoring storage object" :id id :backend backend) + nil) + + (or (= backend "fs") + (= backend "assets-fs")) + (let [sobj (sto/row->storage-object row) + path (-> (sto/get-object-path storage sobj) deref)] + (l/info :hint "change storage object backend" + :id id + :from-backend backend + :to-backend :assets-s3) + (when-not noop? + (-> (impl/resolve-backend storage :assets-s3) + (impl/put-object sobj (sto/content path)) + (deref))) + (assoc row :backend "assets-s3")) + + :else + (throw (IllegalArgumentException. "unexpected backend found")))) + +(try + (db/with-atomic [conn (:app.db/pool system)] + (let [storage (:app.storage/storage system) + storage (assoc storage :conn conn)] + (loop [items (get-candidates conn)] + (when-let [item (first items)] + (when-let [{:keys [id] :as row} (update-fn storage item)] + (db/update! conn :storage-object (dissoc row :id) {:id (:id item)})) + (recur (rest items)))) + (when noop? + (throw (ex-info "explicit rollback" {}))))) + + (catch Throwable cause + (cond + (= "explicit rollback" (ex-message cause)) + (l/warn :hint "transaction aborted") + + :else + (l/error :hint "unexpected exception" :cause cause)))) + +(ig/halt! system) +(System/exit 0) diff --git a/backend/resources/api-doc-entry.tmpl b/backend/resources/api-doc-entry.tmpl new file mode 100644 index 0000000000..97ce8a5077 --- /dev/null +++ b/backend/resources/api-doc-entry.tmpl @@ -0,0 +1,54 @@ +
  • +
    + {#
    {{item.type}}
    #} +
    {{item.module}}:
    +
    {{item.name}}
    +
    + {% if item.deprecated %} + + Deprecated: + since v{{item.deprecated}}, + + {% endif %} + + Auth: + {% if item.auth %}YES{% else %}NO{% endif %} + +
    +
    + +
  • diff --git a/backend/resources/api-doc.css b/backend/resources/api-doc.css index b9b14a889c..b7b4ad5f12 100644 --- a/backend/resources/api-doc.css +++ b/backend/resources/api-doc.css @@ -53,7 +53,7 @@ header { .rpc-item { /* border: 1px solid red; */ - cursor: pointer; + /* cursor: pointer; */ display: flex; flex-direction: column; } @@ -85,6 +85,16 @@ header { .rpc-row-info > .name { width: 280px; /* font-weight: bold; */ + border-right: 1px dotted #777; + padding-right: 10px; +} + +.rpc-row-info > .module { + width: 120px; + font-weight: bold; + border-right: 1px dotted #777; + text-align: right; + padding-right: 10px; } .rpc-row-info > .tags > .tag > span:first-child { @@ -99,3 +109,37 @@ header { padding: 5px 10px; padding-bottom: 20px; } + +.rpc-row-detail p { + font-weight: 200; +} + +.rpc-row-detail p.small { + margin-top: 2px; + margin-bottom: 2px; + font-size: 10px; +} + +.rpc-row-detail p.small { + margin-top: 2px; + margin-bottom: 2px; + font-size: 10px; +} + +.rpc-row-detail strong { + font-weight: 500; +} + +.rpc-row-detail .changes { + font-weight: 200; + list-style: none; + padding: 0px; +} + +.rpc-row-detail .padded-section { + padding: 0px 10px; +} + +p.small strong { + font-size: 10px; +} diff --git a/backend/resources/api-doc.tmpl b/backend/resources/api-doc.tmpl index f319a46925..c7c447b4d3 100644 --- a/backend/resources/api-doc.tmpl +++ b/backend/resources/api-doc.tmpl @@ -5,7 +5,10 @@ Builtin API Documentation - Penpot - + + + + @@ -16,61 +19,28 @@
    -

    Penpot API Documentation

    +

    Penpot API Documentation (v{{version}})

    +

    RPC COMMAND METHODS:

    +
      + {% for item in command-methods %} + {% include "api-doc-entry.tmpl" with item=item %} + {% endfor %} +
    +

    RPC QUERY METHODS:

      {% for item in query-methods %} -
    • -
      - {#
      {{item.type}}
      #} -
      {{item.name}}
      -
      - - Auth: - {% if item.auth %}YES{% else %}NO{% endif %} - -
      -
      - -
    • + {% include "api-doc-entry.tmpl" with item=item %} {% endfor %}

    RPC MUTATION METHODS:

      {% for item in mutation-methods %} -
    • -
      - {#
      {{item.type}}
      #} -
      {{item.name}}
      -
      - - Auth: - {% if item.auth %}YES{% else %}NO{% endif %} - -
      -
      - -
    • + {% include "api-doc-entry.tmpl" with item=item %} {% endfor %}
    diff --git a/backend/resources/log4j2-devenv.xml b/backend/resources/log4j2-devenv.xml index d07a33d7dd..3b28142532 100644 --- a/backend/resources/log4j2-devenv.xml +++ b/backend/resources/log4j2-devenv.xml @@ -20,11 +20,17 @@ - - + + + + + + + + @@ -38,11 +44,6 @@ - - - - - diff --git a/backend/resources/log4j2.xml b/backend/resources/log4j2.xml index d2a045c36c..3a0d04e3f7 100644 --- a/backend/resources/log4j2.xml +++ b/backend/resources/log4j2.xml @@ -7,14 +7,11 @@ + - + - - - - - + diff --git a/backend/resources/templates/debug.tmpl b/backend/resources/templates/debug.tmpl index a3044dba54..7479dd9827 100644 --- a/backend/resources/templates/debug.tmpl +++ b/backend/resources/templates/debug.tmpl @@ -10,23 +10,118 @@ Debug Main Page
    -
    -

    Download file data:

    - Given an FILE-ID, downloads the file data as file. The file data is encoded using transit. -
    - - - -
    +
    +
    + Download file data: + Given an FILE-ID, downloads the file data as file. The file data is encoded using transit. +
    +
    + +
    +
    + + +
    +
    +
    + +
    + Upload File Data: + Create a new file on your draft projects using the file downloaded from the previous section. +
    +
    + +
    +
    + + +
    + + +
    +
    -
    -

    Upload File Data:

    - Create a new file on your draft projects using the file downloaded from the previous section. -
    - - -
    +
    +
    + Export binfile: + Given an FILE-ID, downloads the file and optionally all + the related libraries in a single custom formatted binary + file. + +
    +
    + + + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    +
    +
    + Import binfile: + Import penpot file in binary + format. If overwrite is checked, all files will + be overwriten using the same ids found in the file instead of + generating a new ones. + +
    +
    + +
    + +
    + + +
    + + Instead of creating a new file with all relations remaped, + reuses all ids and updates/overwrites the objects that are + already exists on the database. + Warning, this operation should be used with caution. + +
    + +
    + + +
    + + Applies the file migrations on the importation process. + +
    + +
    + + +
    + + Do not break on index lookup erros (remap operation). + Useful when importing a broken file that has broken + relations or missing pieces. + +
    + +
    + +
    +
    +
    {% endblock %} diff --git a/backend/resources/templates/styles.css b/backend/resources/templates/styles.css index 60db4b548e..32fcea8883 100644 --- a/backend/resources/templates/styles.css +++ b/backend/resources/templates/styles.css @@ -14,7 +14,6 @@ pre { } desc { - display: flex; margin-bottom: 10px; font-size: 10px; color: #666; @@ -28,6 +27,15 @@ main { margin: 20px; } +small { + font-size: 9px; + color: #888; +} + +small > strong { + font-size: 9px; +} + nav { position: fixed; width: 100vw; @@ -95,17 +103,25 @@ nav > div:not(:last-child) { .index { margin-top: 40px; + display: flex; } .index > section { padding: 10px; background-color: #e3e3e3; + max-width: 400px; + margin: 5px; + height: fit-content; } -.index > section:not(:last-child) { - margin-bottom: 10px; +.index fieldset:not(:first-child) { + margin-top: 15px; } +/* .index > section:not(:last-child) { */ +/* margin-bottom: 10px; */ +/* } */ + .index > section > h2 { margin-top: 0px; @@ -148,3 +164,16 @@ nav > div:not(:last-child) { color: inherit; } +form .row { + padding: 5px 0; +} + +.set-of-inputs { + flex-direction: column; + display: flex; +} + +.set-of-inputs input:not(:last-child) { + margin-bottom: 3px; +} + diff --git a/backend/scripts/repl b/backend/scripts/repl index 49e105305b..d200ae3f34 100755 --- a/backend/scripts/repl +++ b/backend/scripts/repl @@ -24,9 +24,8 @@ mc mb penpot-s3/penpot -p export AWS_ACCESS_KEY_ID=penpot-devenv export AWS_SECRET_ACCESS_KEY=penpot-devenv -export PENPOT_ASSETS_STORAGE_BACKEND=assets-fs +export PENPOT_ASSETS_STORAGE_BACKEND=assets-s3 export PENPOT_STORAGE_ASSETS_S3_ENDPOINT=http://minio:9000 -export PENPOT_STORAGE_ASSETS_S3_REGION=eu-central-1 export PENPOT_STORAGE_ASSETS_S3_BUCKET=penpot export OPTIONS=" @@ -40,6 +39,9 @@ export OPTIONS=" -J-XX:+UnlockDiagnosticVMOptions \ -J-XX:+DebugNonSafepoints"; +# Uncomment for use the ImageMagick v7.x +# export OPTIONS="-J-Dim4java.useV7=true $OPTIONS"; + export OPTIONS_EVAL="nil" # export OPTIONS_EVAL="(set! *warn-on-reflection* true)" diff --git a/backend/src/app/auth/ldap.clj b/backend/src/app/auth/ldap.clj new file mode 100644 index 0000000000..f5042e6b1c --- /dev/null +++ b/backend/src/app/auth/ldap.clj @@ -0,0 +1,137 @@ +;; 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.auth.ldap + (:require + [app.common.exceptions :as ex] + [app.common.logging :as l] + [app.common.spec :as us] + [app.config :as cf] + [clj-ldap.client :as ldap] + [clojure.spec.alpha :as s] + [clojure.string] + [integrant.core :as ig])) + +(defn- prepare-params + [cfg] + {:ssl? (:ssl cfg) + :startTLS? (:tls cfg) + :bind-dn (:bind-dn cfg) + :password (:bind-password cfg) + :host {:address (:host cfg) + :port (:port cfg)}}) + +(defn- connect + "Connects to the LDAP provider and returns a connection. An + exception is raised if no connection is possible." + ^java.lang.AutoCloseable + [cfg] + (try + (-> cfg prepare-params ldap/connect) + (catch Throwable cause + (ex/raise :type :restriction + :code :unable-to-connect-to-ldap + :hint "unable to connect to ldap server" + :cause cause)))) + +(defn- replace-several [s & {:as replacements}] + (reduce-kv clojure.string/replace s replacements)) + +(defn- search-user + [{:keys [conn attrs base-dn] :as cfg} email] + (let [query (replace-several (:query cfg) ":username" email) + params {:filter query + :sizelimit 1 + :attributes attrs}] + (first (ldap/search conn base-dn params)))) + +(defn- retrieve-user + [{:keys [conn] :as cfg} {:keys [email password]}] + (when-let [{:keys [dn] :as user} (search-user cfg email)] + (when (ldap/bind? conn dn password) + {:fullname (get user (-> cfg :attrs-fullname keyword)) + :email email + :backend "ldap"}))) + +(s/def ::fullname ::us/not-empty-string) +(s/def ::email ::us/email) +(s/def ::backend ::us/not-empty-string) + +(s/def ::info-data + (s/keys :req-un [::fullname ::email ::backend])) + +(defn authenticate + [cfg params] + (with-open [conn (connect cfg)] + (when-let [user (-> (assoc cfg :conn conn) + (retrieve-user params))] + (when-not (s/valid? ::info-data user) + (let [explain (s/explain-str ::info-data user)] + (l/warn ::l/raw (str "invalid response from ldap, looks like ldap is not configured correctly\n" explain)) + (ex/raise :type :restriction + :code :wrong-ldap-response + :explain explain))) + user))) + +(defn- try-connectivity + [cfg] + ;; If we have ldap parameters, try to establish connection + (when (and (:bind-dn cfg) + (:bind-password cfg) + (:host cfg) + (:port cfg)) + (try + (with-open [_ (connect cfg)] + (l/info :hint "provider initialized" + :provider "ldap" + :host (:host cfg) + :port (:port cfg) + :tls? (:tls cfg) + :ssl? (:ssl cfg) + :bind-dn (:bind-dn cfg) + :base-dn (:base-dn cfg) + :query (:query cfg)) + cfg) + (catch Throwable cause + (l/error :hint "unable to connect to LDAP server (LDAP auth provider disabled)" + :host (:host cfg) :port (:port cfg) :cause cause) + nil)))) + +(defn- prepare-attributes + [cfg] + (assoc cfg :attrs [(:attrs-username cfg) + (:attrs-email cfg) + (:attrs-fullname cfg)])) + +(defmethod ig/init-key ::provider + [_ cfg] + (when (:enabled? cfg) + (some-> cfg try-connectivity prepare-attributes))) + +(s/def ::enabled? ::us/boolean) +(s/def ::host ::cf/ldap-host) +(s/def ::port ::cf/ldap-port) +(s/def ::ssl ::cf/ldap-ssl) +(s/def ::tls ::cf/ldap-starttls) +(s/def ::query ::cf/ldap-user-query) +(s/def ::base-dn ::cf/ldap-base-dn) +(s/def ::bind-dn ::cf/ldap-bind-dn) +(s/def ::bind-password ::cf/ldap-bind-password) +(s/def ::attrs-email ::cf/ldap-attrs-email) +(s/def ::attrs-fullname ::cf/ldap-attrs-fullname) +(s/def ::attrs-username ::cf/ldap-attrs-username) + +(defmethod ig/pre-init-spec ::provider + [_] + (s/keys :opt-un [::host ::port + ::ssl ::tls + ::enabled? + ::bind-dn + ::bind-password + ::query + ::attrs-email + ::attrs-username + ::attrs-fullname])) diff --git a/backend/src/app/http/oauth.clj b/backend/src/app/auth/oidc.clj similarity index 52% rename from backend/src/app/http/oauth.clj rename to backend/src/app/auth/oidc.clj index 869134f18c..39572bb18c 100644 --- a/backend/src/app/http/oauth.clj +++ b/backend/src/app/auth/oidc.clj @@ -4,19 +4,23 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.http.oauth +(ns app.auth.oidc + "OIDC client implementation." (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.logging :as l] [app.common.spec :as us] [app.common.uri :as u] [app.config :as cf] [app.db :as db] + [app.http.middleware :as hmw] [app.loggers.audit :as audit] [app.rpc.queries.profile :as profile] [app.util.json :as json] [app.util.time :as dt] + [app.worker :as wrk] [clojure.set :as set] [clojure.spec.alpha :as s] [cuerdas.core :as str] @@ -25,6 +29,218 @@ [promesa.exec :as px] [yetti.response :as yrs])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; HELPERS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- obfuscate-string + [s] + (if (< (count s) 10) + (apply str (take (count s) (repeat "*"))) + (str (subs s 0 5) + (apply str (take (- (count s) 5) (repeat "*")))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; OIDC PROVIDER (GENERIC) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- discover-oidc-config + [{:keys [http-client]} {:keys [base-uri] :as opts}] + (let [discovery-uri (u/join base-uri ".well-known/openid-configuration") + response (ex/try (http-client {:method :get :uri (str discovery-uri)} {:sync? true}))] + (cond + (ex/exception? response) + (do + (l/warn :hint "unable to discover oidc configuration" + :discover-uri (str discovery-uri) + :cause response) + nil) + + (= 200 (:status response)) + (let [data (json/read (:body response))] + {:token-uri (get data :token_endpoint) + :auth-uri (get data :authorization_endpoint) + :user-uri (get data :userinfo_endpoint)}) + + :else + (do + (l/warn :hint "unable to discover OIDC configuration" + :uri (str discovery-uri) + :response-status-code (:status response)) + nil)))) + +(defn- prepare-oidc-opts + [cfg] + (let [opts {:base-uri (:base-uri cfg) + :client-id (:client-id cfg) + :client-secret (:client-secret cfg) + :token-uri (:token-uri cfg) + :auth-uri (:auth-uri cfg) + :user-uri (:user-uri cfg) + :scopes (:scopes cfg #{"openid" "profile" "email"}) + :roles-attr (:roles-attr cfg) + :roles (:roles cfg) + :name "oidc"} + + opts (d/without-nils opts)] + + (when (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))) + opts + (some-> (discover-oidc-config cfg opts) + (merge opts {:discover? true})))))) + +(defmethod ig/prep-key ::generic-provider + [_ cfg] + (d/without-nils cfg)) + +(defmethod ig/init-key ::generic-provider + [_ cfg] + (when (:enabled? cfg) + (if-let [opts (prepare-oidc-opts cfg)] + (do + (l/info :hint "provider initialized" + :provider :oidc + :method (if (:discover? opts) "discover" "manual") + :client-id (:client-id opts) + :client-secret (obfuscate-string (:client-secret opts)) + :scopes (str/join "," (:scopes opts)) + :auth-uri (:auth-uri opts) + :user-uri (:user-uri opts) + :token-uri (:token-uri opts) + :roles-attr (:roles-attr opts) + :roles (:roles opts)) + opts) + (do + (l/warn :hint "unable to initialize auth provider, missing configuration" :provider :oidc) + nil)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; GOOGLE AUTH PROVIDER +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmethod ig/prep-key ::google-provider + [_ cfg] + (d/without-nils cfg)) + +(defmethod ig/init-key ::google-provider + [_ cfg] + (let [opts {:client-id (:client-id cfg) + :client-secret (:client-secret cfg) + :scopes #{"openid" "email" "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" + :name "google"}] + + (when (:enabled? cfg) + (if (and (string? (:client-id opts)) + (string? (:client-secret opts))) + (do + (l/info :hint "provider initialized" + :provider :google + :client-id (:client-id opts) + :client-secret (obfuscate-string (:client-secret opts))) + opts) + + (do + (l/warn :hint "unable to initialize auth provider, missing configuration" :provider :google) + nil))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; GITHUB AUTH PROVIDER +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- retrieve-github-email + [{:keys [http-client]} tdata info] + (or (some-> info :email p/resolved) + (-> (http-client {:uri "https://api.github.com/user/emails" + :headers {"Authorization" (dm/str (:type tdata) " " (:token tdata))} + :timeout 6000 + :method :get}) + (p/then (fn [{:keys [status body] :as response}] + (when-not (s/int-in-range? 200 300 status) + (ex/raise :type :internal + :code :unable-to-retrieve-github-emails + :hint "unable to retrieve github emails" + :http-status status + :http-body body)) + (->> response :body json/read (filter :primary) first :email)))))) + +(defmethod ig/prep-key ::github-provider + [_ cfg] + (d/without-nils cfg)) + +(defmethod ig/init-key ::github-provider + [_ cfg] + (let [opts {:client-id (:client-id cfg) + :client-secret (:client-secret cfg) + :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" + :name "github" + + ;; Additional hooks for provider specific way of + ;; retrieve emails. + :get-email-fn (partial retrieve-github-email cfg)}] + + (when (:enabled? cfg) + (if (and (string? (:client-id opts)) + (string? (:client-secret opts))) + (do + (l/info :hint "provider initialized" + :provider :github + :client-id (:client-id opts) + :client-secret (obfuscate-string (:client-secret opts))) + opts) + + (do + (l/warn :hint "unable to initialize auth provider, missing configuration" :provider :github) + nil))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; GITLAB AUTH PROVIDER +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmethod ig/prep-key ::gitlab-provider + [_ cfg] + (d/without-nils cfg)) + +(defmethod ig/init-key ::gitlab-provider + [_ cfg] + (let [base (:base-uri cfg "https://gitlab.com") + opts {:base-uri base + :client-id (:client-id cfg) + :client-secret (:client-secret cfg) + :scopes #{"openid" "profile" "email"} + :auth-uri (str base "/oauth/authorize") + :token-uri (str base "/oauth/token") + :user-uri (str base "/oauth/userinfo") + :name "gitlab"}] + (when (:enabled? cfg) + (if (and (string? (:client-id opts)) + (string? (:client-secret opts))) + (do + (l/info :hint "provider initialized" + :provider :gitlab + :base-uri base + :client-id (:client-id opts) + :client-secret (obfuscate-string (:client-secret opts))) + opts) + + (do + (l/warn :hint "unable to initialize auth provider, missing configuration" :provider :gitlab) + nil))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; HANDLERS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn- build-redirect-uri [{:keys [provider] :as cfg}] (let [public (u/uri (:public-uri cfg))] @@ -81,47 +297,35 @@ :timeout 6000 :method :get})) - (retrieve-emails [] - (if (some? (:emails-uri provider)) - (http-client {:uri (:emails-uri provider) - :headers {"Authorization" (str (:type tdata) " " (:token tdata))} - :timeout 6000 - :method :get}) - (p/resolved {:status 200}))) - - (validate-response [[retrieve-res emails-res]] - (when-not (s/int-in-range? 200 300 (:status retrieve-res)) + (validate-response [response] + (when-not (s/int-in-range? 200 300 (:status response)) (ex/raise :type :internal :code :unable-to-retrieve-user-info :hint "unable to retrieve user info" - :http-status (:status retrieve-res) - :http-body (:body retrieve-res))) - (when-not (s/int-in-range? 200 300 (:status emails-res)) - (ex/raise :type :internal - :code :unable-to-retrieve-user-info - :hint "unable to retrieve user info" - :http-status (:status emails-res) - :http-body (:body emails-res))) - [retrieve-res emails-res]) + :http-status (:status response) + :http-body (:body response))) + response) (get-email [info] - (let [attr-kw (cf/get :oidc-email-attr :email)] - (get info attr-kw))) + ;; Allow providers hook into this for custom email + ;; retrieval method. + (if-let [get-email-fn (:get-email-fn provider)] + (get-email-fn tdata info) + (let [attr-kw (cf/get :oidc-email-attr :email)] + (get info attr-kw)))) (get-name [info] (let [attr-kw (cf/get :oidc-name-attr :name)] (get info attr-kw))) - (process-response [[retrieve-res emails-res]] - (let [info (json/read (:body retrieve-res)) - email (if (some? (:extract-email-callback provider)) - ((:extract-email-callback provider) emails-res) - (get-email info))] + (process-response [response] + (p/let [info (-> response :body json/read) + email (get-email info)] {:backend (:name provider) :email email :fullname (or (get-name info) email) - :props (->> (dissoc info :name :email) - (qualify-props provider))})) + :props (->> (dissoc info :name :email) + (qualify-props provider))})) (validate-info [info] (when-not (s/valid? ::info info) @@ -133,10 +337,10 @@ :info info)) info)] - (-> (p/all [(retrieve) (retrieve-emails)]) - (p/then' validate-response) - (p/then' process-response) - (p/then' validate-info)))) + (-> (retrieve) + (p/then validate-response) + (p/then process-response) + (p/then validate-info)))) (s/def ::backend ::us/not-empty-string) (s/def ::email ::us/not-empty-string) @@ -195,8 +399,6 @@ (p/then' validate-oidc) (p/then' (partial post-process state)))))) -;; --- HTTP HANDLERS - (defn- retrieve-profile [{:keys [pool executor] :as cfg} info] (px/with-dispatch executor @@ -256,21 +458,18 @@ (redirect-response uri)))) (defn- auth-handler - [{:keys [tokens] :as cfg} {:keys [params] :as request} respond raise] - (try - (let [props (audit/extract-utm-params params) - state (tokens :generate - {:iss :oauth - :invitation-token (:invitation-token params) - :props props - :exp (dt/in-future "15m")}) - uri (build-auth-uri cfg state)] - (respond (yrs/response 200 {:redirect-uri uri}))) - (catch Throwable cause - (raise cause)))) + [{:keys [tokens] :as cfg} {:keys [params] :as request}] + (let [props (audit/extract-utm-params params) + state (tokens :generate + {:iss :oauth + :invitation-token (:invitation-token params) + :props props + :exp (dt/in-future "15m")}) + uri (build-auth-uri cfg state)] + (yrs/response 200 {:redirect-uri uri}))) (defn- callback-handler - [cfg request respond _] + [cfg request] (letfn [(process-request [] (p/let [info (retrieve-info cfg request) profile (retrieve-profile cfg info)] @@ -278,182 +477,62 @@ (handle-error [cause] (l/error :hint "error on oauth process" :cause cause) - (respond (generate-error-redirect cfg cause)))] + (generate-error-redirect cfg cause))] (-> (process-request) - (p/then respond) (p/catch handle-error)))) -;; --- INIT - -(declare initialize) +(def provider-lookup + {:compile + (fn [& _] + (fn [handler] + (fn [{:keys [providers] :as cfg} request] + (let [provider (some-> request :path-params :provider keyword)] + (if-let [provider (get providers provider)] + (handler (assoc cfg :provider provider) request) + (ex/raise :type :restriction + :code :provider-not-configured + :provider provider + :hint "provider not configured"))))))}) (s/def ::public-uri ::us/not-empty-string) +(s/def ::http-client fn?) (s/def ::session map?) (s/def ::tokens fn?) -(s/def ::rpc map?) +(s/def ::providers map?) -(defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [::public-uri ::session ::tokens ::rpc ::db/pool])) +(defmethod ig/pre-init-spec ::routes + [_] + (s/keys :req-un [::public-uri + ::session + ::tokens + ::http-client + ::providers + ::db/pool + ::wrk/executor])) -(defn wrap-handler - [cfg handler] - (fn [request respond raise] - (let [provider (get-in request [:path-params :provider]) - provider (get-in @cfg [:providers provider])] - (if provider - (handler (assoc @cfg :provider provider) - request - respond - raise) - (raise - (ex/error - :type :not-found - :provider provider - :hint "provider not configured")))))) +(defmethod ig/init-key ::routes + [_ {:keys [executor session] :as cfg}] + (let [cfg (update cfg :provider d/without-nils)] + ["" {:middleware [[(:middleware session)] + [hmw/with-promise-async executor] + [hmw/with-config cfg] + [provider-lookup] + ]} + ;; We maintain the both URI prefixes for backward compatibility. -(defmethod ig/init-key ::handler - [_ cfg] - (let [cfg (initialize cfg)] - {:handler (wrap-handler cfg auth-handler) - :callback-handler (wrap-handler cfg callback-handler)})) + ["/auth/oauth" + ["/:provider" + {:handler auth-handler + :allowed-methods #{:post}}] + ["/:provider/callback" + {:handler callback-handler + :allowed-methods #{:get}}]] -(defn- discover-oidc-config - [{:keys [http-client]} {:keys [base-uri] :as opts}] - - (let [discovery-uri (u/join base-uri ".well-known/openid-configuration") - response (ex/try (http-client {:method :get :uri (str discovery-uri)} {:sync? true}))] - (cond - (ex/exception? response) - (do - (l/warn :hint "unable to discover oidc configuration" - :discover-uri (str discovery-uri) - :cause response) - nil) - - (= 200 (:status response)) - (let [data (json/read (:body response))] - {:token-uri (get data :token_endpoint) - :auth-uri (get data :authorization_endpoint) - :user-uri (get data :userinfo_endpoint)}) - - :else - (do - (l/warn :hint "unable to discover OIDC configuration" - :uri (str discovery-uri) - :response-status-code (:status response)) - nil)))) - -(defn- obfuscate-string - [s] - (if (< (count s) 10) - (apply str (take (count s) (repeat "*"))) - (str (subs s 0 5) - (apply str (take (- (count s) 5) (repeat "*")))))) - -(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) - :scopes (cf/get :oidc-scopes #{"openid" "profile" "email"}) - :roles-attr (cf/get :oidc-roles-attr) - :roles (cf/get :oidc-roles) - :name "oidc"}] - - (if (and (string? (:base-uri opts)) - (string? (:client-id opts)) - (string? (:client-secret opts))) - (do - (l/debug :hint "initialize oidc provider" :name "generic-oidc" - :opts (update opts :client-secret obfuscate-string)) - (if (and (string? (:token-uri opts)) - (string? (:user-uri opts)) - (string? (:auth-uri opts))) - (do - (l/debug :hint "initialized with user provided configuration") - (assoc-in cfg [:providers "oidc"] opts)) - (do - (l/debug :hint "trying to discover oidc provider configuration using BASE_URI") - (if-let [opts' (discover-oidc-config cfg opts)] - (do - (l/debug :hint "discovered opts" :additional-opts opts') - (assoc-in cfg [:providers "oidc"] (merge opts opts'))) - - cfg)))) - cfg))) - -(defn- initialize-google-provider - [cfg] - (let [opts {:client-id (cf/get :google-client-id) - :client-secret (cf/get :google-client-secret) - :scopes #{"openid" "email" "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" - :name "google"}] - (if (and (string? (:client-id opts)) - (string? (:client-secret opts))) - (do - (l/info :action "initialize" :provider "google" - :opts (pr-str (update opts :client-secret obfuscate-string))) - (assoc-in cfg [:providers "google"] opts)) - cfg))) - -(defn extract-github-email - [response] - (let [emails (json/read (:body response)) - primary-email (->> emails - (filter #(:primary %)) - first)] - (:email primary-email))) - -(defn- initialize-github-provider - [cfg] - (let [opts {:client-id (cf/get :github-client-id) - :client-secret (cf/get :github-client-secret) - :scopes #{"read:user" "user:email"} - :auth-uri "https://github.com/login/oauth/authorize" - :token-uri "https://github.com/login/oauth/access_token" - :emails-uri "https://api.github.com/user/emails" - :extract-email-callback extract-github-email - :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" - :opts (pr-str (update opts :client-secret obfuscate-string))) - (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) - :scopes #{"openid" "profile" "email"} - :auth-uri (str base "/oauth/authorize") - :token-uri (str base "/oauth/token") - :user-uri (str base "/oauth/userinfo") - :name "gitlab"}] - (if (and (string? (:client-id opts)) - (string? (:client-secret opts))) - (do - (l/info :action "initialize" :provider "gitlab" - :opts (pr-str (update opts :client-secret obfuscate-string))) - (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)) + ["/auth/oidc" + ["/:provider" + {:handler auth-handler + :allowed-methods #{:post}}] + ["/:provider/callback" + {:handler callback-handler + :allowed-methods #{:get}}]]])) diff --git a/backend/src/app/cli/manage.clj b/backend/src/app/cli/manage.clj index dad68fc6b5..ba0abae85d 100644 --- a/backend/src/app/cli/manage.clj +++ b/backend/src/app/cli/manage.clj @@ -10,6 +10,7 @@ [app.common.logging :as l] [app.db :as db] [app.main :as main] + [app.rpc.commands.auth :as cmd.auth] [app.rpc.mutations.profile :as profile] [app.rpc.queries.profile :refer [retrieve-profile-data-by-email]] [clojure.string :as str] @@ -54,13 +55,13 @@ :type :password}))] (try (db/with-atomic [conn (:app.db/pool system)] - (->> (profile/create-profile conn + (->> (cmd.auth/create-profile conn {:fullname fullname :email email :password password :is-active true :is-demo false}) - (profile/create-profile-relations conn))) + (cmd.auth/create-profile-relations conn))) (when (pos? (:verbosity options)) (println "User created successfully.")) diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 6acb96cf49..5bb0119b2a 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -11,6 +11,7 @@ [app.common.data :as d] [app.common.exceptions :as ex] [app.common.flags :as flags] + [app.common.logging :as l] [app.common.spec :as us] [app.common.version :as v] [app.util.time :as dt] @@ -41,8 +42,7 @@ data)) (def defaults - { - :database-uri "postgresql://postgres/penpot" + {:database-uri "postgresql://postgres/penpot" :database-username "penpot" :database-password "penpot" @@ -79,24 +79,20 @@ :ldap-attrs-username "uid" :ldap-attrs-email "mail" :ldap-attrs-fullname "cn" - :ldap-attrs-photo "jpegPhoto" ;; a server prop key where initial project is stored. :initial-project-skey "initial-project"}) -(s/def ::flags ::us/set-of-keywords) -;; DEPRECATED PROPERTIES -(s/def ::registration-enabled ::us/boolean) -(s/def ::smtp-enabled ::us/boolean) +(s/def ::media-max-file-size ::us/integer) + +(s/def ::flags ::us/vec-of-valid-keywords) (s/def ::telemetry-enabled ::us/boolean) -(s/def ::asserts-enabled ::us/boolean) -;; END DEPRECATED (s/def ::audit-log-archive-uri ::us/string) (s/def ::audit-log-gc-max-age ::dt/duration) -(s/def ::admins ::us/set-of-str) +(s/def ::admins ::us/set-of-non-empty-strings) (s/def ::file-change-snapshot-every ::us/integer) (s/def ::file-change-snapshot-timeout ::dt/duration) @@ -104,10 +100,14 @@ (s/def ::blocking-executor-parallelism ::us/integer) (s/def ::worker-executor-parallelism ::us/integer) +(s/def ::authenticated-cookie-domain ::us/string) +(s/def ::authenticated-cookie-name ::us/string) +(s/def ::auth-token-cookie-name ::us/string) +(s/def ::auth-token-cookie-max-age ::dt/duration) + (s/def ::secret-key ::us/string) (s/def ::allow-demo-users ::us/boolean) (s/def ::assets-path ::us/string) -(s/def ::authenticated-cookie-domain ::us/string) (s/def ::database-password (s/nilable ::us/string)) (s/def ::database-uri ::us/string) (s/def ::database-username (s/nilable ::us/string)) @@ -131,8 +131,8 @@ (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-scopes ::us/set-of-non-empty-strings) +(s/def ::oidc-roles ::us/set-of-non-empty-strings) (s/def ::oidc-roles-attr ::us/keyword) (s/def ::oidc-email-attr ::us/keyword) (s/def ::oidc-name-attr ::us/keyword) @@ -143,13 +143,9 @@ (s/def ::http-server-max-multipart-body-size ::us/integer) (s/def ::http-server-io-threads ::us/integer) (s/def ::http-server-worker-threads ::us/integer) -(s/def ::http-session-idle-max-age ::dt/duration) -(s/def ::http-session-updater-batch-max-age ::dt/duration) -(s/def ::http-session-updater-batch-max-size ::us/integer) (s/def ::initial-project-skey ::us/string) (s/def ::ldap-attrs-email ::us/string) (s/def ::ldap-attrs-fullname ::us/string) -(s/def ::ldap-attrs-photo ::us/string) (s/def ::ldap-attrs-username ::us/string) (s/def ::ldap-base-dn ::us/string) (s/def ::ldap-bind-dn ::us/string) @@ -169,7 +165,7 @@ (s/def ::profile-complaint-threshold ::us/integer) (s/def ::public-uri ::us/string) (s/def ::redis-uri ::us/string) -(s/def ::registration-domain-whitelist ::us/set-of-str) +(s/def ::registration-domain-whitelist ::us/set-of-non-empty-strings) (s/def ::rlimit-font ::us/integer) (s/def ::rlimit-file-update ::us/integer) (s/def ::rlimit-image ::us/integer) @@ -210,6 +206,9 @@ ::allow-demo-users ::audit-log-archive-uri ::audit-log-gc-max-age + ::auth-token-cookie-name + ::auth-token-cookie-max-age + ::authenticated-cookie-name ::authenticated-cookie-domain ::database-password ::database-uri @@ -250,13 +249,9 @@ ::http-server-max-multipart-body-size ::http-server-io-threads ::http-server-worker-threads - ::http-session-idle-max-age - ::http-session-updater-batch-max-age - ::http-session-updater-batch-max-size ::initial-project-skey ::ldap-attrs-email ::ldap-attrs-fullname - ::ldap-attrs-photo ::ldap-attrs-username ::ldap-base-dn ::ldap-bind-dn @@ -269,6 +264,7 @@ ::local-assets-uri ::loggers-loki-uri ::loggers-zmq-uri + ::media-max-file-size ::profile-bounce-max-age ::profile-bounce-threshold ::profile-complaint-max-age @@ -276,7 +272,6 @@ ::public-uri ::redis-uri ::registration-domain-whitelist - ::registration-enabled ::rlimit-font ::rlimit-file-update ::rlimit-image @@ -287,7 +282,6 @@ ::sentry-trace-sample-rate ::smtp-default-from ::smtp-default-reply-to - ::smtp-enabled ::smtp-host ::smtp-password ::smtp-port @@ -314,6 +308,7 @@ (def default-flags [:enable-backend-api-doc + :enable-backend-worker :enable-secure-session-cookies]) (defn- parse-flags @@ -354,8 +349,12 @@ (str/trim)) "%version%"))) -(def ^:dynamic config (read-config)) -(def ^:dynamic flags (parse-flags config)) +(defonce ^:dynamic config (read-config)) + +(defonce ^:dynamic flags + (let [flags (parse-flags config)] + (l/info :hint "flags initialized" :flags (str/join "," (map name flags))) + flags)) (def deletion-delay (dt/duration {:days 7})) diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index c874fb9ccc..8785f272ea 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -55,54 +55,66 @@ (s/def ::migrations map?) (s/def ::name keyword?) (s/def ::password ::us/string) -(s/def ::read-only ::us/boolean) (s/def ::uri ::us/not-empty-string) (s/def ::username ::us/string) (s/def ::validation-timeout ::us/integer) +(s/def ::read-only? ::us/boolean) -(defmethod ig/pre-init-spec ::pool [_] - (s/keys :req-un [::uri ::name +(s/def ::pool-options + (s/keys :opt-un [::uri ::name ::min-size ::max-size ::connection-timeout - ::validation-timeout] - :opt-un [::migrations + ::validation-timeout + ::migrations ::username ::password ::mtx/metrics - ::read-only])) + ::read-only?])) + +(def defaults + {:name :main + :min-size 0 + :max-size 30 + :connection-timeout 10000 + :validation-timeout 10000 + :idle-timeout 120000 ; 2min + :max-lifetime 1800000 ; 30m + :read-only? false}) (defmethod ig/prep-key ::pool [_ cfg] - (merge {:name :main - :min-size 0 - :max-size 30 - :connection-timeout 10000 - :validation-timeout 10000 - :idle-timeout 120000 ; 2min - :max-lifetime 1800000 ; 30m - :read-only false} - (d/without-nils cfg))) + (merge defaults (d/without-nils cfg))) + +;; Don't validate here, just validate that a map is received. +(defmethod ig/pre-init-spec ::pool [_] ::pool-options) (defmethod ig/init-key ::pool - [_ {:keys [migrations name read-only] :as cfg}] - (l/info :hint "initialize connection pool" - :name (d/name name) - :uri (:uri cfg) - :read-only read-only - :with-credentials (and (contains? cfg :username) - (contains? cfg :password)) - :min-size (:min-size cfg) - :max-size (:max-size cfg)) + [_ {:keys [migrations read-only? uri] :as cfg}] + (if uri + (let [pool (create-pool cfg)] + (l/info :hint "initialize connection pool" + :name (d/name (:name cfg)) + :uri uri + :read-only read-only? + :with-credentials (and (contains? cfg :username) + (contains? cfg :password)) + :min-size (:min-size cfg) + :max-size (:max-size cfg)) + (when-not read-only? + (some->> (seq migrations) (apply-migrations! pool))) + pool) - (let [pool (create-pool cfg)] - (when-not read-only - (some->> (seq migrations) (apply-migrations! pool))) - pool)) + (do + (l/warn :hint "unable to initialize pool, missing url" + :name (d/name (:name cfg)) + :read-only read-only?) + nil))) (defmethod ig/halt-key! ::pool [_ pool] - (.close ^HikariDataSource pool)) + (when pool + (.close ^HikariDataSource pool))) (defn- apply-migrations! [pool migrations] @@ -126,7 +138,7 @@ (.setJdbcUrl (str "jdbc:" uri)) (.setPoolName (d/name (:name cfg))) (.setAutoCommit true) - (.setReadOnly (:read-only cfg)) + (.setReadOnly (:read-only? cfg)) (.setConnectionTimeout (:connection-timeout cfg)) (.setValidationTimeout (:validation-timeout cfg)) (.setIdleTimeout (:idle-timeout cfg)) @@ -213,7 +225,7 @@ [& args] `(jdbc/with-transaction ~@args)) -(defn ^Connection open +(defn open [pool] (jdbc/get-connection pool)) @@ -311,9 +323,9 @@ (and (pgarray? v) (= "uuid" (.getBaseTypeName ^PgArray v)))) (defn decode-pgarray - ([v] (into [] (.getArray ^PgArray v))) - ([v in] (into in (.getArray ^PgArray v))) - ([v in xf] (into in xf (.getArray ^PgArray v)))) + ([v] (some->> ^PgArray v .getArray vec)) + ([v in] (some->> ^PgArray v .getArray (into in))) + ([v in xf] (some->> ^PgArray v .getArray (into in xf)))) (defn pgarray->set [v] diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 95631738dd..7bef64e789 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -9,7 +9,6 @@ [app.common.data :as d] [app.common.logging :as l] [app.common.transit :as t] - [app.http.doc :as doc] [app.http.errors :as errors] [app.http.middleware :as middleware] [app.metrics :as mtx] @@ -67,8 +66,10 @@ :xnio/worker-threads (:worker-threads cfg) :xnio/dispatch (:executor cfg) :ring/async true} + handler (if (some? router) (wrap-router router) + handler) server (yt/server handler (d/without-nils options))] (assoc cfg :server (yt/start! server)))) @@ -113,23 +114,35 @@ ;; HTTP ROUTER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(s/def ::rpc map?) (s/def ::oauth map?) (s/def ::storage map?) (s/def ::assets map?) (s/def ::feedback fn?) (s/def ::ws fn?) (s/def ::audit-handler fn?) -(s/def ::debug map?) (s/def ::awsns-handler fn?) (s/def ::session map?) +(s/def ::rpc-routes (s/nilable vector?)) +(s/def ::debug-routes (s/nilable vector?)) +(s/def ::oidc-routes (s/nilable vector?)) +(s/def ::doc-routes (s/nilable vector?)) (defmethod ig/pre-init-spec ::router [_] - (s/keys :req-un [::rpc ::mtx/metrics ::ws ::oauth ::storage ::assets - ::session ::feedback ::awsns-handler ::debug ::audit-handler])) + (s/keys :req-un [::mtx/metrics + ::ws + ::storage + ::assets + ::session + ::feedback + ::awsns-handler + ::debug-routes + ::oidc-routes + ::audit-handler + ::rpc-routes + ::doc-routes])) (defmethod ig/init-key ::router - [_ {:keys [ws session rpc oauth metrics assets feedback debug] :as cfg}] + [_ {:keys [ws session metrics assets feedback] :as cfg}] (rr/router [["" {:middleware [[middleware/server-timing] [middleware/format-response] @@ -137,20 +150,14 @@ [middleware/parse-request] [middleware/errors errors/handle] [middleware/restrict-methods]]} + ["/metrics" {:handler (:handler metrics)}] ["/assets" {:middleware [(:middleware session)]} ["/by-id/:id" {:handler (:objects-handler assets)}] ["/by-file-media-id/:id" {:handler (:file-objects-handler assets)}] ["/by-file-media-id/:id/thumbnail" {:handler (:file-thumbnails-handler assets)}]] - ["/dbg" {:middleware [(:middleware session)]} - ["" {:handler (:index debug)}] - ["/changelog" {:handler (:changelog debug)}] - ["/error-by-id/:id" {:handler (:retrieve-error debug)}] - ["/error/:id" {:handler (:retrieve-error debug)}] - ["/error" {:handler (:retrieve-error-list debug)}] - ["/file/data" {:handler (:file-data debug)}] - ["/file/changes" {:handler (:retrieve-file-changes debug)}]] + (:debug-routes cfg) ["/webhooks" ["/sns" {:handler (:awsns-handler cfg) @@ -161,22 +168,12 @@ :allowed-methods #{:get}}] ["/api" {:middleware [[middleware/cors] - (:middleware session)]} - ["/health" {:handler (:health-check debug)}] - ["/_doc" {:handler (doc/handler rpc) - :allowed-methods #{:get}}] - ["/feedback" {:handler feedback - :allowed-methods #{:post}}] - - ["/auth/oauth/:provider" {:handler (:handler oauth) - :allowed-methods #{:post}}] - ["/auth/oauth/:provider/callback" {:handler (:callback-handler oauth) - :allowed-methods #{:get}}] - + [(:middleware session)]]} ["/audit/events" {:handler (:audit-handler cfg) :allowed-methods #{:post}}] + ["/feedback" {:handler feedback + :allowed-methods #{:post}}] + (:doc-routes cfg) + (:oidc-routes cfg) + (:rpc-routes cfg)]]])) - ["/rpc" - ["/query/:type" {:handler (:query-handler rpc)}] - ["/mutation/:type" {:handler (:mutation-handler rpc) - :allowed-methods #{:post}}]]]]])) diff --git a/backend/src/app/http/assets.clj b/backend/src/app/http/assets.clj index 9061c436d0..39a55c7198 100644 --- a/backend/src/app/http/assets.clj +++ b/backend/src/app/http/assets.clj @@ -29,7 +29,7 @@ (defn coerce-id [id] - (let [res (us/uuid-conformer id)] + (let [res (parse-uuid id)] (when-not (uuid? res) (ex/raise :type :not-found :hint "object not found")) diff --git a/backend/src/app/http/awsns.clj b/backend/src/app/http/awsns.clj index 2844c2d53a..dd5bc35a3c 100644 --- a/backend/src/app/http/awsns.clj +++ b/backend/src/app/http/awsns.clj @@ -16,6 +16,7 @@ [integrant.core :as ig] [jsonista.core :as j] [promesa.exec :as px] + [yetti.request :as yrq] [yetti.response :as yrs])) (declare parse-json) @@ -31,9 +32,9 @@ (defmethod ig/init-key ::handler [_ {:keys [executor] :as cfg}] (fn [request respond _] - (let [data (slurp (:body request))] - (px/run! executor #(handle-request cfg data)) - (respond (yrs/response 200))))) + (let [data (-> request yrq/body slurp)] + (px/run! executor #(handle-request cfg data))) + (respond (yrs/response 200)))) (defn handle-request [{:keys [http-client] :as cfg} data] diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj index 35e9ed27f9..11a29f892b 100644 --- a/backend/src/app/http/debug.clj +++ b/backend/src/app/http/debug.clj @@ -5,36 +5,38 @@ ;; Copyright (c) UXBOX Labs SL (ns app.http.debug + (:refer-clojure :exclude [error-handler]) (:require - [app.common.data :as d] [app.common.exceptions :as ex] - [app.common.spec :as us] + [app.common.pprint :as pp] [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] - [app.db.sql :as sql] - [app.rpc.mutations.files :as m.files] + [app.http.middleware :as mw] + [app.rpc.commands.binfile :as binf] + [app.rpc.mutations.files :refer [create-file]] [app.rpc.queries.profile :as profile] [app.util.blob :as blob] + [app.util.bytes :as bs] [app.util.template :as tmpl] [app.util.time :as dt] [app.worker :as wrk] [clojure.java.io :as io] [clojure.spec.alpha :as s] [cuerdas.core :as str] - [datoteka.core :as fs] [emoji.core :as emj] - [fipp.edn :as fpp] [integrant.core :as ig] [markdown.core :as md] [markdown.transformers :as mdt] - [promesa.core :as p] - [promesa.exec :as px] [yetti.request :as yrq] [yetti.response :as yrs])) ;; (selmer.parser/cache-off!) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; HELPERS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn authorized? [pool {:keys [profile-id]}] (or (= "devenv" (cf/get :host)) @@ -42,7 +44,22 @@ admins (or (cf/get :admins) #{})] (contains? admins (:email profile))))) -(defn index +(defn prepare-response + [body] + (let [headers {"content-type" "application/transit+json"}] + (yrs/response :status 200 :body body :headers headers))) + +(defn prepare-download-response + [body filename] + (let [headers {"content-disposition" (str "attachment; filename=" filename) + "content-type" "application/octet-stream"}] + (yrs/response :status 200 :body body :headers headers))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; INDEX +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn index-handler [{:keys [pool]} request] (when-not (authorized? pool request) (ex/raise :type :authentication @@ -52,6 +69,9 @@ :body (-> (io/resource "templates/debug.tmpl") (tmpl/render {})))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; FILE CHANGES +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def sql:retrieve-range-of-changes "select revn, changes from file_change where file_id=? and revn >= ? and revn <= ? order by revn") @@ -59,28 +79,16 @@ (def sql:retrieve-single-change "select revn, changes, data from file_change where file_id=? and revn = ?") -(defn prepare-response - [{:keys [params] :as request} body filename] - (when-not body - (ex/raise :type :not-found - :code :enpty-data - :hint "empty response")) - - (cond-> (yrs/response :status 200 - :body body - :headers {"content-type" "application/transit+json"}) - (contains? params :download) - (update :headers assoc "content-disposition" (str "attachment; filename=" filename)))) - (defn- retrieve-file-data - [{:keys [pool]} {:keys [params] :as request}] + [{:keys [pool]} {:keys [params profile-id] :as request}] (when-not (authorized? pool request) (ex/raise :type :authentication :code :only-admins-allowed)) - (let [file-id (some-> (get-in request [:params :file-id]) uuid/uuid) - revn (some-> (get-in request [:params :revn]) d/parse-integer) + (let [file-id (some-> params :file-id parse-uuid) + revn (some-> params :revn parse-long) filename (str file-id)] + (when-not file-id (ex/raise :type :validation :code :missing-arguments)) @@ -88,35 +96,63 @@ (let [data (if (integer? revn) (some-> (db/exec-one! pool [sql:retrieve-single-change file-id revn]) :data) (some-> (db/get-by-id pool :file file-id) :data))] - (if (contains? params :download) - (-> (prepare-response request data filename) - (update :headers assoc "content-type" "application/octet-stream")) - (prepare-response request (some-> data blob/decode) filename))))) + + (when-not data + (ex/raise :type :not-found + :code :enpty-data + :hint "empty response")) + (cond + (contains? params :download) + (prepare-download-response data filename) + + (contains? params :clone) + (let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id) + data (some-> data blob/decode)] + (create-file pool {:id (uuid/next) + :name (str "Cloned file: " filename) + :project-id project-id + :profile-id profile-id + :data data}) + (yrs/response 201 "OK CREATED")) + + :else + (prepare-response (some-> data blob/decode)))))) + +(defn- is-file-exists? + [pool id] + (let [sql "select exists (select 1 from file where id=?) as exists;"] + (-> (db/exec-one! pool [sql id]) :exists))) (defn- upload-file-data [{:keys [pool]} {:keys [profile-id params] :as request}] (let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id) - data (some-> params :file :path fs/slurp-bytes blob/decode)] + data (some-> params :file :path bs/read-as-bytes blob/decode)] (if (and data project-id) - (let [fname (str "imported-file-" (dt/now)) - file-id (try - (uuid/uuid (-> params :file :filename)) - (catch Exception _ (uuid/next))) - file (db/exec-one! pool (sql/select :file {:id file-id}))] - (if file - (db/update! pool :file - {:data (blob/encode data)} - {:id file-id}) - (m.files/create-file pool {:id file-id - :name fname - :project-id project-id - :profile-id profile-id - :data data})) - (yrs/response 200 "OK")) + (let [fname (str "Imported file *: " (dt/now)) + overwrite? (contains? params :overwrite?) + file-id (or (and overwrite? (ex/ignoring (-> params :file :filename parse-uuid))) + (uuid/next))] + + (if (and overwrite? file-id + (is-file-exists? pool file-id)) + (do + (db/update! pool :file + {:data (blob/encode data)} + {:id file-id}) + (yrs/response 200 "OK UPDATED")) + + (do + (create-file pool {:id file-id + :name fname + :project-id project-id + :profile-id profile-id + :data data}) + (yrs/response 201 "OK CREATED")))) + (yrs/response 500 "ERROR")))) -(defn file-data +(defn file-data-handler [cfg request] (case (yrq/method request) :get (retrieve-file-data cfg request) @@ -124,47 +160,51 @@ (ex/raise :type :http :code :method-not-found))) -(defn retrieve-file-changes - [{:keys [pool]} request] +(defn file-changes-handler + [{:keys [pool]} {:keys [params] :as request}] (when-not (authorized? pool request) (ex/raise :type :authentication :code :only-admins-allowed)) - (let [file-id (some-> (get-in request [:params :id]) uuid/uuid) - revn (or (get-in request [:params :revn]) "latest") - filename (str file-id)] + (letfn [(retrieve-changes [file-id revn] + (if (str/includes? revn ":") + (let [[start end] (->> (str/split revn #":") + (map str/trim) + (map parse-long))] + (some->> (db/exec! pool [sql:retrieve-range-of-changes file-id start end]) + (map :changes) + (map blob/decode) + (mapcat identity) + (vec))) - (when (or (not file-id) (not revn)) - (ex/raise :type :validation - :code :invalid-arguments - :hint "missing arguments")) + (if-let [revn (parse-long revn)] + (let [item (db/exec-one! pool [sql:retrieve-single-change file-id revn])] + (some-> item :changes blob/decode vec)) + (ex/raise :type :validation :code :invalid-arguments))))] - (cond - (d/num-string? revn) - (let [item (db/exec-one! pool [sql:retrieve-single-change file-id (d/parse-integer revn)])] - (prepare-response request (some-> item :changes blob/decode vec) filename)) + (let [file-id (some-> params :id parse-uuid) + revn (or (some-> params :revn parse-long) "latest") + filename (str file-id)] - (str/includes? revn ":") - (let [[start end] (->> (str/split revn #":") - (map str/trim) - (map d/parse-integer)) - items (db/exec! pool [sql:retrieve-range-of-changes file-id start end])] - (prepare-response request - (some->> items - (map :changes) - (map blob/decode) - (mapcat identity) - (vec)) - filename)) - :else - (ex/raise :type :validation :code :invalid-arguments)))) + (when (or (not file-id) (not revn)) + (ex/raise :type :validation + :code :invalid-arguments + :hint "missing arguments")) + (let [data (retrieve-changes file-id revn)] + (if (contains? params :download) + (prepare-download-response data filename) + (prepare-response data)))))) -(defn retrieve-error +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ERROR BROWSER +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn error-handler [{:keys [pool]} request] (letfn [(parse-id [request] (let [id (get-in request [:path-params :id]) - id (us/uuid-conformer id)] + id (parse-uuid id)] (when (uuid? id) id))) @@ -176,9 +216,8 @@ (let [context (dissoc report :trace :cause :params :data :spec-problems :spec-explain :spec-value :error :explain :hint) - params {:context (with-out-str - (fpp/pprint context {:width 200})) - :hint (:hint report) + params {:context (pp/pprint-str context :width 200) + :hint (:hint report) :spec-explain (:spec-explain report) :spec-problems (:spec-problems report) :spec-value (:spec-value report) @@ -206,7 +245,7 @@ (def sql:error-reports "select id, created_at from server_error_report order by created_at desc limit 100") -(defn retrieve-error-list +(defn error-list-handler [{:keys [pool]} request] (when-not (authorized? pool request) (ex/raise :type :authentication @@ -219,14 +258,94 @@ :headers {"content-type" "text/html; charset=utf-8" "x-robots-tag" "noindex"}))) -(defn health-check +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; EXPORT/IMPORT +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn export-handler + [{:keys [pool] :as cfg} {:keys [params profile-id] :as request}] + + (let [file-ids (->> (:file-ids params) + (remove empty?) + (mapv parse-uuid)) + libs? (contains? params :includelibs) + clone? (contains? params :clone) + embed? (contains? params :embedassets)] + + (when-not (seq file-ids) + (ex/raise :type :validation + :code :missing-arguments)) + + (let [path (-> cfg + (assoc ::binf/file-ids file-ids) + (assoc ::binf/embed-assets? embed?) + (assoc ::binf/include-libraries? libs?) + (binf/export!))] + (if clone? + (let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id)] + (binf/import! + (assoc cfg + ::binf/input path + ::binf/overwrite? false + ::binf/ignore-index-errors? true + ::binf/profile-id profile-id + ::binf/project-id project-id)) + + (yrs/response + :status 200 + :headers {"content-type" "text/plain"} + :body "OK CLONED")) + + (yrs/response + :status 200 + :headers {"content-type" "application/octet-stream" + "content-disposition" (str "attachmen; filename=" (first file-ids) ".penpot")} + :body (io/input-stream path)))))) + + +(defn import-handler + [{:keys [pool] :as cfg} {:keys [params profile-id] :as request}] + (when-not (contains? params :file) + (ex/raise :type :validation + :code :missing-upload-file + :hint "missing upload file")) + + (let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id) + overwrite? (contains? params :overwrite) + migrate? (contains? params :migrate) + ignore-index-errors? (contains? params :ignore-index-errors)] + + (when-not project-id + (ex/raise :type :validation + :code :missing-project + :hint "project not found")) + + (binf/import! + (assoc cfg + ::binf/input (-> params :file :path) + ::binf/overwrite? overwrite? + ::binf/migrate? migrate? + ::binf/ignore-index-errors? ignore-index-errors? + ::binf/profile-id profile-id + ::binf/project-id project-id)) + + (yrs/response + :status 200 + :headers {"content-type" "text/plain"} + :body "OK"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; OTHER SMALL VIEWS/HANDLERS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn health-handler "Mainly a task that performs a health check." [{:keys [pool]} _] (db/with-atomic [conn pool] (db/exec-one! conn ["select count(*) as count from server_prop;"]) (yrs/response 200 "OK"))) -(defn changelog +(defn changelog-handler [_ _] (letfn [(transform-emoji [text state] [(emj/emojify text) state]) @@ -238,22 +357,39 @@ :body (-> clog slurp md->html)) (yrs/response :status 404 :body "NOT FOUND")))) -(defn- wrap-async - [{:keys [executor] :as cfg} f] - (fn [request respond raise] - (-> (px/submit! executor #(f cfg request)) - (p/then respond) - (p/catch raise)))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; INIT +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defmethod ig/pre-init-spec ::handlers [_] - (s/keys :req-un [::db/pool ::wrk/executor])) +(def with-authorization + {:compile + (fn [& _] + (fn [handler pool] + (fn [request respond raise] + (if (authorized? pool request) + (handler request respond raise) + (raise (ex/error :type :authentication + :code :only-admins-allowed))))))}) -(defmethod ig/init-key ::handlers - [_ cfg] - {:index (wrap-async cfg index) - :health-check (wrap-async cfg health-check) - :retrieve-file-changes (wrap-async cfg retrieve-file-changes) - :retrieve-error (wrap-async cfg retrieve-error) - :retrieve-error-list (wrap-async cfg retrieve-error-list) - :file-data (wrap-async cfg file-data) - :changelog (wrap-async cfg changelog)}) + +(s/def ::session map?) + +(defmethod ig/pre-init-spec ::routes [_] + (s/keys :req-un [::db/pool ::wrk/executor ::session])) + +(defmethod ig/init-key ::routes + [_ {:keys [session pool executor] :as cfg}] + ["/dbg" {:middleware [[(:middleware session)] + [with-authorization pool] + [mw/with-promise-async executor] + [mw/with-config cfg]]} + ["" {:handler index-handler}] + ["/health" {:handler health-handler}] + ["/changelog" {:handler changelog-handler}] + ;; ["/error-by-id/:id" {:handler error-handler}] + ["/error/:id" {:handler error-handler}] + ["/error" {:handler error-list-handler}] + ["/file/export" {:handler export-handler}] + ["/file/import" {:handler import-handler}] + ["/file/data" {:handler file-data-handler}] + ["/file/changes" {:handler file-changes-handler}]]) diff --git a/backend/src/app/http/doc.clj b/backend/src/app/http/doc.clj deleted file mode 100644 index a6e88458b8..0000000000 --- a/backend/src/app/http/doc.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) UXBOX Labs SL - -(ns app.http.doc - "API autogenerated documentation." - (:require - [app.common.data :as d] - [app.config :as cf] - [app.util.services :as sv] - [app.util.template :as tmpl] - [clojure.java.io :as io] - [clojure.spec.alpha :as s] - [pretty-spec.core :as ps] - [yetti.response :as yrs])) - -(defn get-spec-str - [k] - (with-out-str - (ps/pprint (s/form k) - {:ns-aliases {"clojure.spec.alpha" "s" - "clojure.core.specs.alpha" "score" - "clojure.core" nil}}))) - -(defn prepare-context - [rpc] - (letfn [(gen-doc [type [name f]] - (let [mdata (meta f)] - ;; (prn name mdata) - {:type (d/name type) - :name (d/name name) - :auth (:auth mdata true) - :docs (::sv/docs mdata) - :spec (get-spec-str (::sv/spec mdata))}))] - {:query-methods - (into [] - (map (partial gen-doc :query)) - (->> rpc :methods :query (sort-by first))) - :mutation-methods - (into [] - (map (partial gen-doc :mutation)) - (->> rpc :methods :mutation (sort-by first)))})) - -(defn handler - [rpc] - (let [context (prepare-context rpc)] - (if (contains? cf/flags :backend-api-doc) - (fn [_ respond _] - (respond (yrs/response 200 (-> (io/resource "api-doc.tmpl") - (tmpl/render context))))) - (fn [_ respond _] - (respond (yrs/response 404)))))) diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index f7533306ec..852cd1cafc 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -71,7 +71,7 @@ [error request] (let [edata (ex-data error) explain (us/pretty-explain edata)] - (l/error ::l/raw (ex-message error) + (l/error ::l/raw (str (ex-message error) "\n" explain) ::l/context (get-context request) :cause error) (yrs/response :status 500 @@ -143,13 +143,11 @@ (defn handle [cause request] - (cond (or (instance? java.util.concurrent.CompletionException cause) (instance? java.util.concurrent.ExecutionException cause)) (handle-exception (.getCause ^Throwable cause) request) - (ex/wrapped? cause) (let [context (meta cause) cause (deref cause)] diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index af7f140a86..626f5a375e 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -12,6 +12,8 @@ [app.config :as cf] [app.util.json :as json] [cuerdas.core :as str] + [promesa.core :as p] + [promesa.exec :as px] [yetti.adapter :as yt] [yetti.middleware :as ymw] [yetti.request :as yrq] @@ -35,14 +37,14 @@ (let [header (yrq/get-header request "content-type")] (cond (str/starts-with? header "application/transit+json") - (with-open [is (-> request yrq/body yrq/body-stream)] + (with-open [is (yrq/body request)] (let [params (t/read! (t/reader is))] (-> request (assoc :body-params params) (update :params merge params)))) (str/starts-with? header "application/json") - (with-open [is (-> request yrq/body yrq/body-stream)] + (with-open [is (yrq/body request)] (let [params (json/read is)] (-> request (assoc :body-params params) @@ -192,3 +194,21 @@ (def restrict-methods {:name ::restrict-methods :compile compile-restrict-methods}) + +(def with-promise-async + {:compile + (fn [& _] + (fn [handler executor] + (fn [request respond raise] + (-> (px/submit! executor #(handler request)) + (p/bind p/wrap) + (p/then respond) + (p/catch raise)))))}) + +(def with-config + {:compile + (fn [& _] + (fn [handler config] + (fn + ([request] (handler config request)) + ([request respond raise] (handler config request respond raise)))))}) diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index b680929409..f3e4016415 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -7,33 +7,35 @@ (ns app.http.session (:require [app.common.data :as d] - [app.common.exceptions :as ex] [app.common.logging :as l] - [app.config :as cfg] + [app.config :as cf] [app.db :as db] [app.db.sql :as sql] - [app.metrics :as mtx] - [app.util.async :as aa] [app.util.time :as dt] [app.worker :as wrk] - [clojure.core.async :as a] [clojure.spec.alpha :as s] [integrant.core :as ig] [promesa.core :as p] [promesa.exec :as px] [yetti.request :as yrq])) -;; A default cookie name for storing the session. We don't allow to configure it. -(def token-cookie-name "auth-token") +;; A default cookie name for storing the session. +(def default-auth-token-cookie-name "auth-token") -;; A cookie that we can use to check from other sites of the same domain if a user -;; is registered. Is not intended for on premise installations, although nothing -;; prevents using it if some one wants to. -(def authenticated-cookie-name "authenticated") +;; A cookie that we can use to check from other sites of the same +;; domain if a user is authenticated. +(def default-authenticated-cookie-name "authenticated") + +;; Default value for cookie max-age +(def default-cookie-max-age (dt/duration {:days 7})) + +;; Default age for automatic session renewal +(def default-renewal-max-age (dt/duration {:hours 6})) (defprotocol ISessionStore (read-session [store key]) (write-session [store key data]) + (update-session [store data]) (delete-session [store key])) (defn- make-database-store @@ -47,18 +49,25 @@ (px/with-dispatch executor (let [profile-id (:profile-id data) user-agent (:user-agent data) + created-at (or (:created-at data) (dt/now)) token (tokens :generate {:iss "authentication" - :iat (dt/now) + :iat created-at :uid profile-id}) - - now (dt/now) params {:user-agent user-agent :profile-id profile-id - :created-at now - :updated-at now + :created-at created-at + :updated-at created-at :id token}] - (db/insert! pool :http-session params) - token))) + (db/insert! pool :http-session params)))) + + (update-session [_ data] + (let [updated-at (dt/now)] + (px/with-dispatch executor + (db/update! pool :http-session + {:updated-at updated-at} + {:id (:id data)}) + (assoc data :updated-at updated-at)))) + (delete-session [_ token] (px/with-dispatch executor @@ -76,15 +85,23 @@ (p/do (let [profile-id (:profile-id data) user-agent (:user-agent data) + created-at (or (:created-at data) (dt/now)) token (tokens :generate {:iss "authentication" - :iat (dt/now) + :iat created-at :uid profile-id}) params {:user-agent user-agent + :created-at created-at + :updated-at created-at :profile-id profile-id :id token}] (swap! cache assoc token params) - token))) + params))) + + (update-session [_ data] + (let [updated-at (dt/now)] + (swap! cache update (:id data) assoc :updated-at updated-at) + (assoc data :updated-at updated-at))) (delete-session [_ token] (p/do @@ -107,76 +124,123 @@ ;; --- IMPL (defn- create-session! - [store request profile-id] - (let [params {:user-agent (yrq/get-header request "user-agent") + [store profile-id user-agent] + (let [params {:user-agent user-agent :profile-id profile-id}] (write-session store nil params))) +(defn- update-session! + [store session] + (update-session store session)) + (defn- delete-session! [store {:keys [cookies] :as request}] - (when-let [token (get-in cookies [token-cookie-name :value])] - (delete-session store token))) + (let [name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)] + (when-let [token (get-in cookies [name :value])] + (delete-session store token)))) (defn- retrieve-session [store request] - (when-let [cookie (yrq/get-cookie request token-cookie-name)] - (-> (read-session store (:value cookie)) - (p/then (fn [session] - (when session - {:session-id (:id session) - :profile-id (:profile-id session)})))))) + (let [cookie-name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)] + (when-let [cookie (yrq/get-cookie request cookie-name)] + (read-session store (:value cookie))))) -(defn- add-cookies - [response token] - (let [cors? (contains? cfg/flags :cors) - secure? (contains? cfg/flags :secure-session-cookies) - authenticated-cookie-domain (cfg/get :authenticated-cookie-domain)] - (update response :cookies - (fn [cookies] - (cond-> cookies - :always - (assoc token-cookie-name {:path "/" - :http-only true - :value token - :same-site (if cors? :none :lax) - :secure secure?}) +(defn assign-auth-token-cookie + [response {token :id updated-at :updated-at}] + (let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age) + created-at (or updated-at (dt/now)) + renewal (dt/plus created-at default-renewal-max-age) + expires (dt/plus created-at max-age) + secure? (contains? cf/flags :secure-session-cookies) + cors? (contains? cf/flags :cors) + name (cf/get :auth-token-cookie-name default-auth-token-cookie-name) + comment (str "Renewal at: " (dt/format-instant renewal :rfc1123)) + cookie {:path "/" + :http-only true + :expires expires + :value token + :comment comment + :same-site (if cors? :none :lax) + :secure secure?}] + (update response :cookies assoc name cookie))) - (some? authenticated-cookie-domain) - (assoc authenticated-cookie-name {:domain authenticated-cookie-domain - :path "/" - :value true - :same-site :strict - :secure secure?})))))) +(defn assign-authenticated-cookie + [response {updated-at :updated-at}] + (let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age) + created-at (or updated-at (dt/now)) + renewal (dt/plus created-at default-renewal-max-age) + expires (dt/plus created-at max-age) + comment (str "Renewal at: " (dt/format-instant renewal :rfc1123)) + secure? (contains? cf/flags :secure-session-cookies) + domain (cf/get :authenticated-cookie-domain) + name (cf/get :authenticated-cookie-name "authenticated") + cookie {:domain domain + :expires expires + :path "/" + :comment comment + :value true + :same-site :strict + :secure secure?}] + (cond-> response + (string? domain) + (update :cookies assoc name cookie)))) -(defn- clear-cookies +(defn clear-auth-token-cookie [response] - (let [authenticated-cookie-domain (cfg/get :authenticated-cookie-domain)] - (assoc response :cookies - {token-cookie-name {:path "/" - :value "" - :max-age -1} - authenticated-cookie-name {:domain authenticated-cookie-domain - :path "/" - :value "" - :max-age -1}}))) + (let [name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)] + (update response :cookies assoc name {:path "/" :value "" :max-age -1}))) + +(defn- clear-authenticated-cookie + [response] + (let [name (cf/get :authenticated-cookie-name default-authenticated-cookie-name) + domain (cf/get :authenticated-cookie-domain)] + (cond-> response + (string? domain) + (update :cookies assoc name {:domain domain :path "/" :value "" :max-age -1})))) (defn- make-middleware - [{:keys [::events-ch store] :as cfg}] - {:name :session-middleware - :wrap (fn [handler] - (fn [request respond raise] - (try - (-> (retrieve-session store request) - (p/then' #(merge request %)) - (p/finally (fn [request cause] - (if cause - (raise cause) - (do - (when-let [session-id (:session-id request)] - (a/offer! events-ch session-id)) - (handler request respond raise)))))) - (catch Throwable cause - (raise cause)))))}) + [{:keys [store] :as cfg}] + (letfn [;; Check if time reached for automatic session renewal + (renew-session? [{:keys [updated-at] :as session}] + (and (dt/instant? updated-at) + (let [elapsed (dt/diff updated-at (dt/now))] + (neg? (compare default-renewal-max-age elapsed))))) + + ;; Wrap respond with session renewal code + (wrap-respond [respond session] + (fn [response] + (p/let [session (update-session! store session)] + (-> response + (assign-auth-token-cookie session) + (assign-authenticated-cookie session) + (respond)))))] + + {:name :session + :compile (fn [& _] + (fn [handler] + (fn [request respond raise] + (try + (-> (retrieve-session store request) + (p/finally (fn [session cause] + (cond + (some? cause) + (raise cause) + + (nil? session) + (handler request respond raise) + + :else + (let [request (-> request + (assoc :profile-id (:profile-id session)) + (assoc :session-id (:id session))) + respond (cond-> respond + (renew-session? session) + (wrap-respond session))] + + (handler request respond raise)))))) + + (catch Throwable cause + (raise cause))))))})) ;; --- STATE INIT: SESSION @@ -193,77 +257,23 @@ (defmethod ig/init-key :app.http/session [_ {:keys [store] :as cfg}] - (let [events-ch (a/chan (a/dropping-buffer (:buffer-size cfg))) - cfg (assoc cfg ::events-ch events-ch)] - - (-> cfg - (assoc :middleware (make-middleware cfg)) - (assoc :create (fn [profile-id] - (fn [request response] - (p/let [token (create-session! store request profile-id)] - (add-cookies response token))))) - (assoc :delete (fn [request response] - (p/do - (delete-session! store request) + (-> cfg + (assoc :middleware (make-middleware cfg)) + (assoc :create (fn [profile-id] + (fn [request response] + (p/let [uagent (yrq/get-header request "user-agent") + session (create-session! store profile-id uagent)] (-> response - (assoc :status 204) - (assoc :body nil) - (clear-cookies)))))))) - -(defmethod ig/halt-key! :app.http/session - [_ data] - (a/close! (::events-ch data))) - -;; --- STATE INIT: SESSION UPDATER - -(declare update-sessions) - -(s/def ::session map?) -(s/def ::max-batch-age ::cfg/http-session-updater-batch-max-age) -(s/def ::max-batch-size ::cfg/http-session-updater-batch-max-size) - -(defmethod ig/pre-init-spec ::updater [_] - (s/keys :req-un [::db/pool ::wrk/executor ::mtx/metrics ::session] - :opt-un [::max-batch-age ::max-batch-size])) - -(defmethod ig/prep-key ::updater - [_ cfg] - (merge {:max-batch-age (dt/duration {:minutes 5}) - :max-batch-size 200} - (d/without-nils cfg))) - -(defmethod ig/init-key ::updater - [_ {:keys [session metrics] :as 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 (aa/batch (::events-ch session) - {:max-batch-size (:max-batch-size cfg) - :max-batch-age (inst-ms (:max-batch-age cfg))})] - (a/go-loop [] - (when-let [[reason batch] (a/ response + (assoc :status 204) + (assoc :body nil) + (clear-auth-token-cookie) + (clear-authenticated-cookie))))))) ;; --- STATE INIT: SESSION GC @@ -277,7 +287,7 @@ (defmethod ig/prep-key ::gc-task [_ cfg] - (merge {:max-age (dt/duration {:days 15})} + (merge {:max-age default-cookie-max-age} (d/without-nils cfg))) (defmethod ig/init-key ::gc-task diff --git a/backend/src/app/http/websocket.clj b/backend/src/app/http/websocket.clj index f39a898911..39f961afbd 100644 --- a/backend/src/app/http/websocket.clj +++ b/backend/src/app/http/websocket.clj @@ -9,28 +9,103 @@ (:require [app.common.exceptions :as ex] [app.common.logging :as l] + [app.common.pprint :as pp] [app.common.spec :as us] [app.db :as db] [app.metrics :as mtx] + [app.util.time :as dt] [app.util.websocket :as ws] [clojure.core.async :as a] [clojure.spec.alpha :as s] [integrant.core :as ig] [yetti.websocket :as yws])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; WEBSOCKET HOOKS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def state (atom {})) + +(defn- on-connect + [{:keys [metrics]} wsp] + (let [created-at (dt/now)] + (swap! state assoc (::ws/id @wsp) wsp) + (mtx/run! metrics {:id :websocket-active-connections :inc 1}) + (fn [] + (swap! state dissoc (::ws/id @wsp)) + (mtx/run! metrics {:id :websocket-active-connections :dec 1}) + (mtx/run! metrics {:id :websocket-session-timing + :val (/ (inst-ms (dt/diff created-at (dt/now))) 1000.0)})))) + +(defn- on-rcv-message + [{:keys [metrics]} _ message] + (mtx/run! metrics {:id :websocket-messages-total :labels ["recv"] :inc 1}) + message) + +(defn- on-snd-message + [{:keys [metrics]} _ message] + (mtx/run! metrics {:id :websocket-messages-total :labels ["send"] :inc 1}) + message) + +;; REPL HELPERS + +(defn repl-get-connections-for-file + [file-id] + (->> (vals @state) + (filter #(= file-id (-> % deref ::file-subscription :file-id))) + (map deref) + (map ::ws/id))) + +(defn repl-get-connections-for-team + [team-id] + (->> (vals @state) + (filter #(= team-id (-> % deref ::team-subscription :team-id))) + (map deref) + (map ::ws/id))) + +(defn repl-close-connection + [id] + (when-let [wsp (get @state id)] + (a/>!! (::ws/close-ch @wsp) [8899 "closed from server"]) + (a/close! (::ws/close-ch @wsp)))) + +(defn repl-get-connection-info + [id] + (when-let [wsp (get @state id)] + {:id id + :created-at (dt/instant id) + :profile-id (::profile-id @wsp) + :session-id (::session-id @wsp) + :user-agent (::ws/user-agent @wsp) + :ip-addr (::ws/remote-addr @wsp) + :last-activity-at (::ws/last-activity-at @wsp) + :http-session-id (::ws/http-session-id @wsp) + :subscribed-file (-> wsp deref ::file-subscription :file-id) + :subscribed-team (-> wsp deref ::team-subscription :team-id)})) + +(defn repl-print-connection-info + [id] + (some-> id repl-get-connection-info pp/pprint)) + +(defn repl-print-connection-info-for-file + [file-id] + (some->> (repl-get-connections-for-file file-id) + (map repl-get-connection-info) + (pp/pprint))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; WEBSOCKET HANDLER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defmulti handle-message - (fn [_ message] + (fn [_ _ message] (:type message))) (defmethod handle-message :connect - [wsp _] - (l/trace :fn "handle-message" :event :connect) + [cfg wsp _] - (let [msgbus-fn (:msgbus @wsp) + (let [msgbus-fn (:msgbus cfg) + conn-id (::ws/id @wsp) profile-id (::profile-id @wsp) session-id (::session-id @wsp) output-ch (::ws/output-ch @wsp) @@ -38,94 +113,122 @@ xform (remove #(= (:session-id %) session-id)) channel (a/chan (a/dropping-buffer 16) xform)] - (swap! wsp assoc ::profile-subs-channel channel) + (l/trace :fn "handle-message" :event :connect :conn-id conn-id) + + ;; Subscribe to the profile channel and forward all messages to + ;; websocket output channel (send them to the client). + (swap! wsp assoc ::profile-subscription channel) (a/pipe channel output-ch false) (msgbus-fn :cmd :sub :topic profile-id :chan channel))) (defmethod handle-message :disconnect - [wsp _] - (l/trace :fn "handle-message" :event :disconnect) - (a/go - (let [msgbus-fn (:msgbus @wsp) - profile-id (::profile-id @wsp) - session-id (::session-id @wsp) - profile-ch (::profile-subs-channel @wsp) - subs (::subscriptions @wsp)] + [cfg wsp _] + (let [msgbus-fn (:msgbus cfg) + conn-id (::ws/id @wsp) + profile-id (::profile-id @wsp) + session-id (::session-id @wsp) + profile-ch (::profile-subscription @wsp) + fsub (::file-subscription @wsp) + tsub (::team-subscription @wsp) + message {:type :disconnect + :subs-id profile-id + :profile-id profile-id + :session-id session-id}] + + (l/trace :fn "handle-message" + :event :disconnect + :conn-id conn-id) + + (a/go ;; Close the main profile subscription (a/close! profile-ch) (a/! output-ch message) - (recur))) + (l/trace :fn "handle-message" + :event :subscribe-team + :team-id team-id + :conn-id conn-id) + + (a/pipe channel output-ch false) + + (let [state {:team-id team-id :channel channel :topic team-id}] + (swap! wsp assoc ::team-subscription state)) + + (a/go + ;; Close previous subscription if exists + (when-let [channel (:channel prev-subs)] + (a/close! channel) + (a/! output-ch message) + (recur)))) (a/go ;; Subscribe to file topic @@ -134,6 +237,7 @@ ;; Notifify the rest of participants of the new connection. (let [message {:type :join-file :file-id file-id + :subs-id file-id :session-id session-id :profile-id profile-id}] (a/ message - (dissoc :subs-id) - (assoc :profile-id profile-id) - (assoc :session-id session-id))] - + [cfg wsp {:keys [file-id] :as message}] + (let [msgbus-fn (:msgbus cfg) + profile-id (::profile-id @wsp) + session-id (::session-id @wsp) + subs (::file-subscription @wsp) + message (-> message + (assoc :subs-id file-id) + (assoc :profile-id profile-id) + (assoc :session-id session-id))] + (a/go + ;; Only allow receive pointer updates when active subscription + (when subs (a/ cfg - (assoc ::profile-id profile-id) - (assoc ::session-id session-id))] - - (l/trace :hint "http request to websocket" :profile-id profile-id :session-id session-id) + (let [{:keys [session-id]} (us/conform ::handler-params params)] (cond (not profile-id) (raise (ex/error :type :authentication @@ -218,6 +327,15 @@ :hint "this endpoint only accepts websocket connections")) :else - (->> (ws/handler handle-message cfg) - (yws/upgrade req) - (respond)))))) + (do + (l/trace :hint "websocket request" :profile-id profile-id :session-id session-id) + + (->> (ws/handler + ::ws/on-rcv-message (partial on-rcv-message cfg) + ::ws/on-snd-message (partial on-snd-message cfg) + ::ws/on-connect (partial on-connect cfg) + ::ws/handler (partial handle-message cfg) + ::profile-id profile-id + ::session-id session-id) + (yws/upgrade req) + (respond))))))) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index bb8b784ec4..dc1289a094 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -257,12 +257,16 @@ (ex/raise :type :internal :code :task-not-configured :hint "archive task not configured, missing uri")) + (when enabled - (loop [] - (let [res (archive-events cfg)] - (when (= res :continue) - (aa/thread-sleep 200) - (recur)))))))) + (loop [total 0] + (let [n (archive-events cfg)] + (if n + (do + (aa/thread-sleep 200) + (recur (+ total n))) + (when (pos? total) + (l/trace :hint "events chunk archived" :num total))))))))) (def sql:retrieve-batch-of-audit-log "select * from audit_log @@ -332,7 +336,7 @@ (l/debug :action "archive-events" :uri uri :events (count events)) (when (send events) (mark-as-archived conn rows) - :continue)))))) + (count events))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; GC Task diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 9fe00155ed..7700c89859 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -6,6 +6,7 @@ (ns app.main (:require + [app.auth.oidc] [app.common.logging :as l] [app.config :as cf] [app.util.time :as dt] @@ -71,11 +72,14 @@ :app.tokens/tokens {:keys (ig/ref :app.setup/keys)} + :app.storage.tmp/cleaner + {:executor (ig/ref [::worker :app.worker/executor]) + :scheduler (ig/ref :app.worker/scheduler)} + :app.storage/gc-deleted-task {:pool (ig/ref :app.db/pool) :storage (ig/ref :app.storage/storage) - :executor (ig/ref [::worker :app.worker/executor]) - :min-age (dt/duration {:hours 2})} + :executor (ig/ref [::worker :app.worker/executor])} :app.storage/gc-touched-task {:pool (ig/ref :app.db/pool)} @@ -93,15 +97,7 @@ :app.http.session/gc-task {:pool (ig/ref :app.db/pool) - :max-age (cf/get :http-session-idle-max-age)} - - :app.http.session/updater - {:pool (ig/ref :app.db/pool) - :metrics (ig/ref :app.metrics/metrics) - :executor (ig/ref [::worker :app.worker/executor]) - :session (ig/ref :app.http/session) - :max-batch-age (cf/get :http-session-updater-batch-max-age) - :max-batch-size (cf/get :http-session-updater-batch-max-size)} + :max-age (cf/get :auth-token-cookie-max-age)} :app.http.awsns/handler {:tokens (ig/ref :app.tokens/tokens) @@ -119,25 +115,88 @@ :max-body-size (cf/get :http-server-max-body-size) :max-multipart-body-size (cf/get :http-server-max-multipart-body-size)} + :app.auth.ldap/provider + {:host (cf/get :ldap-host) + :port (cf/get :ldap-port) + :ssl (cf/get :ldap-ssl) + :tls (cf/get :ldap-starttls) + :query (cf/get :ldap-user-query) + :attrs-email (cf/get :ldap-attrs-email) + :attrs-fullname (cf/get :ldap-attrs-fullname) + :attrs-username (cf/get :ldap-attrs-username) + :base-dn (cf/get :ldap-base-dn) + :bind-dn (cf/get :ldap-bind-dn) + :bind-password (cf/get :ldap-bind-password) + :enabled? (contains? cf/flags :login-with-ldap)} + + :app.auth.oidc/google-provider + {:enabled? (contains? cf/flags :login-with-google) + :client-id (cf/get :google-client-id) + :client-secret (cf/get :google-client-secret)} + + :app.auth.oidc/github-provider + {:enabled? (contains? cf/flags :login-with-github) + :http-client (ig/ref :app.http/client) + :client-id (cf/get :github-client-id) + :client-secret (cf/get :github-client-secret)} + + :app.auth.oidc/gitlab-provider + {:enabled? (contains? cf/flags :login-with-gitlab) + :base-uri (cf/get :gitlab-base-uri "https://gitlab.com") + :client-id (cf/get :gitlab-client-id) + :client-secret (cf/get :gitlab-client-secret)} + + :app.auth.oidc/generic-provider + {:enabled? (contains? cf/flags :login-with-oidc) + :http-client (ig/ref :app.http/client) + + :client-id (cf/get :oidc-client-id) + :client-secret (cf/get :oidc-client-secret) + + :base-uri (cf/get :oidc-base-uri) + + :token-uri (cf/get :oidc-token-uri) + :auth-uri (cf/get :oidc-auth-uri) + :user-uri (cf/get :oidc-user-uri) + + :scopes (cf/get :oidc-scopes) + :roles-attr (cf/get :oidc-roles-attr) + :roles (cf/get :oidc-roles)} + + :app.auth.oidc/routes + {:providers {:google (ig/ref :app.auth.oidc/google-provider) + :github (ig/ref :app.auth.oidc/github-provider) + :gitlab (ig/ref :app.auth.oidc/gitlab-provider) + :oidc (ig/ref :app.auth.oidc/generic-provider)} + :tokens (ig/ref :app.tokens/tokens) + :http-client (ig/ref :app.http/client) + :pool (ig/ref :app.db/pool) + :session (ig/ref :app.http/session) + :public-uri (cf/get :public-uri) + :executor (ig/ref [::default :app.worker/executor])} + :app.http/router {:assets (ig/ref :app.http.assets/handlers) :feedback (ig/ref :app.http.feedback/handler) :session (ig/ref :app.http/session) :awsns-handler (ig/ref :app.http.awsns/handler) - :oauth (ig/ref :app.http.oauth/handler) - :debug (ig/ref :app.http.debug/handlers) + :debug-routes (ig/ref :app.http.debug/routes) + :oidc-routes (ig/ref :app.auth.oidc/routes) :ws (ig/ref :app.http.websocket/handler) :metrics (ig/ref :app.metrics/metrics) :public-uri (cf/get :public-uri) :storage (ig/ref :app.storage/storage) :tokens (ig/ref :app.tokens/tokens) :audit-handler (ig/ref :app.loggers.audit/http-handler) - :rpc (ig/ref :app.rpc/rpc) + :rpc-routes (ig/ref :app.rpc/routes) + :doc-routes (ig/ref :app.rpc.doc/routes) :executor (ig/ref [::default :app.worker/executor])} - :app.http.debug/handlers - {:pool (ig/ref :app.db/pool) - :executor (ig/ref [::worker :app.worker/executor])} + :app.http.debug/routes + {:pool (ig/ref :app.db/pool) + :executor (ig/ref [::worker :app.worker/executor]) + :storage (ig/ref :app.storage/storage) + :session (ig/ref :app.http/session)} :app.http.websocket/handler {:pool (ig/ref :app.db/pool) @@ -156,17 +215,7 @@ {:pool (ig/ref :app.db/pool) :executor (ig/ref [::default :app.worker/executor])} - :app.http.oauth/handler - {:rpc (ig/ref :app.rpc/rpc) - :session (ig/ref :app.http/session) - :pool (ig/ref :app.db/pool) - :tokens (ig/ref :app.tokens/tokens) - :audit (ig/ref :app.loggers.audit/collector) - :executor (ig/ref [::default :app.worker/executor]) - :http-client (ig/ref :app.http/client) - :public-uri (cf/get :public-uri)} - - :app.rpc/rpc + :app.rpc/methods {:pool (ig/ref :app.db/pool) :session (ig/ref :app.http/session) :tokens (ig/ref :app.tokens/tokens) @@ -175,56 +224,15 @@ :msgbus (ig/ref :app.msgbus/msgbus) :public-uri (cf/get :public-uri) :audit (ig/ref :app.loggers.audit/collector) + :ldap (ig/ref :app.auth.ldap/provider) :http-client (ig/ref :app.http/client) :executors (ig/ref :app.worker/executors)} - :app.worker/worker - {:executor (ig/ref [::worker :app.worker/executor]) - :tasks (ig/ref :app.worker/registry) - :metrics (ig/ref :app.metrics/metrics) - :pool (ig/ref :app.db/pool)} + :app.rpc.doc/routes + {:methods (ig/ref :app.rpc/methods)} - :app.worker/cron - {:executor (ig/ref [::worker :app.worker/executor]) - :scheduler (ig/ref :app.worker/scheduler) - :tasks (ig/ref :app.worker/registry) - :pool (ig/ref :app.db/pool) - :entries - [{:cron #app/cron "0 0 0 * * ?" ;; daily - :task :file-gc} - - {:cron #app/cron "0 0 * * * ?" ;; hourly - :task :file-xlog-gc} - - {:cron #app/cron "0 0 0 * * ?" ;; daily - :task :storage-deleted-gc} - - {:cron #app/cron "0 0 0 * * ?" ;; daily - :task :storage-touched-gc} - - {:cron #app/cron "0 0 0 * * ?" ;; daily - :task :session-gc} - - {:cron #app/cron "0 0 0 * * ?" ;; daily - :task :objects-gc} - - {:cron #app/cron "0 0 0 * * ?" ;; daily - :task :tasks-gc} - - {:cron #app/cron "0 30 */3,23 * * ?" - :task :telemetry} - - (when (cf/get :fdata-storage-backed) - {:cron #app/cron "0 0 * * * ?" ;; hourly - :task :file-offload}) - - (when (contains? cf/flags :audit-log-archive) - {:cron #app/cron "0 */5 * * * ?" ;; every 5m - :task :audit-log-archive}) - - (when (contains? cf/flags :audit-log-gc) - {:cron #app/cron "0 0 0 * * ?" ;; daily - :task :audit-log-gc})]} + :app.rpc/routes + {:methods (ig/ref :app.rpc/methods)} :app.worker/registry {:metrics (ig/ref :app.metrics/metrics) @@ -233,12 +241,11 @@ :objects-gc (ig/ref :app.tasks.objects-gc/handler) :file-gc (ig/ref :app.tasks.file-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-gc-deleted (ig/ref :app.storage/gc-deleted-task) + :storage-gc-touched (ig/ref :app.storage/gc-touched-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) - :file-offload (ig/ref :app.tasks.file-offload/handler) :audit-log-archive (ig/ref :app.loggers.audit/archive-task) :audit-log-gc (ig/ref :app.loggers.audit/gc-task)}} @@ -259,22 +266,13 @@ :app.tasks.objects-gc/handler {:pool (ig/ref :app.db/pool) - :storage (ig/ref :app.storage/storage) - :max-age cf/deletion-delay} + :storage (ig/ref :app.storage/storage)} :app.tasks.file-gc/handler - {:pool (ig/ref :app.db/pool) - :max-age cf/deletion-delay} + {:pool (ig/ref :app.db/pool)} :app.tasks.file-xlog-gc/handler - {:pool (ig/ref :app.db/pool) - :max-age (dt/duration {:hours 72})} - - :app.tasks.file-offload/handler - {:pool (ig/ref :app.db/pool) - :max-age (dt/duration {:seconds 5}) - :storage (ig/ref :app.storage/storage) - :backend (cf/get :fdata-storage-backed :fdata-s3)} + {:pool (ig/ref :app.db/pool)} :app.tasks.telemetry/handler {:pool (ig/ref :app.db/pool) @@ -336,23 +334,12 @@ :backends {:assets-s3 (ig/ref [::assets :app.storage.s3/backend]) - :assets-db (ig/ref [::assets :app.storage.db/backend]) :assets-fs (ig/ref [::assets :app.storage.fs/backend]) - :tmp (ig/ref [::tmp :app.storage.fs/backend]) - :fdata-s3 (ig/ref [::fdata :app.storage.s3/backend]) - ;; keep this for backward compatibility :s3 (ig/ref [::assets :app.storage.s3/backend]) :fs (ig/ref [::assets :app.storage.fs/backend])}} - [::fdata :app.storage.s3/backend] - {:region (cf/get :storage-fdata-s3-region) - :bucket (cf/get :storage-fdata-s3-bucket) - :endpoint (cf/get :storage-fdata-s3-endpoint) - :prefix (cf/get :storage-fdata-s3-prefix) - :executor (ig/ref [::default :app.worker/executor])} - [::assets :app.storage.s3/backend] {:region (cf/get :storage-assets-s3-region) :endpoint (cf/get :storage-assets-s3-endpoint) @@ -361,21 +348,64 @@ [::assets :app.storage.fs/backend] {:directory (cf/get :storage-assets-fs-directory)} + }) - [::tmp :app.storage.fs/backend] - {:directory "/tmp/penpot"} - [::assets :app.storage.db/backend] - {:pool (ig/ref :app.db/pool)}}) +(def worker-config + { :app.worker/cron + {:executor (ig/ref [::worker :app.worker/executor]) + :scheduler (ig/ref :app.worker/scheduler) + :tasks (ig/ref :app.worker/registry) + :pool (ig/ref :app.db/pool) + :entries + [{:cron #app/cron "0 0 0 * * ?" ;; daily + :task :file-gc} + + {:cron #app/cron "0 0 * * * ?" ;; hourly + :task :file-xlog-gc} + + {:cron #app/cron "0 0 0 * * ?" ;; daily + :task :storage-gc-deleted} + + {:cron #app/cron "0 0 0 * * ?" ;; daily + :task :storage-gc-touched} + + {:cron #app/cron "0 0 0 * * ?" ;; daily + :task :session-gc} + + {:cron #app/cron "0 0 0 * * ?" ;; daily + :task :objects-gc} + + {:cron #app/cron "0 0 0 * * ?" ;; daily + :task :tasks-gc} + + {:cron #app/cron "0 30 */3,23 * * ?" + :task :telemetry} + + (when (contains? cf/flags :audit-log-archive) + {:cron #app/cron "0 */5 * * * ?" ;; every 5m + :task :audit-log-archive}) + + (when (contains? cf/flags :audit-log-gc) + {:cron #app/cron "0 0 0 * * ?" ;; daily + :task :audit-log-gc})]} + + :app.worker/worker + {:executor (ig/ref [::worker :app.worker/executor]) + :tasks (ig/ref :app.worker/registry) + :metrics (ig/ref :app.metrics/metrics) + :pool (ig/ref :app.db/pool)}}) (def system nil) (defn start [] - (ig/load-namespaces system-config) + (ig/load-namespaces (merge system-config worker-config)) (alter-var-root #'system (fn [sys] (when sys (ig/halt! sys)) (-> system-config + (cond-> (contains? cf/flags :backend-worker) + (merge worker-config)) (ig/prep) (ig/init)))) (l/info :msg "welcome to penpot" diff --git a/backend/src/app/media.clj b/backend/src/app/media.clj index 3814d94732..99cbe15cdd 100644 --- a/backend/src/app/media.clj +++ b/backend/src/app/media.clj @@ -12,18 +12,16 @@ [app.common.media :as cm] [app.common.spec :as us] [app.config :as cf] + [app.storage.tmp :as tmp] + [app.util.bytes :as bs] [app.util.svg :as svg] [buddy.core.bytes :as bb] [buddy.core.codecs :as bc] - [clojure.java.io :as io] [clojure.java.shell :as sh] [clojure.spec.alpha :as s] [cuerdas.core :as str] [datoteka.core :as fs]) (:import - java.io.ByteArrayInputStream - java.io.OutputStream - org.apache.commons.io.IOUtils org.im4java.core.ConvertCmd org.im4java.core.IMOperation org.im4java.core.Info)) @@ -93,18 +91,16 @@ (let [{:keys [path mtype]} input format (or (cm/mtype->format mtype) format) ext (cm/format->extension format) - tmp (fs/create-tempfile :suffix ext)] + tmp (tmp/tempfile :prefix "penpot.media." :suffix ext)] (doto (ConvertCmd.) (.run operation (into-array (map str [path tmp])))) - (let [thumbnail-data (fs/slurp-bytes tmp)] - (fs/delete tmp) - (assoc params - :format format - :mtype (cm/format->mtype format) - :size (alength ^bytes thumbnail-data) - :data (ByteArrayInputStream. thumbnail-data))))) + (assoc params + :format format + :mtype (cm/format->mtype format) + :size (fs/size tmp) + :data tmp))) (defmethod process :generic-thumbnail [{:keys [quality width height] :as params}] @@ -201,59 +197,54 @@ (defmethod process :generate-fonts [{:keys [input] :as params}] (letfn [(ttf->otf [data] - (let [input-file (fs/create-tempfile :prefix "penpot") - output-file (fs/path (str input-file ".otf")) - _ (with-open [out (io/output-stream input-file)] - (IOUtils/writeChunked ^bytes data ^OutputStream out) - (.flush ^OutputStream out)) - res (sh/sh "fontforge" "-lang=ff" "-c" - (str/fmt "Open('%s'); Generate('%s')" - (str input-file) - (str output-file)))] + (let [finput (tmp/tempfile :prefix "penpot.font." :suffix "") + foutput (fs/path (str finput ".otf")) + _ (bs/write-to-file! data finput) + res (sh/sh "fontforge" "-lang=ff" "-c" + (str/fmt "Open('%s'); Generate('%s')" + (str finput) + (str foutput)))] (when (zero? (:exit res)) - (fs/slurp-bytes output-file)))) - + foutput))) (otf->ttf [data] - (let [input-file (fs/create-tempfile :prefix "penpot") - output-file (fs/path (str input-file ".ttf")) - _ (with-open [out (io/output-stream input-file)] - (IOUtils/writeChunked ^bytes data ^OutputStream out) - (.flush ^OutputStream out)) - res (sh/sh "fontforge" "-lang=ff" "-c" - (str/fmt "Open('%s'); Generate('%s')" - (str input-file) - (str output-file)))] + (let [finput (tmp/tempfile :prefix "penpot.font." :suffix "") + foutput (fs/path (str finput ".ttf")) + _ (bs/write-to-file! data finput) + res (sh/sh "fontforge" "-lang=ff" "-c" + (str/fmt "Open('%s'); Generate('%s')" + (str finput) + (str foutput)))] (when (zero? (:exit res)) - (fs/slurp-bytes output-file)))) + foutput))) (ttf-or-otf->woff [data] - (let [input-file (fs/create-tempfile :prefix "penpot" :suffix "") - output-file (fs/path (str input-file ".woff")) - _ (with-open [out (io/output-stream input-file)] - (IOUtils/writeChunked ^bytes data ^OutputStream out) - (.flush ^OutputStream out)) - res (sh/sh "sfnt2woff" (str input-file))] + ;; NOTE: foutput is not used directly, it represents the + ;; default output of the exection of the underlying + ;; command. + (let [finput (tmp/tempfile :prefix "penpot.font." :suffix "") + foutput (fs/path (str finput ".woff")) + _ (bs/write-to-file! data finput) + res (sh/sh "sfnt2woff" (str finput))] (when (zero? (:exit res)) - (fs/slurp-bytes output-file)))) + foutput))) (ttf-or-otf->woff2 [data] - (let [input-file (fs/create-tempfile :prefix "penpot" :suffix "") - output-file (fs/path (str input-file ".woff2")) - _ (with-open [out (io/output-stream input-file)] - (IOUtils/writeChunked ^bytes data ^OutputStream out) - (.flush ^OutputStream out)) - res (sh/sh "woff2_compress" (str input-file))] + ;; NOTE: foutput is not used directly, it represents the + ;; default output of the exection of the underlying + ;; command. + (let [finput (tmp/tempfile :prefix "penpot.font." :suffix ".tmp") + foutput (fs/path (str (fs/base finput) ".woff2")) + _ (bs/write-to-file! data finput) + res (sh/sh "woff2_compress" (str finput))] (when (zero? (:exit res)) - (fs/slurp-bytes output-file)))) + foutput))) (woff->sfnt [data] - (let [input-file (fs/create-tempfile :prefix "penpot" :suffix "") - _ (with-open [out (io/output-stream input-file)] - (IOUtils/writeChunked ^bytes data ^OutputStream out) - (.flush ^OutputStream out)) - res (sh/sh "woff2sfnt" (str input-file) - :out-enc :bytes)] + (let [finput (tmp/tempfile :prefix "penpot" :suffix "") + _ (bs/write-to-file! data finput) + res (sh/sh "woff2sfnt" (str finput) + :out-enc :bytes)] (when (zero? (:exit res)) (:out res)))) diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index bc9f6c94f8..3857f8e711 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -6,7 +6,7 @@ (ns app.migrations (:require - [app.migrations.migration-0023 :as mg0023] + [app.migrations.clj.migration-0023 :as mg0023] [app.util.migrations :as mg] [integrant.core :as ig])) @@ -226,6 +226,24 @@ {:name "0072-mod-file-object-thumbnail-table" :fn (mg/resource "app/migrations/sql/0072-mod-file-object-thumbnail-table.sql")} + + {:name "0073-mod-file-media-object-constraints" + :fn (mg/resource "app/migrations/sql/0073-mod-file-media-object-constraints.sql")} + + {:name "0074-mod-file-library-rel-constraints" + :fn (mg/resource "app/migrations/sql/0074-mod-file-library-rel-constraints.sql")} + + {:name "0075-mod-share-link-table" + :fn (mg/resource "app/migrations/sql/0075-mod-share-link-table.sql")} + + {:name "0076-mod-storage-object-table" + :fn (mg/resource "app/migrations/sql/0076-mod-storage-object-table.sql")} + + {:name "0077-mod-comment-thread-table" + :fn (mg/resource "app/migrations/sql/0077-mod-comment-thread-table.sql")} + + {:name "0078-mod-file-media-object-table-drop-cascade" + :fn (mg/resource "app/migrations/sql/0078-mod-file-media-object-table-drop-cascade.sql")} ]) diff --git a/backend/src/app/migrations/migration_0023.clj b/backend/src/app/migrations/clj/migration_0023.clj similarity index 97% rename from backend/src/app/migrations/migration_0023.clj rename to backend/src/app/migrations/clj/migration_0023.clj index 6f66a79989..ef046a856a 100644 --- a/backend/src/app/migrations/migration_0023.clj +++ b/backend/src/app/migrations/clj/migration_0023.clj @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.migrations.migration-0023 +(ns app.migrations.clj.migration-0023 (:require [app.db :as db] [app.util.blob :as blob])) diff --git a/backend/src/app/migrations/sql/0073-mod-file-media-object-constraints.sql b/backend/src/app/migrations/sql/0073-mod-file-media-object-constraints.sql new file mode 100644 index 0000000000..fd17ab4dd2 --- /dev/null +++ b/backend/src/app/migrations/sql/0073-mod-file-media-object-constraints.sql @@ -0,0 +1,11 @@ +ALTER TABLE file_media_object +ALTER CONSTRAINT file_media_object_media_id_fkey DEFERRABLE INITIALLY IMMEDIATE; + +ALTER TABLE file_media_object +ALTER CONSTRAINT file_media_object_thumbnail_id_fkey DEFERRABLE INITIALLY IMMEDIATE; + +ALTER TABLE file_media_object +RENAME CONSTRAINT media_object_file_id_fkey TO file_media_object_file_id_fkey; + +ALTER TABLE file_media_object +ALTER CONSTRAINT file_media_object_file_id_fkey DEFERRABLE INITIALLY IMMEDIATE; diff --git a/backend/src/app/migrations/sql/0074-mod-file-library-rel-constraints.sql b/backend/src/app/migrations/sql/0074-mod-file-library-rel-constraints.sql new file mode 100644 index 0000000000..f7ed7eb856 --- /dev/null +++ b/backend/src/app/migrations/sql/0074-mod-file-library-rel-constraints.sql @@ -0,0 +1,5 @@ +ALTER TABLE file_library_rel +ALTER CONSTRAINT file_library_rel_file_id_fkey DEFERRABLE INITIALLY IMMEDIATE; + +ALTER TABLE file_library_rel +ALTER CONSTRAINT file_library_rel_library_file_id_fkey DEFERRABLE INITIALLY IMMEDIATE; diff --git a/backend/src/app/migrations/sql/0075-mod-share-link-table.sql b/backend/src/app/migrations/sql/0075-mod-share-link-table.sql new file mode 100644 index 0000000000..d291543fe3 --- /dev/null +++ b/backend/src/app/migrations/sql/0075-mod-share-link-table.sql @@ -0,0 +1,5 @@ +ALTER TABLE share_link + ADD COLUMN who_comment text NOT NULL DEFAULT('team'), + ADD COLUMN who_inspect text NOT NULL DEFAULT('team'); + +--- TODO: remove flags column in 1.15.x diff --git a/backend/src/app/migrations/sql/0076-mod-storage-object-table.sql b/backend/src/app/migrations/sql/0076-mod-storage-object-table.sql new file mode 100644 index 0000000000..6b64ea378c --- /dev/null +++ b/backend/src/app/migrations/sql/0076-mod-storage-object-table.sql @@ -0,0 +1,10 @@ +-- Renames the old, already deprecated backend name with new one on +-- all storage object rows. + +UPDATE storage_object + SET backend = 'assets-fs' + WHERE backend = 'fs'; + +UPDATE storage_object + SET backend = 'assets-s3' + WHERE backend = 's3'; diff --git a/backend/src/app/migrations/sql/0077-mod-comment-thread-table.sql b/backend/src/app/migrations/sql/0077-mod-comment-thread-table.sql new file mode 100644 index 0000000000..7898ca2cf6 --- /dev/null +++ b/backend/src/app/migrations/sql/0077-mod-comment-thread-table.sql @@ -0,0 +1,3 @@ +--- Add frame_id field. +ALTER TABLE comment_thread + ADD COLUMN frame_id uuid NULL DEFAULT '00000000-0000-0000-0000-000000000000'; diff --git a/backend/src/app/migrations/sql/0078-mod-file-media-object-table-drop-cascade.sql b/backend/src/app/migrations/sql/0078-mod-file-media-object-table-drop-cascade.sql new file mode 100644 index 0000000000..1fe423046d --- /dev/null +++ b/backend/src/app/migrations/sql/0078-mod-file-media-object-table-drop-cascade.sql @@ -0,0 +1,9 @@ +ALTER TABLE file_media_object + DROP CONSTRAINT file_media_object_media_id_fkey, + ADD CONSTRAINT file_media_object_media_id_fkey + FOREIGN KEY (media_id) REFERENCES storage_object(id) ON DELETE NO ACTION DEFERRABLE; + +ALTER TABLE file_media_object + DROP CONSTRAINT file_media_object_thumbnail_id_fkey, + ADD CONSTRAINT file_media_object_thumbnail_id_fkey + FOREIGN KEY (thumbnail_id) REFERENCES storage_object(id) ON DELETE NO ACTION DEFERRABLE; diff --git a/backend/src/app/msgbus.clj b/backend/src/app/msgbus.clj index 4bae83abd3..e14bf9e126 100644 --- a/backend/src/app/msgbus.clj +++ b/backend/src/app/msgbus.clj @@ -160,7 +160,6 @@ "Function responsible to attach local subscription to the state. Intended to be used in agent." [state cfg topics chan done-ch] - (l/trace :hint "subscribe-to-topics" :topics topics ::l/async false) (aa/with-closing done-ch (let [state (update state :chans assoc chan topics)] (reduce (fn [state topic] @@ -184,15 +183,15 @@ useful when client disconnects or in-bulk unsubscribe operations. Intended to be executed in agent." [state cfg channels done-ch] - (l/trace :hint "unsubscribe-channels" :chans (count channels) ::l/async false) (aa/with-closing done-ch (reduce #(unsubscribe-single-channel %1 cfg %2) state channels))) + (defn- subscribe [{:keys [::state executor] :as cfg} {:keys [topic topics chan]}] (let [done-ch (a/chan) topics (into [] (map prefix-topic) (if topic [topic] topics))] - (l/trace :hint "subscribe" :topics topics) + (l/debug :hint "subscribe" :topics topics) (send-via executor state subscribe-to-topics cfg topics chan done-ch) done-ch)) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index a737028cea..804a624121 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -86,6 +86,30 @@ (let [context {:profile-id profile-id}] (raise (ex/wrap-with-context cause context))))))))) +(defn- rpc-command-handler + "Ring handler that dispatches cmd requests and convert between + internal async flow into ring async flow." + [methods {:keys [profile-id session-id params] :as request} respond raise] + (letfn [(handle-response [result] + (let [mdata (meta result)] + (p/-> (yrs/response 200 result) + (handle-response-transformation request mdata) + (handle-before-comple-hook mdata))))] + + (let [cmd (keyword (:command params)) + data (into {::request request} params) + data (if profile-id + (assoc data :profile-id profile-id ::session-id session-id) + (dissoc data :profile-id)) + + method (get methods cmd default-handler)] + (-> (method data) + (p/then handle-response) + (p/then respond) + (p/catch (fn [cause] + (let [context {:profile-id profile-id}] + (raise (ex/wrap-with-context cause context))))))))) + (defn- wrap-metrics "Wrap service method with metrics measurement." [{:keys [metrics ::metrics-id]} f mdata] @@ -162,7 +186,7 @@ spec (or (::sv/spec mdata) (s/spec any?)) auth? (:auth mdata true)] - (l/trace :action "register" :name (::sv/name mdata)) + (l/debug :hint "register method" :name (::sv/name mdata)) (with-meta (fn [{:keys [::request] :as params}] ;; Raise authentication error when rpc method requires auth but @@ -180,8 +204,9 @@ (defn- process-method [cfg vfn] (let [mdata (meta vfn)] + ;; (prn mdata) [(keyword (::sv/name mdata)) - (wrap cfg (deref vfn) mdata)])) + (wrap cfg vfn mdata)])) (defn- resolve-query-methods [cfg] @@ -199,35 +224,82 @@ (defn- resolve-mutation-methods [cfg] (let [cfg (assoc cfg ::type "mutation" ::metrics-id :rpc-mutation-timing)] - (->> (sv/scan-ns 'app.rpc.mutations.demo - 'app.rpc.mutations.media + (->> (sv/scan-ns 'app.rpc.mutations.media 'app.rpc.mutations.profile 'app.rpc.mutations.files 'app.rpc.mutations.comments 'app.rpc.mutations.projects 'app.rpc.mutations.teams 'app.rpc.mutations.management - 'app.rpc.mutations.ldap 'app.rpc.mutations.fonts 'app.rpc.mutations.share-link 'app.rpc.mutations.verify-token) (map (partial process-method cfg)) (into {})))) -(s/def ::storage some?) -(s/def ::session map?) -(s/def ::tokens fn?) +(defn- resolve-command-methods + [cfg] + (let [cfg (assoc cfg ::type "command" ::metrics-id :rpc-command-timing)] + (->> (sv/scan-ns 'app.rpc.commands.binfile + 'app.rpc.commands.comments + 'app.rpc.commands.auth + 'app.rpc.commands.ldap + 'app.rpc.commands.demo) + (map (partial process-method cfg)) + (into {})))) + (s/def ::audit (s/nilable fn?)) (s/def ::executors (s/map-of keyword? ::wrk/executor)) +(s/def ::executors map?) +(s/def ::http-client fn?) +(s/def ::ldap (s/nilable map?)) +(s/def ::msgbus fn?) +(s/def ::public-uri ::us/not-empty-string) +(s/def ::session map?) +(s/def ::storage some?) +(s/def ::tokens fn?) -(defmethod ig/pre-init-spec ::rpc [_] - (s/keys :req-un [::storage ::session ::tokens ::audit - ::executors ::mtx/metrics ::db/pool])) +(defmethod ig/pre-init-spec ::methods [_] + (s/keys :req-un [::storage + ::session + ::tokens + ::audit + ::executors + ::public-uri + ::msgbus + ::http-client + ::mtx/metrics + ::db/pool + ::ldap])) -(defmethod ig/init-key ::rpc +(defmethod ig/init-key ::methods [_ cfg] - (let [mq (resolve-query-methods cfg) - mm (resolve-mutation-methods cfg)] - {:methods {:query mq :mutation mm} - :query-handler (partial rpc-query-handler mq) - :mutation-handler (partial rpc-mutation-handler mm)})) + {:mutations (resolve-mutation-methods cfg) + :queries (resolve-query-methods cfg) + :commands (resolve-command-methods cfg)}) + +(s/def ::mutations + (s/map-of keyword? fn?)) + +(s/def ::queries + (s/map-of keyword? fn?)) + +(s/def ::commands + (s/map-of keyword? fn?)) + +(s/def ::methods + (s/keys :req-un [::mutations + ::queries + ::commands])) + +(defmethod ig/pre-init-spec ::routes [_] + (s/keys :req-un [::methods])) + +(defmethod ig/init-key ::routes + [_ {:keys [methods] :as cfg}] + [["/rpc" + ["/command/:command" {:handler (partial rpc-command-handler (:commands methods))}] + ["/query/:type" {:handler (partial rpc-query-handler (:queries methods))}] + ["/mutation/:type" {:handler (partial rpc-mutation-handler (:mutations methods)) + :allowed-methods #{:post}}]]]) + diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj new file mode 100644 index 0000000000..8192e9f612 --- /dev/null +++ b/backend/src/app/rpc/commands/auth.clj @@ -0,0 +1,428 @@ +;; 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.commands.auth + (:require + [app.common.exceptions :as ex] + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.config :as cf] + [app.db :as db] + [app.emails :as eml] + [app.loggers.audit :as audit] + [app.rpc.doc :as-alias doc] + [app.rpc.mutations.teams :as teams] + [app.rpc.queries.profile :as profile] + [app.rpc.rlimit :as rlimit] + [app.util.services :as sv] + [app.util.time :as dt] + [buddy.hashers :as hashers] + [clojure.spec.alpha :as s] + [cuerdas.core :as str])) + +(s/def ::email ::us/email) +(s/def ::fullname ::us/not-empty-string) +(s/def ::lang ::us/string) +(s/def ::path ::us/string) +(s/def ::profile-id ::us/uuid) +(s/def ::password ::us/not-empty-string) +(s/def ::old-password ::us/not-empty-string) +(s/def ::theme ::us/string) +(s/def ::invitation-token ::us/not-empty-string) +(s/def ::token ::us/not-empty-string) + +;; ---- HELPERS + +(defn derive-password + [password] + (hashers/derive password + {:alg :argon2id + :memory 16384 + :iterations 20 + :parallelism 2})) + +(defn verify-password + [attempt password] + (try + (hashers/verify attempt password) + (catch Exception _e + {:update false + :valid false}))) + +(defn email-domain-in-whitelist? + "Returns true if email's domain is in the given whitelist or if + given whitelist is an empty string." + [domains email] + (if (or (empty? domains) + (nil? domains)) + true + (let [[_ candidate] (-> (str/lower email) + (str/split #"@" 2))] + (contains? domains candidate)))) + +(def ^:private sql:profile-existence + "select exists (select * from profile + where email = ? + and deleted_at is null) as val") + +(defn check-profile-existence! + [conn {:keys [email] :as params}] + (let [email (str/lower email) + result (db/exec-one! conn [sql:profile-existence email])] + (when (:val result) + (ex/raise :type :validation + :code :email-already-exists)) + params)) + +;; ---- COMMAND: login with password + +(defn login-with-password + [{:keys [pool session tokens] :as cfg} {:keys [email password] :as params}] + + (when-not (contains? cf/flags :login) + (ex/raise :type :restriction + :code :login-disabled + :hint "login is disabled in this instance")) + + (letfn [(check-password [profile password] + (when (= (:password profile) "!") + (ex/raise :type :validation + :code :account-without-password + :hint "the current account does not have password")) + (:valid (verify-password password (:password profile)))) + + (validate-profile [profile] + (when-not (:is-active profile) + (ex/raise :type :validation + :code :wrong-credentials)) + (when-not profile + (ex/raise :type :validation + :code :wrong-credentials)) + (when-not (check-password profile password) + (ex/raise :type :validation + :code :wrong-credentials)) + profile)] + + (db/with-atomic [conn pool] + (let [profile (->> (profile/retrieve-profile-data-by-email conn email) + (validate-profile) + (profile/strip-private-attrs) + (profile/populate-additional-data conn) + (profile/decode-profile-row)) + + invitation (when-let [token (:invitation-token params)] + (tokens :verify {:token token :iss :team-invitation})) + + ;; If invitation member-id does not matches the profile-id, we just proceed to ignore the + ;; invitation because invitations matches exactly; and user can't loging with other email and + ;; accept invitation with other email + response (if (and (some? invitation) (= (:id profile) (:member-id invitation))) + {:invitation-token (:invitation-token params)} + profile)] + + (with-meta response + {:transform-response ((:create session) (:id profile)) + ::audit/props (audit/profile->props profile) + ::audit/profile-id (:id profile)}))))) + +(s/def ::login-with-password + (s/keys :req-un [::email ::password] + :opt-un [::invitation-token])) + +(sv/defmethod ::login-with-password + "Performs authentication using penpot password." + {:auth false + ::rlimit/permits (cf/get :rlimit-password) + ::doc/added "1.15"} + [cfg params] + (login-with-password cfg params)) + +;; ---- COMMAND: Logout + +(s/def ::logout + (s/keys :opt-un [::profile-id])) + +(sv/defmethod ::logout + "Clears the authentication cookie and logout the current session." + {:auth false + ::doc/added "1.15"} + [{:keys [session] :as cfg} _] + (with-meta {} + {:transform-response (:delete session)})) + +;; ---- COMMAND: Recover Profile + +(defn recover-profile + [{:keys [pool tokens] :as cfg} {:keys [token password]}] + (letfn [(validate-token [token] + (let [tdata (tokens :verify {:token token :iss :password-recovery})] + (:profile-id tdata))) + + (update-password [conn profile-id] + (let [pwd (derive-password password)] + (db/update! conn :profile {:password pwd} {:id profile-id})))] + + (db/with-atomic [conn pool] + (->> (validate-token token) + (update-password conn)) + nil))) + +(s/def ::token ::us/not-empty-string) +(s/def ::recover-profile + (s/keys :req-un [::token ::password])) + +(sv/defmethod ::recover-profile + {:auth false + ::rlimit/permits (cf/get :rlimit-password) + ::doc/added "1.15"} + [cfg params] + (recover-profile cfg params)) + +;; ---- COMMAND: Prepare Register + +(defn prepare-register + [{:keys [pool tokens] :as cfg} params] + (when-not (contains? cf/flags :registration) + (if-not (contains? params :invitation-token) + (ex/raise :type :restriction + :code :registration-disabled) + (let [invitation (tokens :verify {:token (:invitation-token params) :iss :team-invitation})] + (when-not (= (:email params) (:member-email invitation)) + (ex/raise :type :restriction + :code :email-does-not-match-invitation + :hint "email should match the invitation"))))) + + (when-let [domains (cf/get :registration-domain-whitelist)] + (when-not (email-domain-in-whitelist? domains (:email params)) + (ex/raise :type :validation + :code :email-domain-is-not-allowed))) + + ;; Don't allow proceed in preparing registration if the profile is + ;; already reported as spammer. + (when (eml/has-bounce-reports? pool (:email params)) + (ex/raise :type :validation + :code :email-has-permanent-bounces + :hint "looks like the email has one or many bounces reported")) + + (check-profile-existence! pool params) + + (when (= (str/lower (:email params)) + (str/lower (:password params))) + (ex/raise :type :validation + :code :email-as-password + :hint "you can't use your email as password")) + + (let [params {:email (:email params) + :password (:password params) + :invitation-token (:invitation-token params) + :backend "penpot" + :iss :prepared-register + :exp (dt/in-future "48h")} + + token (tokens :generate params)] + (with-meta {:token token} + {::audit/profile-id uuid/zero}))) + +(s/def ::prepare-register-profile + (s/keys :req-un [::email ::password] + :opt-un [::invitation-token])) + +(sv/defmethod ::prepare-register-profile + {:auth false + ::doc/added "1.15"} + [cfg params] + (prepare-register cfg params)) + +;; ---- COMMAND: Register Profile + +(defn create-profile + "Create the profile entry on the database with limited input filling + all the other fields with defaults." + [conn params] + (let [id (or (:id params) (uuid/next)) + + props (-> (audit/extract-utm-params params) + (merge (:props params)) + (db/tjson)) + + password (if-let [password (:password params)] + (derive-password password) + "!") + + locale (:locale params) + locale (when (and (string? locale) (not (str/blank? locale))) + locale) + + backend (:backend params "penpot") + is-demo (:is-demo params false) + is-muted (:is-muted params false) + is-active (:is-active params false) + email (str/lower (:email params)) + + params {:id id + :fullname (:fullname params) + :email email + :auth-backend backend + :lang locale + :password password + :deleted-at (:deleted-at params) + :props props + :is-active is-active + :is-muted is-muted + :is-demo is-demo}] + (try + (-> (db/insert! conn :profile params) + (profile/decode-profile-row)) + (catch org.postgresql.util.PSQLException e + (let [state (.getSQLState e)] + (if (not= state "23505") + (throw e) + (ex/raise :type :validation + :code :email-already-exists + :cause e))))))) + +(defn create-profile-relations + [conn profile] + (let [team (teams/create-team conn {:profile-id (:id profile) + :name "Default" + :is-default true})] + (-> profile + (profile/strip-private-attrs) + (assoc :default-team-id (:id team)) + (assoc :default-project-id (:default-project-id team))))) + +(defn register-profile + [{:keys [conn tokens session] :as cfg} {:keys [token] :as params}] + (let [claims (tokens :verify {:token token :iss :prepared-register}) + params (merge params claims)] + (check-profile-existence! conn params) + (let [is-active (or (:is-active params) + (contains? cf/flags :insecure-register)) + profile (->> (assoc params :is-active is-active) + (create-profile conn) + (create-profile-relations conn) + (profile/decode-profile-row)) + invitation (when-let [token (:invitation-token params)] + (tokens :verify {:token token :iss :team-invitation}))] + (cond + ;; If invitation token comes in params, this is because the user comes from team-invitation process; + ;; in this case, regenerate token and send back to the user a new invitation token (and mark current + ;; session as logged). This happens only if the invitation email matches with the register email. + (and (some? invitation) (= (:email profile) (:member-email invitation))) + (let [claims (assoc invitation :member-id (:id profile)) + token (tokens :generate claims) + resp {:invitation-token token}] + (with-meta resp + {:transform-response ((:create session) (:id profile)) + ::audit/replace-props (audit/profile->props profile) + ::audit/profile-id (:id profile)})) + + ;; If auth backend is different from "penpot" means user is + ;; registering using third party auth mechanism; in this case + ;; we need to mark this session as logged. + (not= "penpot" (:auth-backend profile)) + (with-meta (profile/strip-private-attrs profile) + {:transform-response ((:create session) (:id profile)) + ::audit/replace-props (audit/profile->props profile) + ::audit/profile-id (:id profile)}) + + ;; If the `:enable-insecure-register` flag is set, we proceed + ;; to sign in the user directly, without email verification. + (true? is-active) + (with-meta (profile/strip-private-attrs profile) + {:transform-response ((:create session) (:id profile)) + ::audit/replace-props (audit/profile->props profile) + ::audit/profile-id (:id profile)}) + + ;; In all other cases, send a verification email. + :else + (let [vtoken (tokens :generate + {:iss :verify-email + :exp (dt/in-future "48h") + :profile-id (:id profile) + :email (:email profile)}) + ptoken (tokens :generate-predefined + {:iss :profile-identity + :profile-id (:id profile)})] + (eml/send! {::eml/conn conn + ::eml/factory eml/register + :public-uri (:public-uri cfg) + :to (:email profile) + :name (:fullname profile) + :token vtoken + :extra-data ptoken}) + + (with-meta profile + {::audit/replace-props (audit/profile->props profile) + ::audit/profile-id (:id profile)})))))) + +(s/def ::register-profile + (s/keys :req-un [::token ::fullname])) + +(sv/defmethod ::register-profile + {:auth false + ::rlimit/permits (cf/get :rlimit-password) + ::doc/added "1.15"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (-> (assoc cfg :conn conn) + (register-profile params)))) + +;; ---- COMMAND: Request Profile Recovery + +(defn request-profile-recovery + [{:keys [pool tokens] :as cfg} {:keys [email] :as params}] + (letfn [(create-recovery-token [{:keys [id] :as profile}] + (let [token (tokens :generate + {:iss :password-recovery + :exp (dt/in-future "15m") + :profile-id id})] + (assoc profile :token token))) + + (send-email-notification [conn profile] + (let [ptoken (tokens :generate-predefined + {:iss :profile-identity + :profile-id (:id profile)})] + (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 (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-not (:is-active profile) + (ex/raise :type :validation + :code :profile-not-verified + :hint "the user need to validate profile before recover password")) + + (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")) + + (->> profile + (create-recovery-token) + (send-email-notification conn)))))) + +(s/def ::request-profile-recovery + (s/keys :req-un [::email])) + +(sv/defmethod ::request-profile-recovery + {:auth false + ::doc/added "1.15"} + [cfg params] + (request-profile-recovery cfg params)) + + diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj new file mode 100644 index 0000000000..764f0651e7 --- /dev/null +++ b/backend/src/app/rpc/commands/binfile.clj @@ -0,0 +1,868 @@ +;; 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.commands.binfile + (:refer-clojure :exclude [assert]) + (:require + [app.common.data :as d] + [app.common.exceptions :as ex] + [app.common.logging :as l] + [app.common.pages.migrations :as pmg] + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.config :as cf] + [app.db :as db] + [app.media :as media] + [app.rpc.doc :as-alias doc] + [app.rpc.queries.files :as files] + [app.rpc.queries.projects :as projects] + [app.storage :as sto] + [app.storage.tmp :as tmp] + [app.tasks.file-gc] + [app.util.blob :as blob] + [app.util.bytes :as bs] + [app.util.fressian :as fres] + [app.util.services :as sv] + [app.util.time :as dt] + [clojure.java.io :as io] + [clojure.spec.alpha :as s] + [clojure.walk :as walk] + [cuerdas.core :as str] + [yetti.adapter :as yt]) + (:import + java.io.DataInputStream + java.io.DataOutputStream + java.io.InputStream + java.io.OutputStream + java.lang.AutoCloseable)) + +(set! *warn-on-reflection* true) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; DEFAULTS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Threshold in MiB when we pass from using +;; in-memory byte-array's to use temporal files. +(def temp-file-threshold + (* 1024 1024 2)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; LOW LEVEL STREAM IO API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^:const buffer-size (:xnio/buffer-size yt/defaults)) +(def ^:const penpot-magic-number 800099563638710213) +(def ^:const max-object-size (* 1024 1024 100)) ; Only allow 100MiB max file size. + +(def ^:dynamic *position* nil) + +(defn get-mark + [id] + (case id + :header 1 + :stream 2 + :uuid 3 + :label 4 + :obj 5 + (ex/raise :type :validation + :code :invalid-mark-id + :hint (format "invalid mark id %s" id)))) + +(defmacro assert + [expr hint] + `(when-not ~expr + (ex/raise :type :validation + :code :unexpected-condition + :hint ~hint))) + +(defmacro assert-mark + [v type] + `(let [expected# (get-mark ~type) + val# (long ~v)] + (when (not= val# expected#) + (ex/raise :type :validation + :code :unexpected-mark + :hint (format "received mark %s, expected %s" val# expected#))))) + +(defmacro assert-label + [expr label] + `(let [v# ~expr] + (when (not= v# ~label) + (ex/raise :type :assertion + :code :unexpected-label + :hint (format "received label %s, expected %s" v# ~label))))) + +;; --- PRIMITIVE IO + +(defn write-byte! + [^DataOutputStream output data] + (l/trace :fn "write-byte!" :data data :position @*position* ::l/async false) + (.writeByte output (byte data)) + (swap! *position* inc)) + +(defn read-byte! + [^DataInputStream input] + (let [v (.readByte input)] + (l/trace :fn "read-byte!" :val v :position @*position* ::l/async false) + (swap! *position* inc) + v)) + +(defn write-long! + [^DataOutputStream output data] + (l/trace :fn "write-long!" :data data :position @*position* ::l/async false) + (.writeLong output (long data)) + (swap! *position* + 8)) + + +(defn read-long! + [^DataInputStream input] + (let [v (.readLong input)] + (l/trace :fn "read-long!" :val v :position @*position* ::l/async false) + (swap! *position* + 8) + v)) + +(defn write-bytes! + [^DataOutputStream output ^bytes data] + (let [size (alength data)] + (l/trace :fn "write-bytes!" :size size :position @*position* ::l/async false) + (.write output data 0 size) + (swap! *position* + size))) + +(defn read-bytes! + [^InputStream input ^bytes buff] + (let [size (alength buff) + readed (.readNBytes input buff 0 size)] + (l/trace :fn "read-bytes!" :expected (alength buff) :readed readed :position @*position* ::l/async false) + (swap! *position* + readed) + readed)) + +;; --- COMPOSITE IO + +(defn write-uuid! + [^DataOutputStream output id] + (l/trace :fn "write-uuid!" :position @*position* :WRITTEN? (.size output) ::l/async false) + + (doto output + (write-byte! (get-mark :uuid)) + (write-long! (uuid/get-word-high id)) + (write-long! (uuid/get-word-low id)))) + +(defn read-uuid! + [^DataInputStream input] + (l/trace :fn "read-uuid!" :position @*position* ::l/async false) + (let [m (read-byte! input)] + (assert-mark m :uuid) + (let [a (read-long! input) + b (read-long! input)] + (uuid/custom a b)))) + +(defn write-obj! + [^DataOutputStream output data] + (l/trace :fn "write-obj!" :position @*position* ::l/async false) + (let [^bytes data (fres/encode data)] + (doto output + (write-byte! (get-mark :obj)) + (write-long! (alength data)) + (write-bytes! data)))) + +(defn read-obj! + [^DataInputStream input] + (l/trace :fn "read-obj!" :position @*position* ::l/async false) + (let [m (read-byte! input)] + (assert-mark m :obj) + (let [size (read-long! input)] + (assert (pos? size) "incorrect header size found on reading header") + (let [buff (byte-array size)] + (read-bytes! input buff) + (fres/decode buff))))) + +(defn write-label! + [^DataOutputStream output label] + (l/trace :fn "write-label!" :label label :position @*position* ::l/async false) + (doto output + (write-byte! (get-mark :label)) + (write-obj! label))) + +(defn read-label! + [^DataInputStream input] + (l/trace :fn "read-label!" :position @*position* ::l/async false) + (let [m (read-byte! input)] + (assert-mark m :label) + (read-obj! input))) + +(defn write-header! + [^OutputStream output version] + (l/trace :fn "write-header!" + :version version + :position @*position* + ::l/async false) + (let [vers (-> version name (subs 1) parse-long) + output (bs/data-output-stream output)] + (doto output + (write-byte! (get-mark :header)) + (write-long! penpot-magic-number) + (write-long! vers)))) + +(defn read-header! + [^InputStream input] + (l/trace :fn "read-header!" :position @*position* ::l/async false) + (let [input (bs/data-input-stream input) + mark (read-byte! input) + mnum (read-long! input) + vers (read-long! input)] + + (when (or (not= mark (get-mark :header)) + (not= mnum penpot-magic-number)) + (ex/raise :type :validation + :code :invalid-penpot-file + :hint "invalid penpot file")) + + (keyword (str "v" vers)))) + +(defn copy-stream! + [^OutputStream output ^InputStream input ^long size] + (let [written (bs/copy! input output :size size)] + (l/trace :fn "copy-stream!" :position @*position* :size size :written written ::l/async false) + (swap! *position* + written) + written)) + +(defn write-stream! + [^DataOutputStream output stream size] + (l/trace :fn "write-stream!" :position @*position* ::l/async false :size size) + (doto output + (write-byte! (get-mark :stream)) + (write-long! size)) + + (copy-stream! output stream size)) + +(defn read-stream! + [^DataInputStream input] + (l/trace :fn "read-stream!" :position @*position* ::l/async false) + (let [m (read-byte! input) + s (read-long! input) + p (tmp/tempfile :prefix "penpot.binfile.")] + (assert-mark m :stream) + + (when (> s max-object-size) + (ex/raise :type :validation + :code :max-file-size-reached + :hint (str/ffmt "unable to import storage object with size % bytes" s))) + + (if (> s temp-file-threshold) + (with-open [^OutputStream output (io/output-stream p)] + (let [readed (bs/copy! input output :offset 0 :size s)] + (l/trace :fn "read-stream*!" :expected s :readed readed :position @*position* ::l/async false) + (swap! *position* + readed) + [s p])) + [s (bs/read-as-bytes input :size s)]))) + +(defmacro assert-read-label! + [input expected-label] + `(let [readed# (read-label! ~input) + expected# ~expected-label] + (when (not= readed# expected#) + (ex/raise :type :validation + :code :unexpected-label + :hint (format "unxpected label found: %s, expected: %s" readed# expected#))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; --- HELPERS + +(defn- retrieve-file + [pool file-id] + (->> (db/query pool :file {:id file-id}) + (map files/decode-row) + (first))) + +(def ^:private sql:file-media-objects + "SELECT * FROM file_media_object WHERE id = ANY(?)") + +(defn- retrieve-file-media + [pool {:keys [data id] :as file}] + (with-open [^AutoCloseable conn (db/open pool)] + (let [ids (app.tasks.file-gc/collect-used-media data) + ids (db/create-array conn "uuid" ids)] + + ;; We assoc the file-id again to the file-media-object row + ;; because there are cases that used objects refer to other + ;; files and we need to ensure in the exportation process that + ;; all ids matches + (->> (db/exec! conn [sql:file-media-objects ids]) + (mapv #(assoc % :file-id id)))))) + +(def ^:private storage-object-id-xf + (comp + (mapcat (juxt :media-id :thumbnail-id)) + (filter uuid?))) + +(def ^:private sql:file-libraries + "WITH RECURSIVE libs AS ( + SELECT fl.id, fl.deleted_at + FROM file AS fl + JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id) + WHERE flr.file_id = ANY(?) + UNION + SELECT fl.id, fl.deleted_at + FROM file AS fl + JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id) + JOIN libs AS l ON (flr.file_id = l.id) + ) + SELECT DISTINCT l.id + FROM libs AS l + WHERE l.deleted_at IS NULL OR l.deleted_at > now();") + +(defn- retrieve-libraries + [pool ids] + (with-open [^AutoCloseable conn (db/open pool)] + (let [ids (db/create-array conn "uuid" ids)] + (map :id (db/exec! pool [sql:file-libraries ids]))))) + +(def ^:private sql:file-library-rels + "SELECT * FROM file_library_rel + WHERE file_id = ANY(?)") + +(defn- retrieve-library-relations + [pool ids] + (with-open [^AutoCloseable conn (db/open pool)] + (db/exec! conn [sql:file-library-rels (db/create-array conn "uuid" ids)]))) + + +(defn- create-or-update-file + [conn params] + (let [sql (str "INSERT INTO file (id, project_id, name, revn, is_shared, data, created_at, modified_at) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?) " + "ON CONFLICT (id) DO UPDATE SET data=?")] + (db/exec-one! conn [sql + (:id params) + (:project-id params) + (:name params) + (:revn params) + (:is-shared params) + (:data params) + (:created-at params) + (:modified-at params) + (:data params)]))) + +;; --- GENERAL PURPOSE DYNAMIC VARS + +(def ^:dynamic *state*) +(def ^:dynamic *options*) + +;; --- EXPORT WRITTER + +(defn- embed-file-assets + [data conn file-id] + (letfn [(walk-map-form [form state] + (cond + (uuid? (:fill-color-ref-file form)) + (do + (vswap! state conj [(:fill-color-ref-file form) :colors (:fill-color-ref-id form)]) + (assoc form :fill-color-ref-file file-id)) + + (uuid? (:stroke-color-ref-file form)) + (do + (vswap! state conj [(:stroke-color-ref-file form) :colors (:stroke-color-ref-id form)]) + (assoc form :stroke-color-ref-file file-id)) + + (uuid? (:typography-ref-file form)) + (do + (vswap! state conj [(:typography-ref-file form) :typographies (:typography-ref-id form)]) + (assoc form :typography-ref-file file-id)) + + (uuid? (:component-file form)) + (do + (vswap! state conj [(:component-file form) :components (:component-id form)]) + (assoc form :component-file file-id)) + + :else + form)) + + (process-group-of-assets [data [lib-id items]] + ;; NOTE: there are a posibility that shape refers to a not + ;; existing file because the file was removed. In this + ;; case we just ignore the asset. + (if-let [lib (retrieve-file conn lib-id)] + (reduce (partial process-asset lib) data items) + data)) + + (process-asset [lib data [bucket asset-id]] + (let [asset (get-in lib [:data bucket asset-id]) + ;; Add a special case for colors that need to have + ;; correctly set the :file-id prop (pending of the + ;; refactor that will remove it). + asset (cond-> asset + (= bucket :colors) (assoc :file-id file-id))] + (update data bucket assoc asset-id asset)))] + + (let [assets (volatile! [])] + (walk/postwalk #(cond-> % (map? %) (walk-map-form assets)) data) + (->> (deref assets) + (filter #(as-> (first %) $ (and (uuid? $) (not= $ file-id)))) + (d/group-by first rest) + (reduce (partial process-group-of-assets) data))))) + +(defmulti write-export ::version) +(defmulti write-section ::section) + +(s/def ::output bs/output-stream?) +(s/def ::file-ids (s/every ::us/uuid :kind vector? :min-count 1)) +(s/def ::include-libraries? (s/nilable ::us/boolean)) +(s/def ::embed-assets? (s/nilable ::us/boolean)) + +(s/def ::write-export-options + (s/keys :req-un [::db/pool ::sto/storage] + :req [::output ::file-ids] + :opt [::include-libraries? ::embed-assets?])) + +(defn write-export! + "Do the exportation of a speficied file in custom penpot binary + format. There are some options available for customize the output: + + `::include-libraries?`: additionaly to the specified file, all the + linked libraries also will be included (including transitive + dependencies). + + `::embed-assets?`: instead of including the libraryes, embedd in the + same file library all assets used from external libraries." + [{:keys [::include-libraries? ::embed-assets?] :as options}] + (us/assert! ::write-export-options options) + (us/verify! + :expr (not (and include-libraries? embed-assets?)) + :hint "the `include-libraries?` and `embed-assets?` are mutally excluding options") + (write-export options)) + +(defmethod write-export :default + [{:keys [::output] :as options}] + (write-header! output :v1) + (with-open [output (bs/zstd-output-stream output :level 12)] + (with-open [output (bs/data-output-stream output)] + (binding [*state* (volatile! {})] + (run! (fn [section] + (l/debug :hint "write section" :section section ::l/async false) + (write-label! output section) + (let [options (-> options + (assoc ::output output) + (assoc ::section section))] + (binding [*options* options] + (write-section options)))) + + [:v1/metadata :v1/files :v1/rels :v1/sobjects]))))) + +(defmethod write-section :v1/metadata + [{:keys [pool ::output ::file-ids ::include-libraries?]}] + (let [libs (when include-libraries? + (retrieve-libraries pool file-ids)) + files (into file-ids libs)] + (write-obj! output {:version cf/version :files files}) + (vswap! *state* assoc :files files))) + +(defmethod write-section :v1/files + [{:keys [pool ::output ::embed-assets?]}] + + ;; Initialize SIDS with empty vector + (vswap! *state* assoc :sids []) + + (doseq [file-id (-> *state* deref :files)] + (let [file (cond-> (retrieve-file pool file-id) + embed-assets? + (update :data embed-file-assets pool file-id)) + + media (retrieve-file-media pool file)] + + (l/debug :hint "write penpot file" + :id file-id + :media (count media) + ::l/async false) + + (doto output + (write-obj! file) + (write-obj! media)) + + (vswap! *state* update :sids into storage-object-id-xf media)))) + +(defmethod write-section :v1/rels + [{:keys [pool ::output ::include-libraries?]}] + (let [rels (when include-libraries? + (retrieve-library-relations pool (-> *state* deref :files)))] + (l/debug :hint "found rels" :total (count rels) ::l/async false) + (write-obj! output rels))) + +(defmethod write-section :v1/sobjects + [{:keys [storage ::output]}] + (let [sids (-> *state* deref :sids) + storage (media/configure-assets-storage storage)] + (l/debug :hint "found sobjects" + :items (count sids) + ::l/async false) + + ;; Write all collected storage objects + (write-obj! output sids) + + (doseq [id sids] + (let [{:keys [size] :as obj} @(sto/get-object storage id)] + (l/debug :hint "write sobject" :id id ::l/async false) + (doto output + (write-uuid! id) + (write-obj! (meta obj))) + + (with-open [^InputStream stream @(sto/get-object-data storage obj)] + (let [written (write-stream! output stream size)] + (when (not= written size) + (ex/raise :type :validation + :code :mismatch-readed-size + :hint (str/ffmt "found unexpected object size; size=% written=%" size written))))))))) + +;; --- EXPORT READER + +(declare lookup-index) +(declare update-index) +(declare relink-media) +(declare relink-shapes) + +(defmulti read-import ::version) +(defmulti read-section ::section) + +(s/def ::project-id ::us/uuid) +(s/def ::input bs/input-stream?) +(s/def ::overwrite? (s/nilable ::us/boolean)) +(s/def ::migrate? (s/nilable ::us/boolean)) +(s/def ::ignore-index-errors? (s/nilable ::us/boolean)) + +(s/def ::read-import-options + (s/keys :req-un [::db/pool ::sto/storage] + :req [::project-id ::input] + :opt [::overwrite? ::migrate? ::ignore-index-errors?])) + +(defn read-import! + "Do the importation of the specified resource in penpot custom binary + format. There are some options for customize the importation + behavior: + + `::overwrite?`: if true, instead of creating new files and remaping id references, + it reuses all ids and updates existing objects; defaults to `false`. + + `::migrate?`: if true, applies the migration before persisting the + file data; defaults to `false`. + + `::ignore-index-errors?`: if true, do not fail on index lookup errors, can + happen with broken files; defaults to: `false`. + " + + [{:keys [::input ::timestamp] :or {timestamp (dt/now)} :as options}] + (us/verify! ::read-import-options options) + (let [version (read-header! input)] + (read-import (assoc options ::version version ::timestamp timestamp)))) + +(defmethod read-import :v1 + [{:keys [pool ::input] :as options}] + (with-open [input (bs/zstd-input-stream input)] + (with-open [input (bs/data-input-stream input)] + (db/with-atomic [conn pool] + (db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED;"]) + (binding [*state* (volatile! {:media [] :index {}})] + (run! (fn [section] + (l/debug :hint "reading section" :section section ::l/async false) + (assert-read-label! input section) + (let [options (-> options + (assoc ::section section) + (assoc ::input input) + (assoc :conn conn))] + (binding [*options* options] + (read-section options)))) + [:v1/metadata :v1/files :v1/rels :v1/sobjects]) + + ;; Knowing that the ids of the created files are in + ;; index, just lookup them and return it as a set + (let [files (-> *state* deref :files)] + (into #{} (keep #(get-in @*state* [:index %])) files))))))) + +(defmethod read-section :v1/metadata + [{:keys [::input]}] + (let [{:keys [version files]} (read-obj! input)] + (l/debug :hint "metadata readed" :version (:full version) :files files ::l/async false) + (vswap! *state* update :index update-index files) + (vswap! *state* assoc :version version :files files))) + +(defmethod read-section :v1/files + [{:keys [conn ::input ::migrate? ::project-id ::timestamp ::overwrite?]}] + (doseq [expected-file-id (-> *state* deref :files)] + (let [file (read-obj! input) + media' (read-obj! input) + file-id (:id file)] + + (when (not= file-id expected-file-id) + (ex/raise :type :validation + :code :inconsistent-penpot-file + :hint "the penpot file seems corrupt, found unexpected uuid (file-id)")) + + ;; Update index using with media + (l/debug :hint "update index with media" ::l/async false) + (vswap! *state* update :index update-index (map :id media')) + + ;; Store file media for later insertion + (l/debug :hint "update media references" ::l/async false) + (vswap! *state* update :media into (map #(update % :id lookup-index)) media') + + (l/debug :hint "procesing file" :file-id file-id ::l/async false) + + (let [file-id' (lookup-index file-id) + data (-> (:data file) + (assoc :id file-id') + (cond-> migrate? (pmg/migrate-data)) + (update :pages-index relink-shapes) + (update :components relink-shapes) + (update :media relink-media)) + + params {:id file-id' + :project-id project-id + :name (str "Imported: " (:name file)) + :revn (:revn file) + :is-shared (:is-shared file) + :data (blob/encode data) + :created-at timestamp + :modified-at timestamp}] + + (l/debug :hint "create file" :id file-id' ::l/async false) + + (if overwrite? + (create-or-update-file conn params) + (db/insert! conn :file params)) + + (when overwrite? + (db/delete! conn :file-thumbnail {:file-id file-id'})))))) + +(defmethod read-section :v1/rels + [{:keys [conn ::input ::timestamp]}] + (let [rels (read-obj! input)] + ;; Insert all file relations + (doseq [rel rels] + (let [rel (-> rel + (assoc :synced-at timestamp) + (update :file-id lookup-index) + (update :library-file-id lookup-index))] + (l/debug :hint "create file library link" + :file-id (:file-id rel) + :lib-id (:library-file-id rel) + ::l/async false) + (db/insert! conn :file-library-rel rel))))) + +(defmethod read-section :v1/sobjects + [{:keys [storage conn ::input ::overwrite?]}] + (let [storage (media/configure-assets-storage storage) + ids (read-obj! input)] + + (doseq [expected-storage-id ids] + (let [id (read-uuid! input) + mdata (read-obj! input)] + + (when (not= id expected-storage-id) + (ex/raise :type :validation + :code :inconsistent-penpot-file + :hint "the penpot file seems corrupt, found unexpected uuid (storage-object-id)")) + + (l/debug :hint "readed storage object" :id id ::l/async false) + + (let [[size resource] (read-stream! input) + hash (sto/calculate-hash resource) + content (-> (sto/content resource size) + (sto/wrap-with-hash hash)) + params (-> mdata + (assoc ::sto/deduplicate? true) + (assoc ::sto/content content) + (assoc ::sto/touched-at (dt/now)) + (assoc :bucket "file-media-object")) + + sobject @(sto/put-object! storage params)] + + (l/debug :hint "persisted storage object" :id id :new-id (:id sobject) ::l/async false) + (vswap! *state* update :index assoc id (:id sobject))))) + + (doseq [item (:media @*state*)] + (l/debug :hint "inserting file media object" + :id (:id item) + :file-id (:file-id item) + ::l/async false) + + (let [file-id (lookup-index (:file-id item))] + (if (= file-id (:file-id item)) + (l/warn :hint "ignoring file media object" :file-id (:file-id item) ::l/async false) + (db/insert! conn :file-media-object + (-> item + (assoc :file-id file-id) + (d/update-when :media-id lookup-index) + (d/update-when :thumbnail-id lookup-index)) + {:on-conflict-do-nothing overwrite?})))))) + +(defn- lookup-index + [id] + (let [val (get-in @*state* [:index id])] + (l/trace :fn "lookup-index" :id id :val val ::l/async false) + (when (and (not (::ignore-index-errors? *options*)) (not val)) + (ex/raise :type :validation + :code :incomplete-index + :hint "looks like index has missing data")) + (or val id))) + +(defn- update-index + [index coll] + (loop [items (seq coll) + index index] + (if-let [id (first items)] + (let [new-id (if (::overwrite? *options*) id (uuid/next))] + (l/trace :fn "update-index" :id id :new-id new-id ::l/async false) + (recur (rest items) + (assoc index id new-id))) + index))) + +(defn- relink-shapes + "A function responsible to analyze all file data and + replace the old :component-file reference with the new + ones, using the provided file-index." + [data] + (letfn [(process-map-form [form] + (cond-> form + ;; Relink image shapes + (and (map? (:metadata form)) + (= :image (:type form))) + (update-in [:metadata :id] lookup-index) + + ;; Relink paths with fill image + (and (map? (:fill-image form)) + (= :path (:type form))) + (update-in [:fill-image :id] lookup-index) + + ;; This covers old shapes and the new :fills. + (uuid? (:fill-color-ref-file form)) + (update :fill-color-ref-file lookup-index) + + ;; This covers the old shapes and the new :strokes + (uuid? (:storage-color-ref-file form)) + (update :stroke-color-ref-file lookup-index) + + ;; This covers all text shapes that have typography referenced + (uuid? (:typography-ref-file form)) + (update :typography-ref-file lookup-index) + + ;; This covers the shadows and grids (they have directly + ;; the :file-id prop) + (uuid? (:file-id form)) + (update :file-id lookup-index)))] + + (walk/postwalk (fn [form] + (if (map? form) + (try + (process-map-form form) + (catch Throwable cause + (l/warn :hint "failed form" :form (pr-str form) ::l/async false) + (throw cause))) + form)) + data))) + +(defn- relink-media + "A function responsible of process the :media attr of file data and + remap the old ids with the new ones." + [media] + (reduce-kv (fn [res k v] + (let [id (lookup-index k)] + (if (uuid? id) + (-> res + (assoc id (assoc v :id id)) + (dissoc k)) + res))) + media + media)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; HIGH LEVEL API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn export! + [cfg] + (let [path (tmp/tempfile :prefix "penpot.export.") + id (uuid/next) + ts (dt/now) + cs (volatile! nil)] + (try + (l/info :hint "start exportation" :export-id id) + (with-open [output (io/output-stream path)] + (binding [*position* (atom 0)] + (write-export! (assoc cfg ::output output)) + path)) + + (catch Throwable cause + (vreset! cs cause) + (throw cause)) + + (finally + (l/info :hint "exportation finished" :export-id id + :elapsed (str (inst-ms (dt/diff ts (dt/now))) "ms") + :cause @cs))))) + +(defn import! + [{:keys [::input] :as cfg}] + (let [id (uuid/next) + ts (dt/now) + cs (volatile! nil)] + (try + (l/info :hint "start importation" :import-id id) + (binding [*position* (atom 0)] + (with-open [input (io/input-stream input)] + (read-import! (assoc cfg ::input input)))) + + (catch Throwable cause + (vreset! cs cause) + (throw cause)) + + (finally + (l/info :hint "importation finished" :import-id id + :elapsed (str (inst-ms (dt/diff ts (dt/now))) "ms") + :error? (some? @cs) + :cause @cs))))) + +;; --- Command: export-binfile + +(s/def ::file-id ::us/uuid) +(s/def ::profile-id ::us/uuid) +(s/def ::include-libraries? ::us/boolean) +(s/def ::embed-assets? ::us/boolean) + +(s/def ::export-binfile + (s/keys :req-un [::profile-id ::file-id ::include-libraries? ::embed-assets?])) + +(sv/defmethod ::export-binfile + "Export a penpot file in a binary format." + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id include-libraries? embed-assets?] :as params}] + (db/with-atomic [conn pool] + (files/check-read-permissions! conn profile-id file-id) + (let [path (export! (assoc cfg + ::file-ids [file-id] + ::embed-assets? embed-assets? + ::include-libraries? include-libraries?))] + (with-meta {} + {:transform-response (fn [_ response] + (assoc response + :body (io/input-stream path) + :headers {"content-type" "application/octet-stream"}))})))) + +(s/def ::file ::media/upload) +(s/def ::import-binfile + (s/keys :req-un [::profile-id ::project-id ::file])) + +(sv/defmethod ::import-binfile + "Import a penpot file in a binary format." + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id project-id file] :as params}] + (db/with-atomic [conn pool] + (projects/check-read-permissions! conn profile-id project-id) + (import! (assoc cfg + ::input (:path file) + ::project-id project-id + ::ignore-index-errors? true)))) diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj new file mode 100644 index 0000000000..f7bf9e5212 --- /dev/null +++ b/backend/src/app/rpc/commands/comments.clj @@ -0,0 +1,534 @@ +;; 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.commands.comments + (:require + [app.common.exceptions :as ex] + [app.common.geom.point :as gpt] + [app.common.spec :as us] + [app.db :as db] + [app.rpc.doc :as-alias doc] + [app.rpc.queries.files :as files] + [app.rpc.queries.teams :as teams] + [app.rpc.retry :as retry] + [app.util.blob :as blob] + [app.util.services :as sv] + [app.util.time :as dt] + [clojure.spec.alpha :as s])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; QUERY COMMANDS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn decode-row + [{:keys [participants position] :as row}] + (cond-> row + (db/pgpoint? position) (assoc :position (db/decode-pgpoint position)) + (db/pgobject? participants) (assoc :participants (db/decode-transit-pgobject participants)))) + +;; --- COMMAND: Get Comment Threads + +(declare retrieve-comment-threads) + +(s/def ::team-id ::us/uuid) +(s/def ::file-id ::us/uuid) +(s/def ::share-id (s/nilable ::us/uuid)) + +(s/def ::get-comment-threads + (s/and (s/keys :req-un [::profile-id] + :opt-un [::file-id ::share-id ::team-id]) + #(or (:file-id %) (:team-id %)))) + +(sv/defmethod ::get-comment-threads + [{:keys [pool] :as cfg} params] + (with-open [conn (db/open pool)] + (retrieve-comment-threads conn params))) + +(def sql:comment-threads + "select distinct on (ct.id) + ct.*, + f.name as file_name, + f.project_id as project_id, + first_value(c.content) over w as content, + (select count(1) + from comment as c + where c.thread_id = ct.id) as count_comments, + (select count(1) + from comment as c + where c.thread_id = ct.id + and c.created_at >= coalesce(cts.modified_at, ct.created_at)) as count_unread_comments + from comment_thread as ct + inner join comment as c on (c.thread_id = ct.id) + inner join file as f on (f.id = ct.file_id) + left join comment_thread_status as cts + on (cts.thread_id = ct.id and + cts.profile_id = ?) + where ct.file_id = ? + window w as (partition by c.thread_id order by c.created_at asc)") + +(defn retrieve-comment-threads + [conn {:keys [profile-id file-id share-id]}] + (files/check-comment-permissions! conn profile-id file-id share-id) + (->> (db/exec! conn [sql:comment-threads profile-id file-id]) + (into [] (map decode-row)))) + +;; --- COMMAND: Get Unread Comment Threads + +(declare retrieve-unread-comment-threads) + +(s/def ::team-id ::us/uuid) +(s/def ::get-unread-comment-threads + (s/keys :req-un [::profile-id ::team-id])) + +(sv/defmethod ::get-unread-comment-threads + [{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}] + (with-open [conn (db/open pool)] + (teams/check-read-permissions! conn profile-id team-id) + (retrieve-unread-comment-threads conn params))) + +(def sql:comment-threads-by-team + "select distinct on (ct.id) + ct.*, + f.name as file_name, + f.project_id as project_id, + first_value(c.content) over w as content, + (select count(1) + from comment as c + where c.thread_id = ct.id) as count_comments, + (select count(1) + from comment as c + where c.thread_id = ct.id + and c.created_at >= coalesce(cts.modified_at, ct.created_at)) as count_unread_comments + from comment_thread as ct + inner join comment as c on (c.thread_id = ct.id) + inner join file as f on (f.id = ct.file_id) + inner join project as p on (p.id = f.project_id) + left join comment_thread_status as cts + on (cts.thread_id = ct.id and + cts.profile_id = ?) + where p.team_id = ? + window w as (partition by c.thread_id order by c.created_at asc)") + +(def sql:unread-comment-threads-by-team + (str "with threads as (" sql:comment-threads-by-team ")" + "select * from threads where count_unread_comments > 0")) + +(defn retrieve-unread-comment-threads + [conn {:keys [profile-id team-id]}] + (->> (db/exec! conn [sql:unread-comment-threads-by-team profile-id team-id]) + (into [] (map decode-row)))) + + +;; --- COMMAND: Get Single Comment Thread + +(s/def ::id ::us/uuid) +(s/def ::share-id (s/nilable ::us/uuid)) +(s/def ::get-comment-thread + (s/keys :req-un [::profile-id ::file-id ::id] + :opt-un [::share-id])) + +(sv/defmethod ::get-comment-thread + [{:keys [pool] :as cfg} {:keys [profile-id file-id id share-id] :as params}] + (with-open [conn (db/open pool)] + (files/check-comment-permissions! conn profile-id file-id share-id) + (let [sql (str "with threads as (" sql:comment-threads ")" + "select * from threads where id = ?")] + (-> (db/exec-one! conn [sql profile-id file-id id]) + (decode-row))))) + +(defn get-comment-thread + [conn {:keys [profile-id file-id id] :as params}] + (let [sql (str "with threads as (" sql:comment-threads ")" + "select * from threads where id = ?")] + (-> (db/exec-one! conn [sql profile-id file-id id]) + (decode-row)))) + +;; --- COMMAND: Retrieve Comments + +(declare get-comments) + +(s/def ::file-id ::us/uuid) +(s/def ::share-id (s/nilable ::us/uuid)) +(s/def ::thread-id ::us/uuid) +(s/def ::get-comments + (s/keys :req-un [::profile-id ::thread-id] + :opt-un [::share-id])) + +(sv/defmethod ::get-comments + [{:keys [pool] :as cfg} {:keys [profile-id thread-id share-id] :as params}] + (with-open [conn (db/open pool)] + (let [thread (db/get-by-id conn :comment-thread thread-id)] + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id)) + (get-comments conn thread-id))) + +(def sql:comments + "select c.* from comment as c + where c.thread_id = ? + order by c.created_at asc") + +(defn get-comments + [conn thread-id] + (->> (db/query conn :comment + {:thread-id thread-id} + {:order-by [[:created-at :asc]]}) + (into [] (map decode-row)))) + +;; --- COMMAND: Get file comments users + +(declare get-file-comments-users) + +(s/def ::file-id ::us/uuid) +(s/def ::share-id (s/nilable ::us/uuid)) + +(s/def ::get-profiles-for-file-comments + (s/keys :req-un [::profile-id ::file-id] + :opt-un [::share-id])) + +(sv/defmethod ::get-profiles-for-file-comments + "Retrieves a list of profiles with limited set of properties of all + participants on comment threads of the file." + {::doc/added "1.15" + ::doc/changes ["1.15" "Imported from queries and renamed."]} + [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id]}] + (with-open [conn (db/open pool)] + (files/check-comment-permissions! conn profile-id file-id share-id) + (get-file-comments-users conn file-id profile-id))) + +;; All the profiles that had comment the file, plus the current +;; profile. + +(def sql:file-comment-users + "WITH available_profiles AS ( + SELECT DISTINCT owner_id AS id + FROM comment + WHERE thread_id IN (SELECT id FROM comment_thread WHERE file_id=?) + ) + SELECT p.id, + p.email, + p.fullname AS name, + p.fullname AS fullname, + p.photo_id, + p.is_active + FROM profile AS p + WHERE p.id IN (SELECT id FROM available_profiles) OR p.id=?") + +(defn get-file-comments-users + [conn file-id profile-id] + (db/exec! conn [sql:file-comment-users file-id profile-id])) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; MUTATION COMMANDS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; --- COMMAND: Create Comment Thread + +(declare upsert-comment-thread-status!) +(declare create-comment-thread) +(declare retrieve-page-name) + +(s/def ::page-id ::us/uuid) +(s/def ::file-id ::us/uuid) +(s/def ::share-id (s/nilable ::us/uuid)) +(s/def ::profile-id ::us/uuid) +(s/def ::position ::gpt/point) +(s/def ::content ::us/string) +(s/def ::frame-id ::us/uuid) + +(s/def ::create-comment-thread + (s/keys :req-un [::profile-id ::file-id ::position ::content ::page-id ::frame-id] + :opt-un [::share-id])) + +(sv/defmethod ::create-comment-thread + {::retry/max-retries 3 + ::retry/matches retry/conflict-db-insert? + ::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] + (db/with-atomic [conn pool] + (files/check-comment-permissions! conn profile-id file-id share-id) + (create-comment-thread conn params))) + +(defn- retrieve-next-seqn + [conn file-id] + (let [sql "select (f.comment_thread_seqn + 1) as next_seqn from file as f where f.id = ?" + res (db/exec-one! conn [sql file-id])] + (:next-seqn res))) + +(defn create-comment-thread + [conn {:keys [profile-id file-id page-id position content frame-id] :as params}] + (let [seqn (retrieve-next-seqn conn file-id) + now (dt/now) + pname (retrieve-page-name conn params) + thread (db/insert! conn :comment-thread + {:file-id file-id + :owner-id profile-id + :participants (db/tjson #{profile-id}) + :page-name pname + :page-id page-id + :created-at now + :modified-at now + :seqn seqn + :position (db/pgpoint position) + :frame-id frame-id})] + + + ;; Create a comment entry + (db/insert! conn :comment + {:thread-id (:id thread) + :owner-id profile-id + :created-at now + :modified-at now + :content content}) + + ;; Make the current thread as read. + (upsert-comment-thread-status! conn profile-id (:id thread)) + + ;; Optimistic update of current seq number on file. + (db/update! conn :file + {:comment-thread-seqn seqn} + {:id file-id}) + + (select-keys thread [:id :file-id :page-id]))) + +(defn- retrieve-page-name + [conn {:keys [file-id page-id]}] + (let [{:keys [data]} (db/get-by-id conn :file file-id) + data (blob/decode data)] + (get-in data [:pages-index page-id :name]))) + + +;; --- COMMAND: Update Comment Thread Status + +(s/def ::id ::us/uuid) +(s/def ::share-id (s/nilable ::us/uuid)) + +(s/def ::update-comment-thread-status + (s/keys :req-un [::profile-id ::id] + :opt-un [::share-id])) + +(sv/defmethod ::update-comment-thread-status + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id share-id] :as params}] + (db/with-atomic [conn pool] + (let [cthr (db/get-by-id conn :comment-thread id {:for-update true})] + (when-not cthr + (ex/raise :type :not-found)) + + (files/check-comment-permissions! conn profile-id (:file-id cthr) share-id) + (upsert-comment-thread-status! conn profile-id (:id cthr))))) + +(def sql:upsert-comment-thread-status + "insert into comment_thread_status (thread_id, profile_id) + values (?, ?) + on conflict (thread_id, profile_id) + do update set modified_at = clock_timestamp() + returning modified_at;") + +(defn upsert-comment-thread-status! + [conn profile-id thread-id] + (db/exec-one! conn [sql:upsert-comment-thread-status thread-id profile-id])) + + +;; --- COMMAND: Update Comment Thread + +(s/def ::is-resolved ::us/boolean) +(s/def ::update-comment-thread + (s/keys :req-un [::profile-id ::id ::is-resolved] + :opt-un [::share-id])) + +(sv/defmethod ::update-comment-thread + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id is-resolved share-id] :as params}] + (db/with-atomic [conn pool] + (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] + (when-not thread + (ex/raise :type :not-found)) + + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) + + (db/update! conn :comment-thread + {:is-resolved is-resolved} + {:id id}) + nil))) + + +;; --- COMMAND: Add Comment + +(declare create-comment) + +(s/def ::create-comment + (s/keys :req-un [::profile-id ::thread-id ::content] + :opt-un [::share-id])) + +(sv/defmethod ::create-comment + {::doc/added "1.15"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (create-comment conn params))) + +(defn create-comment + [conn {:keys [profile-id thread-id content share-id] :as params}] + (let [thread (-> (db/get-by-id conn :comment-thread thread-id {:for-update true}) + (decode-row)) + pname (retrieve-page-name conn thread)] + + ;; Standard Checks + (when-not thread (ex/raise :type :not-found)) + + ;; Permission Checks + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) + + ;; Update the page-name cachedattribute on comment thread table. + (when (not= pname (:page-name thread)) + (db/update! conn :comment-thread + {:page-name pname} + {:id thread-id})) + + ;; NOTE: is important that all timestamptz related fields are + ;; created or updated on the database level for avoid clock + ;; inconsistencies (some user sees something read that is not + ;; read, etc...) + (let [ppants (:participants thread #{}) + comment (db/insert! conn :comment + {:thread-id thread-id + :owner-id profile-id + :content content})] + + ;; NOTE: this is done in SQL instead of using db/update! + ;; helper because currently the helper does not allow pass raw + ;; function call parameters to the underlying prepared + ;; statement; in a future when we fix/improve it, this can be + ;; changed to use the helper. + + ;; Update thread modified-at attribute and assoc the current + ;; profile to the participant set. + (let [ppants (conj ppants profile-id) + sql "update comment_thread + set modified_at = clock_timestamp(), + participants = ? + where id = ?"] + (db/exec-one! conn [sql (db/tjson ppants) thread-id])) + + ;; Update the current profile status in relation to the + ;; current thread. + (upsert-comment-thread-status! conn profile-id thread-id) + + ;; Return the created comment object. + comment))) + +;; --- COMMAND: Update Comment + +(declare update-comment) + +(s/def ::update-comment + (s/keys :req-un [::profile-id ::id ::content] + :opt-un [::share-id])) + +(sv/defmethod ::update-comment + {::doc/added "1.15"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (update-comment conn params))) + +(defn update-comment + [conn {:keys [profile-id id content share-id] :as params}] + (let [comment (db/get-by-id conn :comment id {:for-update true}) + _ (when-not comment (ex/raise :type :not-found)) + thread (db/get-by-id conn :comment-thread (:thread-id comment) {:for-update true}) + _ (when-not thread (ex/raise :type :not-found)) + pname (retrieve-page-name conn thread)] + + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) + + ;; Don't allow edit comments to not owners + (when-not (= (:owner-id thread) profile-id) + (ex/raise :type :validation + :code :not-allowed)) + + (db/update! conn :comment + {:content content + :modified-at (dt/now)} + {:id (:id comment)}) + + (db/update! conn :comment-thread + {:modified-at (dt/now) + :page-name pname} + {:id (:id thread)}) + nil)) + + +;; --- COMMAND: Delete Comment Thread + +(s/def ::delete-comment-thread + (s/keys :req-un [::profile-id ::id])) + +(sv/defmethod ::delete-comment-thread + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] + (db/with-atomic [conn pool] + (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] + (when-not (= (:owner-id thread) profile-id) + (ex/raise :type :validation + :code :not-allowed)) + (db/delete! conn :comment-thread {:id id}) + nil))) + + +;; --- COMMAND: Delete comment + +(s/def ::delete-comment + (s/keys :req-un [::profile-id ::id])) + +(sv/defmethod ::delete-comment + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] + (db/with-atomic [conn pool] + (let [comment (db/get-by-id conn :comment id {:for-update true})] + (when-not (= (:owner-id comment) profile-id) + (ex/raise :type :validation + :code :not-allowed)) + + (db/delete! conn :comment {:id id})))) + +;; --- COMMAND: Update comment thread position + +(s/def ::update-comment-thread-position + (s/keys :req-un [::profile-id ::id ::position ::frame-id])) + +(sv/defmethod ::update-comment-thread-position + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id position frame-id] :as params}] + (db/with-atomic [conn pool] + (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] + (when-not (= (:owner-id thread) profile-id) + (ex/raise :type :validation + :code :not-allowed)) + (db/update! conn :comment-thread + {:modified-at (dt/now) + :position (db/pgpoint position) + :frame-id frame-id} + {:id (:id thread)}) + nil))) + +;; --- COMMAND: Update comment frame + +(s/def ::update-comment-thread-frame + (s/keys :req-un [::profile-id ::id ::frame-id])) + +(sv/defmethod ::update-comment-thread-frame + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id frame-id] :as params}] + (db/with-atomic [conn pool] + (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] + (when-not (= (:owner-id thread) profile-id) + (ex/raise :type :validation + :code :not-allowed)) + (db/update! conn :comment-thread + {:modified-at (dt/now) + :frame-id frame-id} + {:id (:id thread)}) + nil))) + diff --git a/backend/src/app/rpc/mutations/demo.clj b/backend/src/app/rpc/commands/demo.clj similarity index 75% rename from backend/src/app/rpc/mutations/demo.clj rename to backend/src/app/rpc/commands/demo.clj index 12786757f5..5ba10ec40f 100644 --- a/backend/src/app/rpc/mutations/demo.clj +++ b/backend/src/app/rpc/commands/demo.clj @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.rpc.mutations.demo +(ns app.rpc.commands.demo "A demo specific mutations." (:require [app.common.exceptions :as ex] @@ -12,7 +12,8 @@ [app.config :as cf] [app.db :as db] [app.loggers.audit :as audit] - [app.rpc.mutations.profile :as profile] + [app.rpc.commands.auth :as cmd.auth] + [app.rpc.doc :as-alias doc] [app.util.services :as sv] [app.util.time :as dt] [buddy.core.codecs :as bc] @@ -21,7 +22,13 @@ (s/def ::create-demo-profile any?) -(sv/defmethod ::create-demo-profile {:auth false} +(sv/defmethod ::create-demo-profile + "A command that is responsible of creating a demo purpose + profile. It only works if the `demo-users` flag is inabled in the + configuration." + {:auth false + ::doc/added "1.15" + ::doc/changes ["1.15" "This methos is migrated from mutations to commands."]} [{:keys [pool] :as cfg} _] (let [id (uuid/next) sem (System/currentTimeMillis) @@ -45,8 +52,8 @@ :hint "Demo users are disabled by config.")) (db/with-atomic [conn pool] - (->> (#'profile/create-profile conn params) - (#'profile/create-profile-relations conn)) + (->> (cmd.auth/create-profile conn params) + (cmd.auth/create-profile-relations conn)) (with-meta {:email email :password password} diff --git a/backend/src/app/rpc/commands/ldap.clj b/backend/src/app/rpc/commands/ldap.clj new file mode 100644 index 0000000000..5c581db7b0 --- /dev/null +++ b/backend/src/app/rpc/commands/ldap.clj @@ -0,0 +1,80 @@ +;; 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.commands.ldap + (:require + [app.auth.ldap :as ldap] + [app.common.exceptions :as ex] + [app.common.spec :as us] + [app.db :as db] + [app.loggers.audit :as-alias audit] + [app.rpc.commands.auth :as cmd.auth] + [app.rpc.doc :as-alias doc] + [app.rpc.queries.profile :as profile] + [app.util.services :as sv] + [clojure.spec.alpha :as s])) + +;; --- COMMAND: login-with-ldap + +(declare login-or-register) + +(s/def ::email ::us/email) +(s/def ::password ::us/string) +(s/def ::invitation-token ::us/string) + +(s/def ::login-with-ldap + (s/keys :req-un [::email ::password] + :opt-un [::invitation-token])) + +(sv/defmethod ::login-with-ldap + "Performs the authentication using LDAP backend. Only works if LDAP + is properly configured and enabled with `login-with-ldap` flag." + {:auth false + ::doc/added "1.15"} + [{:keys [session tokens ldap] :as cfg} params] + (when-not ldap + (ex/raise :type :restriction + :code :ldap-not-initialized + :hide "ldap auth provider is not initialized")) + + (let [info (ldap/authenticate ldap params)] + (when-not info + (ex/raise :type :validation + :code :wrong-credentials)) + + (let [profile (login-or-register cfg info)] + (if-let [token (:invitation-token params)] + ;; If invitation token comes in params, this is because the + ;; user comes from team-invitation process; in this case, + ;; regenerate token and send back to the user a new invitation + ;; token (and mark current session as logged). + (let [claims (tokens :verify {:token token :iss :team-invitation}) + claims (assoc claims + :member-id (:id profile) + :member-email (:email profile)) + token (tokens :generate claims)] + (with-meta {:invitation-token token} + {:transform-response ((:create session) (:id profile)) + ::audit/props (:props profile) + ::audit/profile-id (:id profile)})) + + (with-meta profile + {:transform-response ((:create session) (:id profile)) + ::audit/props (:props profile) + ::audit/profile-id (:id profile)}))))) + +(defn- login-or-register + [{:keys [pool] :as cfg} info] + (db/with-atomic [conn pool] + (or (some->> (:email info) + (profile/retrieve-profile-data-by-email conn) + (profile/populate-additional-data conn) + (profile/decode-profile-row)) + (->> (assoc info :is-active true :is-demo false) + (cmd.auth/create-profile conn) + (cmd.auth/create-profile-relations conn) + (profile/strip-private-attrs))))) + diff --git a/backend/src/app/rpc/doc.clj b/backend/src/app/rpc/doc.clj new file mode 100644 index 0000000000..6afec5643c --- /dev/null +++ b/backend/src/app/rpc/doc.clj @@ -0,0 +1,77 @@ +;; 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.doc + "API autogenerated documentation." + (:require + [app.common.data :as d] + [app.config :as cf] + [app.rpc :as-alias rpc] + [app.util.services :as sv] + [app.util.template :as tmpl] + [clojure.java.io :as io] + [clojure.spec.alpha :as s] + [cuerdas.core :as str] + [integrant.core :as ig] + [pretty-spec.core :as ps] + [yetti.response :as yrs])) + +(defn- get-spec-str + [k] + (with-out-str + (ps/pprint (s/form k) + {:ns-aliases {"clojure.spec.alpha" "s" + "clojure.core.specs.alpha" "score" + "clojure.core" nil}}))) + +(defn- prepare-context + [methods] + (letfn [(gen-doc [type [name f]] + (let [mdata (meta f)] + {:type (d/name type) + :name (d/name name) + :module (-> (:ns mdata) (str/split ".") last) + :auth (:auth mdata true) + :docs (::sv/docstring mdata) + :deprecated (::deprecated mdata) + :added (::added mdata) + :changes (some->> (::changes mdata) (partition-all 2) (map vec)) + :spec (get-spec-str (::sv/spec mdata))}))] + + {:version (:main cf/version) + :command-methods + (->> (:commands methods) + (map (partial gen-doc :command)) + (sort-by (juxt :module :name))) + + :query-methods + (->> (:queries methods) + (map (partial gen-doc :query)) + (sort-by (juxt :module :name))) + :mutation-methods + (->> (:mutations methods) + (map (partial gen-doc :query)) + (sort-by (juxt :module :name)))})) + +(defn- handler + [methods] + (if (contains? cf/flags :backend-api-doc) + (let [context (prepare-context methods)] + (fn [_ respond _] + (respond (yrs/response 200 (-> (io/resource "api-doc.tmpl") + (tmpl/render context)))))) + (fn [_ respond _] + (respond (yrs/response 404))))) + + +(defmethod ig/pre-init-spec ::routes [_] + (s/keys :req-un [::rpc/methods])) + +(defmethod ig/init-key ::routes + [_ {:keys [methods] :as cfg}] + ["/_doc" {:handler (handler methods) + :allowed-methods #{:get}}]) + diff --git a/backend/src/app/rpc/mutations/comments.clj b/backend/src/app/rpc/mutations/comments.clj index 438cfdebd9..2d138886b0 100644 --- a/backend/src/app/rpc/mutations/comments.clj +++ b/backend/src/app/rpc/mutations/comments.clj @@ -7,132 +7,61 @@ (ns app.rpc.mutations.comments (:require [app.common.exceptions :as ex] - [app.common.geom.point :as gpt] [app.common.spec :as us] [app.db :as db] - [app.rpc.queries.comments :as comments] + [app.rpc.commands.comments :as cmd.comments] + [app.rpc.doc :as-alias doc] [app.rpc.queries.files :as files] [app.rpc.retry :as retry] - [app.util.blob :as blob] [app.util.services :as sv] - [app.util.time :as dt] [clojure.spec.alpha :as s])) ;; --- Mutation: Create Comment Thread -(declare upsert-comment-thread-status!) -(declare create-comment-thread) -(declare retrieve-page-name) - -(s/def ::page-id ::us/uuid) -(s/def ::file-id ::us/uuid) -(s/def ::profile-id ::us/uuid) -(s/def ::position ::gpt/point) -(s/def ::content ::us/string) - -(s/def ::create-comment-thread - (s/keys :req-un [::profile-id ::file-id ::position ::content ::page-id])) +(s/def ::create-comment-thread ::cmd.comments/create-comment-thread) (sv/defmethod ::create-comment-thread {::retry/max-retries 3 - ::retry/matches retry/conflict-db-insert?} - [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + ::retry/matches retry/conflict-db-insert? + ::doc/added "1.0" + ::doc/deprecated "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] (db/with-atomic [conn pool] - (files/check-read-permissions! conn profile-id file-id) - (create-comment-thread conn params))) - -(defn- retrieve-next-seqn - [conn file-id] - (let [sql "select (f.comment_thread_seqn + 1) as next_seqn from file as f where f.id = ?" - res (db/exec-one! conn [sql file-id])] - (:next-seqn res))) - -(defn- create-comment-thread - [conn {:keys [profile-id file-id page-id position content] :as params}] - (let [seqn (retrieve-next-seqn conn file-id) - now (dt/now) - pname (retrieve-page-name conn params) - thread (db/insert! conn :comment-thread - {:file-id file-id - :owner-id profile-id - :participants (db/tjson #{profile-id}) - :page-name pname - :page-id page-id - :created-at now - :modified-at now - :seqn seqn - :position (db/pgpoint position)})] - - - ;; Create a comment entry - (db/insert! conn :comment - {:thread-id (:id thread) - :owner-id profile-id - :created-at now - :modified-at now - :content content}) - - ;; Make the current thread as read. - (upsert-comment-thread-status! conn profile-id (:id thread)) - - ;; Optimistic update of current seq number on file. - (db/update! conn :file - {:comment-thread-seqn seqn} - {:id file-id}) - - (select-keys thread [:id :file-id :page-id]))) - -(defn- retrieve-page-name - [conn {:keys [file-id page-id]}] - (let [{:keys [data]} (db/get-by-id conn :file file-id) - data (blob/decode data)] - (get-in data [:pages-index page-id :name]))) - + (files/check-comment-permissions! conn profile-id file-id share-id) + (cmd.comments/create-comment-thread conn params))) ;; --- Mutation: Update Comment Thread Status (s/def ::id ::us/uuid) +(s/def ::share-id (s/nilable ::us/uuid)) -(s/def ::update-comment-thread-status - (s/keys :req-un [::profile-id ::id])) +(s/def ::update-comment-thread-status ::cmd.comments/update-comment-thread-status) (sv/defmethod ::update-comment-thread-status - [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id share-id] :as params}] (db/with-atomic [conn pool] (let [cthr (db/get-by-id conn :comment-thread id {:for-update true})] - (when-not cthr - (ex/raise :type :not-found)) - - (files/check-read-permissions! conn profile-id (:file-id cthr)) - (upsert-comment-thread-status! conn profile-id (:id cthr))))) - -(def sql:upsert-comment-thread-status - "insert into comment_thread_status (thread_id, profile_id) - values (?, ?) - on conflict (thread_id, profile_id) - do update set modified_at = clock_timestamp() - returning modified_at;") - -(defn- upsert-comment-thread-status! - [conn profile-id thread-id] - (db/exec-one! conn [sql:upsert-comment-thread-status thread-id profile-id])) + (when-not cthr (ex/raise :type :not-found)) + (files/check-comment-permissions! conn profile-id (:file-id cthr) share-id) + (cmd.comments/upsert-comment-thread-status! conn profile-id (:id cthr))))) ;; --- Mutation: Update Comment Thread -(s/def ::is-resolved ::us/boolean) -(s/def ::update-comment-thread - (s/keys :req-un [::profile-id ::id ::is-resolved])) +(s/def ::update-comment-thread ::cmd.comments/update-comment-thread) (sv/defmethod ::update-comment-thread - [{:keys [pool] :as cfg} {:keys [profile-id id is-resolved] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id is-resolved share-id] :as params}] (db/with-atomic [conn pool] (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] (when-not thread (ex/raise :type :not-found)) - (files/check-read-permissions! conn profile-id (:file-id thread)) - + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) (db/update! conn :comment-thread {:is-resolved is-resolved} {:id id}) @@ -141,121 +70,54 @@ ;; --- Mutation: Add Comment -(s/def ::add-comment - (s/keys :req-un [::profile-id ::thread-id ::content])) +(s/def ::add-comment ::cmd.comments/create-comment) (sv/defmethod ::add-comment - [{:keys [pool] :as cfg} {:keys [profile-id thread-id content] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.15"} + [{:keys [pool] :as cfg} params] (db/with-atomic [conn pool] - (let [thread (-> (db/get-by-id conn :comment-thread thread-id {:for-update true}) - (comments/decode-row)) - pname (retrieve-page-name conn thread)] - - ;; Standard Checks - (when-not thread (ex/raise :type :not-found)) - - ;; Permission Checks - (files/check-read-permissions! conn profile-id (:file-id thread)) - - ;; Update the page-name cachedattribute on comment thread table. - (when (not= pname (:page-name thread)) - (db/update! conn :comment-thread - {:page-name pname} - {:id thread-id})) - - ;; NOTE: is important that all timestamptz related fields are - ;; created or updated on the database level for avoid clock - ;; inconsistencies (some user sees something read that is not - ;; read, etc...) - (let [ppants (:participants thread #{}) - comment (db/insert! conn :comment - {:thread-id thread-id - :owner-id profile-id - :content content})] - - ;; NOTE: this is done in SQL instead of using db/update! - ;; helper because currently the helper does not allow pass raw - ;; function call parameters to the underlying prepared - ;; statement; in a future when we fix/improve it, this can be - ;; changed to use the helper. - - ;; Update thread modified-at attribute and assoc the current - ;; profile to the participant set. - (let [ppants (conj ppants profile-id) - sql "update comment_thread - set modified_at = clock_timestamp(), - participants = ? - where id = ?"] - (db/exec-one! conn [sql (db/tjson ppants) thread-id])) - - ;; Update the current profile status in relation to the - ;; current thread. - (upsert-comment-thread-status! conn profile-id thread-id) - - ;; Return the created comment object. - comment)))) + (cmd.comments/create-comment conn params))) ;; --- Mutation: Update Comment -(s/def ::update-comment - (s/keys :req-un [::profile-id ::id ::content])) +(s/def ::update-comment ::cmd.comments/update-comment) (sv/defmethod ::update-comment - [{:keys [pool] :as cfg} {:keys [profile-id id content] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.15"} + [{:keys [pool] :as cfg} params] (db/with-atomic [conn pool] - (let [comment (db/get-by-id conn :comment id {:for-update true}) - _ (when-not comment (ex/raise :type :not-found)) - thread (db/get-by-id conn :comment-thread (:thread-id comment) {:for-update true}) - _ (when-not thread (ex/raise :type :not-found)) - pname (retrieve-page-name conn thread)] - - (files/check-read-permissions! conn profile-id (:file-id thread)) - - ;; Don't allow edit comments to not owners - (when-not (= (:owner-id thread) profile-id) - (ex/raise :type :validation - :code :not-allowed)) - - (db/update! conn :comment - {:content content - :modified-at (dt/now)} - {:id (:id comment)}) - - (db/update! conn :comment-thread - {:modified-at (dt/now) - :page-name pname} - {:id (:id thread)}) - nil))) + (cmd.comments/update-comment conn params))) ;; --- Mutation: Delete Comment Thread -(s/def ::delete-comment-thread - (s/keys :req-un [::profile-id ::id])) +(s/def ::delete-comment-thread ::cmd.comments/delete-comment-thread) (sv/defmethod ::delete-comment-thread + {::doc/added "1.0" + ::doc/deprecated "1.15"} [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] (db/with-atomic [conn pool] (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] (when-not (= (:owner-id thread) profile-id) - (ex/raise :type :validation - :code :not-allowed)) + (ex/raise :type :validation :code :not-allowed)) (db/delete! conn :comment-thread {:id id}) nil))) ;; --- Mutation: Delete comment -(s/def ::delete-comment - (s/keys :req-un [::profile-id ::id])) +(s/def ::delete-comment ::cmd.comments/delete-comment) (sv/defmethod ::delete-comment + {::doc/added "1.0" + ::doc/deprecated "1.15"} [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] (db/with-atomic [conn pool] (let [comment (db/get-by-id conn :comment id {:for-update true})] (when-not (= (:owner-id comment) profile-id) - (ex/raise :type :validation - :code :not-allowed)) - + (ex/raise :type :validation :code :not-allowed)) (db/delete! conn :comment {:id id})))) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index f52d4fcfa2..dba09a40a2 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -6,6 +6,7 @@ (ns app.rpc.mutations.files (:require + [app.common.data :as d] [app.common.exceptions :as ex] [app.common.pages :as cp] [app.common.pages.migrations :as pmg] @@ -63,21 +64,23 @@ (db/insert! conn :file-profile-rel)))) (defn create-file - [conn {:keys [id name project-id is-shared data deleted-at revn] - :or {is-shared false - revn 0 - deleted-at nil} + [conn {:keys [id name project-id is-shared data revn + modified-at deleted-at ignore-sync-until] + :or {is-shared false revn 0} :as params}] (let [id (or id (:id data) (uuid/next)) data (or data (cp/make-file-data id)) file (db/insert! conn :file - {:id id - :project-id project-id - :name name - :revn revn - :is-shared is-shared - :data (blob/encode data) - :deleted-at deleted-at})] + (d/without-nils + {:id id + :project-id project-id + :name name + :revn revn + :is-shared is-shared + :data (blob/encode data) + :ignore-sync-until ignore-sync-until + :modified-at modified-at + :deleted-at deleted-at}))] (->> (assoc params :file-id id :role :owner) (create-file-role conn)) diff --git a/backend/src/app/rpc/mutations/fonts.clj b/backend/src/app/rpc/mutations/fonts.clj index 91b8024d88..b24544a88b 100644 --- a/backend/src/app/rpc/mutations/fonts.clj +++ b/backend/src/app/rpc/mutations/fonts.clj @@ -13,6 +13,7 @@ [app.config :as cf] [app.db :as db] [app.media :as media] + [app.rpc.doc :as-alias doc] [app.rpc.queries.teams :as teams] [app.rpc.rlimit :as rlimit] [app.storage :as sto] @@ -71,9 +72,9 @@ data) (persist-font-object [data mtype] - (when-let [fdata (get data mtype)] - (p/let [hash (calculate-hash fdata) - content (-> (sto/content fdata) + (when-let [resource (get data mtype)] + (p/let [hash (calculate-hash resource) + content (-> (sto/content resource) (sto/wrap-with-hash hash))] (sto/put-object! storage {::sto/content content ::sto/touched-at (dt/now) @@ -151,6 +152,7 @@ (s/keys :req-un [::profile-id ::team-id ::id])) (sv/defmethod ::delete-font-variant + {::doc/added "1.3"} [{:keys [pool] :as cfg} {:keys [id team-id profile-id] :as params}] (db/with-atomic [conn pool] (teams/check-edition-permissions! conn profile-id team-id) diff --git a/backend/src/app/rpc/mutations/ldap.clj b/backend/src/app/rpc/mutations/ldap.clj deleted file mode 100644 index b4cc37afb6..0000000000 --- a/backend/src/app/rpc/mutations/ldap.clj +++ /dev/null @@ -1,140 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) UXBOX Labs SL - -(ns app.rpc.mutations.ldap - (:require - [app.common.exceptions :as ex] - [app.common.logging :as l] - [app.common.spec :as us] - [app.config :as cfg] - [app.db :as db] - [app.loggers.audit :as audit] - [app.rpc.mutations.profile :as profile-m] - [app.rpc.queries.profile :as profile-q] - [app.util.services :as sv] - [clj-ldap.client :as ldap] - [clojure.spec.alpha :as s] - [clojure.string])) - - -(s/def ::fullname ::us/not-empty-string) -(s/def ::email ::us/email) -(s/def ::backend ::us/not-empty-string) - -(s/def ::info-data - (s/keys :req-un [::fullname ::email ::backend])) - -(defn ^java.lang.AutoCloseable connect - [] - (let [params {:ssl? (cfg/get :ldap-ssl) - :startTLS? (cfg/get :ldap-starttls) - :bind-dn (cfg/get :ldap-bind-dn) - :password (cfg/get :ldap-bind-password) - :host {:address (cfg/get :ldap-host) - :port (cfg/get :ldap-port)}}] - (try - (ldap/connect params) - (catch Exception e - (ex/raise :type :restriction - :code :ldap-disabled - :hint "ldap disabled or unable to connect" - :cause e))))) - -;; --- Mutation: login-with-ldap - -(declare authenticate) -(declare login-or-register) - -(s/def ::email ::us/email) -(s/def ::password ::us/string) -(s/def ::invitation-token ::us/string) - -(s/def ::login-with-ldap - (s/keys :req-un [::email ::password] - :opt-un [::invitation-token])) - -(sv/defmethod ::login-with-ldap {:auth false} - [{:keys [pool session tokens] :as cfg} params] - (db/with-atomic [conn pool] - (let [info (authenticate params) - cfg (assoc cfg :conn conn)] - - (when-not info - (ex/raise :type :validation - :code :wrong-credentials)) - - (when-not (s/valid? ::info-data info) - (let [explain (s/explain-str ::info-data info)] - (l/warn ::l/raw (str "invalid response from ldap, looks like ldap is not configured correctly\n" explain)) - (ex/raise :type :restriction - :code :wrong-ldap-response - :reason explain))) - - (let [profile (login-or-register cfg {:email (:email info) - :backend (:backend info) - :fullname (:fullname info)})] - (if-let [token (:invitation-token params)] - ;; If invitation token comes in params, this is because the - ;; user comes from team-invitation process; in this case, - ;; regenerate token and send back to the user a new invitation - ;; token (and mark current session as logged). - (let [claims (tokens :verify {:token token :iss :team-invitation}) - claims (assoc claims - :member-id (:id profile) - :member-email (:email profile)) - token (tokens :generate claims)] - (with-meta {:invitation-token token} - {:transform-response ((:create session) (:id profile)) - ::audit/props (:props profile) - ::audit/profile-id (:id profile)})) - - (with-meta profile - {:transform-response ((:create session) (:id profile)) - ::audit/props (:props profile) - ::audit/profile-id (:id profile)})))))) - -(defn- replace-several [s & {:as replacements}] - (reduce-kv clojure.string/replace s replacements)) - -(defn- get-ldap-user - [cpool {:keys [email] :as params}] - (let [query (-> (cfg/get :ldap-user-query) - (replace-several ":username" email)) - - attrs [(cfg/get :ldap-attrs-username) - (cfg/get :ldap-attrs-email) - (cfg/get :ldap-attrs-photo) - (cfg/get :ldap-attrs-fullname)] - - base-dn (cfg/get :ldap-base-dn) - params {:filter query - :sizelimit 1 - :attributes attrs}] - (first (ldap/search cpool base-dn params)))) - -(defn- authenticate - [{:keys [password email] :as params}] - (with-open [conn (connect)] - (when-let [{:keys [dn] :as luser} (get-ldap-user conn params)] - (when (ldap/bind? conn dn password) - {:photo (get luser (keyword (cfg/get :ldap-attrs-photo))) - :fullname (get luser (keyword (cfg/get :ldap-attrs-fullname))) - :email email - :backend "ldap"})))) - -(defn- login-or-register - [{:keys [conn] :as cfg} info] - (or (some->> (:email info) - (profile-q/retrieve-profile-data-by-email conn) - (profile-q/populate-additional-data conn) - (profile-q/decode-profile-row)) - (let [params (-> info - (assoc :is-active true) - (assoc :is-demo false))] - (->> params - (profile-m/create-profile conn) - (profile-m/create-profile-relations conn) - (profile-q/strip-private-attrs))))) diff --git a/backend/src/app/rpc/mutations/media.clj b/backend/src/app/rpc/mutations/media.clj index 70c8c20ff9..25d894d770 100644 --- a/backend/src/app/rpc/mutations/media.clj +++ b/backend/src/app/rpc/mutations/media.clj @@ -17,12 +17,17 @@ [app.rpc.queries.teams :as teams] [app.rpc.rlimit :as rlimit] [app.storage :as sto] + [app.storage.tmp :as tmp] + [app.util.bytes :as bs] [app.util.services :as sv] [app.util.time :as dt] [clojure.spec.alpha :as s] + [cuerdas.core :as str] [promesa.core :as p] [promesa.exec :as px])) +(def default-max-file-size (* 1024 1024 10)) ; 10 MiB + (def thumbnail-options {:width 100 :height 100 @@ -49,10 +54,20 @@ (sv/defmethod ::upload-file-media-object {::rlimit/permits (cf/get :rlimit-image)} - [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id content] :as params}] (let [file (select-file pool file-id) cfg (update cfg :storage media/configure-assets-storage)] + (teams/check-edition-permissions! pool profile-id (:team-id file)) + (media/validate-media-type! content) + + (when (> (:size content) (cf/get :media-max-file-size default-max-file-size)) + (ex/raise :type :restriction + :code :media-max-file-size-reached + :hint (str/ffmt "the uploaded file size % is greater than the maximum %" + (:size content) + default-max-file-size))) + (create-file-media-object cfg params))) (defn- big-enough-for-thumbnail? @@ -92,8 +107,6 @@ (defn create-file-media-object [{:keys [storage pool executors] :as cfg} {:keys [id file-id is-local name content] :as params}] - (media/validate-media-type! content) - (letfn [;; Function responsible to retrieve the file information, as ;; it is synchronous operation it should be wrapped into ;; with-dispatch macro. @@ -132,7 +145,7 @@ :bucket "file-media-object"})))) (create-image [info] - (p/let [data (cond-> (:path info) (= (:mtype info) "image/svg+xml") slurp) + (p/let [data (:path info) hash (calculate-hash data) content (-> (sto/content data) (sto/wrap-with-hash hash))] @@ -175,52 +188,53 @@ (teams/check-edition-permissions! pool profile-id (:team-id file)) (create-file-media-object-from-url cfg params))) -(def max-download-file-size - (* 1024 1024 100)) ; 100MiB - (defn- create-file-media-object-from-url - [{:keys [storage http-client] :as cfg} {:keys [url name] :as params}] + [{:keys [http-client] :as cfg} {:keys [url name] :as params}] (letfn [(parse-and-validate-size [headers] - (let [size (some-> (get headers "content-length") d/parse-integer) - mtype (get headers "content-type") - format (cm/mtype->format mtype)] + (let [size (some-> (get headers "content-length") d/parse-integer) + mtype (get headers "content-type") + format (cm/mtype->format mtype) + max-size (cf/get :media-max-file-size default-max-file-size)] + (when-not size (ex/raise :type :validation :code :unknown-size - :hint "Seems like the url points to resource with unknown size")) + :hint "seems like the url points to resource with unknown size")) - (when (> size max-download-file-size) + (when (> size max-size) (ex/raise :type :validation :code :file-too-large - :hint "Seems like the url points to resource with size greater than 100MiB")) + :hint (str/ffmt "the file size % is greater than the maximum %" + size + default-max-file-size))) (when (nil? format) (ex/raise :type :validation :code :media-type-not-allowed - :hint "Seems like the url points to an invalid media object")) + :hint "seems like the url points to an invalid media object")) {:size size :mtype mtype :format format})) - (get-upload-object [sobj] - (p/let [path (sto/get-object-path storage sobj) - mdata (meta sobj)] - {:filename "tempfile" - :size (:size sobj) - :path path - :mtype (:content-type mdata)})) - (download-media [uri] - (p/let [{:keys [body headers]} (http-client {:method :get :uri uri} {:response-type :input-stream}) - {:keys [size mtype]} (parse-and-validate-size headers)] + (-> (http-client {:method :get :uri uri} {:response-type :input-stream}) + (p/then process-response))) - (-> (assoc storage :backend :tmp) - (sto/put-object! {::sto/content (sto/content body size) - ::sto/expired-at (dt/in-future {:minutes 30}) - :content-type mtype - :bucket "file-media-object"}) - (p/then get-upload-object))))] + (process-response [{:keys [body headers] :as response}] + (let [{:keys [size mtype]} (parse-and-validate-size headers) + path (tmp/tempfile :prefix "penpot.media.download.") + written (bs/write-to-file! body path :size size)] + + (when (not= written size) + (ex/raise :type :internal + :code :mismatch-write-size + :hint "unexpected state: unable to write to file")) + + {:filename "tempfile" + :size size + :path path + :mtype mtype}))] (p/let [content (download-media url)] (->> (merge params {:content content :name (or name (:filename content))}) diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index e82e59a8c1..dea366f5d7 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -9,19 +9,18 @@ [app.common.data :as d] [app.common.exceptions :as ex] [app.common.spec :as us] - [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] [app.emails :as eml] [app.loggers.audit :as audit] [app.media :as media] + [app.rpc.commands.auth :as cmd.auth] [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] [app.rpc.rlimit :as rlimit] [app.storage :as sto] [app.util.services :as sv] [app.util.time :as dt] - [buddy.hashers :as hashers] [clojure.spec.alpha :as s] [cuerdas.core :as str] [promesa.core :as p] @@ -37,310 +36,6 @@ (s/def ::password ::us/not-empty-string) (s/def ::old-password ::us/not-empty-string) (s/def ::theme ::us/string) -(s/def ::invitation-token ::us/not-empty-string) - -(declare check-profile-existence!) -(declare create-profile) -(declare create-profile-relations) -(declare register-profile) - -(defn email-domain-in-whitelist? - "Returns true if email's domain is in the given whitelist or if - given whitelist is an empty string." - [domains email] - (if (or (empty? domains) - (nil? domains)) - true - (let [[_ candidate] (-> (str/lower email) - (str/split #"@" 2))] - (contains? domains candidate)))) - -(def ^:private sql:profile-existence - "select exists (select * from profile - where email = ? - and deleted_at is null) as val") - -(defn check-profile-existence! - [conn {:keys [email] :as params}] - (let [email (str/lower email) - result (db/exec-one! conn [sql:profile-existence email])] - (when (:val result) - (ex/raise :type :validation - :code :email-already-exists)) - params)) - -(defn derive-password - [password] - (hashers/derive password - {:alg :argon2id - :memory 16384 - :iterations 20 - :parallelism 2})) - -(defn verify-password - [attempt password] - (try - (hashers/verify attempt password) - (catch Exception _e - {:update false - :valid false}))) - -(defn decode-profile-row - [{:keys [props] :as profile}] - (cond-> profile - (db/pgobject? props "jsonb") - (assoc :props (db/decode-transit-pgobject props)))) - -;; --- MUTATION: Prepare Register - -(s/def ::prepare-register-profile - (s/keys :req-un [::email ::password] - :opt-un [::invitation-token])) - -(sv/defmethod ::prepare-register-profile {:auth false} - [{:keys [pool tokens] :as cfg} params] - (when-not (contains? cf/flags :registration) - (if-not (contains? params :invitation-token) - (ex/raise :type :restriction - :code :registration-disabled) - (let [invitation (tokens :verify {:token (:invitation-token params) :iss :team-invitation})] - (when-not (= (:email params) (:member-email invitation)) - (ex/raise :type :restriction - :code :email-does-not-match-invitation - :hint "email should match the invitation"))))) - - (when-let [domains (cf/get :registration-domain-whitelist)] - (when-not (email-domain-in-whitelist? domains (:email params)) - (ex/raise :type :validation - :code :email-domain-is-not-allowed))) - - ;; Don't allow proceed in preparing registration if the profile is - ;; already reported as spammer. - (when (eml/has-bounce-reports? pool (:email params)) - (ex/raise :type :validation - :code :email-has-permanent-bounces - :hint "looks like the email has one or many bounces reported")) - - (check-profile-existence! pool params) - - (when (= (str/lower (:email params)) - (str/lower (:password params))) - (ex/raise :type :validation - :code :email-as-password - :hint "you can't use your email as password")) - - (let [params {:email (:email params) - :password (:password params) - :invitation-token (:invitation-token params) - :backend "penpot" - :iss :prepared-register - :exp (dt/in-future "48h")} - - token (tokens :generate params)] - (with-meta {:token token} - {::audit/profile-id uuid/zero}))) - -;; --- MUTATION: Register Profile - -(s/def ::token ::us/not-empty-string) -(s/def ::register-profile - (s/keys :req-un [::token ::fullname])) - -(sv/defmethod ::register-profile - {:auth false ::rlimit/permits (cf/get :rlimit-password)} - [{:keys [pool] :as cfg} params] - (db/with-atomic [conn pool] - (-> (assoc cfg :conn conn) - (register-profile params)))) - -(defn register-profile - [{:keys [conn tokens session] :as cfg} {:keys [token] :as params}] - (let [claims (tokens :verify {:token token :iss :prepared-register}) - params (merge params claims)] - (check-profile-existence! conn params) - (let [is-active (or (:is-active params) - (contains? cf/flags :insecure-register)) - profile (->> (assoc params :is-active is-active) - (create-profile conn) - (create-profile-relations conn) - (decode-profile-row)) - invitation (when-let [token (:invitation-token params)] - (tokens :verify {:token token :iss :team-invitation}))] - (cond - ;; If invitation token comes in params, this is because the user comes from team-invitation process; - ;; in this case, regenerate token and send back to the user a new invitation token (and mark current - ;; session as logged). This happens only if the invitation email matches with the register email. - (and (some? invitation) (= (:email profile) (:member-email invitation))) - (let [claims (assoc invitation :member-id (:id profile)) - token (tokens :generate claims) - resp {:invitation-token token}] - (with-meta resp - {:transform-response ((:create session) (:id profile)) - ::audit/replace-props (audit/profile->props profile) - ::audit/profile-id (:id profile)})) - - ;; If auth backend is different from "penpot" means user is - ;; registering using third party auth mechanism; in this case - ;; we need to mark this session as logged. - (not= "penpot" (:auth-backend profile)) - (with-meta (profile/strip-private-attrs profile) - {:transform-response ((:create session) (:id profile)) - ::audit/replace-props (audit/profile->props profile) - ::audit/profile-id (:id profile)}) - - ;; If the `:enable-insecure-register` flag is set, we proceed - ;; to sign in the user directly, without email verification. - (true? is-active) - (with-meta (profile/strip-private-attrs profile) - {:transform-response ((:create session) (:id profile)) - ::audit/replace-props (audit/profile->props profile) - ::audit/profile-id (:id profile)}) - - ;; In all other cases, send a verification email. - :else - (let [vtoken (tokens :generate - {:iss :verify-email - :exp (dt/in-future "48h") - :profile-id (:id profile) - :email (:email profile)}) - ptoken (tokens :generate-predefined - {:iss :profile-identity - :profile-id (:id profile)})] - (eml/send! {::eml/conn conn - ::eml/factory eml/register - :public-uri (:public-uri cfg) - :to (:email profile) - :name (:fullname profile) - :token vtoken - :extra-data ptoken}) - - (with-meta profile - {::audit/replace-props (audit/profile->props profile) - ::audit/profile-id (:id profile)})))))) - -(defn create-profile - "Create the profile entry on the database with limited input filling - all the other fields with defaults." - [conn params] - (let [id (or (:id params) (uuid/next)) - - props (-> (audit/extract-utm-params params) - (merge (:props params)) - (db/tjson)) - - password (if-let [password (:password params)] - (derive-password password) - "!") - - locale (:locale params) - locale (when (and (string? locale) (not (str/blank? locale))) - locale) - - backend (:backend params "penpot") - is-demo (:is-demo params false) - is-muted (:is-muted params false) - is-active (:is-active params false) - email (str/lower (:email params)) - - params {:id id - :fullname (:fullname params) - :email email - :auth-backend backend - :lang locale - :password password - :deleted-at (:deleted-at params) - :props props - :is-active is-active - :is-muted is-muted - :is-demo is-demo}] - (try - (-> (db/insert! conn :profile params) - (decode-profile-row)) - (catch org.postgresql.util.PSQLException e - (let [state (.getSQLState e)] - (if (not= state "23505") - (throw e) - (ex/raise :type :validation - :code :email-already-exists - :cause e))))))) - -(defn create-profile-relations - [conn profile] - (let [team (teams/create-team conn {:profile-id (:id profile) - :name "Default" - :is-default true})] - (-> profile - (profile/strip-private-attrs) - (assoc :default-team-id (:id team)) - (assoc :default-project-id (:default-project-id team))))) - -;; --- MUTATION: Login - -(s/def ::email ::us/email) -(s/def ::scope ::us/string) - -(s/def ::login - (s/keys :req-un [::email ::password] - :opt-un [::scope ::invitation-token])) - -(sv/defmethod ::login - {:auth false ::rlimit/permits (cf/get :rlimit-password)} - [{:keys [pool session tokens] :as cfg} {:keys [email password] :as params}] - - (when-not (contains? cf/flags :login) - (ex/raise :type :restriction - :code :login-disabled - :hint "login is disabled in this instance")) - - (letfn [(check-password [profile password] - (when (= (:password profile) "!") - (ex/raise :type :validation - :code :account-without-password)) - (:valid (verify-password password (:password profile)))) - - (validate-profile [profile] - (when-not (:is-active profile) - (ex/raise :type :validation - :code :wrong-credentials)) - (when-not profile - (ex/raise :type :validation - :code :wrong-credentials)) - (when-not (check-password profile password) - (ex/raise :type :validation - :code :wrong-credentials)) - profile)] - - (db/with-atomic [conn pool] - (let [profile (->> (profile/retrieve-profile-data-by-email conn email) - (validate-profile) - (profile/strip-private-attrs) - (profile/populate-additional-data conn) - (decode-profile-row)) - - invitation (when-let [token (:invitation-token params)] - (tokens :verify {:token token :iss :team-invitation})) - - ;; If invitation member-id does not matches the profile-id, we just proceed to ignore the - ;; invitation because invitations matches exactly; and user can't loging with other email and - ;; accept invitation with other email - response (if (and (some? invitation) (= (:id profile) (:member-id invitation))) - {:invitation-token (:invitation-token params)} - profile)] - - (with-meta response - {:transform-response ((:create session) (:id profile)) - ::audit/props (audit/profile->props profile) - ::audit/profile-id (:id profile)}))))) - -;; --- MUTATION: Logout - -(s/def ::logout - (s/keys :opt-un [::profile-id])) - -(sv/defmethod ::logout {:auth false} - [{:keys [session] :as cfg} _] - (with-meta {} - {:transform-response (:delete session)})) ;; --- MUTATION: Update Profile (own) @@ -414,7 +109,7 @@ (defn- validate-password! [conn {:keys [profile-id old-password] :as params}] (let [profile (db/get-by-id conn :profile profile-id)] - (when-not (:valid (verify-password old-password (:password profile))) + (when-not (:valid (cmd.auth/verify-password old-password (:password profile))) (ex/raise :type :validation :code :old-password-not-match)) profile)) @@ -422,7 +117,7 @@ (defn update-profile-password! [conn {:keys [id password] :as profile}] (db/update! conn :profile - {:password (derive-password password)} + {:password (cmd.auth/derive-password password)} {:id id})) ;; --- MUTATION: Update Photo @@ -481,7 +176,7 @@ (defn- change-email-immediately [{:keys [conn]} {:keys [profile email] :as params}] (when (not= email (:email profile)) - (check-profile-existence! conn params)) + (cmd.auth/check-profile-existence! conn params)) (db/update! conn :profile {:email email} {:id (:id profile)}) @@ -499,7 +194,7 @@ :profile-id (:id profile)})] (when (not= email (:email profile)) - (check-profile-existence! conn params)) + (cmd.auth/check-profile-existence! conn params)) (when-not (eml/allow-send-emails? conn profile) (ex/raise :type :validation @@ -526,76 +221,6 @@ [conn id] (db/get-by-id conn :profile id {:for-update true})) -;; --- MUTATION: Request Profile Recovery - -(s/def ::request-profile-recovery - (s/keys :req-un [::email])) - -(sv/defmethod ::request-profile-recovery {:auth false} - [{:keys [pool tokens] :as cfg} {:keys [email] :as params}] - (letfn [(create-recovery-token [{:keys [id] :as profile}] - (let [token (tokens :generate - {:iss :password-recovery - :exp (dt/in-future "15m") - :profile-id id})] - (assoc profile :token token))) - - (send-email-notification [conn profile] - (let [ptoken (tokens :generate-predefined - {:iss :profile-identity - :profile-id (:id profile)})] - (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 (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-not (:is-active profile) - (ex/raise :type :validation - :code :profile-not-verified - :hint "the user need to validate profile before recover password")) - - (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")) - - (->> profile - (create-recovery-token) - (send-email-notification conn)))))) - - -;; --- MUTATION: Recover Profile - -(s/def ::token ::us/not-empty-string) -(s/def ::recover-profile - (s/keys :req-un [::token ::password])) - -(sv/defmethod ::recover-profile - {:auth false ::rlimit/permits (cf/get :rlimit-password)} - [{:keys [pool tokens] :as cfg} {:keys [token password]}] - (letfn [(validate-token [token] - (let [tdata (tokens :verify {:token token :iss :password-recovery})] - (:profile-id tdata))) - - (update-password [conn profile-id] - (let [pwd (derive-password password)] - (db/update! conn :profile {:password pwd} {:id profile-id})))] - - (db/with-atomic [conn pool] - (->> (validate-token token) - (update-password conn)) - nil))) ;; --- MUTATION: Update Profile Props @@ -668,3 +293,61 @@ :code :owner-teams-with-people :hint "The user need to transfer ownership of owned teams." :context {:teams (mapv :team-id rows)})))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; DEPRECATED METHODS (TO BE REMOVED ON 1.16.x) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; --- MUTATION: Login + +(s/def ::login ::cmd.auth/login-with-password) + +(sv/defmethod ::login + {:auth false ::rlimit/permits (cf/get :rlimit-password)} + [cfg params] + (cmd.auth/login-with-password cfg params)) + +;; --- MUTATION: Logout + +(s/def ::logout ::cmd.auth/logout) + +(sv/defmethod ::logout {:auth false} + [{:keys [session] :as cfg} _] + (with-meta {} + {:transform-response (:delete session)})) + +;; --- MUTATION: Recover Profile + +(s/def ::recover-profile ::cmd.auth/recover-profile) + +(sv/defmethod ::recover-profile + {:auth false ::rlimit/permits (cf/get :rlimit-password)} + [cfg params] + (cmd.auth/recover-profile cfg params)) + +;; --- MUTATION: Prepare Register + +(s/def ::prepare-register-profile ::cmd.auth/prepare-register-profile) + +(sv/defmethod ::prepare-register-profile {:auth false} + [cfg params] + (cmd.auth/prepare-register cfg params)) + +;; --- MUTATION: Register Profile + +(s/def ::register-profile ::cmd.auth/register-profile) + +(sv/defmethod ::register-profile + {:auth false ::rlimit/permits (cf/get :rlimit-password)} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (-> (assoc cfg :conn conn) + (cmd.auth/register-profile params)))) + +;; --- MUTATION: Request Profile Recovery + +(s/def ::request-profile-recovery ::cmd.auth/request-profile-recovery) + +(sv/defmethod ::request-profile-recovery {:auth false} + [cfg params] + (cmd.auth/request-profile-recovery cfg params)) diff --git a/backend/src/app/rpc/mutations/share_link.clj b/backend/src/app/rpc/mutations/share_link.clj index 6079ecf7de..e9d9efc6c1 100644 --- a/backend/src/app/rpc/mutations/share_link.clj +++ b/backend/src/app/rpc/mutations/share_link.clj @@ -19,7 +19,8 @@ (s/def ::id ::us/uuid) (s/def ::profile-id ::us/uuid) (s/def ::file-id ::us/uuid) -(s/def ::flags (s/every ::us/string :kind set?)) +(s/def ::who-comment ::us/string) +(s/def ::who-inspect ::us/string) (s/def ::pages (s/every ::us/uuid :kind set?)) ;; --- Mutation: Create Share Link @@ -27,14 +28,13 @@ (declare create-share-link) (s/def ::create-share-link - (s/keys :req-un [::profile-id ::file-id ::flags] - :opt-un [::pages])) + (s/keys :req-un [::profile-id ::file-id ::who-comment ::who-inspect ::pages])) (sv/defmethod ::create-share-link "Creates a share-link object. - Share links are resources that allows external users access to - specific files with specific permissions (flags)." + Share links are resources that allows external users access to specific + pages of a file with specific permissions (who-comment and who-inspect)." [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (db/with-atomic [conn pool] @@ -42,19 +42,17 @@ (create-share-link conn params))) (defn create-share-link - [conn {:keys [profile-id file-id pages flags]}] + [conn {:keys [profile-id file-id pages who-comment who-inspect]}] (let [pages (db/create-array conn "uuid" pages) - flags (->> (map name flags) - (db/create-array conn "text")) slink (db/insert! conn :share-link {:id (uuid/next) :file-id file-id - :flags flags + :who-comment who-comment + :who-inspect who-inspect :pages pages :owner-id profile-id})] (-> slink - (update :pages db/decode-pgarray #{}) - (update :flags db/decode-pgarray #{})))) + (update :pages db/decode-pgarray #{})))) ;; --- Mutation: Delete Share Link diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index 4657a425a1..7be2a96aab 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -353,7 +353,7 @@ (declare create-team-invitation) (s/def ::email ::us/email) -(s/def ::emails ::us/set-of-emails) +(s/def ::emails ::us/set-of-valid-emails) (s/def ::invite-team-member (s/keys :req-un [::profile-id ::team-id ::role] :opt-un [::email ::emails])) @@ -443,7 +443,7 @@ ;; --- Mutation: Create Team & Invite Members -(s/def ::emails ::us/set-of-emails) +(s/def ::emails ::us/set-of-valid-emails) (s/def ::create-team-and-invite-members (s/and ::create-team (s/keys :req-un [::emails ::role]))) diff --git a/backend/src/app/rpc/permissions.clj b/backend/src/app/rpc/permissions.clj index 363f967e65..773038253b 100644 --- a/backend/src/app/rpc/permissions.clj +++ b/backend/src/app/rpc/permissions.clj @@ -53,6 +53,16 @@ ([perms] (:can-read perms)) ([conn & args] (check (apply qfn conn args))))) +(defn make-comment-predicate-fn + "A simple factory for comment permission predicate functions." + [qfn] + (us/assert fn? qfn) + (fn check + ([perms] + (and (:is-logged perms) (= (:who-comment perms) "all"))) + ([conn & args] + (check (apply qfn conn args))))) + (defn make-check-fn "Helper that converts a predicate permission function to a check function (function that raises an exception)." diff --git a/backend/src/app/rpc/queries/comments.clj b/backend/src/app/rpc/queries/comments.clj index 1894022f91..6c89f18ec4 100644 --- a/backend/src/app/rpc/queries/comments.clj +++ b/backend/src/app/rpc/queries/comments.clj @@ -6,8 +6,9 @@ (ns app.rpc.queries.comments (:require - [app.common.spec :as us] [app.db :as db] + [app.rpc.commands.comments :as cmd.comments] + [app.rpc.doc :as-alias doc] [app.rpc.queries.files :as files] [app.rpc.queries.teams :as teams] [app.util.services :as sv] @@ -19,137 +20,63 @@ (db/pgpoint? position) (assoc :position (db/decode-pgpoint position)) (db/pgobject? participants) (assoc :participants (db/decode-transit-pgobject participants)))) -;; --- Query: Comment Threads +;; --- QUERY: Comment Threads -(declare retrieve-comment-threads) - -(s/def ::team-id ::us/uuid) -(s/def ::file-id ::us/uuid) - -(s/def ::comment-threads - (s/and (s/keys :req-un [::profile-id] - :opt-un [::file-id ::team-id]) - #(or (:file-id %) (:team-id %)))) +(s/def ::comment-threads ::cmd.comments/get-comment-threads) (sv/defmethod ::comment-threads - [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.15"} + [{:keys [pool] :as cfg} params] (with-open [conn (db/open pool)] - (files/check-read-permissions! conn profile-id file-id) - (retrieve-comment-threads conn params))) + (cmd.comments/retrieve-comment-threads conn params))) -(def sql:comment-threads - "select distinct on (ct.id) - ct.*, - f.name as file_name, - f.project_id as project_id, - first_value(c.content) over w as content, - (select count(1) - from comment as c - where c.thread_id = ct.id) as count_comments, - (select count(1) - from comment as c - where c.thread_id = ct.id - and c.created_at >= coalesce(cts.modified_at, ct.created_at)) as count_unread_comments - from comment_thread as ct - inner join comment as c on (c.thread_id = ct.id) - inner join file as f on (f.id = ct.file_id) - left join comment_thread_status as cts - on (cts.thread_id = ct.id and - cts.profile_id = ?) - where ct.file_id = ? - window w as (partition by c.thread_id order by c.created_at asc)") +;; --- QUERY: Unread Comment Threads -(defn- retrieve-comment-threads - [conn {:keys [profile-id file-id]}] - (files/check-read-permissions! conn profile-id file-id) - (->> (db/exec! conn [sql:comment-threads profile-id file-id]) - (into [] (map decode-row)))) - - -;; --- Query: Unread Comment Threads - -(declare retrieve-unread-comment-threads) - -(s/def ::team-id ::us/uuid) -(s/def ::unread-comment-threads - (s/keys :req-un [::profile-id ::team-id])) +(s/def ::unread-comment-threads ::cmd.comments/get-unread-comment-threads) (sv/defmethod ::unread-comment-threads + {::doc/added "1.0" + ::doc/deprecated "1.15"} [{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}] (with-open [conn (db/open pool)] (teams/check-read-permissions! conn profile-id team-id) - (retrieve-unread-comment-threads conn params))) + (cmd.comments/retrieve-unread-comment-threads conn params))) -(def sql:comment-threads-by-team - "select distinct on (ct.id) - ct.*, - f.name as file_name, - f.project_id as project_id, - first_value(c.content) over w as content, - (select count(1) - from comment as c - where c.thread_id = ct.id) as count_comments, - (select count(1) - from comment as c - where c.thread_id = ct.id - and c.created_at >= coalesce(cts.modified_at, ct.created_at)) as count_unread_comments - from comment_thread as ct - inner join comment as c on (c.thread_id = ct.id) - inner join file as f on (f.id = ct.file_id) - inner join project as p on (p.id = f.project_id) - left join comment_thread_status as cts - on (cts.thread_id = ct.id and - cts.profile_id = ?) - where p.team_id = ? - window w as (partition by c.thread_id order by c.created_at asc)") +;; --- QUERY: Single Comment Thread -(def sql:unread-comment-threads-by-team - (str "with threads as (" sql:comment-threads-by-team ")" - "select * from threads where count_unread_comments > 0")) - -(defn retrieve-unread-comment-threads - [conn {:keys [profile-id team-id]}] - (->> (db/exec! conn [sql:unread-comment-threads-by-team profile-id team-id]) - (into [] (map decode-row)))) - - -;; --- Query: Single Comment Thread - -(s/def ::id ::us/uuid) -(s/def ::comment-thread - (s/keys :req-un [::profile-id ::file-id ::id])) +(s/def ::comment-thread ::cmd.comments/get-comment-thread) (sv/defmethod ::comment-thread - [{:keys [pool] :as cfg} {:keys [profile-id file-id id] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] (with-open [conn (db/open pool)] - (files/check-read-permissions! conn profile-id file-id) - (let [sql (str "with threads as (" sql:comment-threads ")" - "select * from threads where id = ?")] - (-> (db/exec-one! conn [sql profile-id file-id id]) - (decode-row))))) + (files/check-comment-permissions! conn profile-id file-id share-id) + (cmd.comments/get-comment-thread conn params))) -;; --- Query: Comments +;; --- QUERY: Comments -(declare retrieve-comments) - -(s/def ::file-id ::us/uuid) -(s/def ::thread-id ::us/uuid) -(s/def ::comments - (s/keys :req-un [::profile-id ::thread-id])) +(s/def ::comments ::cmd.comments/get-comments) (sv/defmethod ::comments - [{:keys [pool] :as cfg} {:keys [profile-id thread-id] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id thread-id share-id] :as params}] (with-open [conn (db/open pool)] (let [thread (db/get-by-id conn :comment-thread thread-id)] - (files/check-read-permissions! conn profile-id (:file-id thread)) - (retrieve-comments conn thread-id)))) + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id)) + (cmd.comments/get-comments conn thread-id))) -(def sql:comments - "select c.* from comment as c - where c.thread_id = ? - order by c.created_at asc") -(defn- retrieve-comments - [conn thread-id] - (->> (db/exec! conn [sql:comments thread-id]) - (into [] (map decode-row)))) +;; --- QUERY: Get file comments users + +(s/def ::file-comments-users ::cmd.comments/get-profiles-for-file-comments) + +(sv/defmethod ::file-comments-users + {::doc/deprecated "1.15" + ::doc/added "1.13"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id]}] + (with-open [conn (db/open pool)] + (files/check-comment-permissions! conn profile-id file-id share-id) + (cmd.comments/get-file-comments-users conn file-id profile-id))) diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 8efc073d32..18ec92848d 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.exceptions :as ex] + [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] [app.common.pages.migrations :as pmg] [app.common.spec :as us] @@ -84,7 +85,8 @@ :is-owner is-owner :is-admin (or is-owner is-admin) :can-edit (or is-owner is-admin can-edit) - :can-read true}))) + :can-read true + :is-logged (some? profile-id)}))) ([conn profile-id file-id share-id] (let [perms (get-permissions conn profile-id file-id) ldata (retrieve-share-link conn file-id share-id)] @@ -97,7 +99,9 @@ (some? perms) perms (some? ldata) {:type :share-link :can-read true - :flags (:flags ldata)})))) + :is-logged (some? profile-id) + :who-comment (:who-comment ldata) + :who-inspect (:who-inspect ldata)})))) (def has-edit-permissions? (perms/make-edition-predicate-fn get-permissions)) @@ -105,12 +109,26 @@ (def has-read-permissions? (perms/make-read-predicate-fn get-permissions)) +(def has-comment-permissions? + (perms/make-comment-predicate-fn get-permissions)) + (def check-edition-permissions! (perms/make-check-fn has-edit-permissions?)) (def check-read-permissions! (perms/make-check-fn has-read-permissions?)) +;; A user has comment permissions if she has read permissions, or comment permissions +(defn check-comment-permissions! + [conn profile-id file-id share-id] + (let [can-read (has-read-permissions? conn profile-id file-id) + can-comment (has-comment-permissions? conn profile-id file-id share-id) + ] + (when-not (or can-read can-comment) + (ex/raise :type :not-found + :code :object-not-found + :hint "not found")))) + ;; --- Query: Files search ;; TODO: this query need to a good refactor @@ -289,7 +307,7 @@ frame (-> page :objects cph/get-frames)] (assoc frame :page-id (:id page))))) - ;; function responsible to filter objects data strucuture of + ;; function responsible to filter objects data structure of ;; all unneded shapes if a concrete frame is provided. If no ;; frame, the objects is returned untouched. (filter-objects [objects frame-id] @@ -307,10 +325,24 @@ object-id (str page-id frame-id) frame (if-let [thumb (get thumbnails object-id)] (assoc frame :thumbnail thumb :shapes []) - (dissoc frame :thumbnail))] + (dissoc frame :thumbnail)) + + children-ids + (cph/get-children-ids objects frame-id) + + bounds + (when (:show-content frame) + (gsh/selection-rect (concat [frame] (->> children-ids (map (d/getf objects)))))) + + frame + (cond-> frame + (some? bounds) + (assoc :children-bounds bounds))] + (if (:thumbnail frame) - (recur (-> (assoc objects frame-id frame) - (d/without-keys (cph/get-children-ids objects frame-id))) + (recur (-> objects + (assoc frame-id frame) + (d/without-keys children-ids)) (rest frames)) (recur (assoc objects frame-id frame) (rest frames)))) diff --git a/backend/src/app/rpc/queries/share_link.clj b/backend/src/app/rpc/queries/share_link.clj index 0bc5673649..6e9e00a79c 100644 --- a/backend/src/app/rpc/queries/share_link.clj +++ b/backend/src/app/rpc/queries/share_link.clj @@ -11,7 +11,7 @@ (defn decode-share-link-row [row] (-> row - (update :flags db/decode-pgarray #{}) + (dissoc :flags) (update :pages db/decode-pgarray #{}))) (defn retrieve-share-link diff --git a/backend/src/app/rpc/queries/viewer.clj b/backend/src/app/rpc/queries/viewer.clj index b98de0a381..90234e4e24 100644 --- a/backend/src/app/rpc/queries/viewer.clj +++ b/backend/src/app/rpc/queries/viewer.clj @@ -9,9 +9,9 @@ [app.common.exceptions :as ex] [app.common.spec :as us] [app.db :as db] + [app.rpc.commands.comments :as comments] [app.rpc.queries.files :as files] [app.rpc.queries.share-link :as slnk] - [app.rpc.queries.teams :as teams] [app.util.services :as sv] [clojure.spec.alpha :as s] [promesa.core :as p])) @@ -23,11 +23,11 @@ (db/get-by-id pool :project id {:columns [:id :name :team-id]})) (defn- retrieve-bundle - [{:keys [pool] :as cfg} file-id] + [{:keys [pool] :as cfg} file-id profile-id] (p/let [file (files/retrieve-file cfg file-id) project (retrieve-project pool (:project-id file)) libs (files/retrieve-file-libraries cfg false file-id) - users (teams/retrieve-users pool (:team-id project)) + users (comments/get-file-comments-users pool file-id profile-id) links (->> (db/query pool :share-link {:file-id file-id}) (mapv slnk/decode-share-link-row)) @@ -54,7 +54,7 @@ (p/let [slink (slnk/retrieve-share-link pool file-id share-id) perms (files/get-permissions pool profile-id file-id share-id) thumbs (files/retrieve-object-thumbnails cfg file-id) - bundle (p/-> (retrieve-bundle cfg file-id) + bundle (p/-> (retrieve-bundle cfg file-id profile-id) (assoc :permissions perms) (assoc-in [:file :thumbnails] thumbs))] diff --git a/backend/src/app/srepl/dev.clj b/backend/src/app/srepl/dev.clj deleted file mode 100644 index d8d243296c..0000000000 --- a/backend/src/app/srepl/dev.clj +++ /dev/null @@ -1,14 +0,0 @@ -(ns app.srepl.dev - #_:clj-kondo/ignore - (:require - [app.db :as db] - [app.config :as cfg] - [app.rpc.mutations.profile :refer [derive-password]] - [app.main :refer [system]])) - -(defn reset-passwords - [system] - (db/with-atomic [conn (:app.db/pool system)] - (let [password (derive-password "123123")] - (db/exec! conn ["update profile set password=?" password])))) - diff --git a/backend/src/app/srepl/fixes.clj b/backend/src/app/srepl/fixes.clj new file mode 100644 index 0000000000..00022a43a6 --- /dev/null +++ b/backend/src/app/srepl/fixes.clj @@ -0,0 +1,43 @@ +;; 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.srepl.fixes + "A collection of adhoc fixes scripts." + (:require + [app.common.logging :as l] + [app.common.uuid :as uuid] + [app.srepl.helpers :as h])) + +(defn repair-orphaned-shapes + "There are some shapes whose parent has been deleted. This function + detects them and puts them as children of the root node." + ([data] + (letfn [(is-orphan? [shape objects] + (and (some? (:parent-id shape)) + (nil? (get objects (:parent-id shape))))) + + (update-page [page] + (let [objects (:objects page) + orphans (into #{} (filter #(is-orphan? % objects)) (vals objects))] + (if (seq orphans) + (do + (l/info :hint "found a file with orphans" :file-id (:id data) :broken-shapes (count orphans)) + (-> page + (h/update-shapes (fn [shape] + (if (contains? orphans shape) + (assoc shape :parent-id uuid/zero) + shape))) + (update-in [:objects uuid/zero :shapes] into (map :id) orphans))) + page)))] + + (h/update-pages data update-page))) + + ;; special arity for to be called from h/analyze-files to search for + ;; files with possible issues + + ([file state] + (repair-orphaned-shapes (:data file)) + (update state :total (fnil inc 0)))) diff --git a/backend/src/app/srepl/helpers.clj b/backend/src/app/srepl/helpers.clj new file mode 100644 index 0000000000..307ccbc54b --- /dev/null +++ b/backend/src/app/srepl/helpers.clj @@ -0,0 +1,135 @@ +;; 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.srepl.helpers + "A main namespace for server repl." + #_:clj-kondo/ignore + (:require + [app.common.data :as d] + [app.common.exceptions :as ex] + [app.common.logging :as l] + [app.common.pages :as cp] + [app.common.pages.migrations :as pmg] + [app.common.pprint :refer [pprint]] + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.config :as cfg] + [app.db :as db] + [app.db.sql :as sql] + [app.main :refer [system]] + [app.rpc.commands.auth :refer [derive-password]] + [app.rpc.queries.profile :as prof] + [app.util.blob :as blob] + [app.util.time :as dt] + [clojure.spec.alpha :as s] + [clojure.walk :as walk] + [cuerdas.core :as str] + [expound.alpha :as expound])) + +(defn reset-password! + "Reset a password to a specific one for a concrete user or all users + if email is `:all` keyword." + [system & {:keys [email password] :or {password "123123"} :as params}] + (us/verify! (contains? params :email) "`email` parameter is mandatory") + (db/with-atomic [conn (:app.db/pool system)] + (let [password (derive-password password)] + (if (= email :all) + (db/exec! conn ["update profile set password=?" password]) + (let [email (str/lower email)] + (db/exec! conn ["update profile set password=? where email=?" password email])))))) + +(defn reset-file-data! + "Hardcode replace of the data of one file." + [system id data] + (db/with-atomic [conn (:app.db/pool system)] + (db/update! conn :file + {:data data} + {:id id}))) + +(defn get-file + "Get the migrated data of one file." + [system id] + (-> (:app.db/pool system) + (db/get-by-id :file id) + (update :data blob/decode) + (update :data pmg/migrate-data))) + +(defn update-file! + "Apply a function to the data of one file. Optionally save the changes or not. + The function receives the decoded and migrated file data." + [system & {:keys [update-fn id save? migrate? inc-revn?] + :or {save? false migrate? true inc-revn? true}}] + (db/with-atomic [conn (:app.db/pool system)] + (let [file (db/get-by-id conn :file id {:for-update true}) + file (-> file + (update :data blob/decode) + (cond-> migrate? (update :data pmg/migrate-data)) + (update :data update-fn) + (update :data blob/encode) + (cond-> inc-revn? (update :revn inc)))] + (when save? + (db/update! conn :file + {:data (:data file) + :revn (:revn file)} + {:id (:id file)})) + (update file :data blob/decode)))) + +(def ^:private sql:retrieve-files-chunk + "SELECT id, name, modified_at, data FROM file + WHERE created_at < ? AND deleted_at is NULL + ORDER BY created_at desc LIMIT ?") + +(defn analyze-files + "Apply a function to all files in the database, reading them in + batches. Do not change data. + + The `on-file` parameter should be a function that receives the file + and the previous state and returns the new state." + [system & {:keys [chunk-size on-file] :or {chunk-size 10}}] + (letfn [(get-chunk [conn cursor] + (let [rows (db/exec! conn [sql:retrieve-files-chunk cursor chunk-size])] + [(some->> rows peek :created-at) (seq rows)])) + + (get-candidates [conn] + (->> (d/iteration (partial get-chunk conn) + :vf second + :kf first + :initk (dt/now)) + (sequence cat) + (map #(update % :data blob/decode))))] + + (db/with-atomic [conn (:app.db/pool system)] + (loop [state {} + files (get-candidates conn)] + (if-let [file (first files)] + (let [state (on-file file state)] + (recur state (rest files))) + state))))) + + +(defn analyze-file-data + [system & {:keys [id on-form on-data]}] + (let [file (get-file system id)] + (cond + (fn? on-data) + (on-data (:data file)) + + (fn? on-form) + (walk/postwalk (fn [form] + (on-form form) + form) + (:data file))) + nil)) + +(defn update-pages + "Apply a function to all pages of one file. The function receives a page and returns an updated page." + [data f] + (update data :pages-index d/update-vals f)) + +(defn update-shapes + "Apply a function to all shapes of one page The function receives a shape and returns an updated shape" + [page f] + (update page :objects d/update-vals f)) diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index e5455aab6d..685adfdf4f 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -1,204 +1,32 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + (ns app.srepl.main - "A main namespace for server repl." + "A collection of adhoc fixes scripts." #_:clj-kondo/ignore (:require - [app.common.data :as d] - [app.common.exceptions :as ex] [app.common.logging :as l] - [app.common.pages :as cp] - [app.common.pages.migrations :as pmg] - [app.common.spec.file :as spec.file] - [app.common.uuid :as uuid] - [app.config :as cfg] - [app.db :as db] - [app.db.sql :as sql] - [app.main :refer [system]] - [app.rpc.queries.profile :as prof] - [app.srepl.dev :as dev] - [app.util.blob :as blob] - [app.util.time :as dt] - [clojure.spec.alpha :as s] - [clojure.walk :as walk] - [cuerdas.core :as str] - [expound.alpha :as expound] - [fipp.edn :refer [pprint]])) + [app.common.pprint :as p] + [app.srepl.fixes :as f] + [app.srepl.helpers :as h] + [clojure.pprint :refer [pprint]])) -;; ==== Utility functions +;; Empty namespace as main entry point for Server REPL -(defn reset-file-data - "Hardcode replace of the data of one file." - [system id data] - (db/with-atomic [conn (:app.db/pool system)] - (db/update! conn :file - {:data data} - {:id id}))) - -(defn get-file - "Get the migrated data of one file." - [system id] - (-> (:app.db/pool system) - (db/get-by-id :file id) - (update :data app.util.blob/decode) - (update :data pmg/migrate-data))) - -(defn duplicate-file - "This is a raw version of duplication of file just only for forensic analysis." - [system file-id email] - (db/with-atomic [conn (:app.db/pool system)] - (when-let [profile (some->> (prof/retrieve-profile-data-by-email conn (str/lower email)) - (prof/populate-additional-data conn))] - (when-let [file (db/exec-one! conn (sql/select :file {:id file-id}))] - (let [params (assoc file - :id (uuid/next) - :project-id (:default-project-id profile))] - (db/insert! conn :file params) - (:id file)))))) - -(defn update-file - "Apply a function to the data of one file. Optionally save the changes or not. - - The function receives the decoded and migrated file data." - ([system id f] (update-file system id f false)) - ([system id f save?] - (db/with-atomic [conn (:app.db/pool system)] - (let [file (db/get-by-id conn :file id {:for-update true}) - file (-> file - (update :data app.util.blob/decode) - (update :data pmg/migrate-data) - (update :data f) - (update :data blob/encode) - (update :revn inc))] - (when save? - (db/update! conn :file - {:data (:data file)} - {:id (:id file)})) - (update file :data blob/decode))))) - -(defn analyze-files - "Apply a function to all files in the database, reading them in batches. Do not change data. - - The function receives an object with some properties of the file and the decoded data, and - an empty atom where it may accumulate statistics, if desired." - [system {:keys [sleep chunk-size max-chunks on-file] - :or {sleep 1000 chunk-size 10 max-chunks ##Inf}}] - (let [stats (atom {})] - (letfn [(retrieve-chunk [conn cursor] - (let [sql (str "select id, name, modified_at, data from file " - " where modified_at < ? and deleted_at is null " - " order by modified_at desc limit ?")] - (->> (db/exec! conn [sql cursor chunk-size]) - (map #(update % :data blob/decode))))) - - (process-chunk [chunk] - (loop [files chunk] - (when-let [file (first files)] - (on-file file stats) - (recur (rest files)))))] - - (db/with-atomic [conn (:app.db/pool system)] - (loop [cursor (dt/now) - chunks 0] - (when (< chunks max-chunks) - (let [chunk (retrieve-chunk conn cursor)] - (when-not (empty? chunk) - (let [cursor (-> chunk last :modified-at)] - (process-chunk chunk) - (Thread/sleep (inst-ms (dt/duration sleep))) - (recur cursor (inc chunks))))))) - @stats)))) - -(defn update-pages - "Apply a function to all pages of one file. The function receives a page and returns an updated page." - [data f] - (update data :pages-index d/update-vals f)) - -(defn update-shapes - "Apply a function to all shapes of one page The function receives a shape and returns an updated shape" - [page f] - (update page :objects d/update-vals f)) +(defn print-available-tasks + [system] + (let [tasks (:app.worker/registry system)] + (p/pprint (keys tasks) :level 200))) -;; ==== Specific fixes - -(defn repair-orphaned-shapes - "There are some shapes whose parent has been deleted. This - function detects them and puts them as children of the root node." - ([file _] ; to be called from analyze-files to search for files with the problem - (repair-orphaned-shapes (:data file))) - - ([data] - (let [is-orphan? (fn [shape objects] - (and (some? (:parent-id shape)) - (nil? (get objects (:parent-id shape))))) - - update-page (fn [page] - (let [objects (:objects page) - orphans (set (filter #(is-orphan? % objects) (vals objects)))] - (if (seq orphans) - (do - (prn (:id data) "file has" (count orphans) "broken shapes") - (-> page - (update-shapes (fn [shape] - (if (orphans shape) - (assoc shape :parent-id uuid/zero) - shape))) - (update-in [:objects uuid/zero :shapes] - (fn [shapes] (into shapes (map :id orphans)))))) - page)))] - - (update-pages data update-page)))) - - -;; DO NOT DELETE already used scripts, could be taken as templates for easyly writing new ones -;; ------------------------------------------------------------------------------------------- - -;; (defn repair-orphaned-components -;; "We have detected some cases of component instances that are not nested, but -;; however they have not the :component-root? attribute (so the system considers -;; them nested). This script fixes this adding them the attribute. -;; -;; Use it with the update-file function above." -;; [data] -;; (let [update-page -;; (fn [page] -;; (prn "================= Page:" (:name page)) -;; (letfn [(is-nested? [object] -;; (and (some? (:component-id object)) -;; (nil? (:component-root? object)))) -;; -;; (is-instance? [object] -;; (some? (:shape-ref object))) -;; -;; (get-parent [object] -;; (get (:objects page) (:parent-id object))) -;; -;; (update-object [object] -;; (if (and (is-nested? object) -;; (not (is-instance? (get-parent object)))) -;; (do -;; (prn "Orphan:" (:name object)) -;; (assoc object :component-root? true)) -;; object))] -;; -;; (update page :objects d/update-vals update-object)))] -;; -;; (update data :pages-index d/update-vals update-page))) - -;; (defn check-image-shapes -;; [{:keys [data] :as file} stats] -;; (println "=> analizing file:" (:name file) (:id file)) -;; (swap! stats update :total-files (fnil inc 0)) -;; (let [affected? (atom false)] -;; (walk/prewalk (fn [obj] -;; (when (and (map? obj) (= :image (:type obj))) -;; (when-let [fcolor (some-> obj :fill-color str/upper)] -;; (when (or (= fcolor "#B1B2B5") -;; (= fcolor "#7B7D85")) -;; (reset! affected? true) -;; (swap! stats update :affected-shapes (fnil inc 0)) -;; (println "--> image shape:" ((juxt :id :name :fill-color :fill-opacity) obj))))) -;; obj) -;; data) -;; (when @affected? -;; (swap! stats update :affected-files (fnil inc 0))))) - +(defn run-task! + ([system name] + (run-task! system name {})) + ([system name params] + (let [tasks (:app.worker/registry system)] + (if-let [task-fn (get tasks name)] + (task-fn params) + (l/warn :hint "no task found" :name name))))) diff --git a/backend/src/app/storage.clj b/backend/src/app/storage.clj index 10e289b7e4..4fbf05a5a2 100644 --- a/backend/src/app/storage.clj +++ b/backend/src/app/storage.clj @@ -14,7 +14,6 @@ [app.common.spec :as us] [app.common.uuid :as uuid] [app.db :as db] - [app.storage.db :as sdb] [app.storage.fs :as sfs] [app.storage.impl :as impl] [app.storage.s3 :as ss3] @@ -32,14 +31,12 @@ (s/def ::s3 ::ss3/backend) (s/def ::fs ::sfs/backend) -(s/def ::db ::sdb/backend) (s/def ::backends (s/map-of ::us/keyword (s/nilable (s/or :s3 ::ss3/backend - :fs ::sfs/backend - :db ::sdb/backend)))) + :fs ::sfs/backend)))) (defmethod ig/pre-init-spec ::storage [_] (s/keys :req-un [::db/pool ::wrk/executor ::backends])) @@ -84,13 +81,14 @@ " and backend = ?" " and deleted_at is null" " limit 1")] - (db/exec-one! conn [sql hash bucket (name backend)]))) + (some-> (db/exec-one! conn [sql hash bucket (name backend)]) + (update :metadata db/decode-transit-pgobject)))) (defn- create-database-object [{:keys [conn backend executor]} {:keys [::content ::expired-at ::touched-at] :as params}] (us/assert ::storage-content content) (px/with-dispatch executor - (let [id (uuid/random) + (let [id (uuid/next) mdata (cond-> (get-metadata params) (satisfies? impl/IContentHash content) @@ -106,13 +104,15 @@ (get-database-object-by-hash conn backend (:bucket mdata) (:hash mdata))) result (or result - (db/insert! conn :storage-object - {:id id - :size (count content) - :backend (name backend) - :metadata (db/tjson mdata) - :deleted-at expired-at - :touched-at touched-at}))] + (-> (db/insert! conn :storage-object + {:id id + :size (impl/get-size content) + :backend (name backend) + :metadata (db/tjson mdata) + :deleted-at expired-at + :touched-at touched-at}) + (update :metadata db/decode-transit-pgobject) + (update :metadata assoc ::created? true)))] (StorageObject. (:id result) (:size result) @@ -120,7 +120,7 @@ (:deleted-at result) (:touched-at result) backend - mdata + (:metadata result) nil)))) (def ^:private sql:retrieve-storage-object @@ -173,9 +173,10 @@ (p/let [storage (assoc storage :conn (or conn pool)) object (create-database-object storage params)] - ;; Store the data finally on the underlying storage subsystem. - (-> (impl/resolve-backend storage backend) - (impl/put-object object content)) + (when (::created? (meta object)) + ;; Store the data finally on the underlying storage subsystem. + (-> (impl/resolve-backend storage backend) + (impl/put-object object content))) object)) @@ -259,7 +260,8 @@ ;; A task responsible to permanently delete already marked as deleted ;; storage files. The storage objects are practically never marked to ;; be deleted directly by the api call. The touched-gc is responsible -;; of collecting the usage of the object and mark it as deleted. +;; of collecting the usage of the object and mark it as deleted. Only +;; the TMP files are are created with expiration date in future. (declare sql:retrieve-deleted-objects-chunk) @@ -268,39 +270,48 @@ (defmethod ig/pre-init-spec ::gc-deleted-task [_] (s/keys :req-un [::storage ::db/pool ::min-age ::wrk/executor])) +(defmethod ig/prep-key ::gc-deleted-task + [_ cfg] + (merge {:min-age (dt/duration {:hours 2})} + (d/without-nils cfg))) + (defmethod ig/init-key ::gc-deleted-task - [_ {:keys [pool storage min-age] :as cfg}] - (letfn [(retrieve-deleted-objects-chunk [conn cursor] + [_ {:keys [pool storage] :as cfg}] + (letfn [(retrieve-deleted-objects-chunk [conn min-age cursor] (let [min-age (db/interval min-age) rows (db/exec! conn [sql:retrieve-deleted-objects-chunk min-age cursor])] [(some-> rows peek :created-at) (some->> (seq rows) (d/group-by #(-> % :backend keyword) :id #{}) seq)])) - (retrieve-deleted-objects [conn] - (->> (d/iteration (fn [cursor] - (retrieve-deleted-objects-chunk conn cursor)) + (retrieve-deleted-objects [conn min-age] + (->> (d/iteration (partial retrieve-deleted-objects-chunk conn min-age) :initk (dt/now) :vf second :kf first) (sequence cat))) - (delete-in-bulk [conn backend ids] - (let [backend (impl/resolve-backend storage backend) + (delete-in-bulk [conn backend-name ids] + (let [backend (impl/resolve-backend storage backend-name) backend (assoc backend :conn conn)] + + (doseq [id ids] + (l/debug :hint "permanently delete storage object" :task "gc-deleted" :backend backend-name :id id)) + @(impl/del-objects-in-bulk backend ids)))] - (fn [_] - (db/with-atomic [conn pool] - (loop [total 0 - groups (retrieve-deleted-objects conn)] - (if-let [[backend ids] (first groups)] - (do - (delete-in-bulk conn backend ids) - (recur (+ total (count ids)) - (rest groups))) - (do - (l/info :task "gc-deleted" :count total) - {:deleted total}))))))) + (fn [params] + (let [min-age (or (:min-age params) (:min-age cfg))] + (db/with-atomic [conn pool] + (loop [total 0 + groups (retrieve-deleted-objects conn min-age)] + (if-let [[backend ids] (first groups)] + (do + (delete-in-bulk conn backend ids) + (recur (+ total (count ids)) + (rest groups))) + (do + (l/info :hint "task finished" :min-age (dt/format-duration min-age) :task "gc-deleted" :total total) + {:deleted total})))))))) (def sql:retrieve-deleted-objects-chunk "with items_part as ( @@ -343,14 +354,14 @@ (defmethod ig/init-key ::gc-touched-task [_ {:keys [pool] :as cfg}] - (letfn [(has-team-font-variant-nrefs? [conn id] - (-> (db/exec-one! conn [sql:retrieve-team-font-variant-nrefs id id id id]) :nrefs pos?)) + (letfn [(get-team-font-variant-nrefs [conn id] + (-> (db/exec-one! conn [sql:retrieve-team-font-variant-nrefs id id id id]) :nrefs)) - (has-file-media-object-nrefs? [conn id] - (-> (db/exec-one! conn [sql:retrieve-file-media-object-nrefs id id]) :nrefs pos?)) + (get-file-media-object-nrefs [conn id] + (-> (db/exec-one! conn [sql:retrieve-file-media-object-nrefs id id]) :nrefs)) - (has-profile-nrefs? [conn id] - (-> (db/exec-one! conn [sql:retrieve-profile-nrefs id id]) :nrefs pos?)) + (get-profile-nrefs [conn id] + (-> (db/exec-one! conn [sql:retrieve-profile-nrefs id id]) :nrefs)) (mark-freeze-in-bulk [conn ids] (db/exec-one! conn ["update storage_object set touched_at=null where id = ANY(?)" @@ -393,15 +404,23 @@ :kf first) (sequence cat))) - (process-objects! [conn pred-fn ids] + (process-objects! [conn get-fn ids bucket] (loop [to-freeze #{} to-delete #{} ids (seq ids)] (if-let [id (first ids)] - (if (pred-fn conn id) - (recur (conj to-freeze id) to-delete (rest ids)) - (recur to-freeze (conj to-delete id) (rest ids))) - + (let [nrefs (get-fn conn id)] + (if (pos? nrefs) + (do + (l/debug :hint "processing storage object" + :task "gc-touched" :id id :status "freeze" + :bucket bucket :refs nrefs) + (recur (conj to-freeze id) to-delete (rest ids))) + (do + (l/debug :hint "processing storage object" + :task "gc-touched" :id id :status "delete" + :bucket bucket :refs nrefs) + (recur to-freeze (conj to-delete id) (rest ids))))) (do (some->> (seq to-freeze) (mark-freeze-in-bulk conn)) (some->> (seq to-delete) (mark-delete-in-bulk conn)) @@ -415,9 +434,9 @@ groups (retrieve-touched conn)] (if-let [[bucket ids] (first groups)] (let [[f d] (case bucket - "file-media-object" (process-objects! conn has-file-media-object-nrefs? ids) - "team-font-variant" (process-objects! conn has-team-font-variant-nrefs? ids) - "profile" (process-objects! conn has-profile-nrefs? ids) + "file-media-object" (process-objects! conn get-file-media-object-nrefs ids bucket) + "team-font-variant" (process-objects! conn get-team-font-variant-nrefs ids bucket) + "profile" (process-objects! conn get-profile-nrefs ids bucket) (ex/raise :type :internal :code :unexpected-unknown-reference :hint (dm/fmt "unknown reference %" bucket)))] @@ -425,15 +444,16 @@ (+ to-delete d) (rest groups))) (do - (l/info :task "gc-touched" :to-freeze to-freeze :to-delete to-delete) + (l/info :hint "task finished" :task "gc-touched" :to-freeze to-freeze :to-delete to-delete) {:freeze to-freeze :delete to-delete}))))))) (def sql:retrieve-touched-objects-chunk - "select so.* from storage_object as so - where so.touched_at is not null - and so.created_at < ? - order by so.created_at desc - limit 500;") + "SELECT so.* + FROM storage_object AS so + WHERE so.touched_at IS NOT NULL + AND so.created_at < ? + ORDER by so.created_at DESC + LIMIT 500;") (def sql:retrieve-file-media-object-nrefs "select ((select count(*) from file_media_object where media_id = ?) + diff --git a/backend/src/app/storage/db.clj b/backend/src/app/storage/db.clj deleted file mode 100644 index 4ccbf74800..0000000000 --- a/backend/src/app/storage/db.clj +++ /dev/null @@ -1,67 +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.storage.db - (:require - [app.common.spec :as us] - [app.db :as db] - [app.storage.impl :as impl] - [clojure.spec.alpha :as s] - [integrant.core :as ig] - [promesa.exec :as px]) - (:import - java.io.ByteArrayInputStream)) - -;; --- BACKEND INIT - -(defmethod ig/pre-init-spec ::backend [_] - (s/keys :opt-un [::db/pool])) - -(defmethod ig/init-key ::backend - [_ cfg] - (assoc cfg :type :db)) - -(s/def ::type ::us/keyword) -(s/def ::backend - (s/keys :req-un [::type ::db/pool])) - -;; --- API IMPL - -(defmethod impl/put-object :db - [{:keys [conn executor] :as storage} {:keys [id] :as object} content] - (px/with-dispatch executor - (let [data (impl/slurp-bytes content)] - (db/insert! conn :storage-data {:id id :data data}) - object))) - -(defmethod impl/get-object-data :db - [{:keys [conn executor] :as backend} {:keys [id] :as object}] - (px/with-dispatch executor - (let [result (db/exec-one! conn ["select data from storage_data where id=?" id])] - (ByteArrayInputStream. (:data result))))) - -(defmethod impl/get-object-bytes :db - [{:keys [conn executor] :as backend} {:keys [id] :as object}] - (px/with-dispatch executor - (let [result (db/exec-one! conn ["select data from storage_data where id=?" id])] - (:data result)))) - -(defmethod impl/get-object-url :db - [_ _] - (throw (UnsupportedOperationException. "not supported"))) - -(defmethod impl/del-object :db - [_ _] - ;; NOOP: because deleting the row already deletes the file data from - ;; the database. - nil) - -(defmethod impl/del-objects-in-bulk :db - [_ _] - ;; NOOP: because deleting the row already deletes the file data from - ;; the database. - nil) - diff --git a/backend/src/app/storage/fs.clj b/backend/src/app/storage/fs.clj index 2b56549a79..4feaaf6242 100644 --- a/backend/src/app/storage/fs.clj +++ b/backend/src/app/storage/fs.clj @@ -10,11 +10,13 @@ [app.common.spec :as us] [app.common.uri :as u] [app.storage.impl :as impl] + [app.util.bytes :as bs] [clojure.java.io :as io] [clojure.spec.alpha :as s] [cuerdas.core :as str] [datoteka.core :as fs] [integrant.core :as ig] + [promesa.core :as p] [promesa.exec :as px]) (:import java.io.InputStream @@ -72,9 +74,10 @@ (io/input-stream full)))) (defmethod impl/get-object-bytes :fs - [{:keys [executor] :as backend} object] - (px/with-dispatch executor - (fs/slurp-bytes (impl/get-object-data backend object)))) + [backend object] + (p/let [input (impl/get-object-data backend object)] + (ex/with-always (bs/close! input) + (bs/read-as-bytes input)))) (defmethod impl/get-object-url :fs [{:keys [uri executor] :as backend} {:keys [id] :as object} _] diff --git a/backend/src/app/storage/impl.clj b/backend/src/app/storage/impl.clj index c5623dd5a8..bca9b5e200 100644 --- a/backend/src/app/storage/impl.clj +++ b/backend/src/app/storage/impl.clj @@ -9,18 +9,15 @@ (:require [app.common.data.macros :as dm] [app.common.exceptions :as ex] - [app.common.uuid :as uuid] + [app.util.bytes :as bs] [buddy.core.codecs :as bc] [buddy.core.hash :as bh] [clojure.java.io :as io]) (:import java.nio.ByteBuffer - java.util.UUID - java.io.ByteArrayInputStream - java.io.InputStream java.nio.file.Files - org.apache.commons.io.input.BoundedInputStream - )) + java.nio.file.Path + java.util.UUID)) ;; --- API Definition @@ -95,23 +92,23 @@ (defn coerce-id [id] (cond - (string? id) (uuid/uuid id) - (uuid? id) id - :else (ex/raise :type :internal - :code :invalid-id-type - :hint "id should be string or uuid"))) + (string? id) (parse-uuid id) + (uuid? id) id + :else (ex/raise :type :internal + :code :invalid-id-type + :hint "id should be string or uuid"))) (defprotocol IContentObject - (size [_] "get object size")) + (get-size [_] "get object size")) (defprotocol IContentHash (get-hash [_] "get precalculated hash")) -(defn- make-content - [^InputStream is ^long size] +(defn- path->content + [^Path path ^long size] (reify IContentObject - (size [_] size) + (get-size [_] size) io/IOFactory (make-reader [this opts] @@ -119,47 +116,53 @@ (make-writer [_ _] (throw (UnsupportedOperationException. "not implemented"))) (make-input-stream [_ _] - (doto (BoundedInputStream. is size) - (.setPropagateClose false))) + (-> (io/input-stream path) + (bs/bounded-input-stream size))) (make-output-stream [_ _] + (throw (UnsupportedOperationException. "not implemented"))))) + +(defn- bytes->content + [^bytes data ^long size] + (reify + IContentObject + (get-size [_] size) + + io/IOFactory + (make-reader [this opts] + (io/make-reader this opts)) + (make-writer [_ _] (throw (UnsupportedOperationException. "not implemented"))) - - clojure.lang.Counted - (count [_] size) - - java.lang.AutoCloseable - (close [_] - (.close is)))) + (make-input-stream [_ _] + (-> (bs/bytes-input-stream data) + (bs/bounded-input-stream size))) + (make-output-stream [_ _] + (throw (UnsupportedOperationException. "not implemented"))))) (defn content ([data] (content data nil)) ([data size] (cond (instance? java.nio.file.Path data) - (make-content (io/input-stream data) - (Files/size data)) + (path->content data (or size (Files/size data))) (instance? java.io.File data) - (content (.toPath ^java.io.File data) nil) + (content (.toPath ^java.io.File data) size) (instance? String data) - (let [data (.getBytes data "UTF-8") - bais (ByteArrayInputStream. ^bytes data)] - (make-content bais (alength data))) + (let [data (.getBytes data "UTF-8")] + (bytes->content data (alength data))) (bytes? data) - (let [size (alength ^bytes data) - bais (ByteArrayInputStream. ^bytes data)] - (make-content bais size)) + (bytes->content data (or size (alength ^bytes data))) - (instance? InputStream data) - (do - (when-not size - (throw (UnsupportedOperationException. "size should be provided on InputStream"))) - (make-content data size)) + ;; (instance? InputStream data) + ;; (do + ;; (when-not size + ;; (throw (UnsupportedOperationException. "size should be provided on InputStream"))) + ;; (make-content data size)) :else - (throw (UnsupportedOperationException. "type not supported"))))) + (throw (IllegalArgumentException. "invalid argument type"))))) (defn wrap-with-hash [content ^String hash] @@ -171,7 +174,7 @@ (reify IContentObject - (size [_] (size content)) + (get-size [_] (get-size content)) IContentHash (get-hash [_] hash) @@ -184,43 +187,17 @@ (make-input-stream [_ opts] (io/make-input-stream content opts)) (make-output-stream [_ opts] - (io/make-output-stream content opts)) - - clojure.lang.Counted - (count [_] (count content)) - - java.lang.AutoCloseable - (close [_] - (.close ^java.lang.AutoCloseable content)))) + (io/make-output-stream content opts)))) (defn content? [v] (satisfies? IContentObject v)) -(defn slurp-bytes - [content] - (with-open [input (io/input-stream content) - output (java.io.ByteArrayOutputStream. (count content))] - (io/copy input output) - (.toByteArray output))) - (defn calculate-hash - [path-or-stream] - (let [result (cond - (instance? InputStream path-or-stream) - (let [result (-> (bh/blake2b-256 path-or-stream) - (bc/bytes->hex))] - (.reset path-or-stream) - result) - - (string? path-or-stream) - (-> (bh/blake2b-256 path-or-stream) - (bc/bytes->hex)) - - :else - (with-open [is (io/input-stream path-or-stream)] - (-> (bh/blake2b-256 is) - (bc/bytes->hex))))] + [resource] + (let [result (with-open [input (io/input-stream resource)] + (-> (bh/blake2b-256 input) + (bc/bytes->hex)))] (str "blake2b:" result))) (defn resolve-backend diff --git a/backend/src/app/storage/s3.clj b/backend/src/app/storage/s3.clj index c94b21a0c9..72480dd539 100644 --- a/backend/src/app/storage/s3.clj +++ b/backend/src/app/storage/s3.clj @@ -12,14 +12,17 @@ [app.common.spec :as us] [app.common.uri :as u] [app.storage.impl :as impl] + [app.storage.tmp :as tmp] [app.util.time :as dt] [app.worker :as wrk] [clojure.java.io :as io] [clojure.spec.alpha :as s] + [datoteka.core :as fs] [integrant.core :as ig] [promesa.core :as p] [promesa.exec :as px]) (:import + java.io.FilterInputStream java.io.InputStream java.nio.ByteBuffer java.time.Duration @@ -30,6 +33,7 @@ org.reactivestreams.Subscription software.amazon.awssdk.core.ResponseBytes software.amazon.awssdk.core.async.AsyncRequestBody + software.amazon.awssdk.core.async.AsyncResponseTransformer software.amazon.awssdk.core.client.config.ClientAsyncConfiguration software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient @@ -68,9 +72,10 @@ (s/keys :opt-un [::region ::bucket ::prefix ::endpoint ::wrk/executor])) (defmethod ig/prep-key ::backend - [_ {:keys [prefix] :as cfg}] + [_ {:keys [prefix region] :as cfg}] (cond-> (d/without-nils cfg) - prefix (assoc :prefix prefix))) + (some? prefix) (assoc :prefix prefix) + (nil? region) (assoc :region :eu-central-1))) (defmethod ig/init-key ::backend [_ cfg] @@ -106,7 +111,16 @@ (defmethod impl/get-object-data :s3 [backend object] - (get-object-data backend object)) + (letfn [(no-such-key? [cause] + (instance? software.amazon.awssdk.services.s3.model.NoSuchKeyException cause)) + (handle-not-found [cause] + (ex/raise :type :not-found + :code :object-not-found + :hint "s3 object not found" + :cause cause))] + + (-> (get-object-data backend object) + (p/catch no-such-key? handle-not-found)))) (defmethod impl/get-object-bytes :s3 [backend object] @@ -130,7 +144,8 @@ (def default-timeout (dt/duration {:seconds 30})) -(defn- ^Region lookup-region +(defn- lookup-region + ^Region [region] (Region/of (name region))) @@ -202,7 +217,7 @@ (reify AsyncRequestBody (contentLength [_] - (Optional/of (long (count content)))) + (Optional/of (long (impl/get-size content)))) (^void subscribe [_ ^Subscriber s] (let [thread (Thread. #(writer-fn s))] @@ -214,7 +229,6 @@ (cancel [_] (.interrupt thread) (.release sem 1)) - (request [_ n] (.release sem (int n)))))))))) @@ -236,16 +250,31 @@ ^AsyncRequestBody content)))) (defn get-object-data - [{:keys [client bucket prefix]} {:keys [id]}] - (p/let [gor (.. (GetObjectRequest/builder) - (bucket bucket) - (key (str prefix (impl/id->path id))) - (build)) - obj (.getObject ^S3AsyncClient client ^GetObjectRequest gor) - ;; rsp (.response ^ResponseInputStream obj) - ;; len (.contentLength ^GetObjectResponse rsp) - ] - (io/input-stream obj))) + [{:keys [client bucket prefix]} {:keys [id size]}] + (let [gor (.. (GetObjectRequest/builder) + (bucket bucket) + (key (str prefix (impl/id->path id))) + (build))] + + ;; If the file size is greater than 2MiB then stream the content + ;; to the filesystem and then read with buffered inputstream; if + ;; not, read the contento into memory using bytearrays. + (if (> size (* 1024 1024 2)) + (p/let [path (tmp/tempfile :prefix "penpot.storage.s3.") + rxf (AsyncResponseTransformer/toFile path) + _ (.getObject ^S3AsyncClient client + ^GetObjectRequest gor + ^AsyncResponseTransformer rxf)] + (proxy [FilterInputStream] [(io/input-stream path)] + (close [] + (fs/delete path) + (proxy-super close)))) + + (p/let [rxf (AsyncResponseTransformer/toBytes) + obj (.getObject ^S3AsyncClient client + ^GetObjectRequest gor + ^AsyncResponseTransformer rxf)] + (.asInputStream ^ResponseBytes obj))))) (defn get-object-bytes [{:keys [client bucket prefix]} {:keys [id]}] @@ -253,7 +282,10 @@ (bucket bucket) (key (str prefix (impl/id->path id))) (build)) - obj (.getObjectAsBytes ^S3AsyncClient client ^GetObjectRequest gor)] + rxf (AsyncResponseTransformer/toBytes) + obj (.getObjectAsBytes ^S3AsyncClient client + ^GetObjectRequest gor + ^AsyncResponseTransformer rxf)] (.asByteArray ^ResponseBytes obj))) (def default-max-age diff --git a/backend/src/app/storage/tmp.clj b/backend/src/app/storage/tmp.clj new file mode 100644 index 0000000000..cdb1b0cc71 --- /dev/null +++ b/backend/src/app/storage/tmp.clj @@ -0,0 +1,83 @@ +;; 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.storage.tmp + "Temporal files service all created files will be tried to clean after + 1 hour afrer creation. This is a best effort, if this process fails, + the operating system cleaning task should be responsible of + permanently delete these files (look at systemd-tempfiles)." + (:require + [app.common.data :as d] + [app.common.logging :as l] + [app.util.time :as dt] + [app.worker :as wrk] + [clojure.core.async :as a] + [clojure.spec.alpha :as s] + [datoteka.core :as fs] + [integrant.core :as ig] + [promesa.exec :as px])) + +(declare remove-temp-file) +(defonce queue (a/chan 128)) + +(s/def ::min-age ::dt/duration) + +(defmethod ig/pre-init-spec ::cleaner [_] + (s/keys :req-un [::min-age ::wrk/scheduler ::wrk/executor])) + +(defmethod ig/prep-key ::cleaner + [_ cfg] + (merge {:min-age (dt/duration {:minutes 30})} + (d/without-nils cfg))) + +(defmethod ig/init-key ::cleaner + [_ {:keys [scheduler executor min-age] :as cfg}] + (l/info :hint "starting tempfile cleaner service") + (let [cch (a/chan)] + (a/go-loop [] + (let [[path port] (a/alts! [queue cch])] + (when (not= port cch) + (l/trace :hint "schedule tempfile deletion" :path path + :expires-at (dt/plus (dt/now) min-age)) + (px/schedule! scheduler + (inst-ms min-age) + (partial remove-temp-file executor path)) + (recur)))) + cch)) + +(defmethod ig/halt-key! ::cleaner + [_ close-ch] + (l/info :hint "stoping tempfile cleaner service") + (some-> close-ch a/close!)) + +(defn- remove-temp-file + "Permanently delete tempfile" + [executor path] + (px/with-dispatch executor + (l/trace :hint "permanently delete tempfile" :path path) + (when (fs/exists? path) + (fs/delete path)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn tempfile + "Returns a tmpfile candidate (without creating it)" + [& {:keys [suffix prefix] + :or {prefix "penpot." + suffix ".tmp"}}] + (let [candidate (fs/tempfile :suffix suffix :prefix prefix)] + (a/offer! queue candidate) + candidate)) + +(defn create-tempfile + [& {:keys [suffix prefix] + :or {prefix "penpot." + suffix ".tmp"}}] + (let [path (fs/create-tempfile :suffix suffix :prefix prefix)] + (a/offer! queue path) + path)) diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj index 029f0b7fe1..5998b25c9b 100644 --- a/backend/src/app/tasks/file_gc.clj +++ b/backend/src/app/tasks/file_gc.clj @@ -14,6 +14,7 @@ [app.common.logging :as l] [app.common.pages.helpers :as cph] [app.common.pages.migrations :as pmg] + [app.config :as cf] [app.db :as db] [app.util.blob :as blob] [app.util.time :as dt] @@ -29,16 +30,22 @@ ;; HANDLER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(s/def ::max-age ::dt/duration) +(s/def ::min-age ::dt/duration) (defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [::db/pool ::max-age])) + (s/keys :req-un [::db/pool ::min-age])) + +(defmethod ig/prep-key ::handler + [_ cfg] + (merge {:min-age cf/deletion-delay} + (d/without-nils cfg))) (defmethod ig/init-key ::handler [_ {:keys [pool] :as cfg}] - (fn [_] + (fn [params] (db/with-atomic [conn pool] - (let [cfg (assoc cfg :conn conn)] + (let [min-age (or (:min-age params) (:min-age cfg)) + cfg (assoc cfg :min-age min-age :conn conn)] (loop [total 0 files (retrieve-candidates cfg)] (if-let [file (first files)] @@ -47,7 +54,12 @@ (recur (inc total) (rest files))) (do - (l/debug :msg "finished processing files" :processed total) + (l/info :hint "task finished" :min-age (dt/format-duration min-age) :total total) + + ;; Allow optional rollback passed by params + (when (:rollback? params) + (db/rollback! conn)) + {:processed total}))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -69,20 +81,22 @@ for update skip locked") (defn- retrieve-candidates - [{:keys [conn max-age] :as cfg}] - (let [interval (db/interval max-age) + [{:keys [conn min-age id] :as cfg}] + (if id + (do + (l/warn :hint "explicit file id passed on params" :id id) + (db/query conn :file {:id id})) + (let [interval (db/interval min-age) + get-chunk (fn [cursor] + (let [rows (db/exec! conn [sql:retrieve-candidates-chunk interval cursor])] + [(some->> rows peek :modified-at) (seq rows)]))] - get-chunk - (fn [cursor] - (let [rows (db/exec! conn [sql:retrieve-candidates-chunk interval cursor])] - [(some->> rows peek :modified-at) (seq rows)]))] + (sequence cat (d/iteration get-chunk + :vf second + :kf first + :initk (dt/now)))))) - (sequence cat (d/iteration get-chunk - :vf second - :kf first - :initk (dt/now))))) - -(defn- collect-used-media +(defn collect-used-media [data] (let [xform (comp (map :objects) @@ -142,14 +156,14 @@ "delete from file_object_thumbnail " " where file_id=? and object_id=ANY(?)") res (db/exec-one! conn [sql file-id (db/create-array conn "text" unused)])] - (l/debug :hint "delete object thumbnails" :total (:next.jdbc/update-count res)))))) + (l/debug :hint "delete file object thumbnails" :file-id file-id :total (:next.jdbc/update-count res)))))) (defn- clean-file-thumbnails! [conn file-id revn] (let [sql (str "delete from file_thumbnail " " where file_id=? and revn < ?") res (db/exec-one! conn [sql file-id revn])] - (l/debug :hint "delete file thumbnails" :total (:next.jdbc/update-count res)))) + (l/debug :hint "delete file thumbnails" :file-id file-id :total (:next.jdbc/update-count res)))) (defn- process-file [{:keys [conn] :as cfg} {:keys [id data revn modified-at] :as file}] diff --git a/backend/src/app/tasks/file_offload.clj b/backend/src/app/tasks/file_offload.clj deleted file mode 100644 index e429d3872c..0000000000 --- a/backend/src/app/tasks/file_offload.clj +++ /dev/null @@ -1,63 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) UXBOX Labs SL - -(ns app.tasks.file-offload - "A maintenance task that offloads file data to an external storage (S3)." - (:require - [app.common.logging :as l] - [app.common.spec :as us] - [app.db :as db] - [app.storage :as sto] - [app.storage.impl :as simpl] - [app.util.time :as dt] - [clojure.spec.alpha :as s] - [integrant.core :as ig])) - -(def sql:offload-candidates-chunk - "select f.id, f.data from file as f - where f.data is not null - and f.modified_at < now() - ?::interval - order by f.modified_at - limit 10") - -(defn- retrieve-candidates - [{:keys [conn max-age]}] - (db/exec! conn [sql:offload-candidates-chunk max-age])) - -(defn- offload-candidate - [{:keys [storage conn backend] :as cfg} {:keys [id data] :as file}] - (l/debug :hint "offload file data" :id id) - (let [backend (simpl/resolve-backend storage backend)] - (->> (simpl/content data) - (simpl/put-object backend file)) - (db/update! conn :file - {:data nil - :data-backend (name (:id backend))} - {:id id}))) - -;; ---- STATE INIT - -(s/def ::max-age ::dt/duration) -(s/def ::backend ::us/keyword) - -(defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [::db/pool ::max-age ::sto/storage ::backend])) - -(defmethod ig/init-key ::handler - [_ {:keys [pool max-age] :as cfg}] - (fn [_] - (db/with-atomic [conn pool] - (let [max-age (db/interval max-age) - cfg (-> cfg - (assoc :conn conn) - (assoc :max-age max-age))] - (loop [n 0] - (let [candidates (retrieve-candidates cfg)] - (if (seq candidates) - (do - (run! (partial offload-candidate cfg) candidates) - (recur (+ n (count candidates)))) - (l/debug :hint "offload summary" :count n)))))))) diff --git a/backend/src/app/tasks/file_xlog_gc.clj b/backend/src/app/tasks/file_xlog_gc.clj index 7b4e21ad5f..6971f198ad 100644 --- a/backend/src/app/tasks/file_xlog_gc.clj +++ b/backend/src/app/tasks/file_xlog_gc.clj @@ -8,6 +8,7 @@ "A maintenance task that performs a garbage collection of the file change (transaction) log." (:require + [app.common.data :as d] [app.common.logging :as l] [app.db :as db] [app.util.time :as dt] @@ -16,21 +17,31 @@ (declare sql:delete-files-xlog) -(s/def ::max-age ::dt/duration) +(s/def ::min-age ::dt/duration) (defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [::db/pool ::max-age])) + (s/keys :req-un [::db/pool] + :opt-un [::min-age])) + +(defmethod ig/prep-key ::handler + [_ cfg] + (merge {:min-age (dt/duration {:hours 72})} + (d/without-nils cfg))) (defmethod ig/init-key ::handler - [_ {:keys [pool max-age] :as cfg}] - (fn [_] - (db/with-atomic [conn pool] - (let [interval (db/interval max-age) - result (db/exec-one! conn [sql:delete-files-xlog interval]) - result (:next.jdbc/update-count result)] - (l/info :hint "remove old file changes" - :removed result) - result)))) + [_ {:keys [pool] :as cfg}] + (fn [params] + (let [min-age (or (:min-age params) (:min-age cfg))] + (db/with-atomic [conn pool] + (let [interval (db/interval min-age) + result (db/exec-one! conn [sql:delete-files-xlog interval]) + result (:next.jdbc/update-count result)] + (l/info :hint "task finished" :min-age (dt/format-duration min-age) :total result) + + (when (:rollback? params) + (db/rollback! conn)) + + result))))) (def ^:private sql:delete-files-xlog diff --git a/backend/src/app/tasks/objects_gc.clj b/backend/src/app/tasks/objects_gc.clj index 4574d71a54..7db23c7efd 100644 --- a/backend/src/app/tasks/objects_gc.clj +++ b/backend/src/app/tasks/objects_gc.clj @@ -8,7 +8,9 @@ "A maintenance task that performs a general purpose garbage collection of deleted objects." (:require + [app.common.data :as d] [app.common.logging :as l] + [app.config :as cf] [app.db :as db] [app.media :as media] [app.storage :as sto] @@ -41,38 +43,38 @@ ;; --- IMPL: generic object deletion (defmethod delete-objects :default - [{:keys [conn max-age table] :as cfg}] + [{:keys [conn min-age table] :as cfg}] (let [sql (str/fmt sql:delete-objects {:table table :limit 50}) - result (db/exec! conn [sql max-age])] + result (db/exec! conn [sql min-age])] (doseq [{:keys [id] :as item} result] - (l/trace :hint "delete object" :table table :id id)) + (l/debug :hint "permanently delete object" :table table :id id)) (count result))) ;; --- IMPL: file deletion (defmethod delete-objects "file" - [{:keys [conn max-age table] :as cfg}] + [{:keys [conn min-age table] :as cfg}] (let [sql (str/fmt sql:delete-objects {:table table :limit 50}) - result (db/exec! conn [sql max-age])] + result (db/exec! conn [sql min-age])] (doseq [{:keys [id] :as item} result] - (l/trace :hint "delete object" :table table :id id)) + (l/debug :hint "permanently delete object" :table table :id id)) (count result))) ;; --- IMPL: team-font-variant deletion (defmethod delete-objects "team_font_variant" - [{:keys [conn max-age storage table] :as cfg}] + [{:keys [conn min-age storage table] :as cfg}] (let [sql (str/fmt sql:delete-objects {:table table :limit 50}) - fonts (db/exec! conn [sql max-age]) + fonts (db/exec! conn [sql min-age]) storage (media/configure-assets-storage storage conn)] (doseq [{:keys [id] :as font} fonts] - (l/trace :hint "delete object" :table table :id id) + (l/debug :hint "permanently delete object" :table table :id id) (some->> (:woff1-file-id font) (sto/touch-object! storage) deref) (some->> (:woff2-file-id font) (sto/touch-object! storage) deref) (some->> (:otf-file-id font) (sto/touch-object! storage) deref) @@ -82,14 +84,14 @@ ;; --- IMPL: team deletion (defmethod delete-objects "team" - [{:keys [conn max-age storage table] :as cfg}] + [{:keys [conn min-age storage table] :as cfg}] (let [sql (str/fmt sql:delete-objects {:table table :limit 50}) - teams (db/exec! conn [sql max-age]) + teams (db/exec! conn [sql min-age]) storage (assoc storage :conn conn)] (doseq [{:keys [id] :as team} teams] - (l/trace :hint "delete object" :table table :id id) + (l/debug :hint "permanently delete object" :table table :id id) (some->> (:photo-id team) (sto/touch-object! storage) deref)) (count teams))) @@ -115,17 +117,17 @@ where id in (select id from owned)") (defmethod delete-objects "profile" - [{:keys [conn max-age storage table] :as cfg}] + [{:keys [conn min-age storage table] :as cfg}] (let [sql (str/fmt sql:retrieve-deleted-profiles {:limit 50}) - profiles (db/exec! conn [sql max-age]) + profiles (db/exec! conn [sql min-age]) storage (assoc storage :conn conn)] (doseq [{:keys [id] :as profile} profiles] - (l/trace :hint "delete object" :table table :id id) + (l/debug :hint "permanently delete object" :table table :id id) ;; Mark the owned teams as deleted; this enables them to be processed ;; in the same transaction in the "team" table step. - (db/exec-one! conn [sql:mark-owned-teams-deleted id max-age]) + (db/exec-one! conn [sql:mark-owned-teams-deleted id min-age]) ;; Mark as deleted the storage object related with the photo-id ;; field. @@ -144,22 +146,40 @@ (let [res (delete-objects cfg)] (if (pos? res) (recur (+ n res)) - (l/debug :hint "table gc summary" :table table :deleted n))))) + (do + (l/debug :hint "delete summary" :table table :total n) + n))))) -(s/def ::max-age ::dt/duration) +(s/def ::min-age ::dt/duration) (defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [::db/pool ::sto/storage ::max-age])) + (s/keys :req-un [::db/pool ::sto/storage] + :opt-un [::min-age])) + +(defmethod ig/prep-key ::handler + [_ cfg] + (merge {:min-age cf/deletion-delay} + (d/without-nils cfg))) (defmethod ig/init-key ::handler - [_ {:keys [pool max-age] :as cfg}] - (fn [task] + [_ {:keys [pool] :as cfg}] + (fn [params] ;; Checking first on task argument allows properly testing it. - (let [max-age (get task :max-age max-age)] + (let [min-age (or (:min-age params) (:min-age cfg))] (db/with-atomic [conn pool] - (let [max-age (db/interval max-age) - cfg (-> cfg - (assoc :max-age max-age) - (assoc :conn conn))] - (doseq [table target-tables] - (process-table (assoc cfg :table table)))))))) + (let [cfg (-> cfg + (assoc :min-age (db/interval min-age)) + (assoc :conn conn))] + (loop [tables (seq target-tables) + total 0] + (if-let [table (first tables)] + (recur (rest tables) + (+ total (process-table (assoc cfg :table table)))) + (do + (l/info :hint "task finished" :min-age (dt/format-duration min-age) :total total) + + (when (:rollback? params) + (db/rollback! conn)) + + {:processed total})))))))) + diff --git a/backend/src/app/tasks/tasks_gc.clj b/backend/src/app/tasks/tasks_gc.clj index 1350c4abf8..784b7db135 100644 --- a/backend/src/app/tasks/tasks_gc.clj +++ b/backend/src/app/tasks/tasks_gc.clj @@ -8,7 +8,9 @@ "A maintenance task that performs a cleanup of already executed tasks from the database table." (:require + [app.common.data :as d] [app.common.logging :as l] + [app.config :as cf] [app.db :as db] [app.util.time :as dt] [clojure.spec.alpha :as s] @@ -16,20 +18,31 @@ (declare sql:delete-completed-tasks) -(s/def ::max-age ::dt/duration) +(s/def ::min-age ::dt/duration) (defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [::db/pool ::max-age])) + (s/keys :req-un [::db/pool] + :opt-un [::min-age])) + +(defmethod ig/prep-key ::handler + [_ cfg] + (merge {:min-age cf/deletion-delay} + (d/without-nils cfg))) (defmethod ig/init-key ::handler - [_ {:keys [pool max-age] :as cfg}] - (fn [_] - (db/with-atomic [conn pool] - (let [interval (db/interval max-age) - result (db/exec-one! conn [sql:delete-completed-tasks interval]) - result (:next.jdbc/update-count result)] - (l/debug :hint "trim completed tasks table" :removed result) - result)))) + [_ {:keys [pool] :as cfg}] + (fn [params] + (let [min-age (or (:min-age params) (:min-age cfg))] + (db/with-atomic [conn pool] + (let [interval (db/interval min-age) + result (db/exec-one! conn [sql:delete-completed-tasks interval]) + result (:next.jdbc/update-count result)] + (l/debug :hint "task finished" :total result) + + (when (:rollback? params) + (db/rollback! conn)) + + result))))) (def ^:private sql:delete-completed-tasks diff --git a/backend/src/app/tokens.clj b/backend/src/app/tokens.clj index 532055c90e..dc68c6897a 100644 --- a/backend/src/app/tokens.clj +++ b/backend/src/app/tokens.clj @@ -18,7 +18,10 @@ (defn- generate [cfg claims] - (let [payload (-> claims d/without-nils t/encode)] + (let [payload (-> claims + (assoc :iat (dt/now)) + (d/without-nils) + (t/encode))] (jwe/encrypt payload (::secret cfg) {:alg :a256kw :enc :a256gcm}))) (defn- verify diff --git a/backend/src/app/util/bytes.clj b/backend/src/app/util/bytes.clj new file mode 100644 index 0000000000..50a73d3350 --- /dev/null +++ b/backend/src/app/util/bytes.clj @@ -0,0 +1,126 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.util.bytes + "Bytes & Byte Streams helpers" + (:require + [clojure.java.io :as io] + [datoteka.core :as fs] + [yetti.adapter :as yt]) + (:import + com.github.luben.zstd.ZstdInputStream + com.github.luben.zstd.ZstdOutputStream + java.io.ByteArrayInputStream + java.io.ByteArrayOutputStream + java.io.DataInputStream + java.io.DataOutputStream + java.io.OutputStream + java.io.InputStream + java.lang.AutoCloseable + org.apache.commons.io.IOUtils + org.apache.commons.io.input.BoundedInputStream)) + +(set! *warn-on-reflection* true) + +(def ^:const default-buffer-size + (:xnio/buffer-size yt/defaults)) + +(defn input-stream? + [s] + (instance? InputStream s)) + +(defn output-stream? + [s] + (instance? OutputStream s)) + +(defn data-input-stream? + [s] + (instance? DataInputStream s)) + +(defn data-output-stream? + [s] + (instance? DataOutputStream s)) + +(defn copy! + [src dst & {:keys [offset size buffer-size] + :or {offset 0 buffer-size default-buffer-size}}] + (let [^bytes buff (byte-array buffer-size)] + (if size + (IOUtils/copyLarge ^InputStream src ^OutputStream dst (long offset) (long size) buff) + (IOUtils/copyLarge ^InputStream src ^OutputStream dst buff)))) + +(defn write-to-file! + [src dst & {:keys [size]}] + (with-open [^OutputStream output (io/output-stream dst)] + (cond + (bytes? src) + (if size + (with-open [^InputStream input (ByteArrayInputStream. ^bytes src)] + (with-open [^InputStream input (BoundedInputStream. input (or size (alength ^bytes src)))] + (copy! input output :size size))) + + (do + (IOUtils/writeChunked ^bytes src output) + (.flush ^OutputStream output) + (alength ^bytes src))) + + (instance? InputStream src) + (copy! src output :size size) + + :else + (throw (IllegalArgumentException. "invalid arguments"))))) + +(defn read-as-bytes + "Read input stream as byte array." + [input & {:keys [size]}] + (cond + (instance? InputStream input) + (with-open [output (ByteArrayOutputStream. (or size (.available ^InputStream input)))] + (copy! input output :size size) + (.toByteArray output)) + + (fs/path? input) + (with-open [input (io/input-stream input) + output (ByteArrayOutputStream. (or size (.available input)))] + (copy! input output :size size) + (.toByteArray output)) + + :else + (throw (IllegalArgumentException. "invalid arguments")))) + +(defn bytes-input-stream + "Creates an instance of ByteArrayInputStream." + [^bytes data] + (ByteArrayInputStream. data)) + +(defn bounded-input-stream + [input size & {:keys [close?] :or {close? true}}] + (doto (BoundedInputStream. ^InputStream input ^long size) + (.setPropagateClose close?))) + +(defn zstd-input-stream + ^InputStream + [input] + (ZstdInputStream. ^InputStream input)) + +(defn zstd-output-stream + ^OutputStream + [output & {:keys [level] :or {level 0}}] + (ZstdOutputStream. ^OutputStream output (int level))) + +(defn data-input-stream + ^DataInputStream + [input] + (DataInputStream. ^InputStream input)) + +(defn data-output-stream + ^DataOutputStream + [output] + (DataOutputStream. ^OutputStream output)) + +(defn close! + [^AutoCloseable stream] + (.close stream)) diff --git a/backend/src/app/util/emails.clj b/backend/src/app/util/emails.clj index 7136a288cb..dca9b732ea 100644 --- a/backend/src/app/util/emails.clj +++ b/backend/src/app/util/emails.clj @@ -30,7 +30,8 @@ [v] (InternetAddress/parse ^String v)) -(defn- ^Message$RecipientType resolve-recipient-type +(defn- resolve-recipient-type + ^Message$RecipientType [type] (case type :to Message$RecipientType/TO @@ -157,7 +158,8 @@ (.setDebug session debug) session)) -(defn ^MimeMessage smtp-message +(defn smtp-message + ^MimeMessage [cfg message] (let [^Session session (smtp-session cfg)] (build-message cfg session message))) diff --git a/backend/src/app/util/services.clj b/backend/src/app/util/services.clj index 9faa8adcb5..642cf27af9 100644 --- a/backend/src/app/util/services.clj +++ b/backend/src/app/util/services.clj @@ -27,7 +27,7 @@ (throw (IllegalArgumentException. "Missing arguments on `defmethod` macro."))) (let [mdata (assoc mdata - ::docs (some-> docs str/<<-) + ::docstring (some-> docs str/<<-) ::spec sname ::name (name sname)) @@ -40,9 +40,14 @@ (comp (d/domap require) (map find-ns) - (mapcat ns-publics) - (map second) - (filter #(::spec (meta %))))) + (mapcat (fn [ns] + (->> (ns-publics ns) + (map second) + (filter #(::spec (meta %))) + (map (fn [fvar] + (with-meta (deref fvar) + (-> (meta fvar) + (assoc :ns (-> ns ns-name str))))))))))) (defn scan-ns [& nsyms] diff --git a/backend/src/app/util/time.clj b/backend/src/app/util/time.clj index 7adeabf541..422c92fb3e 100644 --- a/backend/src/app/util/time.clj +++ b/backend/src/app/util/time.clj @@ -116,6 +116,9 @@ Duration (-edn [o] (pr-str o))) +(defn format-duration + [o] + (str/lower (subs (str o) 2))) ;; --- INSTANT diff --git a/backend/src/app/util/websocket.clj b/backend/src/app/util/websocket.clj index e4f8a12fef..4909049fe8 100644 --- a/backend/src/app/util/websocket.clj +++ b/backend/src/app/util/websocket.clj @@ -10,9 +10,10 @@ [app.common.exceptions :as ex] [app.common.logging :as l] [app.common.transit :as t] - [app.metrics :as mtx] + [app.loggers.audit :refer [parse-client-ip]] [app.util.time :as dt] [clojure.core.async :as a] + [yetti.request :as yr] [yetti.util :as yu] [yetti.websocket :as yws]) (:import @@ -25,8 +26,10 @@ (declare process-output) (declare ws-ping!) (declare ws-send!) +(declare filter-options) (def noop (constantly nil)) +(def identity-3 (fn [_ _ o] o)) (defn handler "A WebSocket upgrade handler factory. Returns a handler that can be @@ -39,94 +42,123 @@ It also accepts some options that allows you parametrize the protocol behavior. The options map will be used as-as for the initial data of the `ws` data structure" - ([handle-message] (handler handle-message {})) - ([handle-message {:keys [::input-buff-size - ::output-buff-size - ::idle-timeout - metrics] - :or {input-buff-size 64 - output-buff-size 64 - idle-timeout 30000} - :as options}] - (fn [{:keys [::yws/channel] :as request}] - (let [input-ch (a/chan input-buff-size) - output-ch (a/chan output-buff-size) - pong-ch (a/chan (a/sliding-buffer 6)) - close-ch (a/chan) + [& {:keys [::on-rcv-message + ::on-snd-message + ::on-connect + ::input-buff-size + ::output-buff-size + ::handler + ::idle-timeout] + :or {input-buff-size 64 + output-buff-size 64 + idle-timeout 30000 + on-connect noop + on-snd-message identity-3 + on-rcv-message identity-3} + :as options}] - options (atom - (-> options - (assoc ::input-ch input-ch) - (assoc ::output-ch output-ch) - (assoc ::close-ch close-ch) - (assoc ::channel channel) - (dissoc ::metrics))) + (assert (fn? on-rcv-message) "'on-rcv-message' should be a function") + (assert (fn? on-snd-message) "'on-snd-message' should be a function") + (assert (fn? on-connect) "'on-connect' should be a function") - terminated (atom false) - created-at (dt/now) + (fn [{:keys [::yws/channel session-id] :as request}] + (let [input-ch (a/chan input-buff-size) + output-ch (a/chan output-buff-size) + pong-ch (a/chan (a/sliding-buffer 6)) + close-ch (a/chan) + stop-ch (a/chan) - on-open - (fn [channel] - (mtx/run! metrics {:id :websocket-active-connections :inc 1}) - (yws/idle-timeout! channel (dt/duration idle-timeout))) + ip-addr (parse-client-ip request) + uagent (yr/get-header request "user-agent") + id (inst-ms (dt/now)) - on-terminate - (fn [& _args] - (when (compare-and-set! terminated false true) - (mtx/run! metrics {:id :websocket-active-connections :dec 1}) - (mtx/run! metrics {:id :websocket-session-timing :val (/ (inst-ms (dt/diff created-at (dt/now))) 1000.0)}) + options (-> (filter-options options) + (merge {::id id + ::input-ch input-ch + ::output-ch output-ch + ::close-ch close-ch + ::stop-ch stop-ch + ::channel channel + ::remote-addr ip-addr + ::http-session-id session-id + ::user-agent uagent}) + (atom)) - (a/close! close-ch) - (a/close! pong-ch) - (a/close! output-ch) - (a/close! input-ch))) + ;; call the on-connect hook and memoize the on-terminate instance + on-terminate (on-connect options) - on-error - (fn [_ error] - (on-terminate) - ;; TODO: properly log timeout exceptions - (when-not (or (instance? java.nio.channels.ClosedChannelException error) - (instance? java.net.SocketException error)) - (l/error :hint (ex-message error) :cause error))) + on-ws-open + (fn [channel] + (l/trace :fn "on-ws-open" :conn-id id) + (yws/idle-timeout! channel (dt/duration idle-timeout))) - on-message - (fn [_ message] - (mtx/run! metrics {:id :websocket-messages-total :labels ["recv"] :inc 1}) - (try - (let [message (t/decode-str message)] - (a/offer! input-ch message)) - (catch Throwable e - (l/warn :hint "error on decoding incoming message from websocket" - :wsmsg (pr-str message) - :cause e) - (on-terminate)))) + on-ws-terminate + (fn [_ code reason] + (l/trace :fn "on-ws-terminate" :conn-id id :code code :reason reason) + (a/close! close-ch)) - on-pong - (fn [_ buffers] - (a/>!! pong-ch (yu/copy-many buffers)))] + on-ws-error + (fn [_ error] + (a/close! close-ch) + (when-not (or (instance? java.nio.channels.ClosedChannelException error) + (instance? java.net.SocketException error)) + (l/error :hint (ex-message error) :cause error))) - ;; launch heartbeat process - (-> @options - (assoc ::pong-ch pong-ch) - (assoc ::on-close on-terminate) - (process-heartbeat)) + on-ws-message + (fn [_ message] + (try + (let [message (on-rcv-message options message) + message (t/decode-str message)] + (a/offer! input-ch message) + (swap! options assoc ::last-activity-at (dt/now))) + (catch Throwable e + (l/warn :hint "error on decoding incoming message from websocket" + :wsmsg (pr-str message) + :cause e) + (a/>! close-ch [8801 "decode error"]) + (a/close! close-ch)))) - ;; Forward all messages from output-ch to the websocket - ;; connection - (a/go-loop [] - (when-let [val (a/!! pong-ch (yu/copy-many buffers)))] - ;; React on messages received from the client - (process-input options handle-message) + ;; Launch heartbeat process + (-> @options + (assoc ::pong-ch pong-ch) + (process-heartbeat)) - {:on-open on-open - :on-error on-error - :on-close on-terminate - :on-text on-message - :on-pong on-pong})))) + ;; Wait a close signal + (a/go + (let [[code reason] (a/! output-ch {:type :error :error (ex-data val)}) @@ -193,19 +225,21 @@ (a/= (count issued) max-missed-heartbeats) - (on-close channel -1 "heartbeat-timeout") + (do + (a/>! close-ch [8802 "heart-beat timeout"]) + (a/close! close-ch)) (recur (inc i))))))) (a/go-loop [] @@ -213,3 +247,11 @@ (swap! beats disj (decode-beat buffer)) (recur))))) +(defn- filter-options + "Remove from options all namespace qualified keys that matches the + current namespace." + [options] + (into {} + (remove (fn [[key]] + (= (namespace key) "app.util.websocket"))) + options)) diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index 39b4fd8964..c7259b24a2 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -203,8 +203,7 @@ (instance? Exception val) (do - (l/warn :cause val - :hint "unexpected error ocurried on polling the database (will resume in some instants)") + (l/warn :hint "unexpected error ocurried on polling the database (will resume in some instants)" :cause val) (a/> (filter some? entries) (run! (partial schedule-cron-task cfg))) @@ -494,16 +493,12 @@ on conflict (id) do update set cron_expr=?") -(defn- synchronize-cron-item - [conn {:keys [id cron]}] - (let [cron (str cron)] - (l/debug :action "initialize scheduled task" :id id :cron cron) - (db/exec-one! conn [sql:upsert-cron-task id cron cron]))) - -(defn- synchronize-cron-entries +(defn- synchronize-cron-entries! [{:keys [pool entries]}] (db/with-atomic [conn pool] - (run! (partial synchronize-cron-item conn) entries))) + (doseq [{:keys [id cron]} entries] + (l/trace :hint "register cron task" :id id :cron (str cron)) + (db/exec-one! conn [sql:upsert-cron-task id (str cron) (str cron)])))) (def sql:lock-cron-task "select id from scheduled_task where id=? for update skip locked") @@ -512,7 +507,7 @@ [{:keys [executor pool] :as cfg} {:keys [id] :as task}] (letfn [(run-task [conn] (when (db/exec-one! conn [sql:lock-cron-task (d/name id)]) - (l/debug :action "execute scheduled task" :id id) + (l/trace :hint "execute cron task" :id id) ((:fn task) task))) (handle-task [] @@ -567,6 +562,7 @@ (defmethod ig/init-key ::registry [_ {:keys [metrics tasks]}] + (l/info :hint "registry initialized" :tasks (count tasks)) (reduce-kv (fn [res k v] (let [tname (name k)] (l/debug :hint "register task" :name tname) diff --git a/backend/test/app/services_files_test.clj b/backend/test/app/services_files_test.clj index aa188b2157..3f3cd5feb5 100644 --- a/backend/test/app/services_files_test.clj +++ b/backend/test/app/services_files_test.clj @@ -187,23 +187,18 @@ ;; freeze because of the deduplication (we have uploaded 2 times ;; 2 two same files). (let [task (:app.storage/gc-touched-task th/*system*) - res (task {})] - + res (task {:min-age (dt/duration 0)})] (t/is (= 2 (:freeze res))) (t/is (= 0 (:delete res)))) - ;; run the task immediately + ;; run the file-gc task immediately without forced min-age (let [task (:app.tasks.file-gc/handler th/*system*) res (task {})] (t/is (= 0 (:processed res)))) - ;; make the file eligible for GC waiting 300ms (configured - ;; timeout for testing) - (th/sleep 300) - ;; run the task again (let [task (:app.tasks.file-gc/handler th/*system*) - res (task {})] + res (task {:min-age (dt/duration 0)})] (t/is (= 1 (:processed res)))) ;; retrieve file and check trimmed attribute @@ -220,22 +215,36 @@ (t/is (some? @(sto/get-object storage (:media-id fmo1)))) (t/is (some? @(sto/get-object storage (:thumbnail-id fmo1)))) - ;; now, we have deleted the unused file-media-object, if we - ;; execute the touched-gc task, we should see that two of them - ;; are marked to be deleted. + ;; proceed to remove usage of the file + (update-file {:file-id (:id file) + :profile-id (:id profile) + :revn 0 + :changes [{:type :del-obj + :page-id (first (get-in file [:data :pages])) + :id shid}]}) + + ;; Now, we have deleted the usag of pointers to the + ;; file-media-objects, if we pase file-gc, they should be marked + ;; as deleted. + (let [task (:app.tasks.file-gc/handler th/*system*) + res (task {:min-age (dt/duration 0)})] + (t/is (= 1 (:processed res)))) + + ;; Now that file-gc have deleted the file-media-object usage, + ;; lets execute the touched-gc task, we should see that two of + ;; them are marked to be deleted. (let [task (:app.storage/gc-touched-task th/*system*) - res (task {})] - (t/is (= 2 (:freeze res))) - (t/is (= 0 (:delete res)))) + res (task {:min-age (dt/duration 0)})] + (t/is (= 0 (:freeze res))) + (t/is (= 2 (:delete res)))) ;; Finally, check that some of the objects that are marked as ;; deleted we are unable to retrieve them using standard storage ;; public api. - (t/is (some? @(sto/get-object storage (:media-id fmo2)))) - (t/is (some? @(sto/get-object storage (:thumbnail-id fmo2)))) - (t/is (some? @(sto/get-object storage (:media-id fmo1)))) - (t/is (some? @(sto/get-object storage (:thumbnail-id fmo1)))) - + (t/is (nil? @(sto/get-object storage (:media-id fmo2)))) + (t/is (nil? @(sto/get-object storage (:thumbnail-id fmo2)))) + (t/is (nil? @(sto/get-object storage (:media-id fmo1)))) + (t/is (nil? @(sto/get-object storage (:thumbnail-id fmo1)))) ))) (t/deftest permissions-checks-creating-file @@ -353,8 +362,8 @@ :profile-id (:id profile1)})] ;; file is not deleted because it does not meet all ;; conditions to be deleted. - (let [result (task {:max-age (dt/duration 0)})] - (t/is (nil? result))) + (let [result (task {:min-age (dt/duration 0)})] + (t/is (= 0 (:processed result)))) ;; query the list of files (let [data {::th/type :project-files @@ -384,8 +393,8 @@ (t/is (= 0 (count result))))) ;; run permanent deletion (should be noop) - (let [result (task {:max-age (dt/duration {:minutes 1})})] - (t/is (nil? result))) + (let [result (task {:min-age (dt/duration {:minutes 1})})] + (t/is (= 0 (:processed result)))) ;; query the list of file libraries of a after hard deletion (let [data {::th/type :file-libraries @@ -398,8 +407,8 @@ (t/is (= 0 (count result))))) ;; run permanent deletion - (let [result (task {:max-age (dt/duration 0)})] - (t/is (nil? result))) + (let [result (task {:min-age (dt/duration 0)})] + (t/is (= 1 (:processed result)))) ;; query the list of file libraries of a after hard deletion (let [data {::th/type :file-libraries @@ -590,7 +599,7 @@ ;; run the task again (let [task (:app.tasks.file-gc/handler th/*system*) - res (task {})] + res (task {:min-age (dt/duration 0)})] (t/is (= 1 (:processed res)))) ;; check that object thumbnails are still here @@ -617,7 +626,7 @@ ;; run the task again (let [task (:app.tasks.file-gc/handler th/*system*) - res (task {})] + res (task {:min-age (dt/duration 0)})] (t/is (= 1 (:processed res)))) ;; check that the unknown frame thumbnail is deleted @@ -701,7 +710,7 @@ ;; run the task again (let [task (:app.tasks.file-gc/handler th/*system*) - res (task {})] + res (task {:min-age (dt/duration 0)})] (t/is (= 1 (:processed res)))) ;; Then query the specific revn diff --git a/backend/test/app/services_fonts_test.clj b/backend/test/app/services_fonts_test.clj index 71f217b6aa..dfe87e5698 100644 --- a/backend/test/app/services_fonts_test.clj +++ b/backend/test/app/services_fonts_test.clj @@ -11,6 +11,7 @@ [app.http :as http] [app.storage :as sto] [app.test-helpers :as th] + [app.util.bytes :as bs] [clojure.java.io :as io] [clojure.test :as t] [datoteka.core :as fs])) @@ -25,7 +26,8 @@ font-id (uuid/custom 10 1) ttfdata (-> (io/resource "app/test_files/font-1.ttf") - (fs/slurp-bytes)) + io/input-stream + bs/read-as-bytes) params {::th/type :create-font-variant :profile-id (:id prof) @@ -60,7 +62,8 @@ font-id (uuid/custom 10 1) data (-> (io/resource "app/test_files/font-1.woff") - (fs/slurp-bytes)) + io/input-stream + bs/read-as-bytes) params {::th/type :create-font-variant :profile-id (:id prof) diff --git a/backend/test/app/services_media_test.clj b/backend/test/app/services_media_test.clj index 51e8e7d683..a5f971d906 100644 --- a/backend/test/app/services_media_test.clj +++ b/backend/test/app/services_media_test.clj @@ -46,9 +46,11 @@ (t/is (sto/storage-object? mobj1)) (t/is (sto/storage-object? mobj2)) (t/is (= 122785 (:size mobj1))) + ;; This is because in ubuntu 21.04 generates different + ;; thumbnail that in ubuntu 22.04. This hack should be removed + ;; when we all use the ubuntu 22.04 devenv image. (t/is (or (= 3302 (:size mobj2)) - (= 3303 (:size mobj2)))))) - )) + (= 3303 (:size mobj2)))))))) (t/deftest media-object-upload (let [prof (th/create-profile* 1) diff --git a/backend/test/app/services_profile_test.clj b/backend/test/app/services_profile_test.clj index c87e7cfa8f..68f14c3b48 100644 --- a/backend/test/app/services_profile_test.clj +++ b/backend/test/app/services_profile_test.clj @@ -10,6 +10,7 @@ [app.config :as cf] [app.db :as db] [app.rpc.mutations.profile :as profile] + [app.rpc.commands.auth :as cauth] [app.test-helpers :as th] [app.util.time :as dt] [clojure.java.io :as io] @@ -27,11 +28,10 @@ ;; Test with wrong credentials (t/deftest profile-login-failed-1 (let [profile (th/create-profile* 1) - data {::th/type :login + data {::th/type :login-with-password :email "profile1.test@nodomain.com" - :password "foobar" - :scope "foobar"} - out (th/mutation! data)] + :password "foobar"} + out (th/command! data)] #_(th/print-result! out) (let [error (:error out)] @@ -42,11 +42,10 @@ ;; Test with good credentials but profile not activated. (t/deftest profile-login-failed-2 (let [profile (th/create-profile* 1) - data {::th/type :login + data {::th/type :login-with-password :email "profile1.test@nodomain.com" - :password "123123" - :scope "foobar"} - out (th/mutation! data)] + :password "123123"} + out (th/command! data)] ;; (th/print-result! out) (let [error (:error out)] (t/is (th/ex-info? error)) @@ -58,8 +57,7 @@ (let [profile (th/create-profile* 1 {:is-active true}) data {::th/type :login :email "profile1.test@nodomain.com" - :password "123123" - :scope "foobar"} + :password "123123"} out (th/mutation! data)] ;; (th/print-result! out) (t/is (nil? (:error out))) @@ -128,8 +126,8 @@ ;; profile is not deleted because it does not meet all ;; conditions to be deleted. - (let [result (task {:max-age (dt/duration 0)})] - (t/is (nil? result))) + (let [result (task {:min-age (dt/duration 0)})] + (t/is (= 0 (:processed result)))) ;; Request profile to be deleted (let [params {::th/type :delete-profile @@ -147,8 +145,8 @@ (t/is (= 1 (count (:result out))))) ;; execute permanent deletion task - (let [result (task {:max-age (dt/duration "-1m")})] - (t/is (nil? result))) + (let [result (task {:min-age (dt/duration "-1m")})] + (t/is (= 1 (:processed result)))) ;; query profile after delete (let [params {::th/type :profile @@ -161,11 +159,11 @@ (t/deftest registration-domain-whitelist (let [whitelist #{"gmail.com" "hey.com" "ya.ru"}] (t/testing "allowed email domain" - (t/is (true? (profile/email-domain-in-whitelist? whitelist "username@ya.ru"))) - (t/is (true? (profile/email-domain-in-whitelist? #{} "username@somedomain.com")))) + (t/is (true? (cauth/email-domain-in-whitelist? whitelist "username@ya.ru"))) + (t/is (true? (cauth/email-domain-in-whitelist? #{} "username@somedomain.com")))) (t/testing "not allowed email domain" - (t/is (false? (profile/email-domain-in-whitelist? whitelist "username@somedomain.com")))))) + (t/is (false? (cauth/email-domain-in-whitelist? whitelist "username@somedomain.com")))))) (t/deftest prepare-register-and-register-profile (let [data {::th/type :prepare-register-profile diff --git a/backend/test/app/services_projects_test.clj b/backend/test/app/services_projects_test.clj index a59991e38c..3f7cbd63a3 100644 --- a/backend/test/app/services_projects_test.clj +++ b/backend/test/app/services_projects_test.clj @@ -179,8 +179,8 @@ ;; project is not deleted because it does not meet all ;; conditions to be deleted. - (let [result (task {:max-age (dt/duration 0)})] - (t/is (nil? result))) + (let [result (task {:min-age (dt/duration 0)})] + (t/is (= 0 (:processed result)))) ;; query the list of projects (let [data {::th/type :projects @@ -210,8 +210,8 @@ (t/is (= 1 (count result))))) ;; run permanent deletion (should be noop) - (let [result (task {:max-age (dt/duration {:minutes 1})})] - (t/is (nil? result))) + (let [result (task {:min-age (dt/duration {:minutes 1})})] + (t/is (= 0 (:processed result)))) ;; query the list of files of a after soft deletion (let [data {::th/type :project-files @@ -224,8 +224,8 @@ (t/is (= 0 (count result))))) ;; run permanent deletion - (let [result (task {:max-age (dt/duration 0)})] - (t/is (nil? result))) + (let [result (task {:min-age (dt/duration 0)})] + (t/is (= 1 (:processed result)))) ;; query the list of files of a after hard deletion (let [data {::th/type :project-files diff --git a/backend/test/app/services_teams_test.clj b/backend/test/app/services_teams_test.clj index 61199fbf39..275cf58d38 100644 --- a/backend/test/app/services_teams_test.clj +++ b/backend/test/app/services_teams_test.clj @@ -99,8 +99,8 @@ ;; team is not deleted because it does not meet all ;; conditions to be deleted. - (let [result (task {:max-age (dt/duration 0)})] - (t/is (nil? result))) + (let [result (task {:min-age (dt/duration 0)})] + (t/is (= 0 (:processed result)))) ;; query the list of teams (let [data {::th/type :teams @@ -132,8 +132,8 @@ (t/is (= (:default-team-id profile1) (get-in result [0 :id]))))) ;; run permanent deletion (should be noop) - (let [result (task {:max-age (dt/duration {:minutes 1})})] - (t/is (nil? result))) + (let [result (task {:min-age (dt/duration {:minutes 1})})] + (t/is (= 0 (:processed result)))) ;; query the list of projects after hard deletion (let [data {::th/type :projects @@ -147,8 +147,8 @@ (t/is (= (:type error-data) :not-found)))) ;; run permanent deletion - (let [result (task {:max-age (dt/duration 0)})] - (t/is (nil? result))) + (let [result (task {:min-age (dt/duration 0)})] + (t/is (= 1 (:processed result)))) ;; query the list of projects of a after hard deletion (let [data {::th/type :projects diff --git a/backend/test/app/services_viewer_test.clj b/backend/test/app/services_viewer_test.clj index 1b9d7c0133..e8a01c255a 100644 --- a/backend/test/app/services_viewer_test.clj +++ b/backend/test/app/services_viewer_test.clj @@ -49,7 +49,8 @@ :profile-id (:id prof) :file-id (:id file) :pages #{(get-in file [:data :pages 0])} - :flags #{}} + :who-comment "team" + :who-inspect "all"} out (th/mutation! data)] ;; (th/print-result! out) diff --git a/backend/test/app/storage_test.clj b/backend/test/app/storage_test.clj index cab9e01d8a..3a921c8aae 100644 --- a/backend/test/app/storage_test.clj +++ b/backend/test/app/storage_test.clj @@ -12,6 +12,7 @@ [app.storage :as sto] [app.test-helpers :as th] [app.util.time :as dt] + [app.util.bytes :as bs] [clojure.java.io :as io] [clojure.test :as t] [cuerdas.core :as str] @@ -27,11 +28,11 @@ "Given storage map, returns a storage configured with the appropriate backend for assets." ([storage] - (assoc storage :backend :tmp)) + (assoc storage :backend :assets-fs)) ([storage conn] (-> storage (assoc :conn conn) - (assoc :backend :tmp)))) + (assoc :backend :assets-fs)))) (t/deftest put-and-retrieve-object (let [storage (-> (:app.storage/storage th/*system*) @@ -43,7 +44,7 @@ (t/is (sto/storage-object? object)) (t/is (fs/path? @(sto/get-object-path storage object))) (t/is (nil? (:expired-at object))) - (t/is (= :tmp (:backend object))) + (t/is (= :assets-fs (:backend object))) (t/is (= "data" (:other (meta object)))) (t/is (= "text/plain" (:content-type (meta object)))) (t/is (= "content" (slurp @(sto/get-object-data storage object)))) @@ -197,7 +198,8 @@ :is-shared false}) ttfdata (-> (io/resource "app/test_files/font-1.ttf") - (fs/slurp-bytes)) + io/input-stream + bs/read-as-bytes) mfile {:filename "sample.jpg" :path (th/tempfile "app/test_files/sample.jpg") diff --git a/backend/test/app/test_files/template.penpot b/backend/test/app/test_files/template.penpot new file mode 100644 index 0000000000..e0c81bb83e Binary files /dev/null and b/backend/test/app/test_files/template.penpot differ diff --git a/backend/test/app/test_helpers.clj b/backend/test/app/test_helpers.clj index f94e60701e..92642d1718 100644 --- a/backend/test/app/test_helpers.clj +++ b/backend/test/app/test_helpers.clj @@ -9,14 +9,15 @@ [app.common.data :as d] [app.common.flags :as flags] [app.common.pages :as cp] + [app.common.pprint :as pp] [app.common.spec :as us] [app.common.uuid :as uuid] - [app.common.pprint :as pp] [app.config :as cf] [app.db :as db] [app.main :as main] [app.media] [app.migrations] + [app.rpc.commands.auth :as cmd.auth] [app.rpc.mutations.files :as files] [app.rpc.mutations.profile :as profile] [app.rpc.mutations.projects :as projects] @@ -31,8 +32,8 @@ [expound.alpha :as expound] [integrant.core :as ig] [mockery.core :as mk] - [yetti.request :as yrq] - [promesa.core :as p]) + [promesa.core :as p] + [yetti.request :as yrq]) (:import org.postgresql.ds.PGSimpleDataSource)) (def ^:dynamic *system* nil) @@ -50,6 +51,7 @@ (defn state-init [next] (let [config (-> main/system-config + (merge main/worker-config) (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)) @@ -59,10 +61,12 @@ :app.http/router :app.http.awsns/handler :app.http.session/updater - :app.http.oauth/google - :app.http.oauth/gitlab - :app.http.oauth/github - :app.http.oauth/all + :app.auth.oidc/google-provider + :app.auth.oidc/gitlab-provider + :app.auth.oidc/github-provider + :app.auth.oidc/generic-provider + :app.auth.oidc/routes + ;; :app.auth.ldap/provider :app.worker/executors-monitor :app.http.oauth/handler :app.notifications/handler @@ -72,18 +76,16 @@ :app.loggers.database/reporter :app.loggers.zmq/receiver :app.worker/cron - :app.worker/worker) - (d/deep-merge - {:app.tasks.file-gc/handler {:max-age (dt/duration 300)}})) + :app.worker/worker)) _ (ig/load-namespaces config) system (-> (ig/prep config) (ig/init))] (try (binding [*system* system *pool* (:app.db/pool system)] - (mk/with-mocks [mock1 {:target 'app.rpc.mutations.profile/derive-password + (mk/with-mocks [mock1 {:target 'app.rpc.commands.auth/derive-password :return identity} - mock2 {:target 'app.rpc.mutations.profile/verify-password + mock2 {:target 'app.rpc.commands.auth/verify-password :return (fn [a b] {:valid (= a b)})}] (next))) (finally @@ -140,8 +142,8 @@ :is-demo false} params)] (->> params - (#'profile/create-profile conn) - (#'profile/create-profile-relations conn))))) + (cmd.auth/create-profile conn) + (cmd.auth/create-profile-relations conn))))) (defn create-project* ([i params] (create-project* *pool* i params)) @@ -267,17 +269,21 @@ {:error (handle-error e#) :result nil}))) +(defn command! + [{:keys [::type] :as data}] + (let [method-fn (get-in *system* [:app.rpc/methods :commands type])] + ;; (app.common.pprint/pprint (:app.rpc/methods *system*)) + (try-on! (method-fn (dissoc data ::type))))) + (defn mutation! [{:keys [::type] :as data}] - (let [method-fn (get-in *system* [:app.rpc/rpc :methods :mutation type])] - (try-on! - (method-fn (dissoc data ::type))))) + (let [method-fn (get-in *system* [:app.rpc/methods :mutations type])] + (try-on! (method-fn (dissoc data ::type))))) (defn query! [{:keys [::type] :as data}] - (let [method-fn (get-in *system* [:app.rpc/rpc :methods :query type])] - (try-on! - (method-fn (dissoc data ::type))))) + (let [method-fn (get-in *system* [:app.rpc/methods :queries type])] + (try-on! (method-fn (dissoc data ::type))))) ;; --- UTILS diff --git a/common/deps.edn b/common/deps.edn index 019017255f..ae4124dc51 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -1,9 +1,9 @@ {:deps - {org.clojure/clojure {:mvn/version "1.10.3"} + {org.clojure/clojure {:mvn/version "1.11.1"} org.clojure/data.json {:mvn/version "2.4.0"} org.clojure/tools.cli {:mvn/version "1.0.206"} - metosin/jsonista {:mvn/version "0.3.5"} - org.clojure/clojurescript {:mvn/version "1.11.4"} + metosin/jsonista {:mvn/version "0.3.6"} + org.clojure/clojurescript {:mvn/version "1.11.57"} ;; Logging org.apache.logging.log4j/log4j-api {:mvn/version "2.17.2"} @@ -13,7 +13,7 @@ org.apache.logging.log4j/log4j-slf4j18-impl {:mvn/version "2.17.2"} org.slf4j/slf4j-api {:mvn/version "2.0.0-alpha1"} - selmer/selmer {:mvn/version "1.12.50"} + selmer/selmer {:mvn/version "1.12.51"} criterium/criterium {:mvn/version "0.4.6"} expound/expound {:mvn/version "0.9.0"} @@ -22,7 +22,7 @@ java-http-clj/java-http-clj {:mvn/version "0.4.3"} funcool/promesa {:mvn/version "8.0.450"} - funcool/cuerdas {:mvn/version "2022.03.27-397"} + funcool/cuerdas {:mvn/version "2022.06.16-403"} lambdaisland/uri {:mvn/version "1.13.95" :exclusions [org.clojure/data.json]} @@ -33,7 +33,7 @@ com.sun.mail/jakarta.mail {:mvn/version "2.0.1"} ;; exception printing - fipp/fipp {:mvn/version "0.6.25"} + fipp/fipp {:mvn/version "0.6.26"} io.aviso/pretty {:mvn/version "1.1.1"} environ/environ {:mvn/version "1.2.0"}} :paths ["src"] @@ -42,7 +42,7 @@ {:extra-deps {org.clojure/tools.namespace {:mvn/version "RELEASE"} org.clojure/test.check {:mvn/version "RELEASE"} - thheller/shadow-cljs {:mvn/version "2.17.8"} + thheller/shadow-cljs {:mvn/version "2.19.8"} com.bhauman/rebel-readline {:mvn/version "RELEASE"} criterium/criterium {:mvn/version "RELEASE"} mockery/mockery {:mvn/version "RELEASE"}} diff --git a/common/package.json b/common/package.json index 990c172add..68eb896200 100644 --- a/common/package.json +++ b/common/package.json @@ -13,7 +13,7 @@ "test": "yarn run compile-test && yarn run run-test" }, "devDependencies": { - "shadow-cljs": "2.17.8", + "shadow-cljs": "2.19.8", "source-map-support": "^0.5.19", "ws": "^7.4.6" } diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 874ee56b92..baa0f2fe33 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -23,6 +23,9 @@ #?(:clj (:import linked.set.LinkedSet))) +(def boolean-or-nil? + (some-fn nil? boolean?)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Structures ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/common/src/app/common/exceptions.cljc b/common/src/app/common/exceptions.cljc index d76f943e54..424a594a85 100644 --- a/common/src/app/common/exceptions.cljc +++ b/common/src/app/common/exceptions.cljc @@ -50,6 +50,12 @@ [& exprs] `(try* (^:once fn* [] ~@exprs) identity)) +(defn with-always + "A helper that evaluates an exptession independently if the body + raises exception or not." + [always-expr & body] + `(try ~@body (finally ~always-expr))) + (defn ex-info? [v] (instance? #?(:clj clojure.lang.ExceptionInfo :cljs cljs.core.ExceptionInfo) v)) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index eb73bde26c..882991a1f0 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -11,9 +11,9 @@ [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] [app.common.pages.changes :as ch] + [app.common.pages.changes-spec :as pcs] [app.common.pages.init :as init] [app.common.spec :as us] - [app.common.spec.change :as spec.change] [app.common.uuid :as uuid] [cuerdas.core :as str])) @@ -44,9 +44,9 @@ :frame-id (:current-frame-id file)))] (when fail-on-spec? - (us/verify ::spec.change/change change)) + (us/verify ::pcs/change change)) - (let [valid? (us/valid? ::spec.change/change change)] + (let [valid? (us/valid? ::pcs/change change)] #?(:cljs (when-not valid? (.warn js/console "Invalid shape" (clj->js change)))) @@ -222,9 +222,13 @@ (defn close-artboard [file] (assert (nil? (:current-component-id file))) - (-> file - (assoc :current-frame-id root-frame) - (update :parent-stack pop))) + + (let [parent-id (-> file :parent-id peek) + parent (lookup-shape file parent-id) + current-frame-id (or (:frame-id parent) root-frame)] + (-> file + (assoc :current-frame-id current-frame-id) + (update :parent-stack pop)))) (defn add-group [file data] (let [frame-id (:current-frame-id file) diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index b0b2009975..f83f5cca7b 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -14,6 +14,8 @@ [app.common.spec :as us] [clojure.spec.alpha :as s])) +(def precision 3) + ;; --- Matrix Impl (defrecord Matrix [^double a @@ -24,7 +26,13 @@ ^double f] Object (toString [_] - (str "matrix(" a "," b "," c "," d "," e "," f ")"))) + (str "matrix(" + (mth/precision a precision) "," + (mth/precision b precision) "," + (mth/precision c precision) "," + (mth/precision d precision) "," + (mth/precision e precision) "," + (mth/precision f precision) ")"))) (defn matrix? "Return true if `v` is Matrix instance." @@ -66,6 +74,15 @@ (mth/close? (.-e m1) (.-e m2)) (mth/close? (.-f m1) (.-f m2)))) +(defn unit? [m1] + (and (some? m1) + (mth/close? (.-a m1) 1) + (mth/close? (.-b m1) 0) + (mth/close? (.-c m1) 0) + (mth/close? (.-d m1) 1) + (mth/close? (.-e m1) 0) + (mth/close? (.-f m1) 0))) + (defn multiply ([^Matrix m1 ^Matrix m2] (let [m1a (.-a m1) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index ed5c52aa9e..28bc5dcef0 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -98,13 +98,6 @@ (defn distance-shapes [shape other] (distance-selrect (:selrect shape) (:selrect other))) -(defn shape-stroke-margin - [shape stroke-width] - (if (= (:type shape) :path) - ;; TODO: Calculate with the stroke offset (not implemented yet - (mth/sqrt (* 2 stroke-width stroke-width)) - (- (mth/sqrt (* 2 stroke-width stroke-width)) stroke-width))) - (defn close-attrs? "Compares two shapes attributes to see if they are equal or almost equal (in case of numeric). Takes into account attributes that are @@ -159,6 +152,7 @@ (dm/export gtr/move) (dm/export gtr/absolute-move) (dm/export gtr/transform-matrix) +(dm/export gtr/transform-str) (dm/export gtr/inverse-transform-matrix) (dm/export gtr/transform-point-center) (dm/export gtr/transform-rect) @@ -171,6 +165,7 @@ (dm/export gtr/merge-modifiers) (dm/export gtr/transform-shape) (dm/export gtr/transform-selrect) +(dm/export gtr/transform-selrect-matrix) (dm/export gtr/transform-bounds) (dm/export gtr/modifiers->transform) (dm/export gtr/empty-modifiers?) diff --git a/common/src/app/common/geom/shapes/bounds.cljc b/common/src/app/common/geom/shapes/bounds.cljc new file mode 100644 index 0000000000..367935298f --- /dev/null +++ b/common/src/app/common/geom/shapes/bounds.cljc @@ -0,0 +1,158 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.geom.shapes.bounds + (:require + [app.common.data :as d] + [app.common.geom.shapes.rect :as gsr] + [app.common.math :as mth] + [app.common.pages.helpers :as cph])) + +(defn shape-stroke-margin + [shape stroke-width] + (if (= (:type shape) :path) + ;; TODO: Calculate with the stroke offset (not implemented yet + (mth/sqrt (* 2 stroke-width stroke-width)) + (- (mth/sqrt (* 2 stroke-width stroke-width)) stroke-width))) + +(defn blur-filters [type value] + (->> [value] + (remove :hidden) + (filter #(= (:type %) type)) + (map #(hash-map :id (str "filter_" (:id %)) + :type (:type %) + :params %)))) + +(defn shadow-filters [type filters] + (->> filters + (remove :hidden) + (filter #(= (:style %) type)) + (map #(hash-map :id (str "filter_" (:id %)) + :type (:style %) + :params %)))) + +(defn shape->filters + [shape] + (d/concat-vec + [{:id "BackgroundImageFix" :type :image-fix}] + + ;; Background blur won't work in current SVG specification + ;; We can revisit this in the future + #_(->> shape :blur (blur-filters :background-blur)) + + (->> shape :shadow (shadow-filters :drop-shadow)) + [{:id "shape" :type :blend-filters}] + (->> shape :shadow (shadow-filters :inner-shadow)) + (->> shape :blur (blur-filters :layer-blur)))) + +(defn calculate-filter-bounds [{:keys [x y width height]} filter-entry] + (let [{:keys [offset-x offset-y blur spread] :or {offset-x 0 offset-y 0 blur 0 spread 0}} (:params filter-entry) + filter-x (min x (+ x offset-x (- spread) (- blur) -5)) + filter-y (min y (+ y offset-y (- spread) (- blur) -5)) + filter-width (+ width (mth/abs offset-x) (* spread 2) (* blur 2) 10) + filter-height (+ height (mth/abs offset-y) (* spread 2) (* blur 2) 10)] + (gsr/make-selrect filter-x filter-y filter-width filter-height))) + +(defn get-rect-filter-bounds + [selrect filters blur-value] + (let [filter-bounds (->> filters + (filter #(= :drop-shadow (:type %))) + (map (partial calculate-filter-bounds selrect)) + (concat [selrect]) + (gsr/join-selrects)) + delta-blur (* blur-value 2) + + result + (-> filter-bounds + (update :x - delta-blur) + (update :y - delta-blur) + (update :x1 - delta-blur) + (update :x1 - delta-blur) + (update :x2 + delta-blur) + (update :y2 + delta-blur) + (update :width + (* delta-blur 2)) + (update :height + (* delta-blur 2)))] + + result)) + +(defn get-shape-filter-bounds + ([shape] + (let [svg-root? (and (= :svg-raw (:type shape)) (not= :svg (get-in shape [:content :tag])))] + (if svg-root? + (:selrect shape) + + (let [filters (shape->filters shape) + blur-value (or (-> shape :blur :value) 0)] + (get-rect-filter-bounds (-> shape :points gsr/points->selrect) filters blur-value)))))) + +(defn calculate-padding + ([shape] + (calculate-padding shape false)) + + ([shape ignore-margin?] + (let [stroke-width (apply max 0 (map #(case (:stroke-alignment % :center) + :center (/ (:stroke-width % 0) 2) + :outer (:stroke-width % 0) + 0) (:strokes shape))) + + margin (if ignore-margin? + 0 + (apply max 0 (map #(shape-stroke-margin % stroke-width) (:strokes shape)))) + + shadow-width (apply max 0 (map #(case (:style % :drop-shadow) + :drop-shadow (+ (mth/abs (:offset-x %)) (* (:spread %) 2) (* (:blur %) 2) 10) + 0) (:shadow shape))) + + shadow-height (apply max 0 (map #(case (:style % :drop-shadow) + :drop-shadow (+ (mth/abs (:offset-y %)) (* (:spread %) 2) (* (:blur %) 2) 10) + 0) (:shadow shape)))] + + {:horizontal (+ stroke-width margin shadow-width) + :vertical (+ stroke-width margin shadow-height)}))) + +(defn- add-padding + [bounds padding] + (-> bounds + (update :x - (:horizontal padding)) + (update :y - (:vertical padding)) + (update :width + (* 2 (:horizontal padding))) + (update :height + (* 2 (:vertical padding))))) + +(defn get-object-bounds + [objects shape] + + (let [calculate-base-bounds + (fn [shape] + (-> (get-shape-filter-bounds shape) + (add-padding (calculate-padding shape true)))) + + bounds (if (cph/frame-shape? shape) + [(calculate-base-bounds shape)] + (cph/reduce-objects + objects + (fn [shape] + (and (d/not-empty? (:shapes shape)) + (or (not (cph/frame-shape? shape)) + (:show-content shape)) + + (or (not (cph/group-shape? shape)) + (not (:masked-group? shape))))) + + (:id shape) + + (fn [result shape] + (conj result (get-object-bounds objects shape))) + + [(calculate-base-bounds shape)])) + + children-bounds (cond->> (gsr/join-selrects bounds) + (not (cph/frame-shape? shape)) (or (:children-bounds shape))) + + filters (shape->filters shape) + blur-value (or (-> shape :blur :value) 0)] + + (get-rect-filter-bounds children-bounds filters blur-value))) + diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index e7a2ae2cf3..5d99774104 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -7,6 +7,7 @@ (ns app.common.geom.shapes.transforms (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] @@ -143,16 +144,30 @@ ([shape params] (transform-matrix shape params (or (gco/center-shape shape) (gpt/point 0 0)))) - ([{:keys [flip-x flip-y] :as shape} {:keys [no-flip]} shape-center] + ([{:keys [flip-x flip-y transform] :as shape} {:keys [no-flip]} shape-center] (-> (gmt/matrix) (gmt/translate shape-center) - (gmt/multiply (:transform shape (gmt/matrix))) + (cond-> (some? transform) + (gmt/multiply transform)) + (cond-> (and (not no-flip) flip-x) (gmt/scale (gpt/point -1 1)) (and (not no-flip) flip-y) (gmt/scale (gpt/point 1 -1))) (gmt/translate (gpt/negate shape-center))))) +(defn transform-str + ([shape] + (transform-str shape nil)) + + ([{:keys [transform flip-x flip-y] :as shape} {:keys [no-flip]}] + (if (and (some? shape) + (or (some? transform) + (and (not no-flip) flip-x) + (and (not no-flip) flip-y))) + (dm/str (transform-matrix shape)) + ""))) + (defn inverse-transform-matrix ([shape] (let [shape-center (or (gco/center-shape shape) @@ -236,11 +251,6 @@ (gmt/rotate-matrix (- rotation-angle)))] [stretch-matrix stretch-matrix-inverse rotation-angle]))) -(defn is-rotated? - [[a b _c _d]] - ;; true if either a-b or c-d are parallel to the axis - (not (mth/close? (:y a) (:y b)))) - (defn- adjust-rotated-transform [{:keys [transform transform-inverse flip-x flip-y]} points] (let [center (gco/center-points points) @@ -272,12 +282,9 @@ points (gco/transform-points points' transform-mtx) bool? (= (:type shape) :bool) path? (= (:type shape) :path) - rotated? (is-rotated? points) [selrect transform transform-inverse] - (if (not rotated?) - [(gpr/points->selrect points) nil nil] - (adjust-rotated-transform shape points)) + (adjust-rotated-transform shape points) base-rotation (or (:rotation shape) 0) modif-rotation (or (get-in shape [:modifiers :rotation]) 0) @@ -632,6 +639,13 @@ (transform-bounds center modifiers) (gpr/points->selrect)))) +(defn transform-selrect-matrix + [selrect mtx] + (-> selrect + (gpr/rect->points) + (gco/transform-points mtx) + (gpr/points->selrect))) + (defn selection-rect "Returns a rect that contains all the shapes and is aware of the rotation of each shape. Mainly used for multiple selection." diff --git a/common/src/app/common/media.cljc b/common/src/app/common/media.cljc index a024d02b48..23cbe71017 100644 --- a/common/src/app/common/media.cljc +++ b/common/src/app/common/media.cljc @@ -59,8 +59,6 @@ "application/pdf" ".pdf" nil)) -(def max-file-size (* 5 1024 1024)) - (s/def ::id uuid?) (s/def ::name string?) (s/def ::width number?) diff --git a/common/src/app/common/pages.cljc b/common/src/app/common/pages.cljc index 0e6a91a942..e500d372d6 100644 --- a/common/src/app/common/pages.cljc +++ b/common/src/app/common/pages.cljc @@ -26,8 +26,8 @@ (dm/export focus/is-in-focus?) ;; Indices -(dm/export indices/calculate-z-index) -(dm/export indices/update-z-index) +#_(dm/export indices/calculate-z-index) +#_(dm/export indices/update-z-index) (dm/export indices/generate-child-all-parents-index) (dm/export indices/generate-child-parent-index) (dm/export indices/create-clip-index) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 7d511d9ff6..36a8a576ca 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -16,8 +16,8 @@ [app.common.pages.helpers :as cph] [app.common.pages.init :as init] [app.common.spec :as us] - [app.common.spec.change :as spec.change] - [app.common.spec.shape :as spec.shape])) + [app.common.pages.changes-spec :as pcs] + [app.common.types.shape :as cts])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Specific helpers @@ -49,7 +49,7 @@ ;; When verify? false we spec the schema validation. Currently used to make just ;; 1 validation even if the changes are applied twice (when verify? - (us/assert ::spec.change/changes items)) + (us/assert ::pcs/changes items)) (let [result (reduce #(or (process-change %1 %2) %1) data items)] ;; Validate result shapes (only on the backend) @@ -59,7 +59,7 @@ (doseq [[id shape] (:objects page)] (when-not (= shape (get-in data [:pages-index page-id :objects id])) ;; If object has change verify is correct - (us/verify ::spec.shape/shape shape)))))) + (us/verify ::cts/shape shape)))))) result))) @@ -211,7 +211,7 @@ (let [invalid-targets (calculate-invalid-targets objects shape-id)] (and (contains? objects shape-id) (not (invalid-targets parent-id)) - (cph/valid-frame-target? objects parent-id shape-id)))) + #_(cph/valid-frame-target? objects parent-id shape-id)))) (insert-items [prev-shapes index shapes] (let [prev-shapes (or prev-shapes [])] diff --git a/common/src/app/common/spec/change.cljc b/common/src/app/common/pages/changes_spec.cljc similarity index 86% rename from common/src/app/common/spec/change.cljc rename to common/src/app/common/pages/changes_spec.cljc index db9847b753..d53249e06b 100644 --- a/common/src/app/common/spec/change.cljc +++ b/common/src/app/common/pages/changes_spec.cljc @@ -4,14 +4,14 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec.change +(ns app.common.pages.changes-spec (:require [app.common.spec :as us] - [app.common.spec.color :as color] - [app.common.spec.file :as file] - [app.common.spec.page :as page] - [app.common.spec.shape :as shape] - [app.common.spec.typography :as typg] + [app.common.types.color :as ctc] + [app.common.types.file :as ctf] + [app.common.types.page :as ctp] + [app.common.types.shape :as cts] + [app.common.types.typography :as ctt] [clojure.spec.alpha :as s])) (s/def ::index integer?) @@ -52,7 +52,7 @@ (s/keys :req-un [:internal.changes.set-option/option :internal.changes.set-option/value])) -(s/def :internal.changes.add-obj/obj ::shape/shape) +(s/def :internal.changes.add-obj/obj ::cts/shape) (defn- valid-container-id-frame? [o] @@ -89,18 +89,18 @@ valid-container-id?)) (defmethod change-spec :reg-objects [_] - (s/and (s/keys :req-un [::shape/shapes] + (s/and (s/keys :req-un [::cts/shapes] :opt-un [::page-id ::component-id]) valid-container-id?)) (defmethod change-spec :mov-objects [_] - (s/and (s/keys :req-un [::parent-id ::shape/shapes] + (s/and (s/keys :req-un [::parent-id ::cts/shapes] :opt-un [::page-id ::component-id ::index]) valid-container-id?)) (defmethod change-spec :add-page [_] (s/or :empty (s/keys :req-un [::id ::name]) - :complete (s/keys :req-un [::page/page]))) + :complete (s/keys :req-un [::ctp/page]))) (defmethod change-spec :mod-page [_] (s/keys :req-un [::id ::name])) @@ -112,21 +112,21 @@ (s/keys :req-un [::id ::index])) (defmethod change-spec :add-color [_] - (s/keys :req-un [::color/color])) + (s/keys :req-un [::ctc/color])) (defmethod change-spec :mod-color [_] - (s/keys :req-un [::color/color])) + (s/keys :req-un [::ctc/color])) (defmethod change-spec :del-color [_] (s/keys :req-un [::id])) -(s/def :internal.changes.add-recent-color/color ::color/recent-color) +(s/def :internal.changes.add-recent-color/color ::ctc/recent-color) (defmethod change-spec :add-recent-color [_] (s/keys :req-un [:internal.changes.add-recent-color/color])) -(s/def :internal.changes.add-media/object ::file/media-object) +(s/def :internal.changes.add-media/object ::ctf/media-object) (defmethod change-spec :add-media [_] (s/keys :req-un [:internal.changes.add-media/object])) @@ -149,7 +149,7 @@ (s/keys :req-un [::id])) (s/def :internal.changes.add-component/shapes - (s/coll-of ::shape/shape)) + (s/coll-of ::cts/shape)) (defmethod change-spec :add-component [_] (s/keys :req-un [::id ::name :internal.changes.add-component/shapes] @@ -163,13 +163,13 @@ (s/keys :req-un [::id])) (defmethod change-spec :add-typography [_] - (s/keys :req-un [::typg/typography])) + (s/keys :req-un [::ctt/typography])) (defmethod change-spec :mod-typography [_] - (s/keys :req-un [::typg/typography])) + (s/keys :req-un [::ctt/typography])) (defmethod change-spec :del-typography [_] - (s/keys :req-un [::typg/id])) + (s/keys :req-un [::ctt/id])) (s/def ::change (s/multi-spec change-spec :type)) (s/def ::changes (s/coll-of ::change)) diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index c412176f64..1b73deac24 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -80,8 +80,11 @@ :x :y :rx :ry :r1 :r2 :r3 :r4 + :rotation :selrect :points + :show-content + :hide-in-viewer :opacity :blend-mode diff --git a/common/src/app/common/pages/focus.cljc b/common/src/app/common/pages/focus.cljc index df0f2d351f..726c102e69 100644 --- a/common/src/app/common/pages/focus.cljc +++ b/common/src/app/common/pages/focus.cljc @@ -8,26 +8,20 @@ (:require [app.common.data :as d] [app.common.pages.helpers :as cph] - [app.common.pages.indices :as cpi] [app.common.uuid :as uuid])) (defn focus-objects [objects focus] - (let [[ids-with-children z-index] + (let [ids-with-children (when (d/not-empty? focus) - [(into (conj focus uuid/zero) - (mapcat (partial cph/get-children-ids objects)) - focus) - (cpi/calculate-z-index objects)]) - - sort-by-z-index - (fn [coll] - (->> coll (sort-by (fn [a b] (- (get z-index a) (get z-index b))))))] + (into (conj focus uuid/zero) + (mapcat (partial cph/get-children-ids objects)) + focus))] (cond-> objects (some? ids-with-children) (-> (select-keys ids-with-children) - (assoc-in [uuid/zero :shapes] (sort-by-z-index focus)))))) + (assoc-in [uuid/zero :shapes] (cph/sort-z-index objects focus)))))) (defn filter-not-focus [objects focus ids] @@ -46,6 +40,5 @@ (defn is-in-focus? [objects focus id] - (d/seek - #(contains? focus %) - (cph/get-parents-seq objects id))) + (d/seek (partial contains? focus) + (cons id (cph/get-parent-ids objects id)))) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index b768680eb1..3f5ef1ce40 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -9,23 +9,36 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] + [app.common.math :as mth] [app.common.spec :as us] - [app.common.spec.page :as spec.page] + [app.common.types.page :as ctp] [app.common.uuid :as uuid] [cuerdas.core :as str])) +(declare reduce-objects) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; GENERIC SHAPE SELECTORS AND PREDICATES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn root-frame? +(defn root? [{:keys [id type]}] - (and (= type :frame) - (= id uuid/zero))) + (and (= type :frame) (= id uuid/zero))) + +(defn root-frame? + ([objects id] + (root-frame? (get objects id))) + + ([{:keys [frame-id type]}] + (and (= type :frame) + (= frame-id uuid/zero)))) (defn frame-shape? - [{:keys [type]}] - (= type :frame)) + ([objects id] + (frame-shape? (get objects id))) + + ([{:keys [type]}] + (= type :frame))) (defn group-shape? [{:keys [type]}] @@ -39,6 +52,10 @@ [{:keys [type]}] (= type :image)) +(defn svg-raw-shape? + [{:keys [type]}] + (= type :svg-raw)) + (defn unframed-shape? "Checks if it's a non-frame shape in the top level." [shape] @@ -47,7 +64,7 @@ (defn get-shape [container shape-id] - (us/assert ::spec.page/container container) + (us/assert ::ctp/container container) (us/assert ::us/uuid shape-id) (-> container (get :objects) @@ -79,23 +96,14 @@ [objects id] (-> objects (get id) :parent-id)) -(defn get-parents-seq - [objects shape-id] - - (cond - (nil? shape-id) - nil - - :else - (lazy-seq (cons shape-id (get-parents-seq objects (get-in objects [shape-id :parent-id])))))) - (defn get-parent-ids "Returns a vector of parents of the specified shape." [objects shape-id] (loop [result [] id shape-id] - (if-let [parent-id (dm/get-in objects [id :parent-id])] - (recur (conj result parent-id) parent-id) - result))) + (let [parent-id (dm/get-in objects [id :parent-id])] + (if (and (some? parent-id) (not= parent-id id)) + (recur (conj result parent-id) parent-id) + result)))) (defn get-frame "Get the frame that contains the shape. If the shape is already a @@ -140,38 +148,145 @@ (:shapes) (keep lookup))))) -(defn get-frames-ids - "Retrieves all frame objects as vector. It is not implemented in - function of `get-immediate-children` for performance reasons. This - function is executed in the render hot path." - [objects] - (let [lookup (d/getf objects) - xform (comp (keep lookup) - (filter frame-shape?) - (map :id))] - (->> (:shapes (lookup uuid/zero)) - (into [] xform)))) - (defn get-frames + "Retrieves all frame objects as vector" + [objects] + (or (-> objects meta ::index-frames) + (let [lookup (d/getf objects) + xform (comp (remove #(= uuid/zero %)) + (keep lookup) + (filter frame-shape?))] + (->> (keys objects) + (into [] xform))))) + +(defn get-frames-ids + "Retrieves all frame ids as vector" + [objects] + (->> (get-frames objects) + (mapv :id))) + +(defn get-nested-frames + [objects frame-id] + (into #{} + (comp (filter frame-shape?) + (map :id)) + (get-children objects frame-id))) + +(defn get-root-frames-ids "Retrieves all frame objects as vector. It is not implemented in function of `get-immediate-children` for performance reasons. This function is executed in the render hot path." [objects] - (let [lookup (d/getf objects) - xform (comp (keep lookup) - (filter frame-shape?))] - (->> (:shapes (lookup uuid/zero)) - (into [] xform)))) + (let [add-frame + (fn [result shape] + (cond-> result + (frame-shape? shape) + (conj (:id shape))))] + (reduce-objects objects (complement frame-shape?) add-frame []))) + +(defn get-root-objects + "Get all the objects under the root object" + [objects] + (let [add-shape + (fn [result shape] + (conj result shape))] + (reduce-objects objects (complement frame-shape?) add-shape []))) + +(defn get-root-shapes + "Get all shapes that are not frames" + [objects] + (let [add-shape + (fn [result shape] + (cond-> result + (not (frame-shape? shape)) + (conj shape)))] + (reduce-objects objects (complement frame-shape?) add-shape []))) + +(defn get-root-shapes-ids + [objects] + (->> (get-root-shapes objects) + (mapv :id))) + +(defn- get-base + [objects id-a id-b] + + (let [parents-a (reverse (cons id-a (get-parent-ids objects id-a))) + parents-b (reverse (cons id-b (get-parent-ids objects id-b))) + + [base base-child-a base-child-b] + (loop [parents-a (rest parents-a) + parents-b (rest parents-b) + base uuid/zero] + (cond + (not= (first parents-a) (first parents-b)) + [base (first parents-a) (first parents-b)] + + (or (empty? parents-a) (empty? parents-b)) + [uuid/zero (first parents-a) (first parents-b)] + + :else + (recur (rest parents-a) (rest parents-b) (first parents-a)))) + + index-base-a (when base-child-a (get-position-on-parent objects base-child-a)) + index-base-b (when base-child-b (get-position-on-parent objects base-child-b))] + + [base index-base-a index-base-b])) + +(defn is-shape-over-shape? + [objects base-shape-id over-shape-id {:keys [top-frames?]}] + + (let [[base index-a index-b] (get-base objects base-shape-id over-shape-id)] + (cond + (= base base-shape-id) + (and (not top-frames?) + (frame-shape? objects base-shape-id) + (root-frame? objects base-shape-id)) + + (= base over-shape-id) + (or top-frames? + (not (frame-shape? objects over-shape-id)) + (not (root-frame? objects over-shape-id))) + + :else + (< index-a index-b)))) + +(defn sort-z-index + ([objects ids] + (sort-z-index objects ids nil)) + + ([objects ids {:keys [bottom-frames?] :as options}] + (letfn [(comp [id-a id-b] + (let [type-a (dm/get-in objects [id-a :type]) + type-b (dm/get-in objects [id-b :type])] + (cond + (and bottom-frames? (= :frame type-a) (not= :frame type-b)) + 1 + + (and bottom-frames? (not= :frame type-a) (= :frame type-b)) + -1 + + (= id-a id-b) + 0 + + (is-shape-over-shape? objects id-a id-b options) + 1 + + :else + -1)))] + (sort comp ids)))) (defn frame-id-by-position [objects position] - (let [frames (get-frames objects)] - (or - (->> frames - (reverse) - (d/seek #(and position (gsh/has-point? % position))) - :id) - uuid/zero))) + (let [top-frame + (->> (get-frames-ids objects) + (sort-z-index objects) + (d/seek #(and position (gsh/has-point? (get objects %) position))))] + (or top-frame uuid/zero))) + +(defn frame-by-position + [objects position] + (let [frame-id (frame-id-by-position objects position)] + (get objects frame-id))) (declare indexed-shapes) @@ -520,3 +635,85 @@ (-> (select-keys objects selected+parents) (d/update-vals remove-children)))) + +(defn is-child? + [objects parent-id candidate-child-id] + (let [parents (get-parent-ids objects candidate-child-id)] + (some? (d/seek #(= % parent-id) parents)))) + +(defn reduce-objects + ([objects reducer-fn init-val] + (reduce-objects objects nil reducer-fn init-val)) + + ([objects check-children? reducer-fn init-val] + (reduce-objects objects check-children? uuid/zero reducer-fn init-val)) + + ([objects check-children? root-id reducer-fn init-val] + (let [root-children (get-in objects [root-id :shapes])] + (if (empty? root-children) + init-val + + (loop [current-val init-val + current-id (first root-children) + pending-ids (rest root-children)] + + + (let [current-shape (get objects current-id) + next-val (reducer-fn current-val current-shape) + next-pending-ids + (if (or (nil? check-children?) (check-children? current-shape)) + (concat (or (:shapes current-shape) []) pending-ids) + pending-ids)] + + (if (empty? next-pending-ids) + next-val + (recur next-val (first next-pending-ids) (rest next-pending-ids))))))))) + +(defn selected-with-children + [objects selected] + + (into selected + (mapcat #(get-children-ids objects %)) + selected)) + +(defn get-shape-id-root-frame + [objects shape-id] + (->> (get-parent-ids objects shape-id) + (cons shape-id) + (map (d/getf objects)) + (d/seek root-frame?) + :id)) + +(defn get-viewer-frames + ([objects] + (get-viewer-frames objects nil)) + + ([objects {:keys [all-frames?]}] + (into [] + (comp (map (d/getf objects)) + (if all-frames? + (map identity) + (remove :hide-in-viewer))) + (sort-z-index objects (get-frames-ids objects) {:top-frames? true})))) + +(defn start-page-index + [objects] + (with-meta objects {::index-frames (get-frames (with-meta objects nil))})) + +(defn update-page-index + [objects] + (with-meta objects {::index-frames (get-frames (with-meta objects nil))})) + +(defn start-object-indices + [file] + (letfn [(process-index [page-index page-id] + (update-in page-index [page-id :objects] start-page-index))] + (update file :pages-index #(reduce process-index % (keys %))))) + +(defn update-object-indices + [file page-id] + (update-in file [:pages-index page-id :objects] update-page-index)) + +(defn rotated-frame? + [frame] + (not (mth/almost-zero? (:rotation frame 0)))) diff --git a/common/src/app/common/pages/indices.cljc b/common/src/app/common/pages/indices.cljc index 3a5a852eed..f8b85bec01 100644 --- a/common/src/app/common/pages/indices.cljc +++ b/common/src/app/common/pages/indices.cljc @@ -8,76 +8,7 @@ (:require [app.common.data :as d] [app.common.pages.helpers :as cph] - [app.common.uuid :as uuid] - [clojure.set :as set])) - -(defn calculate-frame-z-index - [z-index frame-id base-idx objects] - - (let [is-frame? (fn [id] (= :frame (get-in objects [id :type]))) - children (or (get-in objects [frame-id :shapes]) [])] - - (if (empty? children) - z-index - (loop [current (peek children) - pending (pop children) - current-idx base-idx - z-index z-index] - - (let [children (get-in objects [current :shapes]) - is-frame? (is-frame? current) - pending (if (not is-frame?) - (d/concat-vec pending children) - pending)] - - (if (empty? pending) - (assoc z-index current current-idx) - (recur (peek pending) - (pop pending) - (dec current-idx) - (assoc z-index current current-idx)))))))) - -;; The z-index is really calculated per-frame. Every frame will have its own -;; internal z-index. To calculate the "final" z-index we add the shape z-index with -;; the z-index of its frame. This way we can update the z-index per frame without -;; the need of recalculate all the frames -(defn calculate-z-index - "Given a collection of shapes calculates their z-index. Greater index - means is displayed over other shapes with less index." - [objects] - - (let [frames (cph/get-frames objects) - - by-frame (cph/objects-by-frame objects) - frame-base-idx (d/update-vals by-frame count) - - z-index (calculate-frame-z-index {} uuid/zero (get frame-base-idx uuid/zero) objects)] - (->> frames - (reduce - (fn [z-index {:keys [id]}] - (calculate-frame-z-index z-index id (get frame-base-idx id) objects)) z-index)))) - -(defn update-z-index - "Updates the z-index given a set of ids to change and the old and new objects - representations" - [z-index changed-ids old-objects new-objects] - - (let [old-frames (into #{} (map #(get-in old-objects [% :frame-id])) changed-ids) - new-frames (into #{} (map #(get-in new-objects [% :frame-id])) changed-ids) - - changed-frames (set/union old-frames new-frames) - - frames (->> (cph/get-frames new-objects) - (map :id) - (filter #(contains? changed-frames %))) - - by-frame (cph/objects-by-frame new-objects) - frame-base-idx (d/update-vals by-frame count) - z-index (calculate-frame-z-index z-index uuid/zero (get frame-base-idx uuid/zero) new-objects)] - - (->> frames - (reduce (fn [z-index id] - (calculate-frame-z-index z-index id (get frame-base-idx id) new-objects)) z-index)))) + [app.common.uuid :as uuid])) (defn generate-child-parent-index [objects] @@ -102,11 +33,16 @@ "Retrieves the mask information for an object" [objects parents-index] (let [retrieve-clips - (fn [_ parents] + (fn [parents] (let [lookup-object (fn [id] (get objects id)) get-clip-parents (fn [shape] (cond-> [] + (and (= :frame (:type shape)) + (not (:show-content shape)) + (not= uuid/zero (:id shape))) + (conj shape) + (:masked-group? shape) (conj (get objects (->> shape :shapes first))) @@ -117,5 +53,5 @@ (comp (map lookup-object) (mapcat get-clip-parents)) parents)))] - (->> parents-index - (d/mapm retrieve-clips)))) + (-> parents-index + (d/update-vals retrieve-clips)))) diff --git a/common/src/app/common/pages/init.cljc b/common/src/app/common/pages/init.cljc index 97e4997113..3b1e85b04f 100644 --- a/common/src/app/common/pages/init.cljc +++ b/common/src/app/common/pages/init.cljc @@ -71,7 +71,7 @@ :stroke-opacity 1}]} {:type :frame - :name "Artboard-1" + :name "Board-1" :fills [{:fill-color clr/white :fill-opacity 1}] :strokes [] diff --git a/common/src/app/common/path/shapes_to_path.cljc b/common/src/app/common/path/shapes_to_path.cljc index 37bc286a27..6c3916ba5e 100644 --- a/common/src/app/common/path/shapes_to_path.cljc +++ b/common/src/app/common/path/shapes_to_path.cljc @@ -15,7 +15,7 @@ [app.common.geom.shapes.path :as gsp] [app.common.path.bool :as pb] [app.common.path.commands :as pc] - [app.common.spec.radius :as ctr])) + [app.common.types.shape.radius :as ctsr])) (def ^:const bezier-circle-c 0.551915024494) @@ -152,7 +152,7 @@ (defn rect->path "Creates a bezier curve that approximates a rounded corner rectangle" [{:keys [x y width height] :as shape}] - (case (ctr/radius-mode shape) + (case (ctsr/radius-mode shape) :radius-1 (let [radius (gso/shape-corners-1 shape)] (draw-rounded-rect-path x y width height radius)) @@ -224,7 +224,11 @@ #_:else (rect->path shape)) ;; Apply the transforms that had the shape - transform (:transform shape) + transform + (cond-> (:transform shape (gmt/matrix)) + (:flip-x shape) (gmt/scale (gpt/point -1 1)) + (:flip-y shape) (gmt/scale (gpt/point 1 -1))) + new-content (cond-> new-content (some? transform) (gsp/transform-content (gmt/transform-in (gsc/center-shape shape) transform)))] diff --git a/common/src/app/common/pprint.cljc b/common/src/app/common/pprint.cljc index e95ad84f6f..78fc78b712 100644 --- a/common/src/app/common/pprint.cljc +++ b/common/src/app/common/pprint.cljc @@ -7,21 +7,16 @@ (ns app.common.pprint (:refer-clojure :exclude [prn]) (:require - [cuerdas.core :as str] [fipp.edn :as fpp])) (defn pprint-str - [expr] - (binding [*print-level* 8 - *print-length* 25] + [expr & {:keys [width level length] + :or {width 110 level 8 length 25}}] + (binding [*print-level* level + *print-length* length] (with-out-str - (fpp/pprint expr {:width 110})))) + (fpp/pprint expr {:width width})))) (defn pprint - ([expr] - (println (pprint-str expr))) - ([label expr] - (println (str/concat "============ " label "============")) - (pprint expr))) - - + [expr & {:as opts}] + (println (pprint-str expr opts))) diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index 5fc4b8d9f6..2f280abfe7 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -5,13 +5,13 @@ ;; Copyright (c) UXBOX Labs SL (ns app.common.spec - "Data manipulation and query helper functions." + "Data validation & assertion helpers." (:refer-clojure :exclude [assert bytes?]) #?(:cljs (:require-macros [app.common.spec :refer [assert]])) (:require #?(:clj [clojure.spec.alpha :as s] :cljs [cljs.spec.alpha :as s]) - + [app.common.data.macros :as dm] ;; NOTE: don't remove this, causes exception on advanced build ;; because of some strange interaction with cljs.spec.alpha and ;; modules splitting. @@ -31,88 +31,178 @@ (def max-safe-int (int 1e6)) (def min-safe-int (int -1e6)) -(def valid? s/valid?) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; DEFAULT SPECS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; --- Conformers +;; --- SPEC: uuid -(defn uuid-conformer - [v] - (if (uuid? v) - v - (if (string? v) - (if (re-matches uuid-rx v) - (uuid/uuid v) - ::s/invalid) - ::s/invalid))) +(letfn [(conformer [v] + (if (uuid? v) + v + (if (string? v) + (if (re-matches uuid-rx v) + (uuid/uuid v) + ::s/invalid) + ::s/invalid))) + (unformer [v] + (dm/str v))] + (s/def ::uuid (s/conformer conformer unformer))) -(defn boolean-conformer - [v] - (if (boolean? v) - v - (if (string? v) - (if (re-matches #"^(?:t|true|false|f|0|1)$" v) - (contains? #{"t" "true" "1"} v) - ::s/invalid) - ::s/invalid))) +;; --- SPEC: boolean -(defn boolean-unformer - [v] - (if v "true" "false")) +(letfn [(conformer [v] + (if (boolean? v) + v + (if (string? v) + (if (re-matches #"^(?:t|true|false|f|0|1)$" v) + (contains? #{"t" "true" "1"} v) + ::s/invalid) + ::s/invalid))) + (unformer [v] + (if v "true" "false"))] + (s/def ::boolean (s/conformer conformer unformer))) -(defn- number-conformer - [v] - (cond - (number? v) v - (str/numeric? v) - #?(:clj (Double/parseDouble v) - :cljs (js/parseFloat v)) - :else ::s/invalid)) +;; --- SPEC: number -(defn- integer-conformer - [v] - (cond - (integer? v) v - (string? v) - (if (re-matches #"^[-+]?\d+$" v) - #?(:clj (Long/parseLong v) - :cljs (js/parseInt v 10)) - ::s/invalid) - :else ::s/invalid)) +(letfn [(conformer [v] + (cond + (number? v) v + (str/numeric? v) #?(:cljs (js/parseFloat v) + :clj (Double/parseDouble v)) + :else ::s/invalid))] + (s/def ::number (s/conformer conformer str))) -(defn- color-conformer - [v] - (if (and (string? v) (re-matches #"^#(?:[0-9a-fA-F]{3}){1,2}$" v)) - v - ::s/invalid)) +;; --- SPEC: integer -(defn keyword-conformer - [v] - (cond - (keyword? v) - v +(letfn [(conformer [v] + (cond + (integer? v) v + (string? v) + (if (re-matches #"^[-+]?\d+$" v) + #?(:clj (Long/parseLong v) + :cljs (js/parseInt v 10)) + ::s/invalid) + :else ::s/invalid))] + (s/def ::integer (s/conformer conformer str))) - (string? v) - (keyword v) +;; --- SPEC: keyword - :else - ::s/invalid)) +(letfn [(conformer [v] + (cond + (keyword? v) v + (string? v) (keyword v) + :else ::s/invalid)) + (unformer [v] + (name v))] + (s/def ::keyword (s/conformer conformer unformer))) -;; --- Default Specs +;; --- SPEC: email + +(def email-re #"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+") + +(defn parse-email + [s] + (some->> s (re-seq email-re) first)) + +(letfn [(conformer [v] + (or (parse-email v) ::s/invalid)) + (unformer [v] + (dm/str v))] + (s/def ::email (s/conformer conformer unformer))) + +;; -- SPEC: uri + +(letfn [(conformer [s] + (cond + (u/uri? s) s + (string? s) (u/uri s) + :else ::s/invalid)) + (unformer [v] + (dm/str v))] + (s/def ::uri (s/conformer conformer unformer))) + +;; --- SPEC: color string + +(letfn [(conformer [v] + (if (and (string? v) (re-matches #"^#(?:[0-9a-fA-F]{3}){1,2}$" v)) + v + ::s/invalid)) + (unformer [v] + (dm/str v))] + (s/def ::rgb-color-str (s/conformer conformer unformer))) + +;; --- SPEC: set/vec of valid Keywords + +(letfn [(conform-fn [dest s] + (let [xform (keep (fn [s] + (cond + (string? s) (keyword s) + (keyword? s) s + :else nil)))] + (cond + (set? s) (into dest xform s) + (string? s) (into dest xform (str/words s)) + :else ::s/invalid)))] + + (s/def ::set-of-valid-keywords + (s/conformer + (fn [s] (conform-fn #{} s)) + (fn [s] (str/join " " (map name s))))) + + (s/def ::vec-of-valid-keywords + (s/conformer + (fn [s] (conform-fn [] s)) + (fn [s] (str/join " " (map name s)))))) + +;; --- SPEC: set-of-valid-emails + +(letfn [(conformer [v] + (cond + (string? v) + (into #{} (re-seq email-re v)) + + (or (set? v) (sequential? v)) + (->> (str/join " " v) + (re-seq email-re) + (into #{})) + + :else ::s/invalid)) + (unformer [v] + (str/join " " v))] + (s/def ::set-of-valid-emails (s/conformer conformer unformer))) + +;; --- SPEC: set-of-non-empty-strings + +(def non-empty-strings-xf + (comp + (filter string?) + (remove str/empty?) + (remove str/blank?))) + +(letfn [(conformer [s] + (cond + (string? s) (->> (str/split s #"\s*,\s*") + (into #{} non-empty-strings-xf)) + (set? s) (into #{} non-empty-strings-xf s) + :else ::s/invalid)) + (unformer [s] + (str/join "," s))] + (s/def ::set-of-non-empty-strings (s/conformer conformer unformer))) + +;; --- SPECS WITHOUT CONFORMER -(s/def ::keyword (s/conformer keyword-conformer name)) (s/def ::inst inst?) (s/def ::string string?) -(s/def ::color (s/conformer color-conformer str)) -(s/def ::uuid (s/conformer uuid-conformer str)) -(s/def ::boolean (s/conformer boolean-conformer boolean-unformer)) -(s/def ::number (s/conformer number-conformer str)) -(s/def ::integer (s/conformer integer-conformer str)) (s/def ::not-empty-string (s/and string? #(not (str/empty? %)))) (s/def ::url string?) (s/def ::fn fn?) (s/def ::id ::uuid) +(s/def ::set-of-string (s/every ::string :kind set?)) +(s/def ::coll-of-uuid (s/every ::uuid)) + (defn bytes? "Test if a first parameter is a byte array or not." @@ -139,127 +229,102 @@ (>= % min-safe-int) (<= % max-safe-int))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; MACROS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; --- SPEC: set of Keywords +(defn explain-data + [spec value] + (s/explain-data spec value)) -(letfn [(conform-fn [dest s] - (let [xform (keep (fn [s] - (cond - (string? s) (keyword s) - (keyword? s) s - :else nil)))] - (cond - (set? s) (into dest xform s) - (string? s) (into dest xform (str/words s)) - :else ::s/invalid)))] +(defn valid? + [spec value] + (s/valid? spec value)) - (s/def ::set-of-keywords - (s/conformer - (fn [s] (conform-fn #{} s)) - (fn [s] (str/join " " (map name s))))) +(defmacro assert-expr* + "Auxiliar macro for expression assertion." + [expr hint] + `(when-not ~expr + (ex/raise :type :assertion + :code :expr-validation + :hint ~hint))) - (s/def ::vec-of-keywords - (s/conformer - (fn [s] (conform-fn [] s)) - (fn [s] (str/join " " (map name s)))))) - -;; --- SPEC: email - -(def email-re #"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+") - -(defn parse-email - [s] - (some->> s (re-seq email-re) first)) - -(s/def ::email - (s/conformer - (fn [v] - (or (parse-email v) ::s/invalid)) - str)) - -(s/def ::set-of-emails - (s/conformer - (fn [v] - (cond - (string? v) - (into #{} (re-seq email-re v)) - - (or (set? v) (sequential? v)) - (->> (str/join " " v) - (re-seq email-re) - (into #{})) - - :else ::s/invalid)) - - (fn [v] - (str/join " " v)))) - -(s/def ::uri - (s/conformer - (fn [s] - (cond - (u/uri? s) s - (string? s) (u/uri s) - :else ::s/invalid)) - str)) - -;; --- SPEC: set-of-str - -(s/def ::set-of-str - (s/conformer - (fn [s] - (let [xform (comp - (filter string?) - (remove str/empty?) - (remove str/blank?))] - (cond - (string? s) (->> (str/split s #"\s*,\s*") - (into #{} xform)) - (set? s) (into #{} xform s) - :else ::s/invalid))) - (fn [s] - (str/join "," s)))) - -;; --- Macros - -(defn spec-assert* - [spec val hint ctx] - (if (s/valid? spec val) - val - (let [data (s/explain-data spec val)] - (ex/raise :type :assertion - :code :spec-validation - :hint hint - ::ex/data (merge ctx data))))) - -(defmacro assert - "Development only assertion macro." - [spec x] - (when *assert* - (let [nsdata (:ns &env) - context (if nsdata - {:ns (str (:name nsdata)) - :name (pr-str spec) - :line (:line &env) - :file (:file (:meta nsdata))} - (let [mdata (meta &form)] - {:ns (str (ns-name *ns*)) - :name (pr-str spec) - :line (:line mdata)})) - message (str "spec assert: '" (pr-str spec) "'")] - `(spec-assert* ~spec ~x ~message ~context)))) - -(defmacro verify - "Always active assertion macro (does not obey to :elide-asserts)" - [spec x] - (let [nsdata (:ns &env) - context (when nsdata +(defmacro assert-spec* + "Auxiliar macro for spec assertion." + [spec value hint] + (let [context (if-let [nsdata (:ns &env)] {:ns (str (:name nsdata)) :name (pr-str spec) :line (:line &env) - :file (:file (:meta nsdata))}) - message (str "spec verify: '" (pr-str spec) "'")] - `(spec-assert* ~spec ~x ~message ~context))) + :file (:file (:meta nsdata))} + {:ns (str (ns-name *ns*)) + :name (pr-str spec) + :line (:line (meta &form))}) + hint (or hint (str "spec assert: " (pr-str spec)))] + + `(if (valid? ~spec ~value) + ~value + (let [data# (explain-data ~spec ~value)] + (ex/raise :type :assertion + :code :spec-validation + :hint ~hint + ::ex/data (merge ~context data#)))))) + +(defmacro assert + "Is a spec specific assertion macro that only evaluates if *assert* + is true. DEPRECATED: it should be replaced by the new, general + purpose assert! macro." + [spec value] + (when *assert* + `(assert-spec* ~spec ~value nil))) + +(defmacro verify + "Is a spec specific assertion macro that evaluates always, + independently of *assert* value. DEPRECATED: should be replaced by + the new, general purpose `verify!` macro." + [spec value] + `(assert-spec* ~spec ~value nil)) + +(defmacro assert! + "General purpose assertion macro." + [& params] + ;; If we only receive two arguments, this means we use the simplified form + (let [pcnt (count params)] + (cond + ;; When we have a single argument, this means a simplified form + ;; of expr assertion + (= 1 pcnt) + (let [expr (first params) + hint (str "expr assert failed:" (pr-str expr))] + (when *assert* + `(assert-expr* ~expr ~hint))) + + ;; If we have two arguments, this can be spec or expr + ;; assertion. The spec assertion is determined if the first + ;; argument is a qualified keyword. + (= 2 pcnt) + (let [[spec-or-expr value-or-msg] params] + (if (qualified-keyword? spec-or-expr) + `(assert-spec* ~spec-or-expr ~value-or-msg nil) + `(assert-expr* ~spec-or-expr ~value-or-msg))) + + (= 3 pcnt) + (let [[spec value hint] params] + `(assert-spec* ~spec ~value ~hint)) + + :else + (let [{:keys [spec expr hint always? val]} params] + (when (or always? *assert*) + (if spec + `(assert-spec* ~spec ~val ~hint) + `(assert-expr* ~expr ~hint))))))) + +(defmacro verify! + "A variant of `assert!` macro that evaluates always, independently + of the *assert* value." + [& params] + (binding [*assert* true] + `(assert! ~@params))) ;; --- Public Api diff --git a/common/src/app/common/spec/color.cljc b/common/src/app/common/types/color.cljc similarity index 99% rename from common/src/app/common/spec/color.cljc rename to common/src/app/common/types/color.cljc index 8fbedb11b0..674de1cc2c 100644 --- a/common/src/app/common/spec/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec.color +(ns app.common.types.color (:require [app.common.data :as d] [app.common.spec :as us] diff --git a/common/src/app/common/spec/file.cljc b/common/src/app/common/types/file.cljc similarity index 80% rename from common/src/app/common/spec/file.cljc rename to common/src/app/common/types/file.cljc index 2affdafbda..5c7475d609 100644 --- a/common/src/app/common/spec/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -4,12 +4,11 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec.file +(ns app.common.types.file (:require [app.common.spec :as us] - [app.common.spec.color :as color] - [app.common.spec.page :as page] - [app.common.spec.typography] + [app.common.types.color :as ctc] + [app.common.types.page :as ctp] [clojure.spec.alpha :as s])) (s/def :internal.media-object/name string?) @@ -31,13 +30,13 @@ :opt-un [:internal.media-object/path])) (s/def ::colors - (s/map-of uuid? ::color/color)) + (s/map-of uuid? ::ctc/color)) (s/def ::recent-colors - (s/coll-of ::color/recent-color :kind vector?)) + (s/coll-of ::ctc/recent-color :kind vector?)) (s/def ::typographies - (s/map-of uuid? :app.common.spec.typography/typography)) + (s/map-of uuid? :ctst/typography)) (s/def ::pages (s/coll-of uuid? :kind vector?)) @@ -46,10 +45,10 @@ (s/map-of uuid? ::media-object)) (s/def ::pages-index - (s/map-of uuid? ::page/page)) + (s/map-of uuid? ::ctp/page)) (s/def ::components - (s/map-of uuid? ::page/container)) + (s/map-of uuid? ::ctp/container)) (s/def ::data (s/keys :req-un [::pages-index diff --git a/common/src/app/common/spec/page.cljc b/common/src/app/common/types/page.cljc similarity index 96% rename from common/src/app/common/spec/page.cljc rename to common/src/app/common/types/page.cljc index 135eadb1eb..0ed513dc0d 100644 --- a/common/src/app/common/spec/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -4,11 +4,11 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec.page +(ns app.common.types.page (:require [app.common.data :as d] [app.common.spec :as us] - [app.common.spec.shape :as shape] + [app.common.types.shape :as cts] [clojure.spec.alpha :as s])) ;; --- Grid options @@ -90,7 +90,7 @@ (s/def ::id uuid?) (s/def ::name string?) -(s/def ::objects (s/map-of uuid? ::shape/shape)) +(s/def ::objects (s/map-of uuid? ::cts/shape)) (s/def ::page (s/keys :req-un [::id ::name ::objects ::options])) diff --git a/common/src/app/common/spec/shape.cljc b/common/src/app/common/types/shape.cljc similarity index 91% rename from common/src/app/common/spec/shape.cljc rename to common/src/app/common/types/shape.cljc index 42ae67f60e..317e4af786 100644 --- a/common/src/app/common/spec/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -4,17 +4,17 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec.shape +(ns app.common.types.shape (:require [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.spec :as us] - [app.common.spec.blur :as blur] - [app.common.spec.color :as color] - [app.common.spec.export :as export] - [app.common.spec.interactions :as cti] - [app.common.spec.radius :as radius] - [app.common.spec.shadow :as shadow] + [app.common.types.color :as ctc] + [app.common.types.shape.blur :as ctsb] + [app.common.types.shape.export :as ctse] + [app.common.types.shape.interactions :as ctsi] + [app.common.types.shape.radius :as ctsr] + [app.common.types.shape.shadow :as ctss] [clojure.set :as set] [clojure.spec.alpha :as s])) @@ -47,11 +47,13 @@ (s/def ::fill-color string?) (s/def ::fill-opacity ::us/safe-number) -(s/def ::fill-color-gradient (s/nilable ::color/gradient)) +(s/def ::fill-color-gradient (s/nilable ::ctc/gradient)) (s/def ::fill-color-ref-file (s/nilable uuid?)) (s/def ::fill-color-ref-id (s/nilable uuid?)) (s/def ::hide-fill-on-export boolean?) +(s/def ::show-content boolean?) +(s/def ::hide-in-viewer boolean?) (s/def ::file-thumbnail boolean?) (s/def ::masked-group? boolean?) @@ -67,7 +69,7 @@ (s/def ::proportion ::us/safe-number) (s/def ::proportion-lock boolean?) (s/def ::stroke-color string?) -(s/def ::stroke-color-gradient (s/nilable ::color/gradient)) +(s/def ::stroke-color-gradient (s/nilable ::ctc/gradient)) (s/def ::stroke-color-ref-file (s/nilable uuid?)) (s/def ::stroke-color-ref-id (s/nilable uuid?)) (s/def ::stroke-opacity ::us/safe-number) @@ -100,7 +102,7 @@ (s/keys :req-un [::x ::y ::x1 ::y1 ::x2 ::y2 ::width ::height])) (s/def ::exports - (s/coll-of ::export/export :kind vector?)) + (s/coll-of ::ctse/export :kind vector?)) (s/def ::points (s/every ::gpt/point :kind vector?)) @@ -185,12 +187,12 @@ ::constraints-h ::constraints-v ::fixed-scroll - ::radius/rx - ::radius/ry - ::radius/r1 - ::radius/r2 - ::radius/r3 - ::radius/r4 + ::ctsr/rx + ::ctsr/ry + ::ctsr/r1 + ::ctsr/r2 + ::ctsr/r3 + ::ctsr/r4 ::x ::y ::exports @@ -211,9 +213,9 @@ ::width ::height ::masked-group? - ::cti/interactions - ::shadow/shadow - ::blur/blur + ::ctsi/interactions + ::ctss/shadow + ::ctsb/blur ::opacity ::blend-mode])) @@ -254,8 +256,7 @@ :internal.shape.text.position-data/rtl :internal.shape.text.position-data/text :internal.shape.text.position-data/text-decoration - :internal.shape.text.position-data/text-transform] - )) + :internal.shape.text.position-data/text-transform])) (s/def :internal.shape.text.position-data/x ::us/safe-number) (s/def :internal.shape.text.position-data/y ::us/safe-number) @@ -303,7 +304,9 @@ (defmethod shape-spec :frame [_] (s/and ::shape-attrs (s/keys :opt-un [::file-thumbnail - ::hide-fill-on-export]))) + ::hide-fill-on-export + ::show-content + ::hide-in-viewer]))) (s/def ::shape (s/and (s/multi-spec shape-spec :type) diff --git a/common/src/app/common/spec/blur.cljc b/common/src/app/common/types/shape/blur.cljc similarity index 93% rename from common/src/app/common/spec/blur.cljc rename to common/src/app/common/types/shape/blur.cljc index 04b643e896..03257d8f9c 100644 --- a/common/src/app/common/spec/blur.cljc +++ b/common/src/app/common/types/shape/blur.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec.blur +(ns app.common.types.shape.blur (:require [app.common.spec :as us] [clojure.spec.alpha :as s])) diff --git a/common/src/app/common/spec/export.cljc b/common/src/app/common/types/shape/export.cljc similarity index 93% rename from common/src/app/common/spec/export.cljc rename to common/src/app/common/types/shape/export.cljc index acfe7b2791..057d575e09 100644 --- a/common/src/app/common/spec/export.cljc +++ b/common/src/app/common/types/shape/export.cljc @@ -4,12 +4,11 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec.export +(ns app.common.types.shape.export (:require [app.common.spec :as us] [clojure.spec.alpha :as s])) - (s/def ::suffix string?) (s/def ::scale ::us/safe-number) (s/def ::type keyword?) @@ -19,4 +18,3 @@ ::suffix ::scale])) - diff --git a/common/src/app/common/spec/interactions.cljc b/common/src/app/common/types/shape/interactions.cljc similarity index 99% rename from common/src/app/common/spec/interactions.cljc rename to common/src/app/common/types/shape/interactions.cljc index eb3c2171dc..c28f40d89f 100644 --- a/common/src/app/common/spec/interactions.cljc +++ b/common/src/app/common/types/shape/interactions.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec.interactions +(ns app.common.types.shape.interactions (:require [app.common.data :as d] [app.common.geom.point :as gpt] diff --git a/common/src/app/common/spec/radius.cljc b/common/src/app/common/types/shape/radius.cljc similarity index 86% rename from common/src/app/common/spec/radius.cljc rename to common/src/app/common/types/shape/radius.cljc index bbc077deae..a93a8a927e 100644 --- a/common/src/app/common/spec/radius.cljc +++ b/common/src/app/common/types/shape/radius.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec.radius +(ns app.common.types.shape.radius (:require [app.common.pages.common :refer [editable-attrs]] [app.common.spec :as us] @@ -87,11 +87,17 @@ (defn set-radius-4 [shape attr value] - (cond-> shape - (:rx shape) - (-> (dissoc :rx :rx) - (assoc :r1 0 :r2 0 :r3 0 :r4 0)) + (let [attr (cond->> attr + (:flip-x shape) + (get {:r1 :r2 :r2 :r1 :r3 :r4 :r4 :r3}) - :always - (assoc attr value))) + (:flip-y shape) + (get {:r1 :r4 :r2 :r3 :r3 :r2 :r4 :r1}))] + (cond-> shape + (:rx shape) + (-> (dissoc :rx :rx) + (assoc :r1 0 :r2 0 :r3 0 :r4 0)) + + :always + (assoc attr value)))) diff --git a/common/src/app/common/spec/shadow.cljc b/common/src/app/common/types/shape/shadow.cljc similarity index 91% rename from common/src/app/common/spec/shadow.cljc rename to common/src/app/common/types/shape/shadow.cljc index 5b4bd553af..e2e92e5868 100644 --- a/common/src/app/common/spec/shadow.cljc +++ b/common/src/app/common/types/shape/shadow.cljc @@ -4,10 +4,10 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec.shadow +(ns app.common.types.shape.shadow (:require [app.common.spec :as us] - [app.common.spec.color :as color] + [app.common.types.color :as ctc] [clojure.spec.alpha :as s])) @@ -24,7 +24,7 @@ (s/def ::color string?) (s/def ::opacity ::us/safe-number) -(s/def ::gradient (s/nilable ::color/gradient)) +(s/def ::gradient (s/nilable ::ctc/gradient)) (s/def ::file-id (s/nilable uuid?)) (s/def ::ref-id (s/nilable uuid?)) diff --git a/common/src/app/common/spec/typography.cljc b/common/src/app/common/types/typography.cljc similarity index 97% rename from common/src/app/common/spec/typography.cljc rename to common/src/app/common/types/typography.cljc index 51c54a5171..ff63bf14be 100644 --- a/common/src/app/common/spec/typography.cljc +++ b/common/src/app/common/types/typography.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec.typography +(ns app.common.types.typography (:require [clojure.spec.alpha :as s])) diff --git a/common/src/app/common/uuid.cljc b/common/src/app/common/uuid.cljc index 6045029007..1a71256ef6 100644 --- a/common/src/app/common/uuid.cljc +++ b/common/src/app/common/uuid.cljc @@ -48,3 +48,6 @@ #?(:clj (dm/export impl/get-word-high)) + +#?(:clj + (dm/export impl/get-word-low)) diff --git a/common/test/app/common/spec_interactions_test.cljc b/common/test/app/common/spec_interactions_test.cljc index d874268955..7c90b625c8 100644 --- a/common/test/app/common/spec_interactions_test.cljc +++ b/common/test/app/common/spec_interactions_test.cljc @@ -10,62 +10,62 @@ [clojure.pprint :refer [pprint]] [app.common.exceptions :as ex] [app.common.pages.init :as cpi] - [app.common.spec.interactions :as csi] + [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] [app.common.geom.point :as gpt])) (t/deftest set-event-type - (let [interaction csi/default-interaction + (let [interaction ctsi/default-interaction shape (cpi/make-minimal-shape :rect) frame (cpi/make-minimal-shape :frame)] (t/testing "Set event type unchanged" (let [new-interaction - (csi/set-event-type interaction :click shape)] + (ctsi/set-event-type interaction :click shape)] (t/is (= :click (:event-type new-interaction))))) (t/testing "Set event type changed" (let [new-interaction - (csi/set-event-type interaction :mouse-press shape)] + (ctsi/set-event-type interaction :mouse-press shape)] (t/is (= :mouse-press (:event-type new-interaction))))) (t/testing "Set after delay on non-frame" (let [result (ex/try - (csi/set-event-type interaction :after-delay shape))] + (ctsi/set-event-type interaction :after-delay shape))] (t/is (ex/exception? result)))) (t/testing "Set after delay on frame" (let [new-interaction - (csi/set-event-type interaction :after-delay frame)] + (ctsi/set-event-type interaction :after-delay frame)] (t/is (= :after-delay (:event-type new-interaction))) (t/is (= 600 (:delay new-interaction))))) (t/testing "Set after delay with previous data" (let [interaction (assoc interaction :delay 300) new-interaction - (csi/set-event-type interaction :after-delay frame)] + (ctsi/set-event-type interaction :after-delay frame)] (t/is (= :after-delay (:event-type new-interaction))) (t/is (= 300 (:delay new-interaction))))))) (t/deftest set-action-type - (let [interaction csi/default-interaction] + (let [interaction ctsi/default-interaction] (t/testing "Set action type unchanged" (let [new-interaction - (csi/set-action-type interaction :navigate)] + (ctsi/set-action-type interaction :navigate)] (t/is (= :navigate (:action-type new-interaction))))) (t/testing "Set action type changed" (let [new-interaction - (csi/set-action-type interaction :prev-screen)] + (ctsi/set-action-type interaction :prev-screen)] (t/is (= :prev-screen (:action-type new-interaction))))) (t/testing "Set action type navigate" (let [interaction {:event-type :click :action-type :prev-screen} new-interaction - (csi/set-action-type interaction :navigate)] + (ctsi/set-action-type interaction :navigate)] (t/is (= :navigate (:action-type new-interaction))) (t/is (nil? (:destination new-interaction))) (t/is (= false (:preserve-scroll new-interaction))))) @@ -77,14 +77,14 @@ :destination destination :preserve-scroll true} new-interaction - (csi/set-action-type interaction :navigate)] + (ctsi/set-action-type interaction :navigate)] (t/is (= :navigate (:action-type new-interaction))) (t/is (= destination (:destination new-interaction))) (t/is (= true (:preserve-scroll new-interaction))))) (t/testing "Set action type open-overlay" (let [new-interaction - (csi/set-action-type interaction :open-overlay)] + (ctsi/set-action-type interaction :open-overlay)] (t/is (= :open-overlay (:action-type new-interaction))) (t/is (= :center (:overlay-pos-type new-interaction))) (t/is (= (gpt/point 0 0) (:overlay-position new-interaction))))) @@ -93,14 +93,14 @@ (let [interaction (assoc interaction :overlay-pos-type :top-left :overlay-position (gpt/point 100 200)) new-interaction - (csi/set-action-type interaction :open-overlay)] + (ctsi/set-action-type interaction :open-overlay)] (t/is (= :open-overlay (:action-type new-interaction))) (t/is (= :top-left (:overlay-pos-type new-interaction))) (t/is (= (gpt/point 100 200) (:overlay-position new-interaction))))) (t/testing "Set action type toggle-overlay" (let [new-interaction - (csi/set-action-type interaction :toggle-overlay)] + (ctsi/set-action-type interaction :toggle-overlay)] (t/is (= :toggle-overlay (:action-type new-interaction))) (t/is (= :center (:overlay-pos-type new-interaction))) (t/is (= (gpt/point 0 0) (:overlay-position new-interaction))))) @@ -109,14 +109,14 @@ (let [interaction (assoc interaction :overlay-pos-type :top-left :overlay-position (gpt/point 100 200)) new-interaction - (csi/set-action-type interaction :toggle-overlay)] + (ctsi/set-action-type interaction :toggle-overlay)] (t/is (= :toggle-overlay (:action-type new-interaction))) (t/is (= :top-left (:overlay-pos-type new-interaction))) (t/is (= (gpt/point 100 200) (:overlay-position new-interaction))))) (t/testing "Set action type close-overlay" (let [new-interaction - (csi/set-action-type interaction :close-overlay)] + (ctsi/set-action-type interaction :close-overlay)] (t/is (= :close-overlay (:action-type new-interaction))) (t/is (nil? (:destination new-interaction))))) @@ -124,89 +124,89 @@ (let [destination (uuid/next) interaction (assoc interaction :destination destination) new-interaction - (csi/set-action-type interaction :close-overlay)] + (ctsi/set-action-type interaction :close-overlay)] (t/is (= :close-overlay (:action-type new-interaction))) (t/is (= destination (:destination new-interaction))))) (t/testing "Set action type prev-screen" (let [new-interaction - (csi/set-action-type interaction :prev-screen)] + (ctsi/set-action-type interaction :prev-screen)] (t/is (= :prev-screen (:action-type new-interaction))))) (t/testing "Set action type open-url" (let [new-interaction - (csi/set-action-type interaction :open-url)] + (ctsi/set-action-type interaction :open-url)] (t/is (= :open-url (:action-type new-interaction))) (t/is (= "" (:url new-interaction))))) (t/testing "Set action type open-url with previous data" (let [interaction (assoc interaction :url "https://example.com") new-interaction - (csi/set-action-type interaction :open-url)] + (ctsi/set-action-type interaction :open-url)] (t/is (= :open-url (:action-type new-interaction))) (t/is (= "https://example.com" (:url new-interaction))))))) (t/deftest option-delay (let [frame (cpi/make-minimal-shape :frame) - i1 csi/default-interaction - i2 (csi/set-event-type i1 :after-delay frame)] + i1 ctsi/default-interaction + i2 (ctsi/set-event-type i1 :after-delay frame)] (t/testing "Has delay" - (t/is (not (csi/has-delay i1))) - (t/is (csi/has-delay i2))) + (t/is (not (ctsi/has-delay i1))) + (t/is (ctsi/has-delay i2))) (t/testing "Set delay" - (let [new-interaction (csi/set-delay i2 1000)] + (let [new-interaction (ctsi/set-delay i2 1000)] (t/is (= 1000 (:delay new-interaction))))))) (t/deftest option-destination (let [destination (uuid/next) - i1 csi/default-interaction - i2 (csi/set-action-type i1 :prev-screen) - i3 (csi/set-action-type i1 :open-overlay)] + i1 ctsi/default-interaction + i2 (ctsi/set-action-type i1 :prev-screen) + i3 (ctsi/set-action-type i1 :open-overlay)] (t/testing "Has destination" - (t/is (csi/has-destination i1)) - (t/is (not (csi/has-destination i2)))) + (t/is (ctsi/has-destination i1)) + (t/is (not (ctsi/has-destination i2)))) (t/testing "Set destination" - (let [new-interaction (csi/set-destination i1 destination)] + (let [new-interaction (ctsi/set-destination i1 destination)] (t/is (= destination (:destination new-interaction))) (t/is (nil? (:overlay-pos-type new-interaction))) (t/is (nil? (:overlay-position new-interaction))))) (t/testing "Set destination of overlay" - (let [new-interaction (csi/set-destination i3 destination)] + (let [new-interaction (ctsi/set-destination i3 destination)] (t/is (= destination (:destination new-interaction))) (t/is (= :center (:overlay-pos-type new-interaction))) (t/is (= (gpt/point 0 0) (:overlay-position new-interaction))))))) (t/deftest option-preserve-scroll - (let [i1 csi/default-interaction - i2 (csi/set-action-type i1 :prev-screen)] + (let [i1 ctsi/default-interaction + i2 (ctsi/set-action-type i1 :prev-screen)] (t/testing "Has preserve-scroll" - (t/is (csi/has-preserve-scroll i1)) - (t/is (not (csi/has-preserve-scroll i2)))) + (t/is (ctsi/has-preserve-scroll i1)) + (t/is (not (ctsi/has-preserve-scroll i2)))) (t/testing "Set preserve-scroll" - (let [new-interaction (csi/set-preserve-scroll i1 true)] + (let [new-interaction (ctsi/set-preserve-scroll i1 true)] (t/is (= true (:preserve-scroll new-interaction))))))) (t/deftest option-url - (let [i1 csi/default-interaction - i2 (csi/set-action-type i1 :open-url)] + (let [i1 ctsi/default-interaction + i2 (ctsi/set-action-type i1 :open-url)] (t/testing "Has url" - (t/is (not (csi/has-url i1))) - (t/is (csi/has-url i2))) + (t/is (not (ctsi/has-url i1))) + (t/is (ctsi/has-url i2))) (t/testing "Set url" - (let [new-interaction (csi/set-url i2 "https://example.com")] + (let [new-interaction (ctsi/set-url i2 "https://example.com")] (t/is (= "https://example.com" (:url new-interaction))))))) @@ -220,35 +220,35 @@ objects {(:id base-frame) base-frame (:id overlay-frame) overlay-frame} - i1 csi/default-interaction - i2 (csi/set-action-type i1 :open-overlay) + i1 ctsi/default-interaction + i2 (ctsi/set-action-type i1 :open-overlay) i3 (-> i1 - (csi/set-action-type :open-overlay) - (csi/set-destination (:id overlay-frame)))] + (ctsi/set-action-type :open-overlay) + (ctsi/set-destination (:id overlay-frame)))] (t/testing "Has overlay options" - (t/is (not (csi/has-overlay-opts i1))) - (t/is (csi/has-overlay-opts i2))) + (t/is (not (ctsi/has-overlay-opts i1))) + (t/is (ctsi/has-overlay-opts i2))) (t/testing "Set overlay-pos-type without destination" - (let [new-interaction (csi/set-overlay-pos-type i2 :top-right base-frame objects)] + (let [new-interaction (ctsi/set-overlay-pos-type i2 :top-right base-frame objects)] (t/is (= :top-right (:overlay-pos-type new-interaction))) (t/is (= (gpt/point 0 0) (:overlay-position new-interaction))))) (t/testing "Set overlay-pos-type with destination and auto" - (let [new-interaction (csi/set-overlay-pos-type i3 :bottom-right base-frame objects)] + (let [new-interaction (ctsi/set-overlay-pos-type i3 :bottom-right base-frame objects)] (t/is (= :bottom-right (:overlay-pos-type new-interaction))) (t/is (= (gpt/point 0 0) (:overlay-position new-interaction))))) (t/testing "Set overlay-pos-type with destination and manual" - (let [new-interaction (csi/set-overlay-pos-type i3 :manual base-frame objects)] + (let [new-interaction (ctsi/set-overlay-pos-type i3 :manual base-frame objects)] (t/is (= :manual (:overlay-pos-type new-interaction))) (t/is (= (gpt/point 35 40) (:overlay-position new-interaction))))) (t/testing "Toggle overlay-pos-type" - (let [new-interaction (csi/toggle-overlay-pos-type i3 :center base-frame objects) - new-interaction-2 (csi/toggle-overlay-pos-type new-interaction :center base-frame objects) - new-interaction-3 (csi/toggle-overlay-pos-type new-interaction-2 :top-right base-frame objects)] + (let [new-interaction (ctsi/toggle-overlay-pos-type i3 :center base-frame objects) + new-interaction-2 (ctsi/toggle-overlay-pos-type new-interaction :center base-frame objects) + new-interaction-3 (ctsi/toggle-overlay-pos-type new-interaction-2 :top-right base-frame objects)] (t/is (= :manual (:overlay-pos-type new-interaction))) (t/is (= (gpt/point 35 40) (:overlay-position new-interaction))) (t/is (= :center (:overlay-pos-type new-interaction-2))) @@ -257,73 +257,73 @@ (t/is (= (gpt/point 0 0) (:overlay-position new-interaction-3))))) (t/testing "Set overlay-position" - (let [new-interaction (csi/set-overlay-position i3 (gpt/point 50 60))] + (let [new-interaction (ctsi/set-overlay-position i3 (gpt/point 50 60))] (t/is (= :manual (:overlay-pos-type new-interaction))) (t/is (= (gpt/point 50 60) (:overlay-position new-interaction))))) (t/testing "Set close-click-outside" - (let [new-interaction (csi/set-close-click-outside i3 true)] + (let [new-interaction (ctsi/set-close-click-outside i3 true)] (t/is (not (:close-click-outside i3))) (t/is (:close-click-outside new-interaction)))) (t/testing "Set background-overlay" - (let [new-interaction (csi/set-background-overlay i3 true)] + (let [new-interaction (ctsi/set-background-overlay i3 true)] (t/is (not (:background-overlay i3))) (t/is (:background-overlay new-interaction)))))) (t/deftest animation-checks - (let [i1 csi/default-interaction - i2 (csi/set-action-type i1 :open-overlay) - i3 (csi/set-action-type i1 :toggle-overlay) - i4 (csi/set-action-type i1 :close-overlay) - i5 (csi/set-action-type i1 :prev-screen) - i6 (csi/set-action-type i1 :open-url)] + (let [i1 ctsi/default-interaction + i2 (ctsi/set-action-type i1 :open-overlay) + i3 (ctsi/set-action-type i1 :toggle-overlay) + i4 (ctsi/set-action-type i1 :close-overlay) + i5 (ctsi/set-action-type i1 :prev-screen) + i6 (ctsi/set-action-type i1 :open-url)] (t/testing "Has animation?" - (t/is (csi/has-animation? i1)) - (t/is (csi/has-animation? i2)) - (t/is (csi/has-animation? i3)) - (t/is (csi/has-animation? i4)) - (t/is (not (csi/has-animation? i5))) - (t/is (not (csi/has-animation? i6)))) + (t/is (ctsi/has-animation? i1)) + (t/is (ctsi/has-animation? i2)) + (t/is (ctsi/has-animation? i3)) + (t/is (ctsi/has-animation? i4)) + (t/is (not (ctsi/has-animation? i5))) + (t/is (not (ctsi/has-animation? i6)))) (t/testing "Valid push?" - (t/is (csi/allow-push? (:action-type i1))) - (t/is (not (csi/allow-push? (:action-type i2)))) - (t/is (not (csi/allow-push? (:action-type i3)))) - (t/is (not (csi/allow-push? (:action-type i4)))) - (t/is (not (csi/allow-push? (:action-type i5)))) - (t/is (not (csi/allow-push? (:action-type i6))))))) + (t/is (ctsi/allow-push? (:action-type i1))) + (t/is (not (ctsi/allow-push? (:action-type i2)))) + (t/is (not (ctsi/allow-push? (:action-type i3)))) + (t/is (not (ctsi/allow-push? (:action-type i4)))) + (t/is (not (ctsi/allow-push? (:action-type i5)))) + (t/is (not (ctsi/allow-push? (:action-type i6))))))) (t/deftest set-animation-type - (let [i1 csi/default-interaction - i2 (csi/set-animation-type i1 :dissolve)] + (let [i1 ctsi/default-interaction + i2 (ctsi/set-animation-type i1 :dissolve)] (t/testing "Set animation type nil" (let [new-interaction - (csi/set-animation-type i1 nil)] + (ctsi/set-animation-type i1 nil)] (t/is (nil? (-> new-interaction :animation :animation-type))))) (t/testing "Set animation type unchanged" (let [new-interaction - (csi/set-animation-type i2 :dissolve)] + (ctsi/set-animation-type i2 :dissolve)] (t/is (= :dissolve (-> new-interaction :animation :animation-type))))) (t/testing "Set animation type changed" (let [new-interaction - (csi/set-animation-type i2 :slide)] + (ctsi/set-animation-type i2 :slide)] (t/is (= :slide (-> new-interaction :animation :animation-type))))) (t/testing "Set animation type reset" (let [new-interaction - (csi/set-animation-type i2 nil)] + (ctsi/set-animation-type i2 nil)] (t/is (nil? (-> new-interaction :animation))))) (t/testing "Set animation type dissolve" (let [new-interaction - (csi/set-animation-type i1 :dissolve)] + (ctsi/set-animation-type i1 :dissolve)] (t/is (= :dissolve (-> new-interaction :animation :animation-type))) (t/is (= 300 (-> new-interaction :animation :duration))) (t/is (= :linear (-> new-interaction :animation :easing))))) @@ -336,14 +336,14 @@ :direction :left :offset-effect true}) new-interaction - (csi/set-animation-type interaction :dissolve)] + (ctsi/set-animation-type interaction :dissolve)] (t/is (= :dissolve (-> new-interaction :animation :animation-type))) (t/is (= 1000 (-> new-interaction :animation :duration))) (t/is (= :ease-out (-> new-interaction :animation :easing))))) (t/testing "Set animation type slide" (let [new-interaction - (csi/set-animation-type i1 :slide)] + (ctsi/set-animation-type i1 :slide)] (t/is (= :slide (-> new-interaction :animation :animation-type))) (t/is (= 300 (-> new-interaction :animation :duration))) (t/is (= :linear (-> new-interaction :animation :easing))) @@ -359,7 +359,7 @@ :direction :left :offset-effect true}) new-interaction - (csi/set-animation-type interaction :slide)] + (ctsi/set-animation-type interaction :slide)] (t/is (= :slide (-> new-interaction :animation :animation-type))) (t/is (= 1000 (-> new-interaction :animation :duration))) (t/is (= :ease-out (-> new-interaction :animation :easing))) @@ -369,7 +369,7 @@ (t/testing "Set animation type push" (let [new-interaction - (csi/set-animation-type i1 :push)] + (ctsi/set-animation-type i1 :push)] (t/is (= :push (-> new-interaction :animation :animation-type))) (t/is (= 300 (-> new-interaction :animation :duration))) (t/is (= :linear (-> new-interaction :animation :easing))) @@ -383,7 +383,7 @@ :direction :left :offset-effect true}) new-interaction - (csi/set-animation-type interaction :push)] + (ctsi/set-animation-type interaction :push)] (t/is (= :push (-> new-interaction :animation :animation-type))) (t/is (= 1000 (-> new-interaction :animation :duration))) (t/is (= :ease-out (-> new-interaction :animation :easing))) @@ -391,9 +391,9 @@ (t/deftest allowed-animation - (let [i1 (csi/set-action-type csi/default-interaction :open-overlay) - i2 (csi/set-action-type csi/default-interaction :close-overlay) - i3 (csi/set-action-type csi/default-interaction :toggle-overlay)] + (let [i1 (ctsi/set-action-type ctsi/default-interaction :open-overlay) + i2 (ctsi/set-action-type ctsi/default-interaction :close-overlay) + i3 (ctsi/set-action-type ctsi/default-interaction :toggle-overlay)] (t/testing "Cannot use animation push for an overlay action" (let [bad-interaction-1 (assoc i1 :animation {:animation-type :push @@ -408,72 +408,72 @@ :duration 1000 :easing :ease-out :direction :left})] - (t/is (not (csi/allowed-animation? (:action-type bad-interaction-1) + (t/is (not (ctsi/allowed-animation? (:action-type bad-interaction-1) (-> bad-interaction-1 :animation :animation-type)))) - (t/is (not (csi/allowed-animation? (:action-type bad-interaction-2) + (t/is (not (ctsi/allowed-animation? (:action-type bad-interaction-2) (-> bad-interaction-1 :animation :animation-type)))) - (t/is (not (csi/allowed-animation? (:action-type bad-interaction-3) + (t/is (not (ctsi/allowed-animation? (:action-type bad-interaction-3) (-> bad-interaction-1 :animation :animation-type)))))) (t/testing "Remove animation if moving to an forbidden state" - (let [interaction (csi/set-animation-type csi/default-interaction :push) - new-interaction (csi/set-action-type interaction :open-overlay)] + (let [interaction (ctsi/set-animation-type ctsi/default-interaction :push) + new-interaction (ctsi/set-action-type interaction :open-overlay)] (t/is (nil? (:animation new-interaction))))))) (t/deftest option-duration - (let [i1 csi/default-interaction - i2 (csi/set-animation-type csi/default-interaction :dissolve)] + (let [i1 ctsi/default-interaction + i2 (ctsi/set-animation-type ctsi/default-interaction :dissolve)] (t/testing "Has duration?" - (t/is (not (csi/has-duration? i1))) - (t/is (csi/has-duration? i2))) + (t/is (not (ctsi/has-duration? i1))) + (t/is (ctsi/has-duration? i2))) (t/testing "Set duration" - (let [new-interaction (csi/set-duration i2 1000)] + (let [new-interaction (ctsi/set-duration i2 1000)] (t/is (= 1000 (-> new-interaction :animation :duration))))))) (t/deftest option-easing - (let [i1 csi/default-interaction - i2 (csi/set-animation-type csi/default-interaction :dissolve)] + (let [i1 ctsi/default-interaction + i2 (ctsi/set-animation-type ctsi/default-interaction :dissolve)] (t/testing "Has easing?" - (t/is (not (csi/has-easing? i1))) - (t/is (csi/has-easing? i2))) + (t/is (not (ctsi/has-easing? i1))) + (t/is (ctsi/has-easing? i2))) (t/testing "Set easing" - (let [new-interaction (csi/set-easing i2 :ease-in)] + (let [new-interaction (ctsi/set-easing i2 :ease-in)] (t/is (= :ease-in (-> new-interaction :animation :easing))))))) (t/deftest option-way - (let [i1 csi/default-interaction - i2 (csi/set-animation-type csi/default-interaction :slide) - i3 (csi/set-action-type i2 :open-overlay)] + (let [i1 ctsi/default-interaction + i2 (ctsi/set-animation-type ctsi/default-interaction :slide) + i3 (ctsi/set-action-type i2 :open-overlay)] (t/testing "Has way?" - (t/is (not (csi/has-way? i1))) - (t/is (csi/has-way? i2)) - (t/is (not (csi/has-way? i3))) + (t/is (not (ctsi/has-way? i1))) + (t/is (ctsi/has-way? i2)) + (t/is (not (ctsi/has-way? i3))) (t/is (some? (-> i3 :animation :way)))) ; <- it exists but is ignored (t/testing "Set way" - (let [new-interaction (csi/set-way i2 :out)] + (let [new-interaction (ctsi/set-way i2 :out)] (t/is (= :out (-> new-interaction :animation :way))))))) (t/deftest option-direction - (let [i1 csi/default-interaction - i2 (csi/set-animation-type csi/default-interaction :push) - i3 (csi/set-animation-type csi/default-interaction :dissolve)] + (let [i1 ctsi/default-interaction + i2 (ctsi/set-animation-type ctsi/default-interaction :push) + i3 (ctsi/set-animation-type ctsi/default-interaction :dissolve)] (t/testing "Has direction?" - (t/is (not (csi/has-direction? i1))) - (t/is (csi/has-direction? i2))) + (t/is (not (ctsi/has-direction? i1))) + (t/is (ctsi/has-direction? i2))) (t/testing "Set direction" - (let [new-interaction (csi/set-direction i2 :left)] + (let [new-interaction (ctsi/set-direction i2 :left)] (t/is (= :left (-> new-interaction :animation :direction))))) (t/testing "Invert direction" @@ -483,12 +483,12 @@ a-up (assoc a-right :direction :up) a-down (assoc a-right :direction :down) - a-nil' (csi/invert-direction nil) - a-none' (csi/invert-direction a-none) - a-right' (csi/invert-direction a-right) - a-left' (csi/invert-direction a-left) - a-up' (csi/invert-direction a-up) - a-down' (csi/invert-direction a-down)] + a-nil' (ctsi/invert-direction nil) + a-none' (ctsi/invert-direction a-none) + a-right' (ctsi/invert-direction a-right) + a-left' (ctsi/invert-direction a-left) + a-up' (ctsi/invert-direction a-up) + a-down' (ctsi/invert-direction a-down)] (t/is (nil? a-nil')) (t/is (nil? (:direction a-none'))) @@ -499,44 +499,44 @@ (t/deftest option-offset-effect - (let [i1 csi/default-interaction - i2 (csi/set-animation-type csi/default-interaction :slide) - i3 (csi/set-action-type i2 :open-overlay)] + (let [i1 ctsi/default-interaction + i2 (ctsi/set-animation-type ctsi/default-interaction :slide) + i3 (ctsi/set-action-type i2 :open-overlay)] (t/testing "Has offset-effect" - (t/is (not (csi/has-offset-effect? i1))) - (t/is (csi/has-offset-effect? i2)) - (t/is (not (csi/has-offset-effect? i3))) + (t/is (not (ctsi/has-offset-effect? i1))) + (t/is (ctsi/has-offset-effect? i2)) + (t/is (not (ctsi/has-offset-effect? i3))) (t/is (some? (-> i3 :animation :offset-effect)))) ; <- it exists but is ignored (t/testing "Set offset-effect" - (let [new-interaction (csi/set-offset-effect i2 true)] + (let [new-interaction (ctsi/set-offset-effect i2 true)] (t/is (= true (-> new-interaction :animation :offset-effect))))))) (t/deftest modify-interactions - (let [i1 (csi/set-action-type csi/default-interaction :open-overlay) - i2 (csi/set-action-type csi/default-interaction :close-overlay) - i3 (csi/set-action-type csi/default-interaction :prev-screen) + (let [i1 (ctsi/set-action-type ctsi/default-interaction :open-overlay) + i2 (ctsi/set-action-type ctsi/default-interaction :close-overlay) + i3 (ctsi/set-action-type ctsi/default-interaction :prev-screen) interactions [i1 i2]] (t/testing "Add interaction to nil" - (let [new-interactions (csi/add-interaction nil i3)] + (let [new-interactions (ctsi/add-interaction nil i3)] (t/is (= (count new-interactions) 1)) (t/is (= (:action-type (last new-interactions)) :prev-screen)))) (t/testing "Add interaction to normal" - (let [new-interactions (csi/add-interaction interactions i3)] + (let [new-interactions (ctsi/add-interaction interactions i3)] (t/is (= (count new-interactions) 3)) (t/is (= (:action-type (last new-interactions)) :prev-screen)))) (t/testing "Remove interaction" - (let [new-interactions (csi/remove-interaction interactions 0)] + (let [new-interactions (ctsi/remove-interaction interactions 0)] (t/is (= (count new-interactions) 1)) (t/is (= (:action-type (last new-interactions)) :close-overlay)))) (t/testing "Update interaction" - (let [new-interactions (csi/update-interaction interactions 1 #(csi/set-action-type % :open-url))] + (let [new-interactions (ctsi/update-interaction interactions 1 #(ctsi/set-action-type % :open-url))] (t/is (= (count new-interactions) 2)) (t/is (= (:action-type (last new-interactions)) :open-url)))))) @@ -556,16 +556,16 @@ ids-map {(:id frame1) (:id frame4) (:id frame2) (:id frame5)} - i1 (csi/set-destination csi/default-interaction (:id frame1)) - i2 (csi/set-destination csi/default-interaction (:id frame2)) - i3 (csi/set-destination csi/default-interaction (:id frame3)) - i4 (csi/set-destination csi/default-interaction nil) - i5 (csi/set-destination csi/default-interaction (:id frame6)) + i1 (ctsi/set-destination ctsi/default-interaction (:id frame1)) + i2 (ctsi/set-destination ctsi/default-interaction (:id frame2)) + i3 (ctsi/set-destination ctsi/default-interaction (:id frame3)) + i4 (ctsi/set-destination ctsi/default-interaction nil) + i5 (ctsi/set-destination ctsi/default-interaction (:id frame6)) interactions [i1 i2 i3 i4 i5]] (t/testing "Remap interactions" - (let [new-interactions (csi/remap-interactions interactions ids-map objects)] + (let [new-interactions (ctsi/remap-interactions interactions ids-map objects)] (t/is (= (count new-interactions) 4)) (t/is (= (:id frame4) (:destination (get new-interactions 0)))) (t/is (= (:id frame5) (:destination (get new-interactions 1)))) diff --git a/common/yarn.lock b/common/yarn.lock index 22f689b46e..5ee0581ae7 100644 --- a/common/yarn.lock +++ b/common/yarn.lock @@ -533,10 +533,10 @@ shadow-cljs-jar@1.3.2: resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b" integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg== -shadow-cljs@2.17.3: - version "2.17.3" - resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.17.3.tgz#748e31f67cffdc401691c0cd1bf733a1da53ab5d" - integrity sha512-GxyczUuCtACq/uEOvdTc61wT/aDOZFy8G/AGc322uTX/oUiZaeTJrwpClXe+0+e7VKG9E9RCqP/cjuG3cAG0fw== +shadow-cljs@2.19.8: + version "2.19.8" + resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.19.8.tgz#1ce96cab3e4903bed8d401ffbe88b8939f5454d3" + integrity sha512-6qek3mcAP0hrnC5FxrTebBrgLGpOuhlnp06vdxp6g0M5Gl6w2Y0hzSwa1s2K8fMOkzE4/ciQor75b2y64INgaw== dependencies: node-libs-browser "^2.2.1" readline-sync "^1.4.7" diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index 98ae0514fa..d3af5c86ae 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -1,12 +1,12 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 LABEL maintainer="Andrey Antukh " ARG DEBIAN_FRONTEND=noninteractive -ENV NODE_VERSION=v16.14.2 \ - CLOJURE_VERSION=1.11.0.1100 \ - CLJKONDO_VERSION=2022.03.09 \ - BABASHKA_VERSION=0.8.0 \ +ENV NODE_VERSION=v16.16.0 \ + CLOJURE_VERSION=1.11.1.1149 \ + CLJKONDO_VERSION=2022.06.22 \ + BABASHKA_VERSION=0.8.156 \ LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 @@ -44,7 +44,6 @@ RUN set -ex; \ RUN set -ex; \ apt-get -qq update; \ apt-get -qqy install --no-install-recommends \ - python \ build-essential \ imagemagick \ ghostscript \ @@ -58,6 +57,7 @@ RUN set -ex; \ woff-tools \ woff2 \ fontforge \ + openssh-client \ ; \ rm -rf /var/lib/apt/lists/*; @@ -104,7 +104,7 @@ RUN set -ex; \ rm -rf /var/lib/apt/lists/*; RUN set -ex; \ - curl -LfsSo /tmp/openjdk.tar.gz https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18%2B36/OpenJDK18U-jdk_x64_linux_hotspot_18_36.tar.gz; \ + curl -LfsSo /tmp/openjdk.tar.gz https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18.0.1%2B10/OpenJDK18U-jdk_x64_linux_hotspot_18.0.1_10.tar.gz; \ mkdir -p /usr/lib/jvm/openjdk; \ cd /usr/lib/jvm/openjdk; \ tar -xf /tmp/openjdk.tar.gz --strip-components=1; \ @@ -120,7 +120,7 @@ RUN set -ex; \ RUN set -ex; \ curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -; \ - echo "deb http://apt.postgresql.org/pub/repos/apt focal-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; \ + echo "deb http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; \ apt-get -qq update; \ apt-get -qqy install postgresql-client-13; \ rm -rf /var/lib/apt/lists/*; @@ -132,8 +132,8 @@ RUN set -ex; \ tar -xf /tmp/nodejs.tar.xz --strip-components=1; \ chown -R root /usr/local/nodejs; \ PATH="$PATH:/usr/local/nodejs/bin"; \ - /usr/local/nodejs/bin/npm install -g yarn; \ - /usr/local/nodejs/bin/npm install -g svgo; \ + /usr/local/nodejs/bin/npm install --location=global yarn; \ + /usr/local/nodejs/bin/npm install --location=global svgo; \ rm -rf /tmp/nodejs.tar.xz; # Install clj-kondo @@ -143,7 +143,6 @@ RUN set -ex; \ unzip /tmp/clj-kondo.zip; \ rm /tmp/clj-kondo.zip; -# Install babashka RUN set -ex; \ cd /tmp; \ curl -LfsSo /tmp/babashka.tar.gz https://github.com/babashka/babashka/releases/download/v$BABASHKA_VERSION/babashka-$BABASHKA_VERSION-linux-amd64.tar.gz; \ @@ -151,8 +150,10 @@ RUN set -ex; \ tar -xf /tmp/babashka.tar.gz; \ rm -rf /tmp/babashka.tar.gz; + +# Install minio client RUN set -ex; \ - curl -LfsSo /tmp/mc https://dl.min.io/client/mc/release/linux-amd64/mc --user-agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15"; \ + wget -O /tmp/mc https://dl.min.io/client/mc/release/linux-amd64/mc; \ mv /tmp/mc /usr/local/bin/; \ chmod +x /usr/local/bin/mc; diff --git a/docker/images/Dockerfile.backend b/docker/images/Dockerfile.backend index a2690c8891..4729d7cdb8 100644 --- a/docker/images/Dockerfile.backend +++ b/docker/images/Dockerfile.backend @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 LABEL maintainer="Andrey Antukh " ENV LANG='en_US.UTF-8' LC_ALL='en_US.UTF-8' @@ -27,16 +27,16 @@ RUN set -eux; \ ARCH="$(dpkg --print-architecture)"; \ case "${ARCH}" in \ aarch64|arm64) \ - ESUM='7217a9f9be3b0c8dfc78538f95fd2deb493eb651152d975062920566492b2574'; \ - BINARY_URL='https://github.com/AdoptOpenJDK/openjdk16-binaries/releases/download/jdk-16%2B36/OpenJDK16-jdk_aarch64_linux_hotspot_16_36.tar.gz'; \ + ESUM='37ceaf232a85cce46bcccfd71839854e8b14bf3160e7ef72a676b9cae45ee8af'; \ + BINARY_URL='https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18.0.1%2B10/OpenJDK18U-jdk_aarch64_linux_hotspot_18.0.1_10.tar.gz'; \ ;; \ armhf|armv7l) \ - ESUM='f1d32ba01a40c98889f31368c0e987d6bbda65a7c50b8c088623b48e3a90104a'; \ - BINARY_URL='https://github.com/AdoptOpenJDK/openjdk16-binaries/releases/download/jdk-16%2B36/OpenJDK16-jdk_arm_linux_hotspot_16_36.tar.gz'; \ + ESUM='0ddec3c165ab0b662a57a845db3fdaeb840660b493f164696b03df76aadf61c8'; \ + BINARY_URL='https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18.0.1%2B10/OpenJDK18U-jdk_arm_linux_hotspot_18.0.1_10.tar.gz'; \ ;; \ amd64|x86_64) \ - ESUM='2e031cf37018161c9e59b45fa4b98ff2ce4ce9297b824c512989d579a70f8422'; \ - BINARY_URL='https://github.com/AdoptOpenJDK/openjdk16-binaries/releases/download/jdk-16%2B36/OpenJDK16-jdk_x64_linux_hotspot_16_36.tar.gz'; \ + ESUM='16b1d9d75f22c157af04a1fd9c664324c7f4b5163c022b382a2f2e8897c1b0a2'; \ + BINARY_URL='https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18.0.1%2B10/OpenJDK18U-jdk_x64_linux_hotspot_18.0.1_10.tar.gz'; \ ;; \ *) \ echo "Unsupported arch: ${ARCH}"; \ diff --git a/docker/images/Dockerfile.exporter b/docker/images/Dockerfile.exporter index 6b5d6c9349..5b1e904581 100644 --- a/docker/images/Dockerfile.exporter +++ b/docker/images/Dockerfile.exporter @@ -1,11 +1,11 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 LABEL maintainer="Andrey Antukh " ARG DEBIAN_FRONTEND=noninteractive ENV LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 \ - NODE_VERSION=v16.14.2 + NODE_VERSION=v16.15.1 RUN set -ex; \ mkdir -p /etc/resolvconf/resolv.conf.d; \ @@ -95,7 +95,7 @@ WORKDIR /opt/app ADD ./bundle-exporter/ /opt/app/ RUN set -ex; \ - yarn install; \ + yarn; \ npx playwright install chromium; CMD ["/usr/local/nodejs/bin/node", "app.js"] diff --git a/docker/images/Dockerfile.frontend b/docker/images/Dockerfile.frontend index 697a9c0768..f2708974e9 100644 --- a/docker/images/Dockerfile.frontend +++ b/docker/images/Dockerfile.frontend @@ -1,4 +1,4 @@ -FROM nginx:latest +FROM nginx:1.23 LABEL maintainer="Andrey Antukh " ADD ./bundle-frontend/ /var/www/app/ diff --git a/docker/images/config.env b/docker/images/config.env index eccfbe0bfa..cc880d70f1 100644 --- a/docker/images/config.env +++ b/docker/images/config.env @@ -97,4 +97,3 @@ PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@example.com # PENPOT_LDAP_ATTRS_USERNAME=uid # PENPOT_LDAP_ATTRS_EMAIL=mail # PENPOT_LDAP_ATTRS_FULLNAME=cn -# PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto diff --git a/docker/images/files/nginx-entrypoint.sh b/docker/images/files/nginx-entrypoint.sh index 270b7e26ae..1e349e5866 100644 --- a/docker/images/files/nginx-entrypoint.sh +++ b/docker/images/files/nginx-entrypoint.sh @@ -4,69 +4,10 @@ log() { echo "[$(date +%Y-%m-%dT%H:%M:%S%:z)] $*" } - ######################################### ## App Frontend config ######################################### -update_google_client_id() { - if [ -n "$PENPOT_GOOGLE_CLIENT_ID" ]; then - log "Updating Google Client Id: $PENPOT_GOOGLE_CLIENT_ID" - sed -i \ - -e "s|^//var penpotGoogleClientID = \".*\";|var penpotGoogleClientID = \"$PENPOT_GOOGLE_CLIENT_ID\";|g" \ - "$1" - fi -} - - -update_gitlab_client_id() { - if [ -n "$PENPOT_GITLAB_CLIENT_ID" ]; then - log "Updating GitLab Client Id: $PENPOT_GITLAB_CLIENT_ID" - sed -i \ - -e "s|^//var penpotGitlabClientID = \".*\";|var penpotGitlabClientID = \"$PENPOT_GITLAB_CLIENT_ID\";|g" \ - "$1" - fi -} - - -update_github_client_id() { - if [ -n "$PENPOT_GITHUB_CLIENT_ID" ]; then - log "Updating GitHub Client Id: $PENPOT_GITHUB_CLIENT_ID" - sed -i \ - -e "s|^//var penpotGithubClientID = \".*\";|var penpotGithubClientID = \"$PENPOT_GITHUB_CLIENT_ID\";|g" \ - "$1" - 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 -} - -# DEPRECATED -update_login_with_ldap() { - if [ -n "$PENPOT_LOGIN_WITH_LDAP" ]; then - log "Updating Login with LDAP: $PENPOT_LOGIN_WITH_LDAP" - sed -i \ - -e "s|^//var penpotLoginWithLDAP = .*;|var penpotLoginWithLDAP = $PENPOT_LOGIN_WITH_LDAP;|g" \ - "$1" - fi -} - -# DEPRECATED -update_registration_enabled() { - if [ -n "$PENPOT_REGISTRATION_ENABLED" ]; then - log "Updating Registration Enabled: $PENPOT_REGISTRATION_ENABLED" - sed -i \ - -e "s|^//var penpotRegistrationEnabled = .*;|var penpotRegistrationEnabled = $PENPOT_REGISTRATION_ENABLED;|g" \ - "$1" - fi -} - update_flags() { if [ -n "$PENPOT_FLAGS" ]; then sed -i \ @@ -75,11 +16,5 @@ update_flags() { fi } -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 update_flags /var/www/app/js/config.js exec "$@"; diff --git a/docker/images/files/nginx.conf b/docker/images/files/nginx.conf index 35863895be..0302a2a837 100644 --- a/docker/images/files/nginx.conf +++ b/docker/images/files/nginx.conf @@ -19,8 +19,8 @@ http { server_tokens off; reset_timedout_connection on; - client_body_timeout 20s; - client_header_timeout 20s; + client_body_timeout 30s; + client_header_timeout 30s; include /etc/nginx/mime.types; default_type application/octet-stream; @@ -49,7 +49,7 @@ http { listen 80 default_server; server_name _; - client_max_body_size 50M; + client_max_body_size 100M; charset utf-8; proxy_http_version 1.1; @@ -78,10 +78,6 @@ http { proxy_pass http://penpot-backend:6060/api; } - location /dbg { - proxy_pass http://penpot-backend:6060/dbg; - } - location /ws/notifications { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; diff --git a/exporter/deps.edn b/exporter/deps.edn index b171da17cd..ed25f8ec40 100644 --- a/exporter/deps.edn +++ b/exporter/deps.edn @@ -1,9 +1,9 @@ {:paths ["src" "vendor" "resources" "test"] :deps {penpot/common {:local/root "../common"} - org.clojure/clojure {:mvn/version "1.10.3"} + org.clojure/clojure {:mvn/version "1.11.1"} binaryage/devtools {:mvn/version "RELEASE"} - metosin/reitit-core {:mvn/version "0.5.16"} + metosin/reitit-core {:mvn/version "0.5.18"} funcool/beicon {:mvn/version "2021.07.05-1"} } :aliases @@ -15,7 +15,7 @@ :dev {:extra-deps - {thheller/shadow-cljs {:mvn/version "2.17.8"}}} + {thheller/shadow-cljs {:mvn/version "2.19.8"}}} :shadow-cljs {:main-opts ["-m" "shadow.cljs.devtools.cli"]} diff --git a/exporter/package.json b/exporter/package.json index 83f70bfca1..9d2918379e 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -21,7 +21,7 @@ "xregexp": "^5.0.2" }, "devDependencies": { - "shadow-cljs": "^2.17.8", + "shadow-cljs": "^2.19.8", "source-map-support": "^0.5.21" } } diff --git a/exporter/src/app/browser.cljs b/exporter/src/app/browser.cljs index 588b127498..c885a3d453 100644 --- a/exporter/src/app/browser.cljs +++ b/exporter/src/app/browser.cljs @@ -9,10 +9,8 @@ ["generic-pool" :as gp] ["generic-pool/lib/errors" :as gpe] ["playwright" :as pw] - [app.common.data :as d] [app.common.exceptions :as ex] [app.common.logging :as l] - [app.common.uuid :as uuid] [app.config :as cf] [app.util.object :as obj] [promesa.core :as p])) diff --git a/exporter/src/app/config.cljs b/exporter/src/app/config.cljs index 6a312ab687..2d8729c6d1 100644 --- a/exporter/src/app/config.cljs +++ b/exporter/src/app/config.cljs @@ -7,13 +7,10 @@ (ns app.config (:refer-clojure :exclude [get]) (:require - ["fs" :as fs] ["process" :as process] - [app.common.exceptions :as ex] [app.common.data :as d] [app.common.spec :as us] [app.common.version :as v] - [app.common.uri :as u] [cljs.core :as c] [cljs.pprint] [cljs.spec.alpha :as s] diff --git a/exporter/src/app/core.cljs b/exporter/src/app/core.cljs index c3ebbd46a3..453ff720bc 100644 --- a/exporter/src/app/core.cljs +++ b/exporter/src/app/core.cljs @@ -8,17 +8,17 @@ (:require ["process" :as proc] [app.browser :as bwr] - [app.redis :as redis] [app.common.logging :as l] [app.config] [app.http :as http] + [app.redis :as redis] [promesa.core :as p])) (enable-console-print!) (l/initialize!) (defn start - [& args] + [& _] (l/info :msg "initializing") (p/do! (bwr/init) diff --git a/exporter/src/app/handlers.cljs b/exporter/src/app/handlers.cljs index a1a8496992..14d6a862ec 100644 --- a/exporter/src/app/handlers.cljs +++ b/exporter/src/app/handlers.cljs @@ -10,22 +10,18 @@ [app.common.exceptions :as ex] [app.common.logging :as l] [app.common.spec :as us] - [app.common.uri :as u] - [app.config :as cf] [app.handlers.export-frames :as export-frames] [app.handlers.export-shapes :as export-shapes] [app.handlers.resources :as resources] [app.util.transit :as t] [clojure.spec.alpha :as s] - [cuerdas.core :as str] - [promesa.core :as p] - [reitit.core :as r])) + [cuerdas.core :as str])) (l/set-level! :debug) (defn on-error [error exchange] - (let [{:keys [type message code] :as data} (ex-data error)] + (let [{:keys [type code] :as data} (ex-data error)] (cond (or (= :validation type) (= :assertion type)) diff --git a/exporter/src/app/handlers/export_frames.cljs b/exporter/src/app/handlers/export_frames.cljs index a8a4a0c85b..0c98167103 100644 --- a/exporter/src/app/handlers/export_frames.cljs +++ b/exporter/src/app/handlers/export_frames.cljs @@ -6,13 +6,10 @@ (ns app.handlers.export-frames (:require - ["path" :as path] [app.common.logging :as l] - [app.common.exceptions :as exc] [app.common.spec :as us] - [app.common.pprint :as pp] - [app.handlers.resources :as rsc] [app.handlers.export-shapes :refer [prepare-exports]] + [app.handlers.resources :as rsc] [app.redis :as redis] [app.renderer :as rd] [app.util.shell :as sh] @@ -41,7 +38,7 @@ :opt-un [::name])) (defn handler - [{:keys [:request/auth-token] :as exchange} {:keys [exports profile-id] :as params}] + [{:keys [:request/auth-token] :as exchange} {:keys [exports] :as params}] ;; NOTE: we need to have the `:type` prop because the exports ;; datastructure preparation uses it for creating the groups. (let [exports (-> (map #(assoc % :type :pdf :scale 1 :suffix "") exports) @@ -111,7 +108,8 @@ (-> (p/loop [exports (seq exports)] (when-let [export (first exports)] - (p/let [proc (rd/render export on-object)] + (p/do + (rd/render export on-object) (p/recur (rest exports))))) (p/then (fn [_] (deref result))) @@ -122,14 +120,14 @@ (-> (sh/stat (:path resource)) (p/then #(merge resource %))))) (p/catch on-error) - (p/finally (fn [result cause] + (p/finally (fn [_ cause] (when-not cause (on-complete))))))) (defn- join-pdf [file-id paths] - (p/let [tmpdir (sh/mktmpdir! "join-pdf") - path (path/join tmpdir (str/concat file-id ".pdf"))] + (p/let [prefix (str/concat "penpot.tmp.pdfunite." file-id ".") + path (sh/tempfile :prefix prefix :suffix ".pdf")] (sh/run-cmd! (str "pdfunite " (str/join " " paths) " " path)) path)) @@ -137,5 +135,4 @@ [{:keys [path] :as resource} output-path] (p/do (sh/move! output-path path) - (sh/rmdir! (path/dirname output-path)) resource)) diff --git a/exporter/src/app/handlers/export_shapes.cljs b/exporter/src/app/handlers/export_shapes.cljs index c6ac2f05de..02e7a824d8 100644 --- a/exporter/src/app/handlers/export_shapes.cljs +++ b/exporter/src/app/handlers/export_shapes.cljs @@ -6,9 +6,7 @@ (ns app.handlers.export-shapes (:require - ["path" :as path] [app.common.data :as d] - [app.common.exceptions :as exc] [app.common.logging :as l] [app.common.spec :as us] [app.handlers.resources :as rsc] @@ -102,8 +100,6 @@ total (count exports) topic (str profile-id) - to-delete (atom #{}) - on-progress (fn [{:keys [done]}] (when-not wait (let [data {:type :export-update @@ -137,16 +133,15 @@ :on-progress on-progress) append (fn [{:keys [filename path] :as object}] - (swap! to-delete conj path) (rsc/add-to-zip! zip path filename)) proc (-> (p/do (p/loop [exports (seq exports)] (when-let [export (first exports)] - (p/let [proc (rd/render export append)] + (p/do + (rd/render export append) (p/recur (rest exports))))) (.finalize zip)) - (p/then (fn [_] (p/run! #(sh/rmdir! (path/dirname %)) @to-delete))) (p/then (constantly resource)) (p/catch on-error)) ] diff --git a/exporter/src/app/handlers/resources.cljs b/exporter/src/app/handlers/resources.cljs index e027056101..c6729b08fa 100644 --- a/exporter/src/app/handlers/resources.cljs +++ b/exporter/src/app/handlers/resources.cljs @@ -9,20 +9,18 @@ (:require ["archiver" :as arc] ["fs" :as fs] - ["os" :as os] ["path" :as path] - [app.common.data :as d] [app.common.exceptions :as ex] [app.common.uuid :as uuid] - [app.util.shell :as sh] [app.util.mime :as mime] + [app.util.shell :as sh] [cljs.core :as c] [cuerdas.core :as str] [promesa.core :as p])) (defn- get-path [type id] - (path/join (os/tmpdir) (str/concat "exporter-resource." (c/name type) "." id))) + (path/join sh/tmpdir (str/concat "penpot.resource." (c/name type) "." id))) (defn create "Generates ephimeral resource object." @@ -49,7 +47,7 @@ "content-length" (:size stat)}})) (defn handler - [{:keys [:request/params response] :as exchange}] + [{:keys [:request/params] :as exchange}] (when-not (contains? params :id) (ex/raise :type :validation :code :missing-id)) diff --git a/exporter/src/app/http.cljs b/exporter/src/app/http.cljs index 1e3512ab7a..0621911690 100644 --- a/exporter/src/app/http.cljs +++ b/exporter/src/app/http.cljs @@ -12,7 +12,6 @@ ["raw-body" :as raw-body] ["stream" :as stream] [app.common.logging :as l] - [app.common.spec :as us] [app.common.transit :as t] [app.config :as cf] [app.handlers :as handlers] diff --git a/exporter/src/app/redis.cljs b/exporter/src/app/redis.cljs index 5d704bc861..10b095cf4c 100644 --- a/exporter/src/app/redis.cljs +++ b/exporter/src/app/redis.cljs @@ -28,7 +28,7 @@ (.on client "reconnect" (fn [ms] (l/warn :hint "reconnecting to redis" :ms ms))) (.on client "end" - (fn [ms] (l/warn :hint "client ended, no more connections will be attempted"))) + (fn [] (l/warn :hint "client ended, no more connections will be attempted"))) client)) (defn init diff --git a/exporter/src/app/renderer.cljs b/exporter/src/app/renderer.cljs index 42ab6c6ad5..63f1367f56 100644 --- a/exporter/src/app/renderer.cljs +++ b/exporter/src/app/renderer.cljs @@ -31,7 +31,7 @@ (s/def ::render-params (s/keys :req-un [::file-id ::page-id ::scale ::token ::type ::objects])) -(defn- render +(defn render [{:keys [type] :as params} on-object] (us/verify ::render-params params) (us/verify fn? on-object) diff --git a/exporter/src/app/renderer/bitmap.cljs b/exporter/src/app/renderer/bitmap.cljs index eaee733466..959da4877b 100644 --- a/exporter/src/app/renderer/bitmap.cljs +++ b/exporter/src/app/renderer/bitmap.cljs @@ -7,19 +7,12 @@ (ns app.renderer.bitmap "A bitmap renderer." (:require - ["path" :as path] [app.browser :as bw] - [app.common.data :as d] - [app.common.data.macros :as dm] - [app.common.exceptions :as ex] [app.common.logging :as l] - [app.common.pages :as cp] - [app.common.spec :as us] [app.common.uri :as u] [app.config :as cf] [app.util.mime :as mime] [app.util.shell :as sh] - [cljs.spec.alpha :as s] [cuerdas.core :as str] [promesa.core :as p])) @@ -36,9 +29,8 @@ :userAgent bw/default-user-agent}) (render-object [page {:keys [id] :as object}] - (p/let [tmpdir (sh/mktmpdir! "bitmap-render") - path (path/join tmpdir (str/concat id (mime/get-extension type))) - node (bw/select page (str/concat "#screenshot-" id))] + (p/let [path (sh/tempfile :prefix "penpot.tmp.render.bitmap." :suffix (mime/get-extension type)) + node (bw/select page (str/concat "#screenshot-" id))] (bw/wait-for node) (case type :png (bw/screenshot node {:omit-background? true :type type :path path}) diff --git a/exporter/src/app/renderer/pdf.cljs b/exporter/src/app/renderer/pdf.cljs index 124e545adc..14d9be40ae 100644 --- a/exporter/src/app/renderer/pdf.cljs +++ b/exporter/src/app/renderer/pdf.cljs @@ -7,18 +7,13 @@ (ns app.renderer.pdf "A pdf renderer." (:require - ["path" :as path] [app.browser :as bw] [app.common.data.macros :as dm] - [app.common.exceptions :as ex :include-macros true] [app.common.logging :as l] - [app.common.spec :as us] [app.common.uri :as u] [app.config :as cf] [app.util.mime :as mime] [app.util.shell :as sh] - [cuerdas.core :as str] - [cljs.spec.alpha :as s] [promesa.core :as p])) (defn render @@ -44,8 +39,7 @@ (render-object [page base-uri {:keys [id] :as object}] (p/let [uri (prepare-uri base-uri id) - tmp (sh/mktmpdir! "pdf-render") - path (path/join tmp (str/concat id (mime/get-extension type)))] + path (sh/tempfile :prefix "penpot.tmp.render.pdf." :suffix (mime/get-extension type))] (l/info :uri uri) (bw/nav! page uri) (p/let [dom (bw/select page (dm/str "#screenshot-" id))] @@ -58,8 +52,7 @@ (render [base-uri page] (p/loop [objects (seq objects)] (when-let [object (first objects)] - (p/let [uri (prepare-uri base-uri (:id object)) - path (render-object page base-uri object)] + (p/let [path (render-object page base-uri object)] (on-object (assoc object :path path)) (p/recur (rest objects))))))] diff --git a/exporter/src/app/renderer/svg.cljs b/exporter/src/app/renderer/svg.cljs index 36923bab99..39cd8d17c1 100644 --- a/exporter/src/app/renderer/svg.cljs +++ b/exporter/src/app/renderer/svg.cljs @@ -6,20 +6,14 @@ (ns app.renderer.svg (:require - ["path" :as path] ["xml-js" :as xml] [app.browser :as bw] [app.common.data :as d] - [app.common.data.macros :as dm] - [app.common.exceptions :as ex :include-macros true] [app.common.logging :as l] - [app.common.pages :as cp] - [app.common.spec :as us] [app.common.uri :as u] [app.config :as cf] [app.util.mime :as mime] [app.util.shell :as sh] - [cljs.spec.alpha :as s] [clojure.walk :as walk] [cuerdas.core :as str] [promesa.core :as p])) @@ -116,24 +110,20 @@ (defn render [{:keys [page-id file-id objects token scale type]} on-object] (letfn [(convert-to-ppm [pngpath] - (l/trace :fn :convert-to-ppm) - (let [basepath (path/dirname pngpath) - ppmpath (path/join basepath "origin.ppm")] + (let [ppmpath (str/concat pngpath "origin.ppm")] + (l/trace :fn :convert-to-ppm :path ppmpath) (-> (sh/run-cmd! (str "convert " pngpath " " ppmpath)) (p/then (constantly ppmpath))))) (trace-color-mask [pbmpath] (l/trace :fn :trace-color-mask :pbmpath pbmpath) - (let [basepath (path/dirname pbmpath) - basename (path/basename pbmpath ".pbm") - svgpath (path/join basepath (str basename ".svg"))] + (let [svgpath (str/concat pbmpath ".svg")] (-> (sh/run-cmd! (str "potrace --flat -b svg " pbmpath " -o " svgpath)) (p/then (constantly svgpath))))) (generate-color-layer [ppmpath color] (l/trace :fn :generate-color-layer :ppmpath ppmpath :color color) - (let [basepath (path/dirname ppmpath) - pbmpath (path/join basepath (str "mask-" (subs color 1) ".pbm"))] + (let [pbmpath (str/concat ppmpath ".mask-" (subs color 1) ".pbm")] (-> (sh/run-cmd! (str/format "ppmcolormask \"%s\" %s" color ppmpath)) (p/then (fn [stdout] (-> (sh/write-file! pbmpath stdout) @@ -188,7 +178,7 @@ (get-gradients [id mapping] (->> mapping - (filter (fn [[color data]] + (filter (fn [[_color data]] (= (get data "type") "gradient"))) (mapv (partial data->gradient-def id)))) @@ -231,7 +221,7 @@ elements (cond->> elements - (not (empty? gradient-defs)) + (seq gradient-defs) (into [{"type" "element" "name" "defs" "attributes" {} "elements" gradient-defs}]))] @@ -247,15 +237,14 @@ (trace-node [{:keys [data] :as node}] (l/trace :fn :trace-node) - (p/let [tdpath (sh/mktmpdir! "svgexport") - pngpath (path/join tdpath "origin.png") + (p/let [pngpath (sh/tempfile :prefix "penpot.tmp.render.svg.parse." + :suffix ".origin.png") _ (sh/write-file! pngpath data) ppmpath (convert-to-ppm pngpath) svgdata (convert-to-svg ppmpath node)] (-> node (dissoc :data) - (assoc :tempdir tdpath - :svgdata svgdata)))) + (assoc :svgdata svgdata)))) (extract-element-attrs [^js element] (let [^js attrs (.. element -attributes) @@ -289,17 +278,11 @@ shot (bw/screenshot text-node {:omit-background? true :type "png"})] [shot node])) - (clean-temp-data [{:keys [tempdir] :as node}] - (p/do! - (sh/rmdir! tempdir) - (dissoc node :tempdir))) - (extract-txt-node [page item] (-> (p/resolved item) (p/then (partial resolve-text-node page)) (p/then extract-single-node) - (p/then trace-node) - (p/then clean-temp-data))) + (p/then trace-node))) (extract-txt-nodes [page {:keys [id] :as objects}] (l/trace :fn :process-text-nodes) @@ -323,9 +306,8 @@ :userAgent bw/default-user-agent}) (render-object [page {:keys [id] :as object}] - (p/let [tmpdir (sh/mktmpdir! "svg-render") - path (path/join tmpdir (str/concat id (mime/get-extension type))) - node (bw/select page (str/concat "#screenshot-" id))] + (p/let [path (sh/tempfile :prefix "penpot.tmp.render.svg." :suffix (mime/get-extension type)) + node (bw/select page (str/concat "#screenshot-" id))] (bw/wait-for node) (p/let [xmldata (extract-svg page object) txtdata (extract-txt-nodes page object) @@ -359,7 +341,6 @@ (p/let [params {:file-id file-id :page-id page-id - :render-texts true :render-embed true :object-id (mapv :id objects) :route "objects"} diff --git a/exporter/src/app/util/mime.cljs b/exporter/src/app/util/mime.cljs index ed5a19a4c7..fef7e40534 100644 --- a/exporter/src/app/util/mime.cljs +++ b/exporter/src/app/util/mime.cljs @@ -8,7 +8,6 @@ "Mimetype and file extension helpers." (:refer-clojure :exclude [get]) (:require - [app.common.data :as d] [cljs.core :as c])) (defn get-extension @@ -20,7 +19,7 @@ :pdf ".pdf" :zip ".zip")) -(defn- get +(defn get [type] (case type :zip "application/zip" diff --git a/exporter/src/app/util/shell.cljs b/exporter/src/app/util/shell.cljs index 93b5333ed9..fcb36b9812 100644 --- a/exporter/src/app/util/shell.cljs +++ b/exporter/src/app/util/shell.cljs @@ -11,14 +11,49 @@ ["fs" :as fs] ["os" :as os] ["path" :as path] + [app.common.exceptions :as ex] [app.common.logging :as l] + [app.common.uuid :as uuid] + [cuerdas.core :as str] [promesa.core :as p])) (l/set-level! :trace) -(defn mktmpdir! - [prefix] - (.mkdtemp fs/promises (path/join (os/tmpdir) prefix))) +(def tempfile-minage (* 1000 60 60 1)) ;; 1h + +(def tmpdir + (let [path (path/join (os/tmpdir) "penpot")] + (when-not (fs/existsSync path) + (fs/mkdirSync path #js {:recursive true})) + path)) + + +(defn- schedule-deletion! + [path] + (letfn [(remote-tempfile [] + (when (fs/existsSync path) + (l/trace :hint "permanently remove tempfile" :path path) + (fs/rmSync path #js {:recursive true})))] + (l/trace :hint "schedule tempfile deletion" + :path path + :scheduled-at (.. (js/Date. (+ (js/Date.now) tempfile-minage)) toString)) + (js/setTimeout remote-tempfile tempfile-minage))) + +(defn tempfile + [& {:keys [prefix suffix] + :or {prefix "penpot." + suffix ".tmp"}}] + (loop [i 0] + (if (< i 1000) + (let [path (path/join tmpdir (str/concat prefix (uuid/next) "-" i suffix))] + (if (fs/existsSync path) + (recur (inc i)) + (do + (schedule-deletion! path) + path))) + (ex/raise :type :internal + :code :unable-to-locate-temporal-file + :hint "unable to find a tempfile candidate")))) (defn move! [origin-path dest-path] @@ -50,7 +85,7 @@ (fn [resolve reject] (l/trace :fn :run-cmd :cmd cmd) (proc/exec cmd #js {:encoding "buffer"} - (fn [error stdout stderr] + (fn [error stdout _stderr] ;; (l/trace :fn :run-cmd :stdout stdout) (if error (reject error) diff --git a/exporter/yarn.lock b/exporter/yarn.lock index a33887816b..625173c4f3 100644 --- a/exporter/yarn.lock +++ b/exporter/yarn.lock @@ -1098,10 +1098,10 @@ shadow-cljs-jar@1.3.2: resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b" integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg== -shadow-cljs@^2.17.8: - version "2.17.8" - resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.17.8.tgz#7ee27ccf7585991f6c042f66f07f17582c0b70af" - integrity sha512-O39cLA7ukEh+OeH1yZlaWjGFinPOsDD87TetAWPe1QBD9TZQ0Ail+2ovaXeAyZpJ+6Z37joFfival+LNuCgsmQ== +shadow-cljs@^2.19.8: + version "2.19.8" + resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.19.8.tgz#1ce96cab3e4903bed8d401ffbe88b8939f5454d3" + integrity sha512-6qek3mcAP0hrnC5FxrTebBrgLGpOuhlnp06vdxp6g0M5Gl6w2Y0hzSwa1s2K8fMOkzE4/ciQor75b2y64INgaw== dependencies: node-libs-browser "^2.2.1" readline-sync "^1.4.7" diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000000..eb8da3b690 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,2 @@ +--- +printWidth: 110 \ No newline at end of file diff --git a/frontend/deps.edn b/frontend/deps.edn index a1d592e326..24e203ac30 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -5,7 +5,7 @@ org.clojure/clojure {:mvn/version "1.10.3"} binaryage/devtools {:mvn/version "RELEASE"} - metosin/reitit-core {:mvn/version "0.5.17"} + metosin/reitit-core {:mvn/version "0.5.18"} funcool/beicon {:mvn/version "2021.07.05-1"} funcool/okulary {:mvn/version "2022.04.11-16"} @@ -13,9 +13,9 @@ funcool/rumext {:mvn/version "2022.04.19-148"} funcool/tubax {:mvn/version "2021.05.20-0"} - instaparse/instaparse {:mvn/version "1.4.10"} - garden/garden {:mvn/version "1.3.10"} - + instaparse/instaparse {:mvn/version "1.4.12"} + garden/garden {:git/url "https://github.com/noprompt/garden" + :git/sha "05590ecb5f6fa670856f3d1ab400aa4961047480"} } :aliases @@ -32,9 +32,9 @@ :dev {:extra-paths ["dev"] :extra-deps - {thheller/shadow-cljs {:mvn/version "2.17.8"} + {thheller/shadow-cljs {:mvn/version "2.19.8"} org.clojure/tools.namespace {:mvn/version "RELEASE"} - cider/cider-nrepl {:mvn/version "0.28.3"}}} + cider/cider-nrepl {:mvn/version "0.28.4"}}} :shadow-cljs {:main-opts ["-m" "shadow.cljs.devtools.cli"]} diff --git a/frontend/package.json b/frontend/package.json index 104be46654..d3e294741a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -47,8 +47,8 @@ "postcss-clean": "^1.2.2", "prettier": "^2.6.1", "rimraf": "^3.0.0", - "sass": "^1.49.9", - "shadow-cljs": "2.17.8" + "sass": "^1.53.0", + "shadow-cljs": "2.19.8" }, "dependencies": { "@sentry/browser": "^6.17.4", diff --git a/frontend/resources/fonts/Vazirmatn-Black.ttf b/frontend/resources/fonts/Vazirmatn-Black.ttf new file mode 100644 index 0000000000..4b9bd66179 Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Black.ttf differ diff --git a/frontend/resources/fonts/Vazirmatn-Black.woff2 b/frontend/resources/fonts/Vazirmatn-Black.woff2 new file mode 100644 index 0000000000..f08cace8de Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Black.woff2 differ diff --git a/frontend/resources/fonts/Vazirmatn-Bold.ttf b/frontend/resources/fonts/Vazirmatn-Bold.ttf new file mode 100644 index 0000000000..efa9b095da Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Bold.ttf differ diff --git a/frontend/resources/fonts/Vazirmatn-Bold.woff2 b/frontend/resources/fonts/Vazirmatn-Bold.woff2 new file mode 100644 index 0000000000..65b427f86e Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Bold.woff2 differ diff --git a/frontend/resources/fonts/Vazirmatn-ExtraBold.ttf b/frontend/resources/fonts/Vazirmatn-ExtraBold.ttf new file mode 100644 index 0000000000..380bd15866 Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-ExtraBold.ttf differ diff --git a/frontend/resources/fonts/Vazirmatn-ExtraBold.woff2 b/frontend/resources/fonts/Vazirmatn-ExtraBold.woff2 new file mode 100644 index 0000000000..c074e70fcd Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-ExtraBold.woff2 differ diff --git a/frontend/resources/fonts/Vazirmatn-ExtraLight.ttf b/frontend/resources/fonts/Vazirmatn-ExtraLight.ttf new file mode 100644 index 0000000000..b7b947e6ac Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-ExtraLight.ttf differ diff --git a/frontend/resources/fonts/Vazirmatn-ExtraLight.woff2 b/frontend/resources/fonts/Vazirmatn-ExtraLight.woff2 new file mode 100644 index 0000000000..997dea07b7 Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-ExtraLight.woff2 differ diff --git a/frontend/resources/fonts/Vazirmatn-Light.ttf b/frontend/resources/fonts/Vazirmatn-Light.ttf new file mode 100644 index 0000000000..2dfd5c35ed Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Light.ttf differ diff --git a/frontend/resources/fonts/Vazirmatn-Light.woff2 b/frontend/resources/fonts/Vazirmatn-Light.woff2 new file mode 100644 index 0000000000..d154722a60 Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Light.woff2 differ diff --git a/frontend/resources/fonts/Vazirmatn-Medium.ttf b/frontend/resources/fonts/Vazirmatn-Medium.ttf new file mode 100644 index 0000000000..1e08dd54d9 Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Medium.ttf differ diff --git a/frontend/resources/fonts/Vazirmatn-Medium.woff2 b/frontend/resources/fonts/Vazirmatn-Medium.woff2 new file mode 100644 index 0000000000..495af75762 Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Medium.woff2 differ diff --git a/frontend/resources/fonts/Vazirmatn-Regular.ttf b/frontend/resources/fonts/Vazirmatn-Regular.ttf new file mode 100644 index 0000000000..64e4a81895 Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Regular.ttf differ diff --git a/frontend/resources/fonts/Vazirmatn-Regular.woff2 b/frontend/resources/fonts/Vazirmatn-Regular.woff2 new file mode 100644 index 0000000000..c9824c872b Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Regular.woff2 differ diff --git a/frontend/resources/fonts/Vazirmatn-SemiBold.ttf b/frontend/resources/fonts/Vazirmatn-SemiBold.ttf new file mode 100644 index 0000000000..6b3842aca6 Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-SemiBold.ttf differ diff --git a/frontend/resources/fonts/Vazirmatn-SemiBold.woff2 b/frontend/resources/fonts/Vazirmatn-SemiBold.woff2 new file mode 100644 index 0000000000..53016415ab Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-SemiBold.woff2 differ diff --git a/frontend/resources/fonts/Vazirmatn-Thin.ttf b/frontend/resources/fonts/Vazirmatn-Thin.ttf new file mode 100644 index 0000000000..b7a7d233e2 Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Thin.ttf differ diff --git a/frontend/resources/fonts/Vazirmatn-Thin.woff2 b/frontend/resources/fonts/Vazirmatn-Thin.woff2 new file mode 100644 index 0000000000..b7df278265 Binary files /dev/null and b/frontend/resources/fonts/Vazirmatn-Thin.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-Black.eot b/frontend/resources/fonts/WorkSans-Black.eot deleted file mode 100644 index 53bb9c861a..0000000000 Binary files a/frontend/resources/fonts/WorkSans-Black.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-Black.svg b/frontend/resources/fonts/WorkSans-Black.svg deleted file mode 100644 index 766a534d5e..0000000000 --- a/frontend/resources/fonts/WorkSans-Black.svg +++ /dev/null @@ -1,16248 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-BlackItalic.eot b/frontend/resources/fonts/WorkSans-BlackItalic.eot deleted file mode 100644 index de216159ec..0000000000 Binary files a/frontend/resources/fonts/WorkSans-BlackItalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-BlackItalic.svg b/frontend/resources/fonts/WorkSans-BlackItalic.svg deleted file mode 100644 index fed596c151..0000000000 --- a/frontend/resources/fonts/WorkSans-BlackItalic.svg +++ /dev/null @@ -1,16277 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-Bold.eot b/frontend/resources/fonts/WorkSans-Bold.eot deleted file mode 100644 index 230d453b99..0000000000 Binary files a/frontend/resources/fonts/WorkSans-Bold.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-Bold.svg b/frontend/resources/fonts/WorkSans-Bold.svg deleted file mode 100644 index 780011b827..0000000000 --- a/frontend/resources/fonts/WorkSans-Bold.svg +++ /dev/null @@ -1,25069 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-BoldItalic.eot b/frontend/resources/fonts/WorkSans-BoldItalic.eot deleted file mode 100644 index aab9667dcb..0000000000 Binary files a/frontend/resources/fonts/WorkSans-BoldItalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-BoldItalic.svg b/frontend/resources/fonts/WorkSans-BoldItalic.svg deleted file mode 100644 index e339f34713..0000000000 --- a/frontend/resources/fonts/WorkSans-BoldItalic.svg +++ /dev/null @@ -1,24489 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-ExtraBold.eot b/frontend/resources/fonts/WorkSans-ExtraBold.eot deleted file mode 100644 index 8b733670f8..0000000000 Binary files a/frontend/resources/fonts/WorkSans-ExtraBold.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-ExtraBold.svg b/frontend/resources/fonts/WorkSans-ExtraBold.svg deleted file mode 100644 index 55e6f50e4d..0000000000 --- a/frontend/resources/fonts/WorkSans-ExtraBold.svg +++ /dev/null @@ -1,25065 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-ExtraBoldItalic.eot b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.eot deleted file mode 100644 index 4b032160f9..0000000000 Binary files a/frontend/resources/fonts/WorkSans-ExtraBoldItalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-ExtraBoldItalic.svg b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.svg deleted file mode 100644 index 4ca2b3d807..0000000000 --- a/frontend/resources/fonts/WorkSans-ExtraBoldItalic.svg +++ /dev/null @@ -1,24462 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-ExtraLight.eot b/frontend/resources/fonts/WorkSans-ExtraLight.eot deleted file mode 100644 index 5830248d7b..0000000000 Binary files a/frontend/resources/fonts/WorkSans-ExtraLight.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-ExtraLight.svg b/frontend/resources/fonts/WorkSans-ExtraLight.svg deleted file mode 100644 index cf69107509..0000000000 --- a/frontend/resources/fonts/WorkSans-ExtraLight.svg +++ /dev/null @@ -1,25774 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-ExtraLightItalic.eot b/frontend/resources/fonts/WorkSans-ExtraLightItalic.eot deleted file mode 100644 index 49762c9733..0000000000 Binary files a/frontend/resources/fonts/WorkSans-ExtraLightItalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-ExtraLightItalic.svg b/frontend/resources/fonts/WorkSans-ExtraLightItalic.svg deleted file mode 100644 index b57d337cdc..0000000000 --- a/frontend/resources/fonts/WorkSans-ExtraLightItalic.svg +++ /dev/null @@ -1,25162 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-Italic.eot b/frontend/resources/fonts/WorkSans-Italic.eot deleted file mode 100644 index ac5da85c3a..0000000000 Binary files a/frontend/resources/fonts/WorkSans-Italic.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-Italic.svg b/frontend/resources/fonts/WorkSans-Italic.svg deleted file mode 100644 index 6b80809b58..0000000000 --- a/frontend/resources/fonts/WorkSans-Italic.svg +++ /dev/null @@ -1,21510 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-Light.eot b/frontend/resources/fonts/WorkSans-Light.eot deleted file mode 100644 index 1fb80b6584..0000000000 Binary files a/frontend/resources/fonts/WorkSans-Light.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-Light.svg b/frontend/resources/fonts/WorkSans-Light.svg deleted file mode 100644 index 2f8bd2d709..0000000000 --- a/frontend/resources/fonts/WorkSans-Light.svg +++ /dev/null @@ -1,25793 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-LightItalic.eot b/frontend/resources/fonts/WorkSans-LightItalic.eot deleted file mode 100644 index a2fef572a8..0000000000 Binary files a/frontend/resources/fonts/WorkSans-LightItalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-LightItalic.svg b/frontend/resources/fonts/WorkSans-LightItalic.svg deleted file mode 100644 index d730e13d72..0000000000 --- a/frontend/resources/fonts/WorkSans-LightItalic.svg +++ /dev/null @@ -1,25177 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-Medium.eot b/frontend/resources/fonts/WorkSans-Medium.eot deleted file mode 100644 index 67eda94082..0000000000 Binary files a/frontend/resources/fonts/WorkSans-Medium.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-Medium.svg b/frontend/resources/fonts/WorkSans-Medium.svg deleted file mode 100644 index 2d9fdb520f..0000000000 --- a/frontend/resources/fonts/WorkSans-Medium.svg +++ /dev/null @@ -1,25050 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-MediumItalic.eot b/frontend/resources/fonts/WorkSans-MediumItalic.eot deleted file mode 100644 index 43b2002f08..0000000000 Binary files a/frontend/resources/fonts/WorkSans-MediumItalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-MediumItalic.svg b/frontend/resources/fonts/WorkSans-MediumItalic.svg deleted file mode 100644 index 64377980b3..0000000000 --- a/frontend/resources/fonts/WorkSans-MediumItalic.svg +++ /dev/null @@ -1,24313 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-Regular.eot b/frontend/resources/fonts/WorkSans-Regular.eot deleted file mode 100644 index e3a99d1262..0000000000 Binary files a/frontend/resources/fonts/WorkSans-Regular.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-Regular.svg b/frontend/resources/fonts/WorkSans-Regular.svg deleted file mode 100644 index df6da12323..0000000000 --- a/frontend/resources/fonts/WorkSans-Regular.svg +++ /dev/null @@ -1,21954 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-SemiBold.eot b/frontend/resources/fonts/WorkSans-SemiBold.eot deleted file mode 100644 index 92ca2637eb..0000000000 Binary files a/frontend/resources/fonts/WorkSans-SemiBold.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-SemiBold.svg b/frontend/resources/fonts/WorkSans-SemiBold.svg deleted file mode 100644 index 5cad7ade8c..0000000000 --- a/frontend/resources/fonts/WorkSans-SemiBold.svg +++ /dev/null @@ -1,25059 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-SemiBoldItalic.eot b/frontend/resources/fonts/WorkSans-SemiBoldItalic.eot deleted file mode 100644 index e59f0fcc6a..0000000000 Binary files a/frontend/resources/fonts/WorkSans-SemiBoldItalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-SemiBoldItalic.svg b/frontend/resources/fonts/WorkSans-SemiBoldItalic.svg deleted file mode 100644 index de7a791c33..0000000000 --- a/frontend/resources/fonts/WorkSans-SemiBoldItalic.svg +++ /dev/null @@ -1,24400 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-Thin.eot b/frontend/resources/fonts/WorkSans-Thin.eot deleted file mode 100644 index 547ec1299e..0000000000 Binary files a/frontend/resources/fonts/WorkSans-Thin.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-Thin.svg b/frontend/resources/fonts/WorkSans-Thin.svg deleted file mode 100644 index b158ae3b66..0000000000 --- a/frontend/resources/fonts/WorkSans-Thin.svg +++ /dev/null @@ -1,23764 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/WorkSans-ThinItalic.eot b/frontend/resources/fonts/WorkSans-ThinItalic.eot deleted file mode 100644 index 5c79146567..0000000000 Binary files a/frontend/resources/fonts/WorkSans-ThinItalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/WorkSans-ThinItalic.svg b/frontend/resources/fonts/WorkSans-ThinItalic.svg deleted file mode 100644 index 735b630f30..0000000000 --- a/frontend/resources/fonts/WorkSans-ThinItalic.svg +++ /dev/null @@ -1,23256 +0,0 @@ - - - - -Created by FontForge 20170731 at Wed Feb 26 09:24:02 2020 - By Aleksey,,, -Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/resources/fonts/sourcesanspro-black.eot b/frontend/resources/fonts/sourcesanspro-black.eot deleted file mode 100644 index e2781489ff..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-black.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-black.svg b/frontend/resources/fonts/sourcesanspro-black.svg deleted file mode 100644 index 0831400967..0000000000 --- a/frontend/resources/fonts/sourcesanspro-black.svg +++ /dev/null @@ -1,921 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/sourcesanspro-blackitalic.eot b/frontend/resources/fonts/sourcesanspro-blackitalic.eot deleted file mode 100644 index 397e1c8f64..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-blackitalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-blackitalic.svg b/frontend/resources/fonts/sourcesanspro-blackitalic.svg deleted file mode 100644 index 4c1932e07c..0000000000 --- a/frontend/resources/fonts/sourcesanspro-blackitalic.svg +++ /dev/null @@ -1,822 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/sourcesanspro-bold.eot b/frontend/resources/fonts/sourcesanspro-bold.eot deleted file mode 100644 index c14db153bf..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-bold.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-bold.svg b/frontend/resources/fonts/sourcesanspro-bold.svg deleted file mode 100644 index e1a5e150b0..0000000000 --- a/frontend/resources/fonts/sourcesanspro-bold.svg +++ /dev/null @@ -1,965 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/sourcesanspro-bolditalic.eot b/frontend/resources/fonts/sourcesanspro-bolditalic.eot deleted file mode 100644 index db2164bfba..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-bolditalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-bolditalic.svg b/frontend/resources/fonts/sourcesanspro-bolditalic.svg deleted file mode 100644 index f7beb27abb..0000000000 --- a/frontend/resources/fonts/sourcesanspro-bolditalic.svg +++ /dev/null @@ -1,841 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/sourcesanspro-extralight.eot b/frontend/resources/fonts/sourcesanspro-extralight.eot deleted file mode 100644 index e6f486d2bb..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-extralight.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-extralight.svg b/frontend/resources/fonts/sourcesanspro-extralight.svg deleted file mode 100644 index 270a6f50ca..0000000000 --- a/frontend/resources/fonts/sourcesanspro-extralight.svg +++ /dev/null @@ -1,916 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/sourcesanspro-extralightitalic.eot b/frontend/resources/fonts/sourcesanspro-extralightitalic.eot deleted file mode 100644 index e141d4d60f..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-extralightitalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-extralightitalic.svg b/frontend/resources/fonts/sourcesanspro-extralightitalic.svg deleted file mode 100644 index f3f2cd82be..0000000000 --- a/frontend/resources/fonts/sourcesanspro-extralightitalic.svg +++ /dev/null @@ -1,838 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/sourcesanspro-italic.eot b/frontend/resources/fonts/sourcesanspro-italic.eot deleted file mode 100644 index 35e8462b7d..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-italic.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-italic.svg b/frontend/resources/fonts/sourcesanspro-italic.svg deleted file mode 100644 index 17cb29bd47..0000000000 --- a/frontend/resources/fonts/sourcesanspro-italic.svg +++ /dev/null @@ -1,853 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/sourcesanspro-light.eot b/frontend/resources/fonts/sourcesanspro-light.eot deleted file mode 100644 index 06ea0287b7..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-light.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-light.svg b/frontend/resources/fonts/sourcesanspro-light.svg deleted file mode 100644 index 0ced8aa042..0000000000 --- a/frontend/resources/fonts/sourcesanspro-light.svg +++ /dev/null @@ -1,915 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/sourcesanspro-lightitalic.eot b/frontend/resources/fonts/sourcesanspro-lightitalic.eot deleted file mode 100644 index 63718fb30f..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-lightitalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-lightitalic.svg b/frontend/resources/fonts/sourcesanspro-lightitalic.svg deleted file mode 100644 index 97ada87ecb..0000000000 --- a/frontend/resources/fonts/sourcesanspro-lightitalic.svg +++ /dev/null @@ -1,840 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/sourcesanspro-regular.eot b/frontend/resources/fonts/sourcesanspro-regular.eot deleted file mode 100644 index 6260f8e2ef..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-regular.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-regular.svg b/frontend/resources/fonts/sourcesanspro-regular.svg deleted file mode 100644 index de5d8bab35..0000000000 --- a/frontend/resources/fonts/sourcesanspro-regular.svg +++ /dev/null @@ -1,989 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/sourcesanspro-semibold.eot b/frontend/resources/fonts/sourcesanspro-semibold.eot deleted file mode 100644 index a03a1fa1f9..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-semibold.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-semibold.svg b/frontend/resources/fonts/sourcesanspro-semibold.svg deleted file mode 100644 index a9ea237663..0000000000 --- a/frontend/resources/fonts/sourcesanspro-semibold.svg +++ /dev/null @@ -1,979 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/sourcesanspro-semibolditalic.eot b/frontend/resources/fonts/sourcesanspro-semibolditalic.eot deleted file mode 100644 index cd2dec7571..0000000000 Binary files a/frontend/resources/fonts/sourcesanspro-semibolditalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/sourcesanspro-semibolditalic.svg b/frontend/resources/fonts/sourcesanspro-semibolditalic.svg deleted file mode 100644 index 98de8eff9d..0000000000 --- a/frontend/resources/fonts/sourcesanspro-semibolditalic.svg +++ /dev/null @@ -1,848 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/images/features/1.15-comments.gif b/frontend/resources/images/features/1.15-comments.gif new file mode 100644 index 0000000000..9f7eaa3ee2 Binary files /dev/null and b/frontend/resources/images/features/1.15-comments.gif differ diff --git a/frontend/resources/images/features/1.15-nested-boards.gif b/frontend/resources/images/features/1.15-nested-boards.gif new file mode 100644 index 0000000000..bf10218a7c Binary files /dev/null and b/frontend/resources/images/features/1.15-nested-boards.gif differ diff --git a/frontend/resources/images/features/1.15-share.gif b/frontend/resources/images/features/1.15-share.gif new file mode 100644 index 0000000000..7a4339efe7 Binary files /dev/null and b/frontend/resources/images/features/1.15-share.gif differ diff --git a/frontend/resources/images/features/1.15-view-mode.gif b/frontend/resources/images/features/1.15-view-mode.gif new file mode 100644 index 0000000000..a24922cd91 Binary files /dev/null and b/frontend/resources/images/features/1.15-view-mode.gif differ diff --git a/frontend/resources/images/icons/auto-direction.svg b/frontend/resources/images/icons/auto-direction.svg new file mode 100644 index 0000000000..d002174b69 --- /dev/null +++ b/frontend/resources/images/icons/auto-direction.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-fill.svg b/frontend/resources/images/icons/auto-fill.svg new file mode 100644 index 0000000000..344e188d72 --- /dev/null +++ b/frontend/resources/images/icons/auto-fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-fix-layout.svg b/frontend/resources/images/icons/auto-fix-layout.svg new file mode 100644 index 0000000000..f2be41986b --- /dev/null +++ b/frontend/resources/images/icons/auto-fix-layout.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-gap.svg b/frontend/resources/images/icons/auto-gap.svg new file mode 100644 index 0000000000..307740492a --- /dev/null +++ b/frontend/resources/images/icons/auto-gap.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-hug.svg b/frontend/resources/images/icons/auto-hug.svg new file mode 100644 index 0000000000..f72fbd3c88 --- /dev/null +++ b/frontend/resources/images/icons/auto-hug.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-margin-side.svg b/frontend/resources/images/icons/auto-margin-side.svg new file mode 100644 index 0000000000..087ae82504 --- /dev/null +++ b/frontend/resources/images/icons/auto-margin-side.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-margin.svg b/frontend/resources/images/icons/auto-margin.svg new file mode 100644 index 0000000000..4777ce3b1f --- /dev/null +++ b/frontend/resources/images/icons/auto-margin.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-padding-side.svg b/frontend/resources/images/icons/auto-padding-side.svg new file mode 100644 index 0000000000..d4c56746b1 --- /dev/null +++ b/frontend/resources/images/icons/auto-padding-side.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-padding.svg b/frontend/resources/images/icons/auto-padding.svg new file mode 100644 index 0000000000..e546d0631c --- /dev/null +++ b/frontend/resources/images/icons/auto-padding.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-row-column.svg b/frontend/resources/images/icons/auto-row-column.svg new file mode 100644 index 0000000000..9023c8fcf0 --- /dev/null +++ b/frontend/resources/images/icons/auto-row-column.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/go-next.svg b/frontend/resources/images/icons/go-next.svg new file mode 100644 index 0000000000..ed48fbcde5 --- /dev/null +++ b/frontend/resources/images/icons/go-next.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/icons/go-prev.svg b/frontend/resources/images/icons/go-prev.svg new file mode 100644 index 0000000000..6737a43abd --- /dev/null +++ b/frontend/resources/images/icons/go-prev.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/icons/reset.svg b/frontend/resources/images/icons/reset.svg new file mode 100644 index 0000000000..59dd005eae --- /dev/null +++ b/frontend/resources/images/icons/reset.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/icons/space-around.svg b/frontend/resources/images/icons/space-around.svg new file mode 100644 index 0000000000..94e6e8de4c --- /dev/null +++ b/frontend/resources/images/icons/space-around.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/space-between.svg b/frontend/resources/images/icons/space-between.svg new file mode 100644 index 0000000000..639040b0b4 --- /dev/null +++ b/frontend/resources/images/icons/space-between.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/styles/common/base.scss b/frontend/resources/styles/common/base.scss index ebd5568d1e..f49c000644 100644 --- a/frontend/resources/styles/common/base.scss +++ b/frontend/resources/styles/common/base.scss @@ -5,12 +5,16 @@ // Copyright (c) 2015-2016 Andrey Antukh // Copyright (c) 2015-2016 Juan de la Cruz +:root { + --font-family: "worksans", sans-serif; +} + body { background-color: lighten($color-gray-10, 5%); color: $color-gray-20; display: flex; flex-direction: column; - font-family: "worksans", sans-serif; + font-family: var(--font-family); width: 100vw; height: 100vh; overflow: hidden; diff --git a/frontend/resources/styles/common/dependencies/animations.scss b/frontend/resources/styles/common/dependencies/animations.scss index 2747704117..8b9a0fb031 100644 --- a/frontend/resources/styles/common/dependencies/animations.scss +++ b/frontend/resources/styles/common/dependencies/animations.scss @@ -1809,19 +1809,15 @@ } 40% { - -webkit-transform: perspective(400px) translate3d(0, 0, 150px) - rotate3d(0, 1, 0, -190deg); - transform: perspective(400px) translate3d(0, 0, 150px) - rotate3d(0, 1, 0, -190deg); + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 50% { - -webkit-transform: perspective(400px) translate3d(0, 0, 150px) - rotate3d(0, 1, 0, -170deg); - transform: perspective(400px) translate3d(0, 0, 150px) - rotate3d(0, 1, 0, -170deg); + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } @@ -1850,19 +1846,15 @@ } 40% { - -webkit-transform: perspective(400px) translate3d(0, 0, 150px) - rotate3d(0, 1, 0, -190deg); - transform: perspective(400px) translate3d(0, 0, 150px) - rotate3d(0, 1, 0, -190deg); + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 50% { - -webkit-transform: perspective(400px) translate3d(0, 0, 150px) - rotate3d(0, 1, 0, -170deg); - transform: perspective(400px) translate3d(0, 0, 150px) - rotate3d(0, 1, 0, -170deg); + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } diff --git a/frontend/resources/styles/common/dependencies/colors.scss b/frontend/resources/styles/common/dependencies/colors.scss index 8ca5bbb8a7..eb58034ef5 100644 --- a/frontend/resources/styles/common/dependencies/colors.scss +++ b/frontend/resources/styles/common/dependencies/colors.scss @@ -46,138 +46,42 @@ $mix-percentage-lighter: 20%; $mix-percentage-lightest: 10%; // Lighter colors -$color-success-light: mix( - $color-success, - $color-white, - $mix-percentage-light -); //#79cf7d -$color-success-lighter: mix( - $color-success, - $color-white, - $mix-percentage-lighter -); //#def3de +$color-success-light: mix($color-success, $color-white, $mix-percentage-light); //#79cf7d +$color-success-lighter: mix($color-success, $color-white, $mix-percentage-lighter); //#def3de -$color-complete-light: mix( - $color-complete, - $color-white, - $mix-percentage-light -); //#b7add1 -$color-complete-lighter: mix( - $color-complete, - $color-white, - $mix-percentage-lighter -); //#edebf4 +$color-complete-light: mix($color-complete, $color-white, $mix-percentage-light); //#b7add1 +$color-complete-lighter: mix($color-complete, $color-white, $mix-percentage-lighter); //#edebf4 -$color-primary-light: mix( - $color-primary, - $color-white, - $mix-percentage-light -); //#5af2c6 -$color-primary-lighter: mix( - $color-primary, - $color-white, - $mix-percentage-lighter -); //#d6fcf1 +$color-primary-light: mix($color-primary, $color-white, $mix-percentage-light); //#5af2c6 +$color-primary-lighter: mix($color-primary, $color-white, $mix-percentage-lighter); //#d6fcf1 -$color-warning-light: mix( - $color-warning, - $color-white, - $mix-percentage-light -); //#fda035 -$color-warning-lighter: mix( - $color-warning, - $color-white, - $mix-percentage-lighter -); //#fee7cc; +$color-warning-light: mix($color-warning, $color-white, $mix-percentage-light); //#fda035 +$color-warning-lighter: mix($color-warning, $color-white, $mix-percentage-lighter); //#fee7cc; -$color-danger-light: mix( - $color-danger, - $color-white, - $mix-percentage-light -); //#eb7569 -$color-danger-lighter: mix( - $color-danger, - $color-white, - $mix-percentage-lighter -); //#fadcda +$color-danger-light: mix($color-danger, $color-white, $mix-percentage-light); //#eb7569 +$color-danger-lighter: mix($color-danger, $color-white, $mix-percentage-lighter); //#fadcda -$color-info-light: mix( - $color-info, - $color-white, - $mix-percentage-light -); //#7ac7e8 -$color-info-lighter: mix( - $color-info, - $color-white, - $mix-percentage-lighter -); //#def1f9; +$color-info-light: mix($color-info, $color-white, $mix-percentage-light); //#7ac7e8 +$color-info-lighter: mix($color-info, $color-white, $mix-percentage-lighter); //#def1f9; // Darker colors -$color-success-dark: mix( - $color-success, - $color-black, - $mix-percentage-dark -); //#479e4b; -$color-success-darker: mix( - $color-success, - $color-black, - $mix-percentage-darker -); // #357537; +$color-success-dark: mix($color-success, $color-black, $mix-percentage-dark); //#479e4b; +$color-success-darker: mix($color-success, $color-black, $mix-percentage-darker); // #357537; -$color-complete-dark: mix( - $color-complete, - $color-black, - $mix-percentage-dark -); //#867ca0 -$color-complete-darker: mix( - $color-complete, - $color-black, - $mix-percentage-darker -); //#635c77 +$color-complete-dark: mix($color-complete, $color-black, $mix-percentage-dark); //#867ca0 +$color-complete-darker: mix($color-complete, $color-black, $mix-percentage-darker); //#635c77 -$color-primary-dark: mix( - $color-primary, - $color-black, - $mix-percentage-dark -); //#28c295; -$color-primary-darker: mix( - $color-primary, - $color-black, - $mix-percentage-darker -); // #1d8f6e +$color-primary-dark: mix($color-primary, $color-black, $mix-percentage-dark); //#28c295; +$color-primary-darker: mix($color-primary, $color-black, $mix-percentage-darker); // #1d8f6e -$color-warning-dark: mix( - $color-warning, - $color-black, - $mix-percentage-dark -); // #cc6e02; -$color-warning-darker: mix( - $color-warning, - $color-black, - $mix-percentage-darker -); //#975201 +$color-warning-dark: mix($color-warning, $color-black, $mix-percentage-dark); // #cc6e02; +$color-warning-darker: mix($color-warning, $color-black, $mix-percentage-darker); //#975201 -$color-danger-dark: mix( - $color-danger, - $color-black, - $mix-percentage-dark -); //#ba4237 -$color-danger-darker: mix( - $color-danger, - $color-black, - $mix-percentage-darker -); // #8a3129; +$color-danger-dark: mix($color-danger, $color-black, $mix-percentage-dark); //#ba4237 +$color-danger-darker: mix($color-danger, $color-black, $mix-percentage-darker); // #8a3129; -$color-info-dark: mix( - $color-info, - $color-black, - $mix-percentage-dark -); // #4896b7 -$color-info-darker: mix( - $color-info, - $color-black, - $mix-percentage-darker -); // #356f88; +$color-info-dark: mix($color-info, $color-black, $mix-percentage-dark); // #4896b7 +$color-info-darker: mix($color-info, $color-black, $mix-percentage-darker); // #356f88; // bg transparent $color-dark-bg: rgba(0, 0, 0, 0.4); diff --git a/frontend/resources/styles/common/dependencies/fonts.scss b/frontend/resources/styles/common/dependencies/fonts.scss index 316c531fe4..fbf45fed74 100644 --- a/frontend/resources/styles/common/dependencies/fonts.scss +++ b/frontend/resources/styles/common/dependencies/fonts.scss @@ -59,24 +59,25 @@ $title-lh-sm: 1.15; // Source Sans Pro @include font-face("sourcesanspro", "sourcesanspro-extralight", "200"); -@include font-face( - "sourcesanspro", - "sourcesanspro-extralightitalic", - "200", - italic -); +@include font-face("sourcesanspro", "sourcesanspro-extralightitalic", "200", italic); @include font-face("sourcesanspro", "sourcesanspro-light", "300"); @include font-face("sourcesanspro", "sourcesanspro-lightitalic", "300", italic); @include font-face("sourcesanspro", "sourcesanspro-regular", normal); @include font-face("sourcesanspro", "sourcesanspro-italic", normal, italic); @include font-face("sourcesanspro", "sourcesanspro-semibold", "600"); -@include font-face( - "sourcesanspro", - "sourcesanspro-semibolditalic", - "600", - italic -); +@include font-face("sourcesanspro", "sourcesanspro-semibolditalic", "600", italic); @include font-face("sourcesanspro", "sourcesanspro-bold", bold); @include font-face("sourcesanspro", "sourcesanspro-bolditalic", bold, italic); @include font-face("sourcesanspro", "sourcesanspro-black", "900"); @include font-face("sourcesanspro", "sourcesanspro-blackitalic", "900", italic); + +// Vazirmatn +@include font-face("vazirmatn", "Vazirmatn-Thin", "100"); +@include font-face("vazirmatn", "Vazirmatn-ExtraLight", "200"); +@include font-face("vazirmatn", "Vazirmatn-Light", "300"); +@include font-face("vazirmatn", "Vazirmatn-Regular", normal); +@include font-face("vazirmatn", "Vazirmatn-Medium", "500"); +@include font-face("vazirmatn", "Vazirmatn-SemiBold", "600"); +@include font-face("vazirmatn", "Vazirmatn-Bold", bold); +@include font-face("vazirmatn", "Vazirmatn-ExtraBold", "800"); +@include font-face("vazirmatn", "Vazirmatn-Black", "900"); diff --git a/frontend/resources/styles/common/dependencies/mixin.scss b/frontend/resources/styles/common/dependencies/mixin.scss index c4e19f9164..294a822035 100644 --- a/frontend/resources/styles/common/dependencies/mixin.scss +++ b/frontend/resources/styles/common/dependencies/mixin.scss @@ -111,27 +111,11 @@ text-shadow: none; } -/// Shortcut mixin to add new font-face compatible with all browser. To work you need to add the follow formats of font:".eot", ".woff", ".ttf" and "svg". -/// @group Mixins -/// @parameter $style-name - Name of the font style -/// @parameter $file - Name of the font file. -/// @parameter $weight [normal] - With this variable you can add how much weight you want to add to this specific font. EX: Bold -/// @parameter $style [normal] - With this variable you can add a font style to this specific font. EX: Italic - -@mixin font-face( - $style-name, - $file, - $weight: unquote("normal"), - $style: unquote("normal") -) { +@mixin font-face($style-name, $file, $weight: unquote("normal"), $style: unquote("normal")) { $filepath: "/fonts/" + $file; @font-face { font-family: "#{$style-name}"; - src: url($filepath + ".eot"); - src: url($filepath + ".eot?#iefix") format("embedded-opentype"), - url($filepath + ".woff") format("woff"), - url($filepath + ".ttf") format("truetype"), - url($filepath + ".svg#" + $style-name + "") format("svg"); + src: url($filepath + ".woff2") format("woff2"), url($filepath + ".ttf") format("truetype"); font-weight: unquote($weight); font-style: unquote($style); } diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss index 37e5c275e9..888b2d101d 100644 --- a/frontend/resources/styles/common/framework.scss +++ b/frontend/resources/styles/common/framework.scss @@ -450,6 +450,30 @@ ul.slider-dots { } } + &.maxW { + &::after { + content: attr(alt); + } + } + + &.minW { + &::after { + content: attr(alt); + } + } + + &.maxH { + &::after { + content: attr(alt); + } + } + + &.minH { + &::after { + content: attr(alt); + } + } + &.large { min-width: 7rem; } diff --git a/frontend/resources/styles/main/partials/af-signup-questions.scss b/frontend/resources/styles/main/partials/af-signup-questions.scss index bb5017818b..9283d53f28 100644 --- a/frontend/resources/styles/main/partials/af-signup-questions.scss +++ b/frontend/resources/styles/main/partials/af-signup-questions.scss @@ -243,10 +243,7 @@ } } - .af-field-previous_design_tool - .af-choice-option:nth-child(7) - input:checked - + label, + .af-field-previous_design_tool .af-choice-option:nth-child(7) input:checked + label, .af-field-use_of_penpot .af-choice-option:nth-child(5) input:checked + label { &::before { background-color: $color-primary; diff --git a/frontend/resources/styles/main/partials/color-palette.scss b/frontend/resources/styles/main/partials/color-palette.scss index 78eeed0139..3ef34c52af 100644 --- a/frontend/resources/styles/main/partials/color-palette.scss +++ b/frontend/resources/styles/main/partials/color-palette.scss @@ -82,6 +82,7 @@ .color-palette-actions-button { cursor: pointer; + display: flex; & svg { width: 1rem; height: 1rem; diff --git a/frontend/resources/styles/main/partials/colorpicker.scss b/frontend/resources/styles/main/partials/colorpicker.scss index 45a9daf33d..a9d6587208 100644 --- a/frontend/resources/styles/main/partials/colorpicker.scss +++ b/frontend/resources/styles/main/partials/colorpicker.scss @@ -200,11 +200,7 @@ } &.value { - background: linear-gradient( - var(--gradient-direction), - #fff 0%, - #000 100% - ); + background: linear-gradient(var(--gradient-direction), #fff 0%, #000 100%); } .handler { @@ -237,8 +233,8 @@ border-radius: 6px; z-index: 1; border: 1px solid $color-white; - box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset, - rgb(0 0 0 / 0.25) 0px 4px 4px inset, rgb(0 0 0 / 0.25) 0px 4px 4px; + box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset, rgb(0 0 0 / 0.25) 0px 4px 4px inset, + rgb(0 0 0 / 0.25) 0px 4px 4px; transform: translate(-6px, -6px); left: 50%; top: 50%; @@ -386,8 +382,8 @@ border-radius: 6px; z-index: 1; border: 1px solid $color-white; - box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset, - rgb(0 0 0 / 0.25) 0px 4px 4px inset, rgb(0 0 0 / 0.25) 0px 4px 4px; + box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset, rgb(0 0 0 / 0.25) 0px 4px 4px inset, + rgb(0 0 0 / 0.25) 0px 4px 4px; transform: translate(-6px, -6px); left: 50%; top: 50%; diff --git a/frontend/resources/styles/main/partials/comments.scss b/frontend/resources/styles/main/partials/comments.scss index 7019ee9c31..e3bd0904e4 100644 --- a/frontend/resources/styles/main/partials/comments.scss +++ b/frontend/resources/styles/main/partials/comments.scss @@ -29,6 +29,9 @@ &.unread { background-color: $color-primary; } + span { + user-select: none; + } } .thread-content { @@ -77,7 +80,7 @@ resize: none; width: 100%; border-radius: 2px; - border: 1px solid $color-gray-10; + border: 1px solid $color-gray-20; max-height: 4rem; } @@ -188,6 +191,7 @@ margin: 0 $size-2 0 26px; white-space: pre-wrap; display: inline-block; + word-break: break-all; } } } diff --git a/frontend/resources/styles/main/partials/dashboard-sidebar.scss b/frontend/resources/styles/main/partials/dashboard-sidebar.scss index 96134c663e..2df4152938 100644 --- a/frontend/resources/styles/main/partials/dashboard-sidebar.scss +++ b/frontend/resources/styles/main/partials/dashboard-sidebar.scss @@ -177,7 +177,7 @@ span.element-title { color: $color-gray-60; font-size: $fs14; - overflow-x: hidden; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; user-select: none; diff --git a/frontend/resources/styles/main/partials/dashboard-team.scss b/frontend/resources/styles/main/partials/dashboard-team.scss index 6bab8a4c3c..acef161a43 100644 --- a/frontend/resources/styles/main/partials/dashboard-team.scss +++ b/frontend/resources/styles/main/partials/dashboard-team.scss @@ -289,8 +289,8 @@ display: flex; justify-content: center; align-items: center; - width: 70px; - height: 70px; + width: 71px; + height: 71px; border-radius: 50%; color: $color-white; background: $color-primary-dark; @@ -304,6 +304,10 @@ &:hover { .update-overlay { opacity: 1; + width: 72px; + height: 72px; + top: 14px; + left: 14px; } } } diff --git a/frontend/resources/styles/main/partials/handoff.scss b/frontend/resources/styles/main/partials/handoff.scss index adcb450821..a4eb7dd4f7 100644 --- a/frontend/resources/styles/main/partials/handoff.scss +++ b/frontend/resources/styles/main/partials/handoff.scss @@ -330,26 +330,13 @@ } .code-row-lang { + color: $color-gray-10; position: relative; display: flex; flex-direction: row; + font-size: $fs14; margin: 0.5rem; - .code-selection { - height: 100%; - margin: 0; - padding: 0.5rem; - width: 4.5rem; - font-size: $fs12; - background: $color-gray-50; - color: $color-gray-10; - border-radius: 2px; - border: 1px solid $color-gray-30; - background-image: url("/images/icons/arrow-down-white.svg"); - background-repeat: no-repeat; - background-position: 90% 48%; - background-size: 8px; - } .expand-button, .copy-button { margin-top: 8px; diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index b6bce0d27d..956d755afe 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -1289,8 +1289,7 @@ } .modal-container { - background-image: url("../images/deco-left.png"), - url("../images/deco-right.png"); + background-image: url("../images/deco-left.png"), url("../images/deco-right.png"); background-repeat: no-repeat; background-position: 10% 50px, 90% 50px; background-size: 65px; @@ -1317,18 +1316,8 @@ --checkbox-border-radius: 3px; --dropdown-option-background-color: rgba(0, 195, 139, 1); --dropdown-option-active-background-color: rgba(0, 138, 98, 1); - --invalid-field-background-color: rgba( - 238.51780000000002, - 205.7178, - 204.11780000000002, - 1 - ); - --message-fail-background-color: rgba( - 238.51780000000002, - 205.7178, - 204.11780000000002, - 1 - ); + --invalid-field-background-color: rgba(238.51780000000002, 205.7178, 204.11780000000002, 1); + --message-fail-background-color: rgba(238.51780000000002, 205.7178, 204.11780000000002, 1); --message-success-background-color: rgba(171, 232, 197, 1); } } @@ -1632,3 +1621,65 @@ } } } + +//- LOGIN +.login-register { + background-color: $color-white; + box-shadow: 0 10px 10px rgba(0, 0, 0, 0.2); + display: flex; + font-family: "worksans", sans-serif; + width: 472px; + height: auto; + position: relative; + + .title { + margin-left: 32px; + h2 { + font-size: 24px; + color: $color-black; + line-height: $fs36; + letter-spacing: 0px; + margin: 0 30px 20px 0; + } + + .modal-close-button { + margin-top: 7px; + margin-right: 12px; + justify-content: right; + svg { + fill: $color-black; + } + } + } + + .modal-bottom { + margin: 0 32px; + color: #1f1f1f; + display: flex; + flex-direction: column; + + &.auth-content { + align-items: initial; + height: auto; + } + + .links { + margin: 7px 0 0 0; + text-align: left; + } + } + + .modal-footer { + justify-content: center; + align-items: center; + + .terms-login { + position: relative; + bottom: 0; + } + } + + .hint { + color: #b1b2b5; + } +} diff --git a/frontend/resources/styles/main/partials/share-link.scss b/frontend/resources/styles/main/partials/share-link.scss index 1dd9a5415a..5b96657c65 100644 --- a/frontend/resources/styles/main/partials/share-link.scss +++ b/frontend/resources/styles/main/partials/share-link.scss @@ -1,139 +1,242 @@ -.share-link-dialog { - width: 475px; - background-color: $color-white; +.share-modal { + background: none; + display: block; + top: 50px; + left: calc(100vw - 500px); - .modal-footer { - display: flex; - align-items: center; - justify-content: flex-end; - height: unset; - padding: 16px 26px; + .share-link-dialog { + width: 480px; + background-color: $color-white; + border: 1px solid $color-gray-10; - .btn-primary, - .btn-secondary, - .btn-warning { - width: 126px; - margin-bottom: 0px; - - &:not(:last-child) { - margin-right: 10px; - } - } - - .confirm-dialog { - display: flex; - flex-direction: column; - background-color: unset; - - .description { - font-size: $fs14; - - margin-bottom: 16px; - } - .actions { + .modal-content { + padding: 16px 32px; + &:first-child { + border-top: 0px; + padding: 0; + height: 50px; display: flex; - justify-content: flex-end; + justify-content: center; } - } - } - - .modal-content { - padding: 26px; - - &:first-child { - border-top: 0px; - } - - .title { - display: flex; - justify-content: space-between; - - h2 { - font-size: $fs18; - color: $color-black; - } - - .modal-close-button { - margin-right: 0px; - } - } - - .share-link-section { - margin-top: 12px; - label { - font-size: $fs12; - color: $color-black; - } - - .hint { - padding-top: 10px; - font-size: $fs14; - color: $color-gray-40; - } - - .help-icon { - cursor: pointer; - } - } - - .view-mode, - .access-mode { - display: flex; - flex-direction: column; .title { - color: $color-black; - font-weight: 400; + display: flex; + justify-content: space-between; + align-items: center; + height: 100%; + margin-left: 32px; + h2 { + font-size: $fs18; + color: $color-black; + } + + .modal-close-button { + margin-right: 16px; + } } - .items { - padding-left: 20px; - display: flex; - - > .input-checkbox, - > .input-radio { + .share-link-section { + .custom-input { display: flex; - user-select: none; - - /* input { */ - /* appearance: checkbox; */ - /* } */ - - label { + flex-direction: row; + margin-bottom: 15px; + border: 1px solid $color-gray-20; + input { + padding: 0 0 0 15px; + border: none; + } + } + .hint-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + .hint { + font-size: $fs12; + color: $color-gray-40; + } + .confirm-dialog { display: flex; - align-items: center; - color: $color-black; + flex-direction: column; + background-color: unset; + .actions { + display: flex; + justify-content: flex-end; + gap: 16px; + } + .description { + font-size: $fs12; + margin-bottom: 16px; + color: $color-black; + } + .btn-primary, + .btn-secondary, + .btn-warning { + width: 126px; + margin-bottom: 0px; - .hint { - margin-left: 5px; + &:not(:last-child) { + margin-right: 10px; + } + } + } + } + + label { + font-size: $fs12; + color: $color-black; + } + + .help-icon { + height: 40px; + width: 40px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + position: relative; + right: 0; + top: 0; + background-color: $color-gray-10; + border-left: 1px solid $color-gray-20; + svg { + fill: $color-gray-30; + } + &:hover { + background-color: $color-primary; + svg { + fill: $color-gray-60; + } + } + } + input { + margin: 0; + } + } + + &.ops-section { + .manage-permissions { + display: flex; + color: $color-primary-dark; + font-size: $fs12; + cursor: pointer; + .icon { + svg { + height: 16px; + width: 16px; + fill: $color-primary-dark; + } + } + .title { + margin-left: 8px; + } + } + .view-mode { + min-height: 34px; + .subtitle { + height: 32px; + } + .row { + display: flex; + justify-content: space-between; + align-items: center; + .count-pages { + font-size: $fs12; color: $color-gray-30; } } - - &.disabled { - label { - color: $color-gray-30; + .current-tag { + font-size: $fs12; + color: $color-gray-30; + } + label { + color: $color-black; + } + } + .access-mode, + .inspect-mode { + display: grid; + grid-template-columns: auto 1fr; + .items { + display: flex; + justify-content: flex-end; + align-items: center; + } + } + .view-mode, + .access-mode, + .inspect-mode { + margin: 8px 0; + .subtitle { + display: flex; + justify-content: flex-start; + align-items: center; + color: $color-black; + font-size: $fs16; + .icon { + display: flex; + justify-content: center; + align-items: center; + margin-right: 10px; + svg { + height: 16px; + width: 16px; + } } } + .items { + .input-select { + background-image: url("/images/icons/arrow-down.svg"); + margin: 0; + padding-right: 28px; + border: 1px solid $color-gray-10; + max-width: 227px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + > .input-radio { + display: flex; + user-select: none; + margin-top: 0; + margin-bottom: 0; + label { + display: flex; + align-items: center; + color: $color-black; + max-width: 115px; + + &::before { + height: 16px; + width: 16px; + } + .hint { + margin-left: 5px; + color: $color-gray-30; + } + } + + &.disabled { + label { + color: $color-gray-30; + } + } + } + } + } + + .pages-selection { + border-top: 1px solid $color-gray-10; + border-bottom: 1px solid $color-gray-10; + padding-left: 20px; + max-height: 200px; + overflow-y: scroll; + user-select: none; + + label { + color: $color-black; + } } } } - - .pages-selection { - padding-left: 20px; - max-height: 200px; - overflow-y: scroll; - user-select: none; - - label { - color: $color-black; - } - } - - .custom-input { - input { - padding: 0 40px 0 15px; - } - } } } diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index f678044e19..845e7a5ea4 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -92,7 +92,7 @@ color: $color-gray-20; font-size: $fs12; max-width: 75%; - overflow-x: hidden; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } @@ -625,7 +625,9 @@ } } -.radius-options { +.radius-options, +.padding-options, +.margin-options { align-items: center; border: 1px solid $color-gray-60; border-radius: 4px; @@ -634,7 +636,9 @@ padding: 8px; width: 64px; - .radius-icon { + .radius-icon, + .padding-icon, + .margin-icon { display: flex; align-items: center; @@ -1583,3 +1587,267 @@ } } } + +.layout-menu { + svg { + height: 16px; + width: 16px; + fill: $color-gray-30; + } + .direction-gap { + display: flex; + justify-content: space-between; + .direction { + display: flex; + .dir { + margin-right: 4px; + display: flex; + justify-content: center; + align-items: center; + background: none; + border: none; + cursor: pointer; + + &.right { + svg { + transform: rotate(180deg); + } + } + &.top { + svg { + transform: rotate(-90deg); + } + } + &.bottom { + svg { + transform: rotate(90deg); + } + } + &.active, + &:hover { + svg { + fill: $color-primary; + } + } + } + } + .gap { + display: flex; + justify-content: center; + align-items: center; + .icon { + display: flex; + justify-content: center; + align-items: center; + margin-right: 7px; + &.rotated { + transform: rotate(90deg); + } + &.activated { + svg { + fill: $color-primary; + } + } + } + input { + font-size: 0.75rem; + min-width: 0; + padding: 0.25rem; + width: 50px; + height: 20px; + margin: 0; + } + } + } + + .layout-container { + border: 1px solid $color-gray-60; + border-radius: 3px; + margin: 5px 0; + .layout-entry { + display: flex; + align-items: center; + color: $color-gray-20; + height: 38px; + cursor: pointer; + &:hover { + svg { + fill: $color-primary; + } + } + } + + .layout-info { + font-size: $fs12; + width: 100%; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + &::first-letter { + text-transform: capitalize; + } + } + + .layout-body { + display: flex; + align-items: center; + margin: 7px; + .selects-wrapper { + width: 100%; + margin-left: 12px; + select { + text-transform: capitalize; + } + option { + text-transform: capitalize; + } + } + + .orientation-grid { + background-color: $color-gray-60; + .button-wrapper { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr 1fr; + width: 47px; + height: 47px; + border: 1px solid $color-gray-30; + margin: 12px; + .orientation { + background: none; + border: none; + height: 14px; + width: 14px; + display: flex; + justify-content: center; + align-items: center; + padding: 2px; + cursor: pointer; + + .icon { + display: flex; + justify-content: center; + align-items: center; + svg { + fill: none; + height: 10px; + width: 10px; + } + &.rotated { + transform: rotate(90deg); + } + } + + &.active { + .icon { + svg { + fill: $color-primary; + } + } + } + &:hover { + .icon { + svg { + fill: $color-gray-20; + } + } + } + } + } + &.col { + .button-wrapper { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 1fr; + .orientation { + height: 100%; + justify-content: space-between; + flex-direction: column; + } + } + } + &.row { + .button-wrapper { + display: grid; + grid-template-rows: 1fr 1fr 1fr; + grid-template-columns: 1fr; + .orientation { + width: 100%; + justify-content: space-between; + padding: 2px; + } + } + } + } + } + } +} + +.layout-item-menu { + .layout-behavior { + display: flex; + align-items: center; + margin: 9px 0; + .button-wrapper { + border: 1px solid $color-gray-60; + border-radius: 4px; + display: flex; + align-items: center; + .behavior-btn { + background: none; + border: none; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + padding: 0; + .icon { + display: flex; + justify-content: center; + align-items: center; + height: 26px; + width: 26px; + svg { + height: 16px; + width: 16px; + fill: $color-gray-30; + } + } + &:hover, + &.activated { + svg { + fill: $color-primary; + } + } + } + &.horizontal { + margin-right: 8px; + svg { + transform: rotate(90deg); + } + } + } + } + .advanced-ops { + display: flex; + align-items: center; + cursor: pointer; + font-size: $fs12; + color: $color-gray-30; + &:hover { + svg { + fill: $color-primary; + } + } + } + .advanced-ops-body { + display: grid; + grid-template-columns: 1fr 1fr; + .input-element { + width: 100%; + &::after { + width: 100px; + } + } + } +} diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss index 2b67b255d3..1b8e001c68 100644 --- a/frontend/resources/styles/main/partials/sidebar-layers.scss +++ b/frontend/resources/styles/main/partials/sidebar-layers.scss @@ -212,7 +212,7 @@ span.element-name { max-width: 75%; min-width: 40px; min-height: 16px; - overflow-x: hidden; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; diff --git a/frontend/resources/styles/main/partials/sidebar-sitemap.scss b/frontend/resources/styles/main/partials/sidebar-sitemap.scss index ffc67c1371..3568ce3605 100644 --- a/frontend/resources/styles/main/partials/sidebar-sitemap.scss +++ b/frontend/resources/styles/main/partials/sidebar-sitemap.scss @@ -28,7 +28,7 @@ color: $color-gray-20; font-size: $fs12; max-width: 75%; - overflow-x: hidden; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index 8ecf0266e7..92c7cce166 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -51,7 +51,7 @@ color: $color-gray-10; font-size: $fs14; max-width: 100%; - overflow-x: hidden; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } diff --git a/frontend/resources/styles/main/partials/viewer-header.scss b/frontend/resources/styles/main/partials/viewer-header.scss index 378019f3ce..11871eee23 100644 --- a/frontend/resources/styles/main/partials/viewer-header.scss +++ b/frontend/resources/styles/main/partials/viewer-header.scss @@ -3,11 +3,12 @@ background-color: $color-gray-50; border-bottom: 1px solid $color-gray-60; display: grid; - grid-template-columns: 1fr auto 1fr; + grid-template-columns: 45% 10% 45%; height: 48px; padding: 0 $size-4 0 55px; position: relative; justify-content: space-between; + width: 100vw; a { font-size: $fs12; @@ -15,6 +16,7 @@ .nav-zone { justify-content: flex-start; + width: 100%; } .main-icon { @@ -54,10 +56,25 @@ > * { margin-left: $size-5; + @media only screen and (max-width: 1366px) { + margin-left: 0.5rem; + } } .btn-primary { flex-shrink: 0; + svg { + display: none; + } + @media only screen and (max-width: 1366px) { + padding: 0 0.5rem; + svg { + display: inline-block; + } + span { + display: none; + } + } } .view-options { @@ -105,6 +122,7 @@ display: flex; padding: $size-1; position: relative; + width: 100%; .icon { display: flex; @@ -119,16 +137,20 @@ } } + .breadcrumb { + display: grid; + grid-template-columns: auto 10px auto 10px auto; + } + .breadcrumb, .current-frame { - display: flex; position: relative; > span { color: $color-gray-20; margin-right: $size-1; font-size: $fs14; - overflow-x: hidden; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } @@ -140,7 +162,8 @@ } .current-frame { - display: flex; + display: grid; + grid-template-columns: 14px 1fr; span { color: $color-white; margin-right: $size-1; diff --git a/frontend/resources/styles/main/partials/viewer-thumbnails.scss b/frontend/resources/styles/main/partials/viewer-thumbnails.scss index 03b60ae12a..8d9067ed07 100644 --- a/frontend/resources/styles/main/partials/viewer-thumbnails.scss +++ b/frontend/resources/styles/main/partials/viewer-thumbnails.scss @@ -147,6 +147,7 @@ height: 120px; border: 1px solid $color-gray-20; border-radius: 2px; + padding: 4px; display: flex; justify-content: center; diff --git a/frontend/resources/styles/main/partials/viewer.scss b/frontend/resources/styles/main/partials/viewer.scss index 4f9f13fe14..00a0c14bcd 100644 --- a/frontend/resources/styles/main/partials/viewer.scss +++ b/frontend/resources/styles/main/partials/viewer.scss @@ -21,6 +21,112 @@ overflow: auto; + & .viewer-go-prev, + & .viewer-go-next { + position: absolute; + height: 100%; + display: flex; + align-items: center; + width: 53px; + + .arrow { + display: none; + align-items: center; + justify-content: center; + border-radius: 12px; + background: $color-gray-50; + width: 24px; + height: 24px; + cursor: pointer; + fill: $color-gray-30; + + svg { + width: 12px; + height: 12px; + } + + &:hover { + background: $color-primary; + fill: $color-black; + } + } + + &:hover .arrow { + display: flex; + } + } + + & .viewer-go-next { + right: 0; + padding-right: 29px; + svg { + margin-left: 2px; + } + } + + & .viewer-go-next.right-bar { + right: 256px; + } + + & .viewer-go-prev { + left: 0; + padding-left: 29px; + svg { + margin-right: 2px; + } + } + + & .viewer-go-prev.left-bar { + left: 256px; + } + + & .viewer-bottom { + position: absolute; + bottom: 0; + height: 50px; + width: 100%; + display: flex; + justify-content: space-between; + + &.left-bar { + width: calc(100% - 512px); + } + + .reset { + display: flex; + align-items: center; + border-radius: 12px; + background: $color-gray-50; + width: 24px; + height: 24px; + cursor: pointer; + fill: $color-gray-30; + margin-left: 29px; + + svg { + margin-left: 4px; + width: 15px; + height: 15px; + } + + &:hover { + background: $color-primary; + fill: $color-black; + } + } + + .counter { + display: flex; + align-items: center; + justify-content: center; + border-radius: 12px; + background: $color-gray-50; + width: 67px; + height: 25px; + fill: $color-gray-20; + } + } + & .viewer-wrapper { position: relative; } @@ -42,6 +148,24 @@ transform-origin: center; } } + + & .viewer-wrapper-out { + position: relative; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + + & .comments-right-sidebar { + position: absolute; + right: 0; + top: 50px; + width: 256px; + height: 100%; + z-index: 10; + } } .viewport-container { diff --git a/frontend/resources/styles/main/partials/workspace-header.scss b/frontend/resources/styles/main/partials/workspace-header.scss index d05041bf78..4102152f02 100644 --- a/frontend/resources/styles/main/partials/workspace-header.scss +++ b/frontend/resources/styles/main/partials/workspace-header.scss @@ -122,7 +122,7 @@ span { color: $color-white; font-size: $fs14; - overflow-x: hidden; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index 60985de8c4..9945e0ac39 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -29,7 +29,7 @@ :depends-on #{:shared}}} :compiler-options - {:output-feature-set :es8 + {:output-feature-set :es2020 :output-wrapper false :warnings {:fn-deprecated false}} diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 7cf0ede1a1..1de776e7c6 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -80,10 +80,6 @@ (def default-theme "default") (def default-language "en") -(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 worker-uri (obj/get global "penpotWorkerURI" "/js/worker.js")) (def translations (obj/get global "penpotTranslations")) (def themes (obj/get global "penpotThemes")) @@ -100,14 +96,6 @@ (def terms-of-service-uri (obj/get global "penpotTermsOfServiceURI" nil)) (def privacy-policy-uri (obj/get global "penpotPrivacyPolicyURI" nil)) -;; maintain for backward compatibility -(let [login-with-ldap (obj/get global "penpotLoginWithLDAP" false) - registration (obj/get global "penpotRegistrationEnabled" true)] - (when login-with-ldap - (swap! flags conj :login-with-ldap)) - (when (false? registration) - (swap! flags disj :registration))) - (defn get-public-uri [] (let [uri (u/uri (or (obj/get global "penpotPublicURI") @@ -123,11 +111,11 @@ ;; --- Helper Functions (defn ^boolean check-browser? [candidate] - (us/verify ::browser candidate) + (us/verify! ::browser candidate) (= candidate @browser)) (defn ^boolean check-platform? [candidate] - (us/verify ::platform candidate) + (us/verify! ::platform candidate) (= candidate @platform)) (defn resolve-profile-photo-url diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 287e3fe039..a7e38e1850 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -25,6 +25,7 @@ [app.util.theme :as theme] [beicon.core :as rx] [debug] + [features] [potok.core :as ptk] [rumext.alpha :as mf])) diff --git a/frontend/src/app/main/broadcast.cljs b/frontend/src/app/main/broadcast.cljs new file mode 100644 index 0000000000..ef50e4b31b --- /dev/null +++ b/frontend/src/app/main/broadcast.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/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.broadcast + "BroadcastChannel API." + (:require + [app.common.transit :as t] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defrecord BroadcastMessage [id type data] + cljs.core/IDeref + (-deref [_] data)) + +(def ^:const default-topic "penpot") + +;; The main broadcast channel instance, used for emit data +(defonce default-channel + (js/BroadcastChannel. default-topic)) + +(defonce stream + (->> (rx/create (fn [subs] + (let [chan (js/BroadcastChannel. default-topic)] + (unchecked-set chan "onmessage" #(rx/push! subs (unchecked-get % "data"))) + (fn [] (.close ^js chan))))) + (rx/map t/decode-str) + (rx/map map->BroadcastMessage) + (rx/share))) + +(defn emit! + ([type data] + (.postMessage ^js default-channel (t/encode-str {:id nil :type type :data data})) + nil) + ([id type data] + (.postMessage ^js default-channel (t/encode-str {:id id :type type :data data})) + nil)) + +(defn type? + ([type] + (fn [obj] (= (:type obj) type))) + ([obj type] + (= (:type obj) type))) + +(defn event + [type data] + (ptk/reify ::event + ptk/EffectEvent + (effect [_ _ _] + (emit! type data)))) diff --git a/frontend/src/app/main/constants.cljs b/frontend/src/app/main/constants.cljs index 8247b92962..5f9e844862 100644 --- a/frontend/src/app/main/constants.cljs +++ b/frontend/src/app/main/constants.cljs @@ -23,3 +23,172 @@ :grid-alignment true :background "var(--color-white)"}) +(def has-layout-item false) + +(def size-presets + [{:name "APPLE"} + {:name "iPhone 12/12 Pro" + :width 390 + :height 844} + {:name "iPhone 12 Mini" + :width 360 + :height 780} + {:name "iPhone 12 Pro Max" + :width 428 + :height 926} + {:name "iPhone X/XS/11 Pro" + :width 375 + :height 812} + {:name "iPhone XS Max/XR/11" + :width 414 + :height 896} + {:name "iPhone 6/7/8 Plus" + :width 414 + :height 736} + {:name "iPhone 6/7/8/SE2" + :width 375 + :height 667} + {:name "iPhone 5/SE" + :width 320 + :height 568} + {:name "iPad" + :width 768 + :height 1024} + {:name "iPad Pro 10.5in" + :width 834 + :height 1112} + {:name "iPad Pro 12.9in" + :width 1024 + :height 1366} + {:name "Watch 44mm" + :width 368 + :height 448} + {:name "Watch 42mm" + :width 312 + :height 390} + {:name "Watch 40mm" + :width 324 + :height 394} + {:name "Watch 38mm" + :width 272 + :height 340} + + {:name "ANDROID"} + {:name "Mobile" + :width 360 + :height 640} + {:name "Tablet" + :width 768 + :height 1024} + {:name "Google Pixel 4a/5" + :width 393 + :height 851} + {:name "Samsung Galaxy S20+" + :width 384 + :height 854} + {:name "Samsung Galaxy A71/A51" + :width 412 + :height 914} + + {:name "MICROSOFT"} + {:name "Surface Pro 3" + :width 1440 + :height 960} + {:name "Surface Pro 4/5/6/7" + :width 1368 + :height 912} + + {:name "ReMarkable"} + {:name "Remarkable 2" + :width 840 + :height 1120} + + {:name "WEB"} + {:name "Web 1280" + :width 1280 + :height 800} + {:name "Web 1366" + :width 1366 + :height 768} + {:name "Web 1024" + :width 1024 + :height 768} + {:name "Web 1920" + :width 1920 + :height 1080} + + {:name "PRINT (96dpi)"} + {:name "A0" + :width 3179 + :height 4494} + {:name "A1" + :width 2245 + :height 3179} + {:name "A2" + :width 1587 + :height 2245} + {:name "A3" + :width 1123 + :height 1587} + {:name "A4" + :width 794 + :height 1123} + {:name "A5" + :width 559 + :height 794} + {:name "A6" + :width 397 + :height 559} + {:name "Letter" + :width 816 + :height 1054} + {:name "DIN Lang" + :width 835 + :height 413} + + {:name "SOCIAL MEDIA"} + {:name "Instagram profile" + :width 320 + :height 320} + {:name "Instagram post" + :width 1080 + :height 1080} + {:name "Instagram story" + :width 1080 + :height 1920} + {:name "Facebook profile" + :width 720 + :height 720} + {:name "Facebook cover" + :width 820 + :height 312} + {:name "Facebook post" + :width 1200 + :height 630} + {:name "LinkedIn profile" + :width 400 + :height 400} + {:name "LinkedIn cover" + :width 1584 + :height 396} + {:name "LinkedIn post" + :width 1200 + :height 627} + {:name "Twitter profile" + :width 400 + :height 400} + {:name "Twitter header" + :width 1500 + :height 500} + {:name "Twitter post" + :width 1024 + :height 512} + {:name "YouTube profile" + :width 800 + :height 800} + {:name "YouTube banner" + :width 2560 + :height 1440} + {:name "YouTube thumb" + :width 1280 + :height 720}]) diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs index 36a9a9e719..50f6dcff0f 100644 --- a/frontend/src/app/main/data/comments.cljs +++ b/frontend/src/app/main/data/comments.cljs @@ -8,7 +8,10 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] + [app.common.pages.helpers :as cph] [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.main.data.workspace.state-helpers :as wsh] [app.main.repo :as rp] [beicon.core :as rx] [cljs.spec.alpha :as s] @@ -59,26 +62,69 @@ (declare retrieve-comment-threads) (declare refresh-comment-thread) -(s/def ::create-thread-params +(s/def ::create-thread-on-workspace-params (s/keys :req-un [::page-id ::file-id ::position ::content])) -(defn create-thread - [params] - (us/assert ::create-thread-params params) - (letfn [(created [{:keys [id comment] :as thread} state] - (-> state - (update :comment-threads assoc id (dissoc thread :comment)) - (update :comments-local assoc :open id) - (update :comments-local dissoc :draft) - (update :workspace-drawing dissoc :comment) - (update-in [:comments id] assoc (:id comment) comment)))] +(s/def ::create-thread-on-viewer-params + (s/keys :req-un [::page-id ::file-id ::position ::content ::frame-id])) - (ptk/reify ::create-comment-thread - ptk/WatchEvent - (watch [_ _ _] - (->> (rp/mutation :create-comment-thread params) - (rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %)})) - (rx/map #(partial created %)) +(defn created-thread-on-workspace + [{:keys [id comment page-id] :as thread}] + + (ptk/reify ::created-thread-on-workspace + ptk/UpdateEvent + (update [_ state] + (-> state + (update :comment-threads assoc id (dissoc thread :comment)) + (update-in [:workspace-data :pages-index page-id :options :comment-threads-position] assoc id (select-keys thread [:position :frame-id])) + (update :comments-local assoc :open id) + (update :comments-local dissoc :draft) + (update :workspace-drawing dissoc :comment) + (update-in [:comments id] assoc (:id comment) comment))))) + +(defn create-thread-on-workspace + [params] + (us/assert ::create-thread-on-workspace-params params) + + (ptk/reify ::create-thread-on-workspace + ptk/WatchEvent + (watch [_ state _] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + frame-id (cph/frame-id-by-position objects (:position params)) + params (assoc params :frame-id frame-id)] + (->> (rp/cmd! :create-comment-thread params) + (rx/mapcat #(rp/cmd! :get-comment-thread {:file-id (:file-id %) :id (:id %)})) + (rx/map created-thread-on-workspace) + (rx/catch #(rx/throw {:type :comment-error}))))))) + +(defn created-thread-on-viewer + [{:keys [id comment page-id] :as thread}] + + (ptk/reify ::created-thread-on-workspace + ptk/UpdateEvent + (update [_ state] + (-> state + (update :comment-threads assoc id (dissoc thread :comment)) + (update-in [:viewer :pages page-id :options :comment-threads-position] assoc id (select-keys thread [:position :frame-id])) + (update :comments-local assoc :open id) + (update :comments-local dissoc :draft) + (update :workspace-drawing dissoc :comment) + (update-in [:comments id] assoc (:id comment) comment))))) + +(defn create-thread-on-viewer + [params] + (us/assert ::create-thread-on-viewer-params params) + + (ptk/reify ::create-thread-on-viewer + ptk/WatchEvent + (watch [_ state _] + (let [share-id (-> state :viewer-local :share-id) + frame-id (:frame-id params) + params (assoc params :share-id share-id :frame-id frame-id)] + (->> (rp/cmd! :create-comment-thread params) + (rx/mapcat #(rp/cmd! :get-comment-thread {:file-id (:file-id %) :id (:id %) :share-id share-id})) + (rx/map created-thread-on-viewer) (rx/catch #(rx/throw {:type :comment-error}))))))) (defn update-comment-thread-status @@ -86,13 +132,13 @@ (us/assert ::comment-thread thread) (ptk/reify ::update-comment-thread-status ptk/WatchEvent - (watch [_ _ _] - (let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0)] - (->> (rp/mutation :update-comment-thread-status {:id id}) + (watch [_ state _] + (let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0) + share-id (-> state :viewer-local :share-id)] + (->> (rp/cmd! :update-comment-thread-status {:id id :share-id share-id}) (rx/map (constantly done)) (rx/catch #(rx/throw {:type :comment-error}))))))) - (defn update-comment-thread [{:keys [id is-resolved] :as thread}] (us/assert ::comment-thread thread) @@ -105,11 +151,11 @@ (d/update-in-when state [:comment-threads id] assoc :is-resolved is-resolved)) ptk/WatchEvent - (watch [_ _ _] - (->> (rp/mutation :update-comment-thread {:id id :is-resolved is-resolved}) - (rx/catch #(rx/throw {:type :comment-error})) - (rx/ignore))))) - + (watch [_ state _] + (let [share-id (-> state :viewer-local :share-id)] + (->> (rp/cmd! :update-comment-thread {:id id :is-resolved is-resolved :share-id share-id}) + (rx/catch #(rx/throw {:type :comment-error})) + (rx/ignore)))))) (defn add-comment [thread content] @@ -119,12 +165,13 @@ (update-in state [:comments (:id thread)] assoc (:id comment) comment))] (ptk/reify ::create-comment ptk/WatchEvent - (watch [_ _ _] - (rx/concat - (->> (rp/mutation :add-comment {:thread-id (:id thread) :content content}) - (rx/map #(partial created %)) - (rx/catch #(rx/throw {:type :comment-error}))) - (rx/of (refresh-comment-thread thread))))))) + (watch [_ state _] + (let [share-id (-> state :viewer-local :share-id)] + (rx/concat + (->> (rp/cmd! :create-comment {:thread-id (:id thread) :content content :share-id share-id}) + (rx/map #(partial created %)) + (rx/catch #(rx/throw {:type :comment-error}))) + (rx/of (refresh-comment-thread thread)))))))) (defn update-comment [{:keys [id content thread-id] :as comment}] @@ -135,27 +182,49 @@ (d/update-in-when state [:comments thread-id id] assoc :content content)) ptk/WatchEvent - (watch [_ _ _] - (->> (rp/mutation :update-comment {:id id :content content}) - (rx/catch #(rx/throw {:type :comment-error})) - (rx/ignore))))) + (watch [_ state _] + (let [share-id (-> state :viewer-local :share-id)] + (->> (rp/cmd! :update-comment {:id id :content content :share-id share-id}) + (rx/catch #(rx/throw {:type :comment-error})) + (rx/ignore)))))) -(defn delete-comment-thread +(defn delete-comment-thread-on-workspace [{:keys [id] :as thread}] (us/assert ::comment-thread thread) - (ptk/reify ::delete-comment-thread + (ptk/reify ::delete-comment-thread-on-workspace ptk/UpdateEvent (update [_ state] - (-> state - (update :comments dissoc id) - (update :comment-threads dissoc id))) + (let [page-id (:current-page-id state)] + (-> state + (update-in [:workspace-data :pages-index page-id :options :comment-threads-position] dissoc id) + (update :comments dissoc id) + (update :comment-threads dissoc id)))) ptk/WatchEvent (watch [_ _ _] - (->> (rp/mutation :delete-comment-thread {:id id}) + (->> (rp/cmd! :delete-comment-thread {:id id}) (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore))))) +(defn delete-comment-thread-on-viewer + [{:keys [id] :as thread}] + (us/assert ::comment-thread thread) + (ptk/reify ::delete-comment-thread-on-viewer + ptk/UpdateEvent + (update [_ state] + (let [page-id (:current-page-id state)] + (-> state + (update-in [:viewer :pages page-id :options :comment-threads-position] dissoc id) + (update :comments dissoc id) + (update :comment-threads dissoc id)))) + + ptk/WatchEvent + (watch [_ state _] + (let [share-id (-> state :viewer-local :share-id)] + (->> (rp/cmd! :delete-comment-thread {:id id :share-id share-id}) + (rx/catch #(rx/throw {:type :comment-error})) + (rx/ignore)))))) + (defn delete-comment [{:keys [id thread-id] :as comment}] (us/assert ::comment comment) @@ -165,10 +234,11 @@ (d/update-in-when state [:comments thread-id] dissoc id)) ptk/WatchEvent - (watch [_ _ _] - (->> (rp/mutation :delete-comment {:id id}) - (rx/catch #(rx/throw {:type :comment-error})) - (rx/ignore))))) + (watch [_ state _] + (let [share-id (-> state :viewer-local :share-id)] + (->> (rp/cmd! :delete-comment {:id id :share-id share-id}) + (rx/catch #(rx/throw {:type :comment-error})) + (rx/ignore)))))) (defn refresh-comment-thread [{:keys [id file-id] :as thread}] @@ -177,22 +247,34 @@ (assoc-in state [:comment-threads id] thread))] (ptk/reify ::refresh-comment-thread ptk/WatchEvent - (watch [_ _ _] - (->> (rp/query :comment-thread {:file-id file-id :id id}) - (rx/map #(partial fetched %)) - (rx/catch #(rx/throw {:type :comment-error}))))))) + (watch [_ state _] + (let [share-id (-> state :viewer-local :share-id)] + (->> (rp/cmd! :get-comment-thread {:file-id file-id :id id :share-id share-id}) + (rx/map #(partial fetched %)) + (rx/catch #(rx/throw {:type :comment-error})))))))) (defn retrieve-comment-threads [file-id] (us/assert ::us/uuid file-id) - (letfn [(fetched [data state] - (assoc state :comment-threads (d/index-by :id data)))] + (letfn [(set-comment-threds [state comment-thread] + (let [path [:workspace-data :pages-index (:page-id comment-thread) :options :comment-threads-position (:id comment-thread)] + thread-position (get-in state path)] + (cond-> state + (nil? thread-position) + (-> + (assoc-in (conj path :position) (:position comment-thread)) + (assoc-in (conj path :frame-id) (:frame-id comment-thread)))))) + (fetched [data state] + (let [state (assoc state :comment-threads (d/index-by :id data))] + (reduce set-comment-threds state data)))] + (ptk/reify ::retrieve-comment-threads ptk/WatchEvent - (watch [_ _ _] - (->> (rp/query :comment-threads {:file-id file-id}) - (rx/map #(partial fetched %)) - (rx/catch #(rx/throw {:type :comment-error}))))))) + (watch [_ state _] + (let [share-id (-> state :viewer-local :share-id)] + (->> (rp/cmd! :get-comment-threads {:file-id file-id :share-id share-id}) + (rx/map #(partial fetched %)) + (rx/catch #(rx/throw {:type :comment-error})))))))) (defn retrieve-comments [thread-id] @@ -201,10 +283,11 @@ (update state :comments assoc thread-id (d/index-by :id comments)))] (ptk/reify ::retrieve-comments ptk/WatchEvent - (watch [_ _ _] - (->> (rp/query :comments {:thread-id thread-id}) - (rx/map #(partial fetched %)) - (rx/catch #(rx/throw {:type :comment-error}))))))) + (watch [_ state _] + (let [share-id (-> state :viewer-local :share-id)] + (->> (rp/cmd! :get-comments {:thread-id thread-id :share-id share-id}) + (rx/map #(partial fetched %)) + (rx/catch #(rx/throw {:type :comment-error})))))))) (defn retrieve-unread-comment-threads "A event used mainly in dashboard for retrieve all unread threads of a team." @@ -214,7 +297,7 @@ ptk/WatchEvent (watch [_ _ _] (let [fetched #(assoc %2 :comment-threads (d/index-by :id %1))] - (->> (rp/query :unread-comment-threads {:team-id team-id}) + (->> (rp/cmd! :get-unread-comment-threads {:team-id team-id}) (rx/map #(partial fetched %)) (rx/catch #(rx/throw {:type :comment-error}))))))) @@ -243,7 +326,7 @@ (update :workspace-drawing dissoc :comment))))) (defn update-filters - [{:keys [mode show] :as params}] + [{:keys [mode show list] :as params}] (ptk/reify ::update-filters ptk/UpdateEvent (update [_ state] @@ -254,7 +337,17 @@ (assoc :mode mode) (some? show) - (assoc :show show))))))) + (assoc :show show) + + (some? list) + (assoc :list list))))))) + +(defn update-options + [params] + (ptk/reify ::update-options + ptk/UpdateEvent + (update [_ state] + (update state :comments-local merge params)))) (s/def ::create-draft-params (s/keys :req-un [::page-id ::file-id ::position])) @@ -324,3 +417,41 @@ (= :yours mode) (filter #(contains? (:participants %) (:id profile)))))) + +(defn update-comment-thread-frame + ([thread ] + (update-comment-thread-frame thread uuid/zero)) + + ([thread frame-id] + (us/assert ::comment-thread thread) + (ptk/reify ::update-comment-thread-frame + ptk/UpdateEvent + (update [_ state] + (let [thread-id (:id thread)] + (assoc-in state [:comment-threads thread-id :frame-id] frame-id))) + + ptk/WatchEvent + (watch [_ _ _] + (let [thread-id (:id thread)] + (->> (rp/cmd! :update-comment-thread-frame {:id thread-id :frame-id frame-id}) + (rx/catch #(rx/throw {:type :comment-error :code :update-comment-thread-frame})) + (rx/ignore))))))) + +(defn detach-comment-thread + "Detach comment threads that are inside a frame when that frame is deleted" + [ids] + (us/assert! ::us/coll-of-uuid ids) + + (ptk/reify ::detach-comment-thread + ptk/WatchEvent + (watch [_ state _] + (let [objects (wsh/lookup-page-objects state) + is-frame? (fn [id] (= :frame (get-in objects [id :type]))) + frame-ids? (into #{} (filter is-frame?) ids)] + + (->> state + :comment-threads + (vals) + (filter (fn [comment] (some #(= % (:frame-id comment)) frame-ids?))) + (map update-comment-thread-frame) + (rx/from)))))) diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index e51ed446ca..05f5a20af4 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -427,9 +427,9 @@ (defn invite-team-members [{:keys [emails role team-id resend?] :as params}] - (us/assert ::us/set-of-emails emails) - (us/assert ::us/keyword role) - (us/assert ::us/uuid team-id) + (us/assert! ::us/set-of-valid-emails emails) + (us/assert! ::us/keyword role) + (us/assert! ::us/uuid team-id) (ptk/reify ::invite-team-members IDeref (-deref [_] {:role role :team-id team-id :resend? resend?}) diff --git a/frontend/src/app/main/data/exports.cljs b/frontend/src/app/main/data/exports.cljs index 768108d8e6..cb70f6362d 100644 --- a/frontend/src/app/main/data/exports.cljs +++ b/frontend/src/app/main/data/exports.cljs @@ -145,7 +145,7 @@ ptk/WatchEvent (watch [_ _ _] (when (= status "ended") - (->> (rp/query! :exporter {:cmd :get-resource :blob? true :id resource-id}) + (->> (rp/command! :export {:cmd :get-resource :blob? true :id resource-id}) (rx/delay 500) (rx/map #(dom/trigger-download filename %))))))) @@ -165,9 +165,9 @@ :wait true}] (rx/concat (rx/of ::dwp/force-persist) - (->> (rp/query! :exporter params) + (->> (rp/command! :export params) (rx/mapcat (fn [{:keys [id filename]}] - (->> (rp/query! :exporter {:cmd :get-resource :blob? true :id id}) + (->> (rp/command! :export {:cmd :get-resource :blob? true :id id}) (rx/map (fn [data] (dom/trigger-download filename data) (clear-export-state uuid/zero)))))) @@ -213,7 +213,7 @@ ;; Launch the exportation process and stores the resource id ;; locally. - (->> (rp/query! :exporter params) + (->> (rp/command! :export params) (rx/map (fn [{:keys [id] :as resource}] (vreset! resource-id id) (initialize-export-status exports cmd resource)))) diff --git a/frontend/src/app/main/data/media.cljs b/frontend/src/app/main/data/media.cljs index d4b28a8050..d101dccd55 100644 --- a/frontend/src/app/main/data/media.cljs +++ b/frontend/src/app/main/data/media.cljs @@ -35,12 +35,9 @@ ;; --- Utility functions -(defn validate-file ;; Check that a file obtained with the file javascript API is valid. +(defn validate-file + "Check that a file obtained with the file javascript API is valid." [file] - (when (> (.-size file) cm/max-file-size) - (ex/raise :type :validation - :code :media-too-large - :hint (str/fmt "media size is large than 5mb (size: %s)" (.-size file)))) (when-not (contains? cm/valid-image-types (.-type file)) (ex/raise :type :validation :code :media-type-not-allowed diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index 275c4b1942..a0c6bfdea4 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -173,8 +173,7 @@ (when (is-authenticated? profile) (->> (rx/of (profile-fetched profile) (fetch-teams) - (get-redirect-event) - (ws/initialize)) + (get-redirect-event)) (rx/observe-on :async))))))) (s/def ::invitation-token ::us/not-empty-string) @@ -207,7 +206,7 @@ ;; the returned profile is an NOT authenticated profile, we ;; proceed to logout and show an error message. - (->> (rp/mutation :login (d/without-nils params)) + (->> (rp/command! :login-with-password (d/without-nils params)) (rx/merge-map (fn [data] (rx/merge (rx/of (fetch-profile)) @@ -293,7 +292,7 @@ (ptk/reify ::logout ptk/WatchEvent (watch [_ _ _] - (->> (rp/mutation :logout) + (->> (rp/command! :logout) (rx/delay-at-least 300) (rx/catch (constantly (rx/of 1))) (rx/map #(logged-out params))))))) @@ -436,7 +435,6 @@ (rx/map (constantly (fetch-profile))) (rx/catch on-error)))))) - (defn fetch-users [{:keys [team-id] :as params}] (us/assert ::us/uuid team-id) @@ -447,9 +445,23 @@ (ptk/reify ::fetch-team-users ptk/WatchEvent (watch [_ _ _] - (->> (rp/query :team-users {:team-id team-id}) + (->> (rp/query! :team-users {:team-id team-id}) (rx/map #(partial fetched %))))))) +(defn fetch-file-comments-users + [{:keys [team-id] :as params}] + (us/assert ::us/uuid team-id) + (letfn [(fetched [users state] + (->> users + (d/index-by :id) + (assoc state :file-comments-users)))] + (ptk/reify ::fetch-file-comments-users + ptk/WatchEvent + (watch [_ state _] + (let [share-id (-> state :viewer-local :share-id)] + (->> (rp/command! :get-profiles-for-file-comments {:team-id team-id :share-id share-id}) + (rx/map #(partial fetched %)))))))) + ;; --- EVENT: request-account-deletion (defn request-account-deletion @@ -482,7 +494,7 @@ :or {on-error rx/throw on-success identity}} (meta data)] - (->> (rp/mutation :request-profile-recovery data) + (->> (rp/command! :request-profile-recovery data) (rx/tap on-success) (rx/catch on-error)))))) @@ -501,7 +513,7 @@ (let [{:keys [on-error on-success] :or {on-error rx/throw on-success identity}} (meta data)] - (->> (rp/mutation :recover-profile data) + (->> (rp/command! :recover-profile data) (rx/tap on-success) (rx/catch on-error)))))) @@ -512,7 +524,7 @@ (ptk/reify ::create-demo-profile ptk/WatchEvent (watch [_ _ _] - (->> (rp/mutation :create-demo-profile {}) + (->> (rp/command! :create-demo-profile {}) (rx/map login))))) diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index c79909eecc..c8061dac60 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -7,11 +7,11 @@ (ns app.main.data.viewer (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] [app.common.spec :as us] - [app.common.spec.interactions :as cti] - [app.common.uuid :as uuid] + [app.common.types.shape.interactions :as ctsi] [app.main.data.comments :as dcm] [app.main.data.fonts :as df] [app.main.repo :as rp] @@ -21,6 +21,9 @@ [cljs.spec.alpha :as s] [potok.core :as ptk])) +(s/def ::nilable-boolean (s/nilable ::us/boolean)) +(s/def ::nilable-animation (s/nilable ::ctsi/animation)) + ;; --- Local State Initialization (def ^:private @@ -33,8 +36,9 @@ :comments-show :unresolved :selected #{} :collapsed #{} - :overlays [] - :hover nil}) + :hover nil + :share-id "" + :file-comments-users []}) (declare fetch-comment-threads) (declare fetch-bundle) @@ -51,7 +55,7 @@ :opt-un [::share-id ::page-id])) (defn initialize - [{:keys [file-id] :as params}] + [{:keys [file-id share-id] :as params}] (us/assert ::initialize-params params) (ptk/reify ::initialize ptk/UpdateEvent @@ -62,7 +66,8 @@ (fn [lstate] (if (nil? lstate) default-local-state - lstate))))) + lstate))) + (assoc-in [:viewer-local :share-id] share-id))) ptk/WatchEvent (watch [_ _ _] @@ -86,13 +91,6 @@ (update [_ state] (dissoc state :viewer)))) -(defn select-frames - [{:keys [objects] :as page}] - (let [root (get objects uuid/zero)] - (into [] (comp (map #(get objects %)) - (filter #(= :frame (:type %)))) - (reverse (:shapes root))))) - ;; --- Data Fetching (s/def ::fetch-bundle-params @@ -107,7 +105,7 @@ (watch [_ _ _] (let [params' (cond-> {:file-id file-id} (uuid? share-id) (assoc :share-id share-id))] - (->> (rp/query :view-only-bundle params') + (->> (rp/query! :view-only-bundle params') (rx/mapcat (fn [{:keys [fonts] :as bundle}] (rx/of (df/fonts-fetched fonts) @@ -120,7 +118,9 @@ (let [pages (->> (get-in file [:data :pages]) (map (fn [page-id] (let [data (get-in file [:data :pages-index page-id])] - [page-id (assoc data :frames (select-frames data))]))) + [page-id (assoc data + :frames (cph/get-viewer-frames (:objects data)) + :all-frames (cph/get-viewer-frames (:objects data) {:all-frames? true}))]))) (into {}))] (ptk/reify ::bundle-fetched @@ -144,7 +144,7 @@ (rx/of (go-to-frame-auto)))))))) (defn fetch-comment-threads - [{:keys [file-id page-id] :as params}] + [{:keys [file-id page-id share-id] :as params}] (letfn [(fetched [data state] (->> data (filter #(= page-id (:page-id %))) @@ -159,7 +159,7 @@ (ptk/reify ::fetch-comment-threads ptk/WatchEvent (watch [_ _ _] - (->> (rp/query :comment-threads {:file-id file-id}) + (->> (rp/cmd! :get-comment-threads {:file-id file-id :share-id share-id}) (rx/map #(partial fetched %)) (rx/catch on-error)))))) @@ -170,7 +170,7 @@ (ptk/reify ::refresh-comment-thread ptk/WatchEvent (watch [_ _ _] - (->> (rp/query :comment-thread {:file-id file-id :id id}) + (->> (rp/cmd! :get-comment-thread {:file-id file-id :id id}) (rx/map #(partial fetched %))))))) (defn fetch-comments @@ -181,7 +181,7 @@ (ptk/reify ::retrieve-comments ptk/WatchEvent (watch [_ _ _] - (->> (rp/query :comments {:thread-id thread-id}) + (->> (rp/cmd! :get-comments {:thread-id thread-id}) (rx/map #(partial fetched %))))))) ;; --- Zoom Management @@ -303,6 +303,18 @@ (dcm/close-thread) (rt/nav :viewer pparams (assoc qparams :index (inc index))))))))) + +(def select-first-frame + (ptk/reify ::select-first-frame + ptk/WatchEvent + (watch [_ state _] + (let [route (:route state) + qparams (:query-params route) + pparams (:path-params route)] + (rx/of + (dcm/close-thread) + (rt/nav :viewer pparams (assoc qparams :index 0))))))) + (s/def ::interactions-mode #{:hide :show :show-on-click}) (defn set-interactions-mode @@ -320,7 +332,8 @@ (declare flash-done) -(def flash-interactions +(defn flash-interactions + [] (ptk/reify ::flash-interactions ptk/UpdateEvent (update [_ state] @@ -358,7 +371,7 @@ (ptk/reify ::complete-animation ptk/UpdateEvent (update [_ state] - (d/dissoc-in state [:viewer-local :current-animation])))) + (dissoc state :viewer-animation)))) ;; --- Navigation inside page @@ -367,7 +380,7 @@ (ptk/reify ::go-to-frame-by-index ptk/UpdateEvent (update [_ state] - (assoc-in state [:viewer-local :overlays] [])) + (assoc state :viewer-overlays [])) ptk/WatchEvent (watch [_ state _] @@ -378,10 +391,13 @@ (rx/of (rt/nav screen pparams (assoc qparams :index index))))))) (defn go-to-frame - ([frame-id] (go-to-frame frame-id nil)) + ([frame-id] + (go-to-frame frame-id nil)) + ([frame-id animation] - (us/verify ::us/uuid frame-id) - (us/verify (s/nilable ::cti/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::nilable-animation animation) + (ptk/reify ::go-to-frame ptk/UpdateEvent (update [_ state] @@ -393,13 +409,13 @@ frame (get frames index)] (cond-> state :always - (assoc-in [:viewer-local :overlays] []) + (assoc :viewer-overlays []) (some? animation) - (assoc-in [:viewer-local :current-animation] - {:kind :go-to-frame - :orig-frame-id (:id frame) - :animation animation})))) + (assoc :viewer-animation + {:kind :go-to-frame + :orig-frame-id (:id frame) + :animation animation})))) ptk/WatchEvent (watch [_ state _] @@ -408,8 +424,7 @@ page-id (:page-id qparams) frames (get-in state [:viewer :pages page-id :frames]) index (d/index-of-pred frames #(= (:id %) frame-id))] - (when index - (rx/of (go-to-frame-by-index index)))))))) + (rx/of (go-to-frame-by-index (or index 0)))))))) (defn go-to-frame-auto [] @@ -430,7 +445,7 @@ (ptk/reify ::go-to-section ptk/UpdateEvent (update [_ state] - (assoc-in state [:viewer-local :overlays] [])) + (assoc state :viewer-overlays [])) ptk/WatchEvent (watch [_ state _] @@ -441,95 +456,101 @@ ;; --- Overlays -(defn- do-open-overlay +(defn- open-overlay* [state frame position close-click-outside background-overlay animation] (cond-> state :always - (update-in [:viewer-local :overlays] conj - {:frame frame - :position position - :close-click-outside close-click-outside - :background-overlay background-overlay}) - (some? animation) - (assoc-in [:viewer-local :current-animation] - {:kind :open-overlay - :overlay-id (:id frame) - :animation animation}))) + (update :viewer-overlays conj + {:frame frame + :id (:id frame) + :position position + :close-click-outside close-click-outside + :background-overlay background-overlay}) -(defn- do-close-overlay + (some? animation) + (assoc :viewer-animation + {:kind :open-overlay + :overlay-id (:id frame) + :animation animation}))) + +(defn- close-overlay* [state frame-id animation] (if (nil? animation) - (update-in state [:viewer-local :overlays] - (fn [overlays] - (d/removev #(= (:id (:frame %)) frame-id) overlays))) - (assoc-in state [:viewer-local :current-animation] - {:kind :close-overlay - :overlay-id frame-id - :animation animation}))) + (update state :viewer-overlays + (fn [overlays] + (d/removev #(= (:id (:frame %)) frame-id) overlays))) + (assoc state :viewer-animation + {:kind :close-overlay + :overlay-id frame-id + :animation animation}))) (defn open-overlay [frame-id position close-click-outside background-overlay animation] - (us/verify ::us/uuid frame-id) - (us/verify ::gpt/point position) - (us/verify (s/nilable ::us/boolean) close-click-outside) - (us/verify (s/nilable ::us/boolean) background-overlay) - (us/verify (s/nilable ::cti/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::gpt/point position) + (us/assert! ::nilable-boolean close-click-outside) + (us/assert! ::nilable-boolean background-overlay) + (us/assert! ::nilable-animation animation) + (ptk/reify ::open-overlay ptk/UpdateEvent (update [_ state] (let [route (:route state) qparams (:query-params route) page-id (:page-id qparams) - frames (get-in state [:viewer :pages page-id :frames]) + frames (dm/get-in state [:viewer :pages page-id :all-frames]) frame (d/seek #(= (:id %) frame-id) frames) - overlays (get-in state [:viewer-local :overlays])] + overlays (:viewer-overlays state)] (if-not (some #(= (:frame %) frame) overlays) - (do-open-overlay state - frame - position - close-click-outside - background-overlay - animation) + (open-overlay* state + frame + position + close-click-outside + background-overlay + animation) state))))) + (defn toggle-overlay [frame-id position close-click-outside background-overlay animation] - (us/verify ::us/uuid frame-id) - (us/verify ::gpt/point position) - (us/verify (s/nilable ::us/boolean) close-click-outside) - (us/verify (s/nilable ::us/boolean) background-overlay) - (us/verify (s/nilable ::cti/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::gpt/point position) + (us/assert! ::nilable-boolean close-click-outside) + (us/assert! ::nilable-boolean background-overlay) + (us/assert! ::nilable-animation animation) + (ptk/reify ::toggle-overlay ptk/UpdateEvent (update [_ state] (let [route (:route state) qparams (:query-params route) page-id (:page-id qparams) - frames (get-in state [:viewer :pages page-id :frames]) + frames (get-in state [:viewer :pages page-id :all-frames]) frame (d/seek #(= (:id %) frame-id) frames) - overlays (get-in state [:viewer-local :overlays])] + overlays (:viewer-overlays state)] (if-not (some #(= (:frame %) frame) overlays) - (do-open-overlay state - frame - position - close-click-outside - background-overlay - animation) - (do-close-overlay state - (:id frame) - (cti/invert-direction animation))))))) + (open-overlay* state + frame + position + close-click-outside + background-overlay + animation) + (close-overlay* state + (:id frame) + (ctsi/invert-direction animation))))))) (defn close-overlay ([frame-id] (close-overlay frame-id nil)) ([frame-id animation] - (us/verify ::us/uuid frame-id) - (us/verify (s/nilable ::cti/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::nilable-animation animation) + (ptk/reify ::close-overlay ptk/UpdateEvent (update [_ state] - (do-close-overlay state - frame-id - animation))))) + (close-overlay* state + frame-id + animation))))) ;; --- Objects selection diff --git a/frontend/src/app/main/data/viewer/shortcuts.cljs b/frontend/src/app/main/data/viewer/shortcuts.cljs index 4c3aa333ec..68971ea27e 100644 --- a/frontend/src/app/main/data/viewer/shortcuts.cljs +++ b/frontend/src/app/main/data/viewer/shortcuts.cljs @@ -42,12 +42,12 @@ :fn #(st/emit! dv/toggle-fullscreen)} :next-frame {:tooltip ds/left-arrow - :command "left" + :command ["left" "up"] :subsections [:general-viewer] :fn #(st/emit! dv/select-prev-frame)} :prev-frame {:tooltip ds/right-arrow - :command "right" + :command ["right" "down"] :subsections [:general-viewer] :fn #(st/emit! dv/select-next-frame)} diff --git a/frontend/src/app/main/data/websocket.cljs b/frontend/src/app/main/data/websocket.cljs index 1fbb26770d..a0b552393c 100644 --- a/frontend/src/app/main/data/websocket.cljs +++ b/frontend/src/app/main/data/websocket.cljs @@ -7,14 +7,19 @@ (ns app.main.data.websocket (:require [app.common.data.macros :as dm] + [app.common.logging :as l] [app.common.uri :as u] [app.config :as cf] [app.util.websocket :as ws] [beicon.core :as rx] [potok.core :as ptk])) +(l/set-level! :error) + (dm/export ws/send!) +(defonce ws-conn (volatile! nil)) + (defn- prepare-uri [params] (let [base (-> (u/join cf/public-uri "ws/notifications") @@ -30,41 +35,46 @@ [message] (ptk/reify ::send-message ptk/EffectEvent - (effect [_ state _] - (let [ws-conn (:ws-conn state)] - (ws/send! ws-conn message))))) + (effect [_ _ _] + (some-> @ws-conn (ws/send! message))))) (defn initialize [] (ptk/reify ::initialize - ptk/UpdateEvent - (update [_ state] - (let [sid (:session-id state) - uri (prepare-uri {:session-id sid})] - (assoc state :ws-conn (ws/create uri)))) - ptk/WatchEvent (watch [_ state stream] - (let [ws-conn (:ws-conn state) - stoper (rx/merge - (rx/filter (ptk/type? ::finalize) stream) - (rx/filter (ptk/type? ::initialize) stream))] + (l/trace :hint "event:initialize" :fn "watch") + (let [sid (:session-id state) + uri (prepare-uri {:session-id sid}) + ws (ws/create uri)] - (->> (rx/merge - (->> (ws/get-rcv-stream ws-conn) - (rx/filter ws/message-event?) - (rx/map :payload) - (rx/map #(ptk/data-event ::message %))) - (->> (ws/get-rcv-stream ws-conn) - (rx/filter ws/opened-event?) - (rx/map (fn [_] (ptk/data-event ::opened {}))))) - (rx/take-until stoper)))))) + (vreset! ws-conn ws) + + (let [stoper (rx/merge + (rx/filter (ptk/type? ::finalize) stream) + (rx/filter (ptk/type? ::initialize) stream))] + + (->> (rx/merge + (rx/of #(assoc % :ws-conn ws)) + (->> (ws/get-rcv-stream ws) + (rx/filter ws/message-event?) + (rx/map :payload) + (rx/map #(ptk/data-event ::message %))) + (->> (ws/get-rcv-stream ws) + (rx/filter ws/opened-event?) + (rx/map (fn [_] (ptk/data-event ::opened {}))))) + (rx/take-until stoper))))))) ;; --- Finalize Websocket (defn finalize [] (ptk/reify ::finalize + ptk/UpdateEvent + (update [_ state] + (dissoc state :ws-conn)) + ptk/EffectEvent - (effect [_ state _] - (some-> (:ws-conn state) ws/close!)))) + (effect [_ _ _] + (l/trace :hint "event:finalize" :fn "effect") + (some-> @ws-conn ws/close!)))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index b69e77cb64..c333f8874e 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -13,14 +13,13 @@ [app.common.geom.point :as gpt] [app.common.geom.proportions :as gpr] [app.common.geom.shapes :as gsh] - [app.common.math :as mth] [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] - [app.common.spec.shape :as spec.shape] [app.common.text :as txt] [app.common.transit :as t] + [app.common.types.shape :as cts] [app.common.uuid :as uuid] [app.config :as cfg] [app.main.data.events :as ev] @@ -28,8 +27,10 @@ [app.main.data.users :as du] [app.main.data.workspace.bool :as dwb] [app.main.data.workspace.changes :as dch] + [app.main.data.workspace.collapse :as dwco] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.drawing :as dwd] + [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.fix-bool-contents :as fbc] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.guides :as dwgu] @@ -43,10 +44,12 @@ [app.main.data.workspace.path.shapes-to-path :as dwps] [app.main.data.workspace.persistence :as dwp] [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwth] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] + [app.main.data.workspace.viewport :as dwv] [app.main.data.workspace.zoom :as dwz] [app.main.repo :as rp] [app.main.streams :as ms] @@ -54,6 +57,7 @@ [app.util.globals :as ug] [app.util.http :as http] [app.util.i18n :as i18n] + [app.util.names :as un] [app.util.router :as rt] [app.util.timers :as tm] [app.util.webapi :as wapi] @@ -62,7 +66,7 @@ [cuerdas.core :as str] [potok.core :as ptk])) -(s/def ::shape-attrs ::spec.shape/shape-attrs) +(s/def ::shape-attrs ::cts/shape-attrs) (s/def ::set-of-string (s/every string? :kind set?)) @@ -129,7 +133,6 @@ (rx/merge (rx/of (dwn/initialize team-id file-id) (dwp/initialize-file-persistence file-id)) - (->> stream (rx/filter #(= ::dwc/index-initialized %)) (rx/take 1) @@ -141,7 +144,7 @@ (unchecked-set ug/global "name" name))))) (defn- file-initialized - [{:keys [file users project libraries] :as bundle}] + [{:keys [file users project libraries file-comments-users] :as bundle}] (ptk/reify ::file-initialized ptk/UpdateEvent (update [_ state] @@ -152,11 +155,13 @@ :workspace-project project :workspace-file (assoc file :initialized true) :workspace-data (-> (:data file) + (cph/start-object-indices) ;; DEBUG: Uncomment this to try out migrations in local without changing ;; the version number #_(assoc :version 17) #_(app.common.pages.migrations/migrate-data 19)) - :workspace-libraries (d/index-by :id libraries))) + :workspace-libraries (d/index-by :id libraries) + :current-file-comments-users (d/index-by :id file-comments-users))) ptk/WatchEvent (watch [_ _ _] @@ -263,8 +268,8 @@ ptk/WatchEvent (watch [it state _] (let [pages (get-in state [:workspace-data :pages-index]) - unames (dwc/retrieve-used-names pages) - name (dwc/generate-unique-name unames "Page-1") + unames (un/retrieve-used-names pages) + name (un/generate-unique-name unames "Page-1") changes (-> (pcb/empty-changes it) (pcb/add-empty-page id name))] @@ -278,9 +283,9 @@ (watch [it state _] (let [id (uuid/next) pages (get-in state [:workspace-data :pages-index]) - unames (dwc/retrieve-used-names pages) + unames (un/retrieve-used-names pages) page (get-in state [:workspace-data :pages-index page-id]) - name (dwc/generate-unique-name unames (:name page)) + name (un/generate-unique-name unames (:name page)) no_thumbnails_objects (->> (:objects page) (d/mapm (fn [_ val] (dissoc val :use-for-thumbnail?)))) @@ -392,140 +397,6 @@ (assoc-in state [:workspace-global :tooltip] content) (assoc-in state [:workspace-global :tooltip] nil))))) -;; --- Viewport Sizing - -(defn initialize-viewport - [{:keys [width height] :as size}] - (letfn [(update* [{:keys [vport] :as local}] - (let [wprop (/ (:width vport) width) - hprop (/ (:height vport) height)] - (-> local - (assoc :vport size) - (update :vbox (fn [vbox] - (-> vbox - (update :width #(/ % wprop)) - (update :height #(/ % hprop)))))))) - - (initialize [state local] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - shapes (cph/get-immediate-children objects) - srect (gsh/selection-rect shapes) - local (assoc local :vport size :zoom 1)] - (cond - (or (not (d/num? (:width srect))) - (not (d/num? (:height srect)))) - (assoc local :vbox (assoc size :x 0 :y 0)) - - (or (> (:width srect) width) - (> (:height srect) height)) - (let [srect (gal/adjust-to-viewport size srect {:padding 40}) - zoom (/ (:width size) (:width srect))] - (-> local - (assoc :zoom zoom) - (update :vbox merge srect))) - - :else - (assoc local :vbox (assoc size - :x (+ (:x srect) (/ (- (:width srect) width) 2)) - :y (+ (:y srect) (/ (- (:height srect) height) 2))))))) - - (setup [state local] - (if (and (:vbox local) (:vport local)) - (update* local) - (initialize state local)))] - - (ptk/reify ::initialize-viewport - ptk/UpdateEvent - (update [_ state] - (update state :workspace-local - (fn [local] - (setup state local))))))) - -(defn update-viewport-position - [{:keys [x y] :or {x identity y identity}}] - (us/assert fn? x) - (us/assert fn? y) - (ptk/reify ::update-viewport-position - ptk/UpdateEvent - (update [_ state] - (update-in state [:workspace-local :vbox] - (fn [vbox] - (-> vbox - (update :x x) - (update :y y))))))) - -(defn update-viewport-size - [resize-type {:keys [width height] :as size}] - (ptk/reify ::update-viewport-size - ptk/UpdateEvent - (update [_ state] - (update state :workspace-local - (fn [{:keys [vport] :as local}] - (if (or (nil? vport) - (mth/almost-zero? width) - (mth/almost-zero? height)) - ;; If we have a resize to zero just keep the old value - local - (let [wprop (/ (:width vport) width) - hprop (/ (:height vport) height) - - vbox (:vbox local) - vbox-x (:x vbox) - vbox-y (:y vbox) - vbox-width (:width vbox) - vbox-height (:height vbox) - - vbox-width' (/ vbox-width wprop) - vbox-height' (/ vbox-height hprop) - - vbox-x' - (case resize-type - :left (+ vbox-x (- vbox-width vbox-width')) - :right vbox-x - (+ vbox-x (/ (- vbox-width vbox-width') 2))) - - vbox-y' - (case resize-type - :top (+ vbox-y (- vbox-height vbox-height')) - :bottom vbox-y - (+ vbox-y (/ (- vbox-height vbox-height') 2)))] - (-> local - (assoc :vport size) - (assoc-in [:vbox :x] vbox-x') - (assoc-in [:vbox :y] vbox-y') - (assoc-in [:vbox :width] vbox-width') - (assoc-in [:vbox :height] vbox-height'))))))))) - -(defn start-panning [] - (ptk/reify ::start-panning - ptk/WatchEvent - (watch [_ state stream] - (let [stopper (->> stream (rx/filter (ptk/type? ::finish-panning))) - zoom (-> (get-in state [:workspace-local :zoom]) gpt/point)] - (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 - ptk/UpdateEvent - (update [_ state] - (-> state - (update :workspace-local dissoc :panning))))) - - - - ;; --- Update Shape Attrs (defn update-shape @@ -576,7 +447,7 @@ hover-guides (get-in state [:workspace-guides :hover])] (cond (d/not-empty? selected) - (rx/of (dwc/delete-shapes selected) + (rx/of (dwsh/delete-shapes selected) (dws/deselect-all)) (d/not-empty? hover-guides) @@ -794,7 +665,7 @@ ids)] (rx/of (dch/commit-changes changes) - (dwc/expand-collapse parent-id)))))) + (dwco/expand-collapse parent-id)))))) (defn relocate-selected-shapes [parent-id to-index] @@ -819,15 +690,15 @@ (case type :text - (rx/of (dwc/start-edition-mode id)) + (rx/of (dwe/start-edition-mode id)) (:group :bool) - (rx/of (dwc/select-shapes (into (d/ordered-set) [(last shapes)]))) + (rx/of (dws/select-shapes (into (d/ordered-set) [(last shapes)]))) :svg-raw nil - (rx/of (dwc/start-edition-mode id) + (rx/of (dwe/start-edition-mode id) (dwdp/start-path-edit id))))))))) @@ -1215,16 +1086,9 @@ ;; selected and its parents objects (cph/selected-subtree objects selected) - z-index (cp/calculate-z-index objects) - z-values (->> selected - (map #(vector % - (+ (get z-index %) - (get z-index (get-in objects [% :frame-id])))))) - selected - (->> z-values - (sort-by second) - (map first) - (into (d/ordered-set)))] + selected (->> (cph/sort-z-index objects selected) + (reverse) + (into (d/ordered-set)))] (assoc data :selected selected))) @@ -1241,8 +1105,8 @@ ;; Prepare the shape object. Mainly needed for image shapes ;; for retrieve the image data and convert it to the ;; data-url. - (prepare-object [objects selected {:keys [type] :as obj}] - (let [obj (maybe-translate obj objects selected)] + (prepare-object [objects selected+children {:keys [type] :as obj}] + (let [obj (maybe-translate obj objects selected+children)] (if (= type :image) (let [url (cfg/resolve-file-media (:metadata obj))] (->> (http/send! {:method :get @@ -1265,9 +1129,9 @@ (update res :images conj img-part)) res))) - (maybe-translate [shape objects selected] + (maybe-translate [shape objects selected+children] (if (and (not= (:type shape) :frame) - (not (contains? selected (:frame-id shape)))) + (not (contains? selected+children (:frame-id shape)))) ;; When the parent frame is not selected we change to relative ;; coordinates (let [frame (get objects (:frame-id shape))] @@ -1284,6 +1148,8 @@ (let [objects (wsh/lookup-page-objects state) selected (->> (wsh/lookup-selected state) (cph/clean-loops objects)) + + selected+children (cph/selected-with-children objects selected) pdata (reduce (partial collect-object-ids objects) {} selected) initial {:type :copied-shapes :file-id (:current-file-id state) @@ -1293,7 +1159,7 @@ (->> (rx/from (seq (vals pdata))) - (rx/merge-map (partial prepare-object objects selected)) + (rx/merge-map (partial prepare-object objects selected+children)) (rx/reduce collect-data initial) (rx/map (partial sort-selected state)) (rx/map t/encode-str) @@ -1553,7 +1419,7 @@ (into (d/ordered-set)))] (rx/of (dch/commit-changes changes) - (dwc/select-shapes selected))))] + (dws/select-shapes selected))))] (ptk/reify ::paste-shape ptk/WatchEvent @@ -1602,7 +1468,7 @@ :content (as-content text)})] (rx/of (dwu/start-undo-transaction) (dws/deselect-all) - (dwc/add-shape shape) + (dwsh/add-shape shape) (dwu/commit-undo-transaction)))))) ;; TODO: why not implement it in terms of upload-media-workspace? @@ -1673,21 +1539,23 @@ (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - shapes (cph/get-immediate-children objects) selected (wsh/lookup-selected state) - selected-objs (map #(get objects %) selected) - has-frame? (some #(= (:type %) :frame) selected-objs)] - (when (not (or (empty? selected) has-frame?)) + selected-objs (map #(get objects %) selected)] + (when (d/not-empty? selected) (let [srect (gsh/selection-rect selected-objs) - frame-id (:frame-id (first shapes)) + frame-id (get-in objects [(first selected) :frame-id]) + parent-id (get-in objects [(first selected) :parent-id]) shape (-> (cp/make-minimal-shape :frame) (merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)}) - (assoc :frame-id frame-id) + (assoc :frame-id frame-id :parent-id parent-id) + (cond-> (not= frame-id uuid/zero) + (assoc :fills [] :hide-in-viewer true)) (cp/setup-rect-selrect))] (rx/of (dwu/start-undo-transaction) - (dwc/add-shape shape) - (dwc/move-shapes-into-frame (:id shape) selected) + (dwsh/add-shape shape) + (dwsh/move-shapes-into-frame (:id shape) selected) + (dwu/commit-undo-transaction)))))))) @@ -1710,10 +1578,10 @@ (dm/export dwly/set-opacity) ;; Common -(dm/export dwc/add-shape) -(dm/export dwc/clear-edition-mode) -(dm/export dwc/select-shapes) -(dm/export dwc/start-edition-mode) +(dm/export dwsh/add-shape) +(dm/export dwe/clear-edition-mode) +(dm/export dws/select-shapes) +(dm/export dwe/start-edition-mode) ;; Drawing (dm/export dwd/select-for-drawing) @@ -1761,3 +1629,10 @@ ;; Thumbnails (dm/export dwth/update-thumbnail) + +;; Viewport +(dm/export dwv/initialize-viewport) +(dm/export dwv/update-viewport-position) +(dm/export dwv/update-viewport-size) +(dm/export dwv/start-panning) +(dm/export dwv/finish-panning) diff --git a/frontend/src/app/main/data/workspace/bool.cljs b/frontend/src/app/main/data/workspace/bool.cljs index 2c316d294e..771c06bb26 100644 --- a/frontend/src/app/main/data/workspace/bool.cljs +++ b/frontend/src/app/main/data/workspace/bool.cljs @@ -13,8 +13,9 @@ [app.common.path.shapes-to-path :as stp] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] + [app.util.names :as un] [beicon.core :as rx] [cuerdas.core :as str] [potok.core :as ptk])) @@ -89,8 +90,8 @@ (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state) base-name (-> bool-type d/name str/capital (str "-1")) - name (-> (dwc/retrieve-used-names objects) - (dwc/generate-unique-name base-name)) + name (-> (un/retrieve-used-names objects) + (un/generate-unique-name base-name)) shapes (selected-shapes state)] (when-not (empty? shapes) @@ -101,7 +102,7 @@ (pcb/add-object boolean-data {:index index}) (pcb/change-parent shape-id shapes))] (rx/of (dch/commit-changes changes) - (dwc/select-shapes (d/ordered-set shape-id))))))))) + (dws/select-shapes (d/ordered-set shape-id))))))))) (defn group-to-bool [shape-id bool-type] diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index fa2235c1a1..209c8f5492 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -10,8 +10,9 @@ [app.common.logging :as log] [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] + [app.common.pages.changes-spec :as pcs] + [app.common.pages.helpers :as cph] [app.common.spec :as us] - [app.common.spec.change :as spec.change] [app.common.uuid :as uuid] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] @@ -126,9 +127,7 @@ []))] (into #{} (comp (mapcat change->ids) - (keep #(if (= :frame (get-in objects [% :type])) - % - (get-in objects [% :frame-id]))) + (keep #(cph/get-shape-id-root-frame objects %)) (remove #(= uuid/zero %))) changes))) @@ -160,10 +159,13 @@ [:workspace-data] [:workspace-libraries file-id :data])] (try - (us/assert ::spec.change/changes redo-changes) - (us/assert ::spec.change/changes undo-changes) + (us/assert ::pcs/changes redo-changes) + (us/assert ::pcs/changes undo-changes) - (update-in state path cp/process-changes redo-changes false) + (update-in state path (fn [file] + (-> file + (cp/process-changes redo-changes false) + (cph/update-object-indices page-id)))) (catch :default err (log/error :js/error err) diff --git a/frontend/src/app/main/data/workspace/collapse.cljs b/frontend/src/app/main/data/workspace/collapse.cljs new file mode 100644 index 0000000000..4c23b1747f --- /dev/null +++ b/frontend/src/app/main/data/workspace/collapse.cljs @@ -0,0 +1,51 @@ +;; 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.collapse + (:require + [app.common.pages.helpers :as cph] + [app.common.uuid :as uuid] + [potok.core :as ptk])) + +;; --- Shape attrs (Layers Sidebar) + +(defn expand-all-parents + [ids objects] + (ptk/reify ::expand-all-parents + ptk/UpdateEvent + (update [_ state] + (let [expand-fn (fn [expanded] + (merge expanded + (->> ids + (map #(cph/get-parent-ids objects %)) + flatten + (remove #(= % uuid/zero)) + (map (fn [id] {id true})) + (into {}))))] + (update-in state [:workspace-local :expanded] expand-fn))))) + + +(defn toggle-collapse + [id] + (ptk/reify ::toggle-collapse + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-local :expanded id] not)))) + +(defn expand-collapse + [id] + (ptk/reify ::expand-collapse + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :expanded id] true)))) + +(defn collapse-all + [] + (ptk/reify ::collapse-all + ptk/UpdateEvent + (update [_ state] + (update state :workspace-local dissoc :expanded)))) + diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index d1243cbea0..61f91a062e 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -6,79 +6,32 @@ (ns app.main.data.workspace.colors (:require - [app.common.colors :as clr] + [app.common.colors :as colors] [app.common.data :as d] [app.common.pages.helpers :as cph] + [app.main.broadcast :as mbc] [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.layout :as layout] + [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.texts :as dwt] - [app.main.repo :as rp] [app.util.color :as uc] [beicon.core :as rx] [potok.core :as ptk])) -(def clear-color-for-rename - (ptk/reify ::clear-color-for-rename - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-global :color-for-rename] nil)))) - -(declare rename-color-result) - -(defn rename-color - [file-id color-id name] - (ptk/reify ::rename-color - ptk/WatchEvent - (watch [_ _ _] - (->> (rp/mutation! :rename-color {:id color-id :name name}) - (rx/map (partial rename-color-result file-id)))))) - -(defn rename-color-result - [_file-id color] - (ptk/reify ::rename-color-result - ptk/UpdateEvent - (update [_ state] - (update-in state [:workspace-file :colors] #(d/replace-by-id % color))))) - -(defn change-palette-selected - "Change the library used by the general palette tool" - [selected] - (ptk/reify ::change-palette-selected - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-global :selected-palette] selected)) - - ptk/EffectEvent - (effect [_ state _] - (let [wglobal (:workspace-global state)] - (layout/persist-layout-state! wglobal))))) - -(defn change-palette-selected-colorpicker - "Change the library used by the color picker" - [selected] - (ptk/reify ::change-palette-selected-colorpicker - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-global :selected-palette-colorpicker] selected)) - - ptk/EffectEvent - (effect [_ state _] - (let [wglobal (:workspace-global state)] - (layout/persist-layout-state! wglobal))))) +;; A set of keys that are used for shared state identifiers +(def ^:const colorpicker-selected-broadcast-key ::colorpicker-selected) +(def ^:const colorpalette-selected-broadcast-key ::colorpalette-selected) (defn show-palette "Show the palette tool and change the library it uses" [selected] (ptk/reify ::show-palette - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-global :selected-palette] selected)) - ptk/WatchEvent (watch [_ _ _] - (rx/of (layout/toggle-layout-flag :colorpalette :force? true))) + (rx/of (layout/toggle-layout-flag :colorpalette :force? true) + (mbc/event colorpalette-selected-broadcast-key selected))) ptk/EffectEvent (effect [_ state _] @@ -182,10 +135,10 @@ ptk/WatchEvent (watch [_ state _] (let [change-fn (fn [shape attrs] - (-> shape - (cond-> (not (contains? shape :fills)) - (assoc :fills [])) - (assoc-in [:fills position] (into {} attrs))))] + (-> shape + (cond-> (not (contains? shape :fills)) + (assoc :fills [])) + (assoc-in [:fills position] (into {} attrs))))] (transform-fill state ids color change-fn))))) (defn change-fill-and-clear @@ -366,45 +319,11 @@ (-> state (assoc-in [:workspace-global :picking-color?] true) (assoc ::md/modal {:id (random-uuid) - :data {:color clr/black :opacity 1} + :data {:color colors/black :opacity 1} :type :colorpicker :props {:on-change handle-change-color} :allow-click-outside true}))))))) -(defn start-gradient - [gradient] - (ptk/reify ::start-gradient - ptk/UpdateEvent - (update [_ state] - (let [id (-> state wsh/lookup-selected first)] - (-> state - (assoc-in [:workspace-global :current-gradient] gradient) - (assoc-in [:workspace-global :current-gradient :shape-id] id)))))) - -(defn stop-gradient - [] - (ptk/reify ::stop-gradient - ptk/UpdateEvent - (update [_ state] - (-> state - (update :workspace-global dissoc :current-gradient))))) - -(defn update-gradient - [changes] - (ptk/reify ::update-gradient - ptk/UpdateEvent - (update [_ state] - (-> state - (update-in [:workspace-global :current-gradient] merge changes))))) - -(defn select-gradient-stop - [spot] - (ptk/reify ::select-gradient-stop - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:workspace-global :editing-stop] spot))))) - (defn color-att->text [color] {:fill-color (:color color) @@ -433,7 +352,9 @@ :fill (change-fill [(:shape-id shape)] new-color (:index shape)) :stroke (change-stroke [(:shape-id shape)] new-color (:index shape)) :shadow (change-shadow [(:shape-id shape)] new-color (:index shape)) - :content (dwt/update-text-with-function (:shape-id shape) (partial change-text-color old-color new-color (:index shape)))))))))) + :content (dwt/update-text-with-function + (:shape-id shape) + (partial change-text-color old-color new-color (:index shape)))))))))) (defn apply-color-from-palette [color is-alt?] @@ -455,3 +376,177 @@ (if is-alt? (rx/of (change-stroke ids (merge uc/empty-color color) 0)) (rx/of (change-fill ids (merge uc/empty-color color) 0))))))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; COLORPICKER STATE MANAGEMENT +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn split-color-components + [{:keys [color opacity] :as data}] + (let [value (if (uc/hex? color) color colors/black) + [r g b] (uc/hex->rgb value) + [h s v] (uc/hex->hsv value)] + (merge data + {:hex (or value "000000") + :alpha (or opacity 1) + :r r :g g :b b + :h h :s s :v v}))) + +(defn materialize-color-components + [{:keys [hex alpha] :as data}] + (-> data + (assoc :color hex) + (assoc :opacity alpha))) + +(defn clear-color-components + [data] + (dissoc data :hex :alpha :r :g :b :h :s :v)) + +(defn- create-gradient + [type] + {:start-x 0.5 + :start-y (if (= type :linear-gradient) 0.0 0.5) + :end-x 0.5 + :end-y 1 + :width 1.0}) + +(defn get-color-from-colorpicker-state + [{:keys [type current-color stops gradient] :as state}] + (if (= type :color) + (clear-color-components current-color) + {:gradient (-> gradient + (assoc :type (case type + :linear-gradient :linear + :radial-gradient :radial)) + (assoc :stops (mapv clear-color-components stops)) + (dissoc :shape-id))})) + +(defn- colorpicker-onchange-runner + "Effect event that runs the on-change callback with the latest + colorpicker state converted to color object." + [on-change] + (ptk/reify ::colorpicker-onchange-runner + ptk/WatchEvent + (watch [_ state _] + (when-let [color (some-> state :colorpicker get-color-from-colorpicker-state)] + (on-change color) + (rx/of (dwl/add-recent-color color)))))) + +(defn initialize-colorpicker + [on-change] + (ptk/reify ::initialize-colorpicker + ptk/WatchEvent + (watch [_ _ stream] + (let [stoper (rx/merge + (rx/filter (ptk/type? ::finalize-colorpicker) stream) + (rx/filter (ptk/type? ::initialize-colorpicker) stream))] + + (->> (rx/merge + (->> stream + (rx/filter (ptk/type? ::update-colorpicker-gradient)) + (rx/debounce 200)) + (rx/filter (ptk/type? ::update-colorpicker-color) stream) + (rx/filter (ptk/type? ::activate-colorpicker-gradient) stream)) + (rx/map (constantly (colorpicker-onchange-runner on-change))) + (rx/take-until stoper)))))) + +(defn finalize-colorpicker + [] + (ptk/reify ::finalize-colorpicker + ptk/UpdateEvent + (update [_ state] + (dissoc state :colorpicker)))) + +(defn update-colorpicker + [{:keys [gradient] :as data}] + (ptk/reify ::update-colorpicker + ptk/UpdateEvent + (update [_ state] + (let [shape-id (-> state wsh/lookup-selected first)] + (update state :colorpicker + (fn [state] + (if (some? gradient) + (let [stop (or (:editing-stop state) 0) + stops (mapv split-color-components (:stops gradient)) + type (case (:type gradient) + :linear :linear-gradient + :radial :radial-gradient)] + (-> state + (assoc :type type) + (assoc :current-color (nth stops stop)) + (assoc :stops stops) + (assoc :gradient (-> gradient + (dissoc :stops) + (assoc :shape-id shape-id))) + (assoc :editing-stop stop))) + + (-> state + (assoc :type :color) + (assoc :current-color (split-color-components (dissoc data :gradient))) + (dissoc :editing-stop) + (dissoc :gradient) + (dissoc :stops))))))))) + +(defn update-colorpicker-color + [changes] + (ptk/reify ::update-colorpicker-color + ptk/UpdateEvent + (update [_ state] + (update state :colorpicker + (fn [state] + (let [state (-> state + (update :current-color merge changes) + (update :current-color materialize-color-components))] + (if-let [stop (:editing-stop state)] + (update-in state [:stops stop] (fn [data] (->> changes + (merge data) + (materialize-color-components)))) + (-> state + (assoc :type :color) + (dissoc :gradient :stops :editing-stop))))))))) + +(defn update-colorpicker-gradient + [changes] + (ptk/reify ::update-colorpicker-gradient + ptk/UpdateEvent + (update [_ state] + (update-in state [:colorpicker :gradient] merge changes)))) + +(defn select-colorpicker-gradient-stop + [stop] + (ptk/reify ::select-colorpicket-gradient-stop + ptk/UpdateEvent + (update [_ state] + (update state :colorpicker + (fn [state] + (if-let [color (get-in state [:stops stop])] + (assoc state + :current-color color + :editing-stop stop) + state)))))) + +(defn activate-colorpicker-gradient + [type] + (ptk/reify ::activate-colorpicker-gradient + ptk/UpdateEvent + (update [_ state] + (update state :colorpicker + (fn [state] + (if (= type (:type state)) + (do + (-> state + (assoc :type :color) + (dissoc :editing-stop :stops :gradient))) + (let [gradient (create-gradient type) + color (:current-color state)] + (-> state + (assoc :type type) + (assoc :gradient gradient) + (cond-> (not (:stops state)) + (assoc :editing-stop 0 + :stops [(assoc color :offset 0) + (-> color + (assoc :alpha 0) + (assoc :offset 1) + (materialize-color-components))])))))))))) diff --git a/frontend/src/app/main/data/workspace/comments.cljs b/frontend/src/app/main/data/workspace/comments.cljs index b7f9f20308..2ce75718e5 100644 --- a/frontend/src/app/main/data/workspace/comments.cljs +++ b/frontend/src/app/main/data/workspace/comments.cljs @@ -6,10 +6,18 @@ (ns app.main.data.workspace.comments (:require + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.pages.changes-builder :as pcb] + [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.main.data.comments :as dcm] - [app.main.data.workspace :as dw] - [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.changes :as dwc] + [app.main.data.workspace.common :as dwco] + [app.main.data.workspace.drawing :as dwd] + [app.main.data.workspace.state-helpers :as wsh] + [app.main.data.workspace.viewport :as dwv] + [app.main.repo :as rp] [app.main.streams :as ms] [app.util.router :as rt] [beicon.core :as rx] @@ -33,7 +41,7 @@ (rx/map handle-comment-layer-click) (rx/take-until stoper)) (->> stream - (rx/filter dwc/interrupt?) + (rx/filter dwco/interrupt?) (rx/map handle-interrupt) (rx/take-until stoper))))))) @@ -95,8 +103,75 @@ (rx/merge (rx/of (rt/nav :workspace pparams qparams)) (->> stream - (rx/filter (ptk/type? ::dw/initialize-viewport)) + (rx/filter (ptk/type? ::dwv/initialize-viewport)) (rx/take 1) (rx/mapcat #(rx/of (center-to-comment-thread thread) - (dw/select-for-drawing :comments) + (dwd/select-for-drawing :comments) (dcm/open-thread thread))))))))) + +(defn update-comment-thread-position + ([thread [new-x new-y]] + (update-comment-thread-position thread [new-x new-y] nil)) + + ([thread [new-x new-y] frame-id] + (us/assert ::dcm/comment-thread thread) + (ptk/reify ::update-comment-thread-position + ptk/WatchEvent + (watch [it state _] + (let [thread-id (:id thread) + page (wsh/lookup-page state) + page-id (:id page) + objects (wsh/lookup-page-objects state page-id) + new-frame-id (if (nil? frame-id) + (cph/frame-id-by-position objects {:x new-x :y new-y}) + (:frame-id thread)) + thread (assoc thread + :position {:x new-x :y new-y} + :frame-id new-frame-id) + + changes + (-> (pcb/empty-changes it) + (pcb/with-page page) + (pcb/update-page-option :comment-threads-position assoc thread-id (select-keys thread [:position :frame-id])))] + + (rx/merge + (rx/of (dwc/commit-changes changes)) + (->> (rp/cmd! :update-comment-thread-position thread) + (rx/catch #(rx/throw {:type :update-comment-thread-position})) + (rx/ignore)))))))) + +(defn move-frame-comment-threads + "Move comment threads that are inside a frame when that frame is moved" + [ids] + (us/assert! ::us/coll-of-uuid ids) + (ptk/reify ::move-frame-comment-threads + ptk/WatchEvent + (watch [_ state _] + (let [objects (wsh/lookup-page-objects state) + + is-frame? (fn [id] (= :frame (get-in objects [id :type]))) + frame-ids? (into #{} (filter is-frame?) ids) + + object-modifiers (:workspace-modifiers state) + + threads-position-map (:comment-threads-position (wsh/lookup-page-options state)) + + build-move-event + (fn [comment-thread] + (let [frame (get objects (:frame-id comment-thread)) + frame' (-> (merge frame (get object-modifiers (:frame-id comment-thread))) + (gsh/transform-shape)) + moved (gpt/to-vec (gpt/point (:x frame) (:y frame)) + (gpt/point (:x frame') (:y frame'))) + position (get-in threads-position-map [(:id comment-thread) :position]) + new-x (+ (:x position) (:x moved)) + new-y (+ (:y position) (:y moved))] + (update-comment-thread-position comment-thread [new-x new-y] (:id frame))))] + + (->> (:comment-threads state) + (vals) + (map #(assoc % :position (get-in threads-position-map [(:id %) :position]))) + (map #(assoc % :frame-id (get-in threads-position-map [(:id %) :frame-id]))) + (filter (comp frame-ids? :frame-id)) + (map build-move-event) + (rx/from)))))) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index bdc2b61103..d9671bfedf 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -6,34 +6,16 @@ (ns app.main.data.workspace.common (:require - [app.common.data :as d] - [app.common.geom.proportions :as gpr] - [app.common.geom.shapes :as gsh] [app.common.logging :as log] - [app.common.pages :as cp] - [app.common.pages.changes-builder :as pcb] - [app.common.pages.helpers :as cph] - [app.common.spec :as us] - [app.common.spec.interactions :as csi] - [app.common.spec.page :as csp] - [app.common.spec.shape :as spec.shape] - [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] - [app.main.streams :as ms] [app.main.worker :as uw] [beicon.core :as rx] - [cljs.spec.alpha :as s] [potok.core :as ptk])) ;; Change this to :info :debug or :trace to debug this module (log/set-level! :warn) -(s/def ::shape-attrs ::spec.shape/shape-attrs) -(s/def ::set-of-string (s/every string? :kind set?)) -(s/def ::ordered-set-of-uuid (s/every uuid? :kind d/ordered-set?)) - (defn initialized? "Check if the state is properly intialized in a workspace. This means it has the `:current-page-id` and `:current-file-id` properly set." @@ -57,64 +39,6 @@ (->> (uw/ask! msg) (rx/map (constantly ::index-initialized))))))) -;; --- Common Helpers & Events - -;; TODO: looks duplicate - -(defn get-frame-at-point - [objects point] - (let [frames (cph/get-frames objects)] - (d/seek #(gsh/has-point? % point) frames))) - -(defn- extract-numeric-suffix - [basename] - (if-let [[_ p1 p2] (re-find #"(.*)-([0-9]+)$" basename)] - [p1 (+ 1 (d/parse-integer p2))] - [basename 1])) - -(defn retrieve-used-names - [objects] - (into #{} (comp (map :name) (remove nil?)) (vals objects))) - - -(defn generate-unique-name - "A unique name generator" - [used basename] - (s/assert ::set-of-string used) - (s/assert ::us/string basename) - (if-not (contains? used basename) - basename - (let [[prefix initial] (extract-numeric-suffix basename)] - (loop [counter initial] - (let [candidate (str prefix "-" counter)] - (if (contains? used candidate) - (recur (inc counter)) - candidate)))))) - -;; --- Shape attrs (Layers Sidebar) - -(defn toggle-collapse - [id] - (ptk/reify ::toggle-collapse - ptk/UpdateEvent - (update [_ state] - (update-in state [:workspace-local :expanded id] not)))) - -(defn expand-collapse - [id] - (ptk/reify ::expand-collapse - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-local :expanded id] true)))) - -(defn collapse-all - [] - (ptk/reify ::collapse-all - ptk/UpdateEvent - (update [_ state] - (update state :workspace-local dissoc :expanded)))) - - ;; These functions should've been in `src/app/main/data/workspace/undo.cljs` but doing that causes ;; a circular dependency with `src/app/main/data/workspace/changes.cljs` (def undo @@ -185,313 +109,3 @@ :origin it :save-undo? false}))))))))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Shapes -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn expand-all-parents - [ids objects] - (ptk/reify ::expand-all-parents - ptk/UpdateEvent - (update [_ state] - (let [expand-fn (fn [expanded] - (merge expanded - (->> ids - (map #(cph/get-parent-ids objects %)) - flatten - (remove #(= % uuid/zero)) - (map (fn [id] {id true})) - (into {}))))] - (update-in state [:workspace-local :expanded] expand-fn))))) - -;; --- Update Shape Attrs - -;; NOTE: This is a generic implementation for update multiple shapes -;; in one single commit/undo entry. - - -(defn select-shapes - [ids] - (us/verify ::ordered-set-of-uuid ids) - (ptk/reify ::select-shapes - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-local :selected] ids)) - - ptk/WatchEvent - (watch [_ state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id)] - (rx/of (expand-all-parents ids objects)))))) - -(declare clear-edition-mode) - -(defn start-edition-mode - [id] - (us/assert ::us/uuid id) - (ptk/reify ::start-edition-mode - ptk/UpdateEvent - (update [_ state] - (let [objects (wsh/lookup-page-objects state)] - ;; Can only edit objects that exist - (if (contains? objects id) - (-> state - (assoc-in [:workspace-local :selected] #{id}) - (assoc-in [:workspace-local :edition] id)) - state))) - - ptk/WatchEvent - (watch [_ _ stream] - (->> stream - (rx/filter interrupt?) - (rx/take 1) - (rx/map (constantly clear-edition-mode)))))) - -;; If these event change modules review /src/app/main/data/workspace/path/undo.cljs -(def clear-edition-mode - (ptk/reify ::clear-edition-mode - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition])] - (-> state - (update :workspace-local dissoc :edition) - (cond-> (some? id) (update-in [:workspace-local :edit-path] dissoc id))))))) - -(defn get-shape-layer-position - [objects selected attrs] - - (if (= :frame (:type attrs)) - ;; Frames are always positioned on the root frame - [uuid/zero uuid/zero nil] - - ;; Calculate the frame over which we're drawing - (let [position @ms/mouse-position - frame-id (:frame-id attrs (cph/frame-id-by-position objects position)) - shape (when-not (empty? selected) - (cph/get-base-shape objects selected))] - - ;; When no shapes has been selected or we're over a different frame - ;; we add it as the latest shape of that frame - (if (or (not shape) (not= (:frame-id shape) frame-id)) - [frame-id frame-id nil] - - ;; Otherwise, we add it to next to the selected shape - (let [index (cph/get-position-on-parent objects (:id shape)) - {:keys [frame-id parent-id]} shape] - [frame-id parent-id (inc index)]))))) - -(defn make-new-shape - [attrs objects selected] - (let [default-attrs (if (= :frame (:type attrs)) - cp/default-frame-attrs - cp/default-shape-attrs) - - selected-non-frames - (into #{} (comp (map (d/getf objects)) - (remove cph/frame-shape?)) - selected) - - [frame-id parent-id index] - (get-shape-layer-position objects selected-non-frames attrs)] - - (-> (merge default-attrs attrs) - (gpr/setup-proportions) - (assoc :frame-id frame-id - :parent-id parent-id - :index index)))) - -(defn add-shape - ([attrs] - (add-shape attrs {})) - - ([attrs {:keys [no-select?]}] - (us/verify ::shape-attrs attrs) - (ptk/reify ::add-shape - ptk/WatchEvent - (watch [it state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - selected (wsh/lookup-selected state) - - id (or (:id attrs) (uuid/next)) - name (-> objects - (retrieve-used-names) - (generate-unique-name (:name attrs))) - - shape (make-new-shape - (assoc attrs :id id :name name) - objects - selected) - - changes (-> (pcb/empty-changes it page-id) - (pcb/add-object shape {:index (when (= :frame (:type shape)) 0)}))] - - (rx/concat - (rx/of (dch/commit-changes changes) - (when-not no-select? - (select-shapes (d/ordered-set id)))) - (when (= :text (:type attrs)) - (->> (rx/of (start-edition-mode id)) - (rx/observe-on :async))))))))) - -(defn move-shapes-into-frame [frame-id shapes] - (ptk/reify ::move-shapes-into-frame - ptk/WatchEvent - (watch [it state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - - to-move-shapes (->> (cph/get-immediate-children objects) - (remove cph/frame-shape?) - (d/enumerate) - (filterv (comp shapes :id second)) - (mapv second)) - - changes (-> (pcb/empty-changes it page-id) - (pcb/with-objects objects) - (pcb/change-parent frame-id to-move-shapes 0))] - - (rx/of (dch/commit-changes changes)))))) - -(s/def ::set-of-uuid - (s/every ::us/uuid :kind set?)) - -(defn delete-shapes - [ids] - (us/assert ::set-of-uuid ids) - (ptk/reify ::delete-shapes - ptk/WatchEvent - (watch [it state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - page (wsh/lookup-page state page-id) - - ids (cph/clean-loops objects ids) - - groups-to-unmask - (reduce (fn [group-ids id] - ;; When the shape to delete is the mask of a masked group, - ;; the mask condition must be removed, and it must be - ;; converted to a normal group. - (let [obj (get objects id) - parent (get objects (:parent-id obj))] - (if (and (:masked-group? parent) - (= id (first (:shapes parent)))) - (conj group-ids (:id parent)) - group-ids))) - #{} - ids) - - interacting-shapes - (filter (fn [shape] - ;; If any of the deleted shapes is the destination of - ;; some interaction, this must be deleted, too. - (let [interactions (:interactions shape)] - (some #(and (csi/has-destination %) - (contains? ids (:destination %))) - interactions))) - (vals objects)) - - ;; If any of the deleted shapes is a frame with guides - guides (into {} (map (juxt :id identity) (->> (get-in page [:options :guides]) - (vals) - (filter #(not (contains? ids (:frame-id %))))))) - - starting-flows - (filter (fn [flow] - ;; If any of the deleted is a frame that starts a flow, - ;; this must be deleted, too. - (contains? ids (:starting-frame flow))) - (-> page :options :flows)) - - all-parents - (reduce (fn [res id] - ;; All parents of any deleted shape must be resized. - (into res (cph/get-parent-ids objects id))) - (d/ordered-set) - ids) - - all-children - (->> ids ;; Children of deleted shapes must be also deleted. - (reduce (fn [res id] - (into res (cph/get-children-ids objects id))) - []) - (reverse) - (into (d/ordered-set))) - - find-all-empty-parents (fn recursive-find-empty-parents [empty-parents] - (let [all-ids (into empty-parents ids) - empty-parents-xform - (comp - (map (fn [id] (get objects id))) - (map (fn [{:keys [shapes type] :as obj}] - (when (and (= :group type) - (zero? (count (remove #(contains? all-ids %) shapes)))) - obj))) - (take-while some?) - (map :id)) - calculated-empty-parents (into #{} empty-parents-xform all-parents)] - - (if (= empty-parents calculated-empty-parents) - empty-parents - (recursive-find-empty-parents calculated-empty-parents)))) - - empty-parents - ;; Any parent whose children are all deleted, must be deleted too. - (into (d/ordered-set) (find-all-empty-parents #{})) - - changes (-> (pcb/empty-changes it page-id) - (pcb/with-page page) - (pcb/with-objects objects) - (pcb/set-page-option :guides guides) - (pcb/remove-objects all-children) - (pcb/remove-objects ids) - (pcb/remove-objects empty-parents) - (pcb/resize-parents all-parents) - (pcb/update-shapes groups-to-unmask - (fn [shape] - (assoc shape :masked-group? false))) - (pcb/update-shapes (map :id interacting-shapes) - (fn [shape] - (update shape :interactions - (fn [interactions] - (when interactions - (d/removev #(and (csi/has-destination %) - (contains? ids (:destination %))) - interactions)))))) - (cond-> - (seq starting-flows) - (pcb/update-page-option :flows (fn [flows] - (reduce #(csp/remove-flow %1 (:id %2)) - flows - starting-flows)))))] - - (rx/of (dch/commit-changes changes)))))) - -;; --- Add shape to Workspace - -(defn- viewport-center - [state] - (let [{:keys [x y width height]} (get-in state [:workspace-local :vbox])] - [(+ x (/ width 2)) (+ y (/ height 2))])) - -(defn create-and-add-shape - [type frame-x frame-y data] - (ptk/reify ::create-and-add-shape - ptk/WatchEvent - (watch [_ state _] - (let [{:keys [width height]} data - - [vbc-x vbc-y] (viewport-center state) - x (:x data (- vbc-x (/ width 2))) - y (:y data (- vbc-y (/ height 2))) - page-id (:current-page-id state) - frame-id (-> (wsh/lookup-page-objects state page-id) - (cph/frame-id-by-position {:x frame-x :y frame-y})) - shape (-> (cp/make-minimal-shape type) - (merge data) - (merge {:x x :y y}) - (assoc :frame-id frame-id) - (cp/setup-rect-selrect))] - (rx/of (add-shape shape)))))) diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index c2eeefc703..bdffbe1f70 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -65,22 +65,21 @@ focus (:workspace-focus-selected state) zoom (get-in state [:workspace-local :zoom] 1) - frames (cph/get-frames objects) - fid (or (->> frames - (filter #(gsh/has-point? % initial)) - first - :id) - uuid/zero) + fid (cph/frame-id-by-position objects initial) - shape (-> state - (get-in [:workspace-drawing :object]) - (cp/setup-shape {:x (:x initial) - :y (:y initial) - :width 0.01 - :height 0.01}) - (assoc :frame-id fid) - (assoc :initialized? true) - (assoc :click-draw? true))] + shape (get-in state [:workspace-drawing :object]) + shape (-> shape + (cp/setup-shape {:x (:x initial) + :y (:y initial) + :width 0.01 + :height 0.01}) + (cond-> (and (cph/frame-shape? shape) + (not= fid uuid/zero)) + (assoc :fills [] :hide-in-viewer true)) + + (assoc :frame-id fid) + (assoc :initialized? true) + (assoc :click-draw? true))] (rx/concat ;; Add shape to drawing state (rx/of #(assoc-in state [:workspace-drawing :object] shape)) diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index 3bd78df3ff..03f956aa59 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -10,7 +10,9 @@ [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.pages :as cp] - [app.main.data.workspace.common :as dwc] + [app.common.pages.helpers :as cph] + [app.main.data.workspace.shapes :as dwsh] + [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] [app.main.worker :as uw] [beicon.core :as rx] @@ -29,7 +31,8 @@ ptk/WatchEvent (watch [_ state _] (let [tool (get-in state [:workspace-drawing :tool]) - shape (get-in state [:workspace-drawing :object])] + shape (get-in state [:workspace-drawing :object]) + objects (wsh/lookup-page-objects state)] (rx/concat (when (:initialized? shape) (let [page-id (:current-page-id state) @@ -63,13 +66,16 @@ (rx/of (dwu/start-undo-transaction)) (rx/empty)) - (rx/of (dwc/add-shape shape {:no-select? (= tool :curve)})) + (rx/of (dwsh/add-shape shape {:no-select? (= tool :curve)})) (if (= :frame (:type shape)) (->> (uw/ask! {:cmd :selection/query :page-id page-id - :rect (:selrect shape)}) - (rx/map #(dwc/move-shapes-into-frame (:id shape) %))) + :rect (:selrect shape) + :include-frames? true + :full-frame? true}) + (rx/map #(cph/clean-loops objects %)) + (rx/map #(dwsh/move-shapes-into-frame (:id shape) %))) (rx/empty))))) ;; Delay so the mouse event can read the drawing state diff --git a/frontend/src/app/main/data/workspace/edition.cljs b/frontend/src/app/main/data/workspace/edition.cljs new file mode 100644 index 0000000000..b5514b6f32 --- /dev/null +++ b/frontend/src/app/main/data/workspace/edition.cljs @@ -0,0 +1,47 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.data.workspace.edition + (:require + [app.common.spec :as us] + [app.main.data.workspace.state-helpers :as wsh] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defn interrupt? [e] (= e :interrupt)) + +(declare clear-edition-mode) + +(defn start-edition-mode + [id] + (us/assert ::us/uuid id) + (ptk/reify ::start-edition-mode + ptk/UpdateEvent + (update [_ state] + (let [objects (wsh/lookup-page-objects state)] + ;; Can only edit objects that exist + (if (contains? objects id) + (-> state + (assoc-in [:workspace-local :selected] #{id}) + (assoc-in [:workspace-local :edition] id)) + state))) + + ptk/WatchEvent + (watch [_ _ stream] + (->> stream + (rx/filter interrupt?) + (rx/take 1) + (rx/map (constantly clear-edition-mode)))))) + +;; If these event change modules review /src/app/main/data/workspace/path/undo.cljs +(def clear-edition-mode + (ptk/reify ::clear-edition-mode + ptk/UpdateEvent + (update [_ state] + (let [id (get-in state [:workspace-local :edition])] + (-> state + (update :workspace-local dissoc :edition) + (cond-> (some? id) (update-in [:workspace-local :edit-path] dissoc id))))))) diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index fc7d905be3..2b315fc5e1 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -12,8 +12,9 @@ [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] + [app.util.names :as un] [beicon.core :as rx] [potok.core :as ptk])) @@ -21,7 +22,6 @@ [objects selected] (->> selected (map #(get objects %)) - (filter #(not= :frame (:type %))) (map #(assoc % ::index (cph/get-position-on-parent objects (:id %)))) (sort-by ::index))) @@ -71,8 +71,8 @@ (= (count shapes) 1) (= (:type (first shapes)) :group)) (:name (first shapes)) - (-> (dwc/retrieve-used-names objects) - (dwc/generate-unique-name base-name))) + (-> (un/retrieve-used-names objects) + (un/generate-unique-name base-name))) selrect (gsh/selection-rect shapes) group (-> (cp/make-minimal-group frame-id selrect gname) @@ -143,7 +143,7 @@ (let [[group changes] (prepare-create-group it objects page-id shapes "Group-1" false)] (rx/of (dch/commit-changes changes) - (dwc/select-shapes (d/ordered-set (:id group)))))))))) + (dws/select-shapes (d/ordered-set (:id group)))))))))) (def ungroup-selected (ptk/reify ::ungroup-selected @@ -204,7 +204,7 @@ (pcb/resize-parents [(:id group)]))] (rx/of (dch/commit-changes changes) - (dwc/select-shapes (d/ordered-set (:id group)))))))))) + (dws/select-shapes (d/ordered-set (:id group)))))))))) (def unmask-group (ptk/reify ::unmask-group diff --git a/frontend/src/app/main/data/workspace/guides.cljs b/frontend/src/app/main/data/workspace/guides.cljs index e9d16710ce..c6060daa5f 100644 --- a/frontend/src/app/main/data/workspace/guides.cljs +++ b/frontend/src/app/main/data/workspace/guides.cljs @@ -10,7 +10,7 @@ [app.common.geom.shapes :as gsh] [app.common.pages.changes-builder :as pcb] [app.common.spec :as us] - [app.common.spec.page :as csp] + [app.common.types.page :as ctp] [app.main.data.workspace.changes :as dwc] [app.main.data.workspace.state-helpers :as wsh] [beicon.core :as rx] @@ -24,7 +24,7 @@ (merge guide)))) (defn update-guides [guide] - (us/verify ::csp/guide guide) + (us/verify ::ctp/guide guide) (ptk/reify ::update-guides ptk/WatchEvent (watch [it state _] @@ -36,7 +36,7 @@ (rx/of (dwc/commit-changes changes)))))) (defn remove-guide [guide] - (us/verify ::csp/guide guide) + (us/verify ::ctp/guide guide) (ptk/reify ::remove-guide ptk/UpdateEvent (update [_ state] diff --git a/frontend/src/app/main/data/workspace/interactions.cljs b/frontend/src/app/main/data/workspace/interactions.cljs index d44a55a776..40c6a80f9e 100644 --- a/frontend/src/app/main/data/workspace/interactions.cljs +++ b/frontend/src/app/main/data/workspace/interactions.cljs @@ -11,13 +11,13 @@ [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] - [app.common.spec.interactions :as csi] - [app.common.spec.page :as csp] + [app.common.types.page :as ctp] + [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.state-helpers :as wsh] [app.main.streams :as ms] + [app.util.names :as un] [beicon.core :as rx] [potok.core :as ptk])) @@ -32,7 +32,7 @@ flows (get-in page [:options :flows] []) unames (into #{} (map :name flows)) - name (dwc/generate-unique-name unames "Flow-1") + name (un/generate-unique-name unames "Flow-1") new-flow {:id (uuid/next) :name name @@ -41,7 +41,7 @@ (rx/of (dch/commit-changes (-> (pcb/empty-changes it) (pcb/with-page page) - (pcb/update-page-option :flows csp/add-flow new-flow)))))))) + (pcb/update-page-option :flows ctp/add-flow new-flow)))))))) (defn add-flow-selected-frame [] @@ -61,7 +61,7 @@ (rx/of (dch/commit-changes (-> (pcb/empty-changes it) (pcb/with-page page) - (pcb/update-page-option :flows csp/remove-flow flow-id)))))))) + (pcb/update-page-option :flows ctp/remove-flow flow-id)))))))) (defn rename-flow [flow-id name] @@ -74,8 +74,8 @@ (rx/of (dch/commit-changes (-> (pcb/empty-changes it) (pcb/with-page page) - (pcb/update-page-option :flows csp/update-flow flow-id - #(csp/rename-flow % name))))))))) + (pcb/update-page-option :flows ctp/update-flow flow-id + #(ctp/rename-flow % name))))))))) (defn start-rename-flow [id] @@ -99,8 +99,8 @@ in the page" [objects frame-id] (let [children (cph/get-children-with-self objects frame-id)] - (or (some csi/flow-origin? (map :interactions children)) - (some #(csi/flow-to? % frame-id) (map :interactions (vals objects)))))) + (or (some ctsi/flow-origin? (map :interactions children)) + (some #(ctsi/flow-to? % frame-id) (map :interactions (vals objects)))))) (defn add-new-interaction ([shape] (add-new-interaction shape nil)) @@ -116,15 +116,15 @@ page-id :options :flows] []) - flow (csp/get-frame-flow flows (:id frame))] + flow (ctp/get-frame-flow flows (:id frame))] (rx/concat (rx/of (dch/update-shapes [(:id shape)] (fn [shape] - (let [new-interaction (csi/set-destination - csi/default-interaction + (let [new-interaction (ctsi/set-destination + ctsi/default-interaction destination)] (update shape :interactions - csi/add-interaction new-interaction))))) + ctsi/add-interaction new-interaction))))) (when (and (not (connected-frame? objects (:id frame))) (nil? flow)) (rx/of (add-flow (:id frame)))))))))) @@ -137,7 +137,7 @@ (rx/of (dch/update-shapes [(:id shape)] (fn [shape] (update shape :interactions - csi/remove-interaction index))))))) + ctsi/remove-interaction index))))))) (defn update-interaction [shape index update-fn] @@ -147,7 +147,7 @@ (rx/of (dch/update-shapes [(:id shape)] (fn [shape] (update shape :interactions - csi/update-interaction index update-fn))))))) + ctsi/update-interaction index update-fn))))))) (declare move-edit-interaction) (declare finish-edit-interaction) @@ -171,21 +171,33 @@ (rx/map #(move-edit-interaction initial-pos %))) (rx/of (finish-edit-interaction index initial-pos)))))))) + +(defn get-target-frame + [state position] + + (let [objects (wsh/lookup-page-objects state) + from-id (-> state wsh/lookup-selected first) + from-shape (wsh/lookup-shape state from-id) + + from-frame-id (if (cph/frame-shape? from-shape) + from-id (:frame-id from-shape)) + + target-frame (cph/frame-by-position objects position)] + + (when (and (not= (:id target-frame) uuid/zero) + (not= (:id target-frame) from-frame-id) + (not (:hide-in-viewer target-frame))) + target-frame))) + (defn move-edit-interaction - [initial-pos position] + [_initial-pos position] (ptk/reify ::move-edit-interaction ptk/UpdateEvent (update [_ state] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - selected-shape-id (-> state wsh/lookup-selected first) - selected-shape (get objects selected-shape-id) - selected-shape-frame-id (:frame-id selected-shape) - start-frame (get objects selected-shape-frame-id) - end-frame (dwc/get-frame-at-point objects position)] - (cond-> state - (not= position initial-pos) (assoc-in [:workspace-local :draw-interaction-to] position) - (not= start-frame end-frame) (assoc-in [:workspace-local :draw-interaction-to-frame] end-frame)))))) + (let [end-frame (get-target-frame state position)] + (-> state + (assoc-in [:workspace-local :draw-interaction-to] position) + (assoc-in [:workspace-local :draw-interaction-to-frame] end-frame)))))) (defn finish-edit-interaction [index initial-pos] @@ -199,32 +211,40 @@ ptk/WatchEvent (watch [_ state _] - (let [position @ms/mouse-position - page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - frame (dwc/get-frame-at-point objects position) + (let [position @ms/mouse-position + target-frame (get-target-frame state position) + shape-id (-> state wsh/lookup-selected first) + shape (wsh/lookup-shape state shape-id) - shape-id (-> state wsh/lookup-selected first) - shape (get objects shape-id)] + change-interaction + (fn [interaction] + (cond-> interaction + (not (ctsi/has-destination interaction)) + (ctsi/set-action-type :navigate) - (when (and shape (not (= position initial-pos))) - (if (nil? frame) - (when index - (rx/of (remove-interaction shape index))) - (let [frame (if (or (= (:id frame) (:id shape)) - (= (:id frame) (:frame-id shape))) - nil ;; Drop onto self frame -> set destination to none - frame)] - (if (nil? index) - (rx/of (add-new-interaction shape (:id frame))) - (rx/of (update-interaction shape index - (fn [interaction] - (cond-> interaction - (not (csi/has-destination interaction)) - (csi/set-action-type :navigate) + :always + (ctsi/set-destination (:id target-frame))))] + + (cond + (or (nil? shape) + + ;; Didn't changed the position for the interaction + (= position initial-pos) + + ;; New interaction but invalid target + (and (nil? index) (nil? target-frame))) + nil + + ;; Dropped interaction in an invalid target. We remove it + (and (some? index) (nil? target-frame)) + (rx/of (remove-interaction shape index)) + + (nil? index) + (rx/of (add-new-interaction shape (:id target-frame))) + + :else + (rx/of (update-interaction shape index change-interaction))))))) - :always - (csi/set-destination (:id frame)))))))))))))) ;; --- Overlays (declare move-overlay-pos) @@ -302,7 +322,7 @@ new-interactions (update interactions index - #(csi/set-overlay-position % overlay-pos))] + #(ctsi/set-overlay-position % overlay-pos))] (rx/of (dch/update-shapes [(:id shape)] #(merge % {:interactions new-interactions}))))))) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index cc63069ced..de23cd4b27 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -11,25 +11,26 @@ [app.common.logging :as log] [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] + [app.common.pages.changes-spec :as pcs] [app.common.pages.helpers :as cph] [app.common.spec :as us] - [app.common.spec.change :as spec.change] - [app.common.spec.color :as spec.color] - [app.common.spec.file :as spec.file] - [app.common.spec.typography :as spec.typography] + [app.common.types.color :as ctc] + [app.common.types.file :as ctf] + [app.common.types.typography :as ctt] [app.common.uuid :as uuid] [app.main.data.dashboard :as dd] [app.main.data.events :as ev] [app.main.data.messages :as dm] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.libraries-helpers :as dwlh] + [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] [app.main.repo :as rp] [app.main.store :as st] [app.util.i18n :refer [tr]] + [app.util.names :as un] [app.util.router :as rt] [app.util.time :as dt] [beicon.core :as rx] @@ -98,7 +99,7 @@ color (-> color (assoc :id id) (assoc :name (default-color-name color)))] - (us/assert ::spec.color/color color) + (us/assert ::ctc/color color) (ptk/reify ::add-color IDeref (-deref [_] color) @@ -112,7 +113,7 @@ (defn add-recent-color [color] - (us/assert ::spec.color/recent-color color) + (us/assert! ::ctc/recent-color color) (ptk/reify ::add-recent-color ptk/WatchEvent (watch [it _ _] @@ -141,7 +142,7 @@ (defn update-color [color file-id] - (us/assert ::spec.color/color color) + (us/assert ::ctc/color color) (us/assert ::us/uuid file-id) (ptk/reify ::update-color ptk/WatchEvent @@ -175,7 +176,7 @@ (defn add-media [media] - (us/assert ::spec.file/media-object media) + (us/assert ::ctf/media-object media) (ptk/reify ::add-media ptk/WatchEvent (watch [it _ _] @@ -217,7 +218,7 @@ ([typography] (add-typography typography true)) ([typography edit?] (let [typography (update typography :id #(or % (uuid/next)))] - (us/assert ::spec.typography/typography typography) + (us/assert ::ctt/typography typography) (ptk/reify ::add-typography IDeref (-deref [_] typography) @@ -246,7 +247,7 @@ (defn update-typography [typography file-id] - (us/assert ::spec.typography/typography typography) + (us/assert ::ctt/typography typography) (us/assert ::us/uuid file-id) (ptk/reify ::update-typography ptk/WatchEvent @@ -297,7 +298,7 @@ (dwlh/generate-add-component it shapes objects page-id file-id)] (when-not (empty? (:redo-changes changes)) (rx/of (dch/commit-changes changes) - (dwc/select-shapes (d/ordered-set (:id group))))))))))) + (dws/select-shapes (d/ordered-set (:id group))))))))))) (defn add-component "Add a new component to current file library, from the currently selected shapes. @@ -353,7 +354,7 @@ component (cph/get-component libraries id) all-components (-> state :workspace-data :components vals) unames (into #{} (map :name) all-components) - new-name (dwc/generate-unique-name unames (:name component)) + new-name (un/generate-unique-name unames (:name component)) [new-shape new-shapes _updated-shapes] (dwlh/duplicate-component component) @@ -403,7 +404,7 @@ page libraries)] (rx/of (dch/commit-changes changes) - (dwc/select-shapes (d/ordered-set (:id new-shape)))))))) + (dws/select-shapes (d/ordered-set (:id new-shape)))))))) (defn detach-component "Remove all references to components in the shape with the given id, @@ -464,7 +465,7 @@ (defn ext-library-changed [file-id modified-at revn changes] (us/assert ::us/uuid file-id) - (us/assert ::spec.change/changes changes) + (us/assert ::pcs/changes changes) (ptk/reify ::ext-library-changed ptk/UpdateEvent (update [_ state] diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 5172481ca5..a1e50c89f2 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -8,17 +8,17 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] - [app.common.geom.shapes :as geom] + [app.common.geom.shapes :as gsh] [app.common.logging :as log] [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] - [app.common.spec.color :as color] [app.common.text :as txt] - [app.main.data.workspace.common :as dwc] + [app.common.types.color :as ctc] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.state-helpers :as wsh] + [app.util.names :as un] [cljs.spec.alpha :as s] [clojure.set :as set])) @@ -144,13 +144,13 @@ delta (gpt/subtract position orig-pos) objects (:objects page) - unames (volatile! (dwc/retrieve-used-names objects)) + unames (volatile! (un/retrieve-used-names objects)) frame-id (cph/frame-id-by-position objects (gpt/add orig-pos delta)) update-new-shape (fn [new-shape original-shape] - (let [new-name (dwc/generate-unique-name @unames (:name new-shape))] + (let [new-name (un/generate-unique-name @unames (:name new-shape))] (when (nil? (:parent-id original-shape)) (vswap! unames conj new-name)) @@ -158,7 +158,7 @@ (cond-> new-shape true (as-> $ - (geom/move $ delta) + (gsh/move $ delta) (assoc $ :frame-id frame-id) (assoc $ :parent-id (or (:parent-id $) (:frame-id $))) @@ -299,7 +299,7 @@ (defmethod uses-assets? :colors [_ shape library-id _] - (color/uses-library-colors? shape library-id)) + (ctc/uses-library-colors? shape library-id)) (defmethod uses-assets? :typographies [_ shape library-id _] @@ -331,7 +331,7 @@ (let [library-colors (get-assets library-id :colors state)] (pcb/update-shapes changes [(:id shape)] - #(color/sync-shape-colors % library-id library-colors)))) + #(ctc/sync-shape-colors % library-id library-colors)))) (defmethod generate-sync-shape :typographies [_ changes library-id state container shape] @@ -1150,7 +1150,7 @@ origin-root-pos (shape-pos origin-root) dest-root-pos (shape-pos dest-root) delta (gpt/subtract dest-root-pos origin-root-pos)] - (geom/move shape delta))) + (gsh/move shape delta))) (defn- make-change [container change] diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 777dc8d8de..428d5ac449 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -10,8 +10,8 @@ [app.common.spec :as us] [app.main.data.media :as dmm] [app.main.data.messages :as dm] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.svg-upload :as svg] [app.main.repo :as rp] [app.main.store :as st] @@ -72,7 +72,7 @@ :height height :mtype mtype :id id}}] - (rx/of (dwc/create-and-add-shape :image x y shape)))))) + (rx/of (dwsh/create-and-add-shape :image x y shape)))))) (defn svg-uploaded [svg-data file-id position] @@ -200,7 +200,7 @@ (= (:code error) :invalid-image) (rx/of (dm/error (tr "errors.media-type-not-allowed"))) - (= (:code error) :media-too-large) + (= (:code error) :media-max-file-size-reached) (rx/of (dm/error (tr "errors.media-too-large"))) (= (:code error) :media-type-mismatch) diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index c87ed12999..a9388898c7 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -7,9 +7,8 @@ (ns app.main.data.workspace.notifications (:require [app.common.data :as d] + [app.common.pages.changes-spec :as pcs] [app.common.spec :as us] - [app.common.spec.change :as spec.change] - [app.common.uuid :as uuid] [app.main.data.websocket :as dws] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.libraries :as dwl] @@ -34,51 +33,53 @@ (ptk/reify ::initialize ptk/WatchEvent (watch [_ state stream] - (let [subs-id (uuid/next) - stoper (rx/filter (ptk/type? ::finalize) stream) + (let [stoper (rx/filter (ptk/type? ::finalize) stream) + profile-id (:profile-id state) - initmsg [{:type :subscribe-file - :subs-id subs-id - :file-id file-id} - {:type :subscribe-team - :team-id team-id}] + initmsg [{:type :subscribe-file + :file-id file-id} + {:type :subscribe-team + :team-id team-id}] - endmsg {:type :unsubscribe-file - :subs-id subs-id} + endmsg {:type :unsubscribe-file + :file-id file-id} - stream (->> (rx/merge - ;; Send the subscription message - (->> (rx/from initmsg) - (rx/map dws/send)) + stream (->> (rx/merge + ;; Send the subscription message + (->> (rx/from initmsg) + (rx/map dws/send)) - ;; Subscribe to notifications of the subscription - (->> stream - (rx/filter (ptk/type? ::dws/message)) - (rx/map deref) ;; :library-change events occur in a different file, but need to be processed anyway - (rx/filter #(or (= subs-id (:subs-id %)) (= (:type %) :library-change))) - (rx/map process-message)) + ;; Subscribe to notifications of the subscription + (->> stream + (rx/filter (ptk/type? ::dws/message)) + (rx/map deref) + (rx/filter (fn [{:keys [subs-id] :as msg}] + (or (= subs-id team-id) + (= subs-id profile-id) + (= subs-id file-id)))) + (rx/map process-message)) - ;; On reconnect, send again the subscription messages - (->> stream - (rx/filter (ptk/type? ::dws/opened)) - (rx/mapcat #(->> (rx/from initmsg) - (rx/map dws/send)))) + ;; On reconnect, send again the subscription messages + (->> stream + (rx/filter (ptk/type? ::dws/opened)) + (rx/mapcat #(->> (rx/from initmsg) + (rx/map dws/send)))) - ;; Emit presence event for current user; - ;; this is because websocket server don't - ;; emits this for the same user. - (rx/of (handle-presence {:type :connect - :session-id (:session-id state) - :profile-id (:profile-id state)})) + ;; Emit presence event for current user; + ;; this is because websocket server don't + ;; emits this for the same user. + (rx/of (handle-presence {:type :connect + :session-id (:session-id state) + :profile-id (:profile-id state)})) - ;; Emit to all other connected users the current pointer - ;; position changes. - (->> stream - (rx/filter ms/pointer-event?) - (rx/sample 50) - (rx/map #(handle-pointer-send subs-id file-id (:pt %))))) + ;; Emit to all other connected users the current pointer + ;; position changes. + (->> stream + (rx/filter ms/pointer-event?) + (rx/sample 50) + (rx/map #(handle-pointer-send file-id (:pt %))))) - (rx/take-until stoper))] + (rx/take-until stoper))] (rx/concat stream (rx/of (dws/send endmsg))))))) @@ -95,13 +96,12 @@ nil)) (defn- handle-pointer-send - [subs-id file-id point] + [file-id point] (ptk/reify ::handle-pointer-send ptk/WatchEvent (watch [_ state _] (let [page-id (:current-page-id state) message {:type :pointer-update - :subs-id subs-id :file-id file-id :page-id page-id :position point}] @@ -184,7 +184,7 @@ (s/def ::file-id uuid?) (s/def ::session-id uuid?) (s/def ::revn integer?) -(s/def ::changes ::spec.change/changes) +(s/def ::changes ::pcs/changes) (s/def ::file-change-event (s/keys :req-un [::type ::profile-id ::file-id ::session-id ::revn ::changes])) diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index 5c46ecc456..ff61c094a1 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -13,8 +13,8 @@ [app.common.path.shapes-to-path :as upsp] [app.common.spec :as us] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.drawing.common :as dwdc] + [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.path.changes :as changes] [app.main.data.workspace.path.common :as common] [app.main.data.workspace.path.helpers :as helpers] @@ -276,7 +276,7 @@ (watch [_ _ _] (rx/of (setup-frame-path) (dwdc/handle-finish-drawing) - (dwc/start-edition-mode shape-id) + (dwe/start-edition-mode shape-id) (change-edit-mode :draw))))) (defn handle-new-shape diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index fa8c00f51d..b3b0b68123 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -13,7 +13,7 @@ [app.common.path.shapes-to-path :as upsp] [app.common.path.subpaths :as ups] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.path.changes :as changes] [app.main.data.workspace.path.drawing :as drawing] [app.main.data.workspace.path.helpers :as helpers] @@ -64,7 +64,7 @@ (let [changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)] (if (empty? new-content) (rx/of (dch/commit-changes changes) - dwc/clear-edition-mode) + dwe/clear-edition-mode) (rx/of (dch/commit-changes changes) (selection/update-selection point-change) (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler)))))))))) diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs index 71a6866623..3338c0c9ef 100644 --- a/frontend/src/app/main/data/workspace/path/tools.cljs +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -9,7 +9,7 @@ [app.common.path.shapes-to-path :as upsp] [app.common.path.subpaths :as ups] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.path.changes :as changes] [app.main.data.workspace.path.state :as st] [app.main.data.workspace.state-helpers :as wsh] @@ -40,7 +40,7 @@ (rx/of (dch/update-shapes [id] upsp/convert-to-path)) (rx/of (dch/commit-changes changes) (when (empty? new-content) - dwc/clear-edition-mode)))))))))) + dwe/clear-edition-mode)))))))))) (defn make-corner ([] diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 64c237b69d..e88d0c7505 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -9,9 +9,9 @@ [app.common.data :as d] [app.common.logging :as log] [app.common.pages :as cp] + [app.common.pages.changes-spec :as pcs] [app.common.spec :as us] - [app.common.spec.change :as spec.change] - [app.common.spec.file :as spec.file] + [app.common.types.file :as ctf] [app.common.uuid :as uuid] [app.config :as cf] [app.main.data.dashboard :as dd] @@ -157,13 +157,15 @@ (->> (rx/from frame-updates) (rx/flat-map (fn [[page-id frames]] (->> frames (map #(vector page-id %))))) - (rx/map (fn [[page-id frame-id]] (dwt/update-thumbnail page-id frame-id)))) + (rx/map (fn [[page-id frame-id]] (dwt/update-thumbnail (:id file) page-id frame-id)))) (->> (rx/of lagged) (rx/mapcat seq) (rx/map #(shapes-changes-persisted file-id %))))))) (rx/catch (fn [cause] (rx/concat - (rx/of (rt/assign-exception cause)) + (if (= :authentication (:type cause)) + (rx/empty) + (rx/of (rt/assign-exception cause))) (rx/throw cause)))))))))) @@ -199,7 +201,7 @@ :updated-at (dt/now))))))) (s/def ::shapes-changes-persisted - (s/keys :req-un [::revn ::spec.change/changes])) + (s/keys :req-un [::revn ::pcs/changes])) (defn shapes-persisted-event? [event] (= (ptk/type event) ::changes-persisted)) @@ -237,7 +239,7 @@ (s/def ::version ::us/integer) (s/def ::revn ::us/integer) (s/def ::ordering ::us/integer) -(s/def ::data ::spec.file/data) +(s/def ::data ::ctf/data) (s/def ::file ::dd/file) (s/def ::project ::dd/project) @@ -258,20 +260,23 @@ [project-id file-id] (ptk/reify ::fetch-bundle ptk/WatchEvent - (watch [_ _ _] - (->> (rx/zip (rp/query :file-raw {:id file-id}) - (rp/query :team-users {:file-id file-id}) - (rp/query :project {:id project-id}) - (rp/query :file-libraries {:file-id file-id})) - (rx/take 1) - (rx/map (fn [[file-raw users project libraries]] - {:file-raw file-raw - :users users - :project project - :libraries libraries})) - (rx/mapcat (fn [{:keys [project] :as bundle}] - (rx/of (ptk/data-event ::bundle-fetched bundle) - (df/load-team-fonts (:team-id project))))))))) + (watch [_ state _] + (let [share-id (-> state :viewer-local :share-id)] + (->> (rx/zip (rp/query! :file-raw {:id file-id}) + (rp/query! :team-users {:file-id file-id}) + (rp/query! :project {:id project-id}) + (rp/query! :file-libraries {:file-id file-id}) + (rp/cmd! :get-profiles-for-file-comments {:file-id file-id :share-id share-id})) + (rx/take 1) + (rx/map (fn [[file-raw users project libraries file-comments-users]] + {:file-raw file-raw + :users users + :project project + :libraries libraries + :file-comments-users file-comments-users})) + (rx/mapcat (fn [{:keys [project] :as bundle}] + (rx/of (ptk/data-event ::bundle-fetched bundle) + (df/load-team-fonts (:team-id project)))))))))) ;; --- Helpers diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 5a039e34ef..72d3298fb8 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -8,24 +8,25 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] - [app.common.geom.shapes :as geom] + [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] - [app.common.spec.interactions :as cti] - [app.common.spec.page :as ctp] + [app.common.types.page :as ctp] + [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.collapse :as dwc] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] [app.main.data.workspace.zoom :as dwz] [app.main.refs :as refs] [app.main.streams :as ms] [app.main.worker :as uw] + [app.util.names :as un] [beicon.core :as rx] [cljs.spec.alpha :as s] [clojure.set :as set] @@ -41,6 +42,8 @@ (s/def ::set-of-string (s/every string? :kind set?)) +(defn interrupt? [e] (= e :interrupt)) + ;; --- Selection Rect (declare select-shapes-by-current-selrect) @@ -59,7 +62,7 @@ ptk/WatchEvent (watch [_ state stream] (let [zoom (get-in state [:workspace-local :zoom] 1) - stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) + stop? (fn [event] (or (interrupt? event) (ms/mouse-up? event))) stoper (->> stream (rx/filter stop?)) init-selrect @@ -180,24 +183,28 @@ (ptk/reify ::select-all ptk/WatchEvent (watch [_ state _] - (let [focus (:workspace-focus-selected state) + (let [;; Make the select-all aware of the focus mode; in this + ;; case delimit the objects to the focused shapes if focus + ;; mode is active + focus (:workspace-focus-selected state) objects (-> (wsh/lookup-page-objects state) (cp/focus-objects focus)) - selected (let [frame-ids (into #{} (comp - (map (d/getf objects)) - (map :frame-id)) - (wsh/lookup-selected state)) - frame-id (if (= 1 (count frame-ids)) - (first frame-ids) - uuid/zero)] - (cph/get-immediate-children objects frame-id)) + lookup (d/getf objects) + parents (->> (wsh/lookup-selected state) + (into #{} (comp (keep lookup) (map :parent-id)))) - selected (into (d/ordered-set) - (comp (remove :blocked) (map :id)) - selected)] + ;; If we have a only unique parent, then use it as main + ;; anchor for the selection; if not, use the root frame as + ;; parent + parent (if (= 1 (count parents)) + (-> parents first lookup) + (lookup uuid/zero)) - (rx/of (select-shapes selected)))))) + toselect (->> (cph/get-immediate-children objects (:id parent)) + (into (d/ordered-set) (comp (remove :blocked) (map :id))))] + + (rx/of (select-shapes toselect)))))) (defn deselect-all "Clear all possible state of drawing, edition @@ -264,7 +271,7 @@ ;; in the later vector position selected (->> children reverse - (d/seek #(geom/has-point? % position)))] + (d/seek #(gsh/has-point? % position)))] (when selected (rx/of (select-shape (:id selected)))))))) @@ -281,7 +288,7 @@ move to the desired position, and recalculate parents and frames as needed." [all-objects page ids delta it] (let [shapes (map (d/getf all-objects) ids) - unames (volatile! (dwc/retrieve-used-names (:objects page))) + unames (volatile! (un/retrieve-used-names (:objects page))) update-unames! (fn [new-name] (vswap! unames conj new-name)) all-ids (reduce #(into %1 (cons %2 (cph/get-children-ids all-objects %2))) (d/ordered-set) ids) ids-map (into {} (map #(vector % (uuid/next))) all-ids) @@ -316,7 +323,7 @@ (defn- prepare-duplicate-frame-change [changes objects page unames update-unames! ids-map obj delta] (let [new-id (ids-map (:id obj)) - frame-name (dwc/generate-unique-name @unames (:name obj)) + frame-name (un/generate-unique-name @unames (:name obj)) _ (update-unames! frame-name) new-frame (-> obj @@ -325,8 +332,8 @@ :frame-id uuid/zero :shapes []) (dissoc :use-for-thumbnail?) - (geom/move delta) - (d/update-when :interactions #(cti/remap-interactions % ids-map objects))) + (gsh/move delta) + (d/update-when :interactions #(ctsi/remap-interactions % ids-map objects))) changes (-> (pcb/add-object changes new-frame) (pcb/amend-last-change #(assoc % :old-id (:id obj)))) @@ -351,7 +358,7 @@ (if (some? obj) (let [new-id (ids-map (:id obj)) parent-id (or parent-id frame-id) - name (dwc/generate-unique-name @unames (:name obj)) + name (un/generate-unique-name @unames (:name obj)) _ (update-unames! name) new-obj (-> obj @@ -360,8 +367,8 @@ :parent-id parent-id :frame-id frame-id) (dissoc :shapes) - (geom/move delta) - (d/update-when :interactions #(cti/remap-interactions % ids-map objects))) + (gsh/move delta) + (d/update-when :interactions #(ctsi/remap-interactions % ids-map objects))) changes (-> (pcb/add-object changes new-obj {:ignore-touched true}) (pcb/amend-last-change #(assoc % :old-id (:id obj))))] @@ -392,7 +399,7 @@ (let [update-flows (fn [flows] (reduce (fn [flows frame] - (let [name (dwc/generate-unique-name @unames "Flow-1") + (let [name (un/generate-unique-name @unames "Flow-1") _ (vswap! unames conj name) new-flow {:id (uuid/next) :name name @@ -412,7 +419,7 @@ (fn [g frame] (let [new-id (ids-map (:id frame)) new-frame (-> frame - (geom/move delta)) + (gsh/move delta)) new-guides (->> guides (vals) (filter #(= (:frame-id %) (:id frame))) @@ -422,11 +429,9 @@ (assoc :position (if (= (:axis %) :x) (+ (:position %) (- (:x new-frame) (:x frame))) (+ (:position %) (- (:y new-frame) (:y frame))))))))] - - (if-not (empty? new-guides) - (conj g - (into {} (map (juxt :id identity) new-guides))) - {}))) + (cond-> g + (not-empty new-guides) + (conj (into {} (map (juxt :id identity) new-guides)))))) guides frames)] (-> (pcb/with-page changes page) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs new file mode 100644 index 0000000000..a072e30235 --- /dev/null +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -0,0 +1,269 @@ +;; 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.shapes + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.geom.proportions :as gpr] + [app.common.pages :as cp] + [app.common.pages.changes-builder :as pcb] + [app.common.pages.helpers :as cph] + [app.common.spec :as us] + [app.common.types.page :as csp] + [app.common.types.shape :as spec.shape] + [app.common.types.shape.interactions :as csi] + [app.common.uuid :as uuid] + [app.main.data.comments :as dc] + [app.main.data.workspace.changes :as dch] + [app.main.data.workspace.edition :as dwe] + [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.state-helpers :as wsh] + [app.main.streams :as ms] + [app.util.names :as un] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [potok.core :as ptk])) + +(s/def ::shape-attrs ::spec.shape/shape-attrs) + +(defn get-shape-layer-position + [objects selected attrs] + + ;; Calculate the frame over which we're drawing + (let [position @ms/mouse-position + frame-id (:frame-id attrs (cph/frame-id-by-position objects position)) + shape (when-not (empty? selected) + (cph/get-base-shape objects selected))] + + ;; When no shapes has been selected or we're over a different frame + ;; we add it as the latest shape of that frame + (if (or (not shape) (not= (:frame-id shape) frame-id)) + [frame-id frame-id nil] + + ;; Otherwise, we add it to next to the selected shape + (let [index (cph/get-position-on-parent objects (:id shape)) + {:keys [frame-id parent-id]} shape] + [frame-id parent-id (inc index)])))) + +(defn make-new-shape + [attrs objects selected] + (let [default-attrs (if (= :frame (:type attrs)) + cp/default-frame-attrs + cp/default-shape-attrs) + + selected-non-frames + (into #{} (comp (map (d/getf objects)) + (remove cph/frame-shape?)) + selected) + + [frame-id parent-id index] + (get-shape-layer-position objects selected-non-frames attrs)] + + (-> (merge default-attrs attrs) + (gpr/setup-proportions) + (assoc :frame-id frame-id + :parent-id parent-id + :index index)))) + +(defn add-shape + ([attrs] + (add-shape attrs {})) + + ([attrs {:keys [no-select?]}] + (us/verify ::shape-attrs attrs) + (ptk/reify ::add-shape + ptk/WatchEvent + (watch [it state _] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + selected (wsh/lookup-selected state) + + id (or (:id attrs) (uuid/next)) + name (-> objects + (un/retrieve-used-names) + (un/generate-unique-name (:name attrs))) + + shape (make-new-shape + (assoc attrs :id id :name name) + objects + selected) + + changes (-> (pcb/empty-changes it page-id) + (pcb/with-objects objects) + (pcb/add-object shape) + (cond-> (some? (:parent-id attrs)) + (pcb/change-parent (:parent-id attrs) [shape])))] + + (rx/concat + (rx/of (dch/commit-changes changes) + (when-not no-select? + (dws/select-shapes (d/ordered-set id)))) + (when (= :text (:type attrs)) + (->> (rx/of (dwe/start-edition-mode id)) + (rx/observe-on :async))))))))) + +(defn move-shapes-into-frame [frame-id shapes] + (ptk/reify ::move-shapes-into-frame + ptk/WatchEvent + (watch [it state _] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + + to-move-shapes + (into [] + (map (d/getf objects)) + (reverse (cph/sort-z-index objects shapes))) + + changes + (when (d/not-empty? to-move-shapes) + (-> (pcb/empty-changes it page-id) + (pcb/with-objects objects) + (pcb/change-parent frame-id to-move-shapes 0)))] + + (if (some? changes) + (rx/of (dch/commit-changes changes)) + (rx/empty)))))) + +(s/def ::set-of-uuid + (s/every ::us/uuid :kind set?)) + +(defn delete-shapes + [ids] + (us/assert ::set-of-uuid ids) + (ptk/reify ::delete-shapes + ptk/WatchEvent + (watch [it state _] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + page (wsh/lookup-page state page-id) + + ids (cph/clean-loops objects ids) + lookup (d/getf objects) + + groups-to-unmask + (reduce (fn [group-ids id] + ;; When the shape to delete is the mask of a masked group, + ;; the mask condition must be removed, and it must be + ;; converted to a normal group. + (let [obj (lookup id) + parent (lookup (:parent-id obj))] + (if (and (:masked-group? parent) + (= id (first (:shapes parent)))) + (conj group-ids (:id parent)) + group-ids))) + #{} + ids) + + interacting-shapes + (filter (fn [shape] + ;; If any of the deleted shapes is the destination of + ;; some interaction, this must be deleted, too. + (let [interactions (:interactions shape)] + (some #(and (csi/has-destination %) + (contains? ids (:destination %))) + interactions))) + (vals objects)) + + ;; If any of the deleted shapes is a frame with guides + guides (into {} + (comp (map second) + (remove #(contains? ids (:frame-id %))) + (map (juxt :id identity))) + (dm/get-in page [:options :guides])) + + starting-flows + (filter (fn [flow] + ;; If any of the deleted is a frame that starts a flow, + ;; this must be deleted, too. + (contains? ids (:starting-frame flow))) + (-> page :options :flows)) + + all-parents + (reduce (fn [res id] + ;; All parents of any deleted shape must be resized. + (into res (cph/get-parent-ids objects id))) + (d/ordered-set) + ids) + + all-children + (->> ids ;; Children of deleted shapes must be also deleted. + (reduce (fn [res id] + (into res (cph/get-children-ids objects id))) + []) + (reverse) + (into (d/ordered-set))) + + find-all-empty-parents + (fn recursive-find-empty-parents [empty-parents] + (let [all-ids (into empty-parents ids) + contains? (partial contains? all-ids) + xform (comp (map lookup) + (filter cph/group-shape?) + (remove #(->> (:shapes %) (remove contains?) seq)) + (map :id)) + parents (into #{} xform all-parents)] + (if (= empty-parents parents) + empty-parents + (recursive-find-empty-parents parents)))) + + empty-parents + ;; Any parent whose children are all deleted, must be deleted too. + (into (d/ordered-set) (find-all-empty-parents #{})) + + changes (-> (pcb/empty-changes it page-id) + (pcb/with-page page) + (pcb/with-objects objects) + (pcb/set-page-option :guides guides) + (pcb/remove-objects all-children) + (pcb/remove-objects ids) + (pcb/remove-objects empty-parents) + (pcb/resize-parents all-parents) + (pcb/update-shapes groups-to-unmask + (fn [shape] + (assoc shape :masked-group? false))) + (pcb/update-shapes (map :id interacting-shapes) + (fn [shape] + (d/update-when shape :interactions + (fn [interactions] + (into [] + (remove #(and (csi/has-destination %) + (contains? ids (:destination %)))) + interactions))))) + (cond-> (seq starting-flows) + (pcb/update-page-option :flows (fn [flows] + (->> (map :id starting-flows) + (reduce csp/remove-flow flows))))))] + + (rx/of + (dc/detach-comment-thread ids) + (dch/commit-changes changes)))))) + +(defn- viewport-center + [state] + (let [{:keys [x y width height]} (get-in state [:workspace-local :vbox])] + [(+ x (/ width 2)) (+ y (/ height 2))])) + +(defn create-and-add-shape + [type frame-x frame-y data] + (ptk/reify ::create-and-add-shape + ptk/WatchEvent + (watch [_ state _] + (prn ">>>create-") + (let [{:keys [width height]} data + + [vbc-x vbc-y] (viewport-center state) + x (:x data (- vbc-x (/ width 2))) + y (:y data (- vbc-y (/ height 2))) + page-id (:current-page-id state) + frame-id (-> (wsh/lookup-page-objects state page-id) + (cph/frame-id-by-position {:x frame-x :y frame-y})) + shape (-> (cp/make-minimal-shape type) + (merge data) + (merge {:x x :y y}) + (assoc :frame-id frame-id) + (cp/setup-rect-selrect))] + (rx/of (add-shape shape)))))) diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index 150cf4375f..25c82a8db4 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -208,8 +208,8 @@ ;; TOOLS - :draw-frame {:tooltip "A" - :command "a" + :draw-frame {:tooltip "B" + :command ["b" "a"] :subsections [:tools :basics] :fn #(st/emit! (dwd/select-for-drawing :frame))} diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index e0f60d196a..c0733644f2 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -70,6 +70,14 @@ selected (dm/get-in state [:workspace-local :selected])] (process-selected-shapes objects selected options)))) +(defn lookup-shape + ([state id] + (lookup-shape state (:current-page-id state) id)) + + ([state page-id id] + (let [objects (lookup-page-objects state page-id)] + (get objects id)))) + (defn lookup-shapes ([state ids] (lookup-shapes state (:current-page-id state) ids)) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 0424e73908..3c830bd59b 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -17,9 +17,11 @@ [app.common.spec :refer [max-safe-int min-safe-int]] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] [app.util.color :as uc] + [app.util.names :as un] [app.util.path.parser :as upp] [app.util.svg :as usvg] [beicon.core :as rx] @@ -358,7 +360,7 @@ (let [{:keys [tag attrs hidden]} element-data attrs (usvg/format-styles attrs) element-data (cond-> element-data (map? element-data) (assoc :attrs attrs)) - name (dwc/generate-unique-name unames (or (:id attrs) (tag->name tag))) + name (un/generate-unique-name unames (or (:id attrs) (tag->name tag))) att-refs (usvg/find-attr-references attrs) references (usvg/find-def-references (:defs svg-data) att-refs) @@ -415,7 +417,7 @@ (if (some? shape) (let [shape-id (:id shape) - new-shape (dwc/make-new-shape shape objects selected) + new-shape (dwsh/make-new-shape shape objects selected) changes (-> changes (pcb/add-object new-shape) (pcb/change-parent parent-id [new-shape] index)) @@ -442,10 +444,10 @@ x (- x vb-x (/ vb-width 2)) y (- y vb-y (/ vb-height 2)) - unames (dwc/retrieve-used-names objects) + unames (un/retrieve-used-names objects) svg-name (->> (str/replace (:name svg-data) ".svg" "") - (dwc/generate-unique-name unames)) + (un/generate-unique-name unames)) svg-data (-> svg-data (assoc :x x @@ -482,7 +484,7 @@ (assoc :content (into [base-background-shape] (:content svg-data)))) ;; Creates the root shape - new-shape (dwc/make-new-shape root-shape objects selected) + new-shape (dwsh/make-new-shape root-shape objects selected) changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) @@ -506,7 +508,7 @@ vec))] (rx/of (dch/commit-changes changes) - (dwc/select-shapes (d/ordered-set root-id)))) + (dws/select-shapes (d/ordered-set root-id)))) (catch :default e (.error js/console "Error SVG" e) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 30637d2927..1744642185 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -17,6 +17,7 @@ [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] [app.util.router :as rt] @@ -78,7 +79,7 @@ (when (some? id) (rx/of (dws/deselect-shape id) - (dwc/delete-shapes #{id}))))))))) + (dwsh/delete-shapes #{id}))))))))) (defn initialize-editor-state [{:keys [id content] :as shape} decorator] diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 60e6a039c6..7ca84cda11 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -34,7 +34,7 @@ (fn [subs] ;; We look in the DOM a canvas that 1) matches the id and 2) that it's not empty ;; will be empty on first rendering before drawing the thumbnail and we don't want to store that - (let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%']:not([data-empty])" object-id))] + (let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'][data-empty='false']" object-id))] (if (some? node) (-> node (.toBlob (fn [blob] @@ -56,29 +56,35 @@ (defn update-thumbnail "Updates the thumbnail information for the given frame `id`" - [page-id frame-id] - (ptk/reify ::update-thumbnail - ptk/WatchEvent - (watch [_ state _] - (let [object-id (dm/str page-id frame-id) - file-id (:current-file-id state) - blob-result (thumbnail-stream object-id)] + ([page-id frame-id] + (update-thumbnail nil page-id frame-id)) - (->> blob-result - (rx/merge-map - (fn [blob] - (if (some? blob) - (wapi/read-file-as-data-url blob) - (rx/of nil)))) + ([file-id page-id frame-id] + (ptk/reify ::update-thumbnail + ptk/WatchEvent + (watch [_ state _] + (let [object-id (dm/str page-id frame-id) + file-id (or file-id (:current-file-id state)) + blob-result (thumbnail-stream object-id)] - (rx/merge-map - (fn [data] - (let [params {:file-id file-id :object-id object-id :data data}] - (rx/merge - ;; Update the local copy of the thumbnails so we don't need to request it again - (rx/of #(assoc-in % [:workspace-file :thumbnails object-id] data)) - (->> (rp/mutation! :upsert-file-object-thumbnail params) - (rx/ignore))))))))))) + (->> blob-result + (rx/merge-map + (fn [blob] + (if (some? blob) + (wapi/read-file-as-data-url blob) + (rx/of nil)))) + + (rx/merge-map + (fn [data] + (if (some? file-id) + (let [params {:file-id file-id :object-id object-id :data data}] + (rx/merge + ;; Update the local copy of the thumbnails so we don't need to request it again + (rx/of #(assoc-in % [:workspace-file :thumbnails object-id] data)) + (->> (rp/mutation! :upsert-file-object-thumbnail params) + (rx/ignore)))) + + (rx/empty)))))))))) (defn- extract-frame-changes "Process a changes set in a commit to extract the frames that are changing" diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 9ab913ff41..02d8d7fc8d 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -17,7 +17,8 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.collapse :as dwc] + [app.main.data.workspace.comments :as dwcm] [app.main.data.workspace.guides :as dwg] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] @@ -181,50 +182,59 @@ (assoc :grow-type :fixed)))) (defn- apply-modifiers - [ids] - (us/verify (s/coll-of uuid?) ids) - (ptk/reify ::apply-modifiers - ptk/WatchEvent - (watch [_ state _] - (let [objects (wsh/lookup-page-objects state) - ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids) - object-modifiers (get state :workspace-modifiers) - shapes (map (d/getf objects) ids) - ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes) - (reduce merge {}))] + ([ids] + (apply-modifiers ids nil)) - (rx/of (dwu/start-undo-transaction) - (dwg/move-frame-guides ids-with-children) - (dch/update-shapes - ids-with-children - (fn [shape] - (let [modif (get object-modifiers (:id shape)) - text-shape? (cph/text-shape? shape)] - (-> shape - (merge modif) - (gsh/transform-shape) - (cond-> text-shape? - (update-grow-type shape))))) - {:reg-objects? true - :ignore-tree ignore-tree - ;; Attributes that can change in the transform. This way we don't have to check - ;; all the attributes - :attrs [:selrect - :points - :x - :y - :width - :height - :content - :transform - :transform-inverse - :rotation - :position-data - :flip-x - :flip-y - :grow-type]}) - (clear-local-transform) - (dwu/commit-undo-transaction)))))) + ([ids {:keys [undo-transation?] :or {undo-transation? true}}] + (us/verify (s/coll-of uuid?) ids) + (ptk/reify ::apply-modifiers + ptk/WatchEvent + (watch [_ state _] + (let [objects (wsh/lookup-page-objects state) + ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids) + object-modifiers (get state :workspace-modifiers) + shapes (map (d/getf objects) ids) + ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes) + (reduce merge {}))] + + (rx/concat + (if undo-transation? + (rx/of (dwu/start-undo-transaction)) + (rx/empty)) + (rx/of (dwg/move-frame-guides ids-with-children) + (dwcm/move-frame-comment-threads ids-with-children) + (dch/update-shapes + ids-with-children + (fn [shape] + (let [modif (get object-modifiers (:id shape)) + text-shape? (cph/text-shape? shape)] + (-> shape + (merge modif) + (gsh/transform-shape) + (cond-> text-shape? + (update-grow-type shape))))) + {:reg-objects? true + :ignore-tree ignore-tree + ;; Attributes that can change in the transform. This way we don't have to check + ;; all the attributes + :attrs [:selrect + :points + :x + :y + :width + :height + :content + :transform + :transform-inverse + :rotation + :position-data + :flip-x + :flip-y + :grow-type]}) + (clear-local-transform)) + (if undo-transation? + (rx/of (dwu/commit-undo-transaction)) + (rx/empty)))))))) (defn- check-delta "If the shape is a component instance, check its relative position respect the @@ -274,9 +284,9 @@ (defn set-pixel-precision "Adjust modifiers so they adjust to the pixel grid" - [modifiers shape] + [{:keys [resize-transform] :as modifiers} shape] - (if (some? (:resize-transform modifiers)) + (if (and (some? resize-transform) (not (gmt/unit? resize-transform))) ;; If we're working with a rotation we don't handle pixel precision because ;; the transformation won't have the precision anyway modifiers @@ -762,9 +772,11 @@ (rx/map (partial set-modifiers ids)) (rx/take-until stopper)) - (rx/of (apply-modifiers ids) + (rx/of (dwu/start-undo-transaction) (calculate-frame-for-move ids) - (finish-transform))))))))) + (apply-modifiers ids {:undo-transation? false}) + (finish-transform) + (dwu/commit-undo-transaction))))))))) (s/def ::direction #{:up :down :right :left}) @@ -852,20 +864,23 @@ objects (wsh/lookup-page-objects state page-id) frame-id (cph/frame-id-by-position objects position) - moving-shapes (->> ids - (cph/clean-loops objects) - (map #(get objects %)) - (remove #(or (nil? %) - (= (:frame-id %) frame-id)))) + moving-shapes + (->> ids + (cph/clean-loops objects) + (keep #(get objects %)) + (remove #(= (:frame-id %) frame-id))) + + moving-frames + (->> ids + (filter #(cph/frame-shape? objects %))) changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) + (pcb/update-shapes moving-frames (fn [shape] (assoc shape :hide-in-viewer true))) (pcb/change-parent frame-id moving-shapes))] (when-not (empty? changes) - (rx/of dwu/pop-undo-into-transaction - (dch/commit-changes changes) - (dwu/commit-undo-transaction) + (rx/of (dch/commit-changes changes) (dwc/expand-collapse frame-id))))))) (defn- get-displacement diff --git a/frontend/src/app/main/data/workspace/undo.cljs b/frontend/src/app/main/data/workspace/undo.cljs index b4a93cd53a..ac30d5ab15 100644 --- a/frontend/src/app/main/data/workspace/undo.cljs +++ b/frontend/src/app/main/data/workspace/undo.cljs @@ -6,8 +6,8 @@ (ns app.main.data.workspace.undo (:require + [app.common.pages.changes-spec :as pcs] [app.common.spec :as us] - [app.common.spec.change :as spec.change] [cljs.spec.alpha :as s] [potok.core :as ptk])) @@ -15,8 +15,8 @@ ;; Undo / Redo ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(s/def ::undo-changes ::spec.change/changes) -(s/def ::redo-changes ::spec.change/changes) +(s/def ::undo-changes ::pcs/changes) +(s/def ::redo-changes ::pcs/changes) (s/def ::undo-entry (s/keys :req-un [::undo-changes ::redo-changes])) diff --git a/frontend/src/app/main/data/workspace/viewport.cljs b/frontend/src/app/main/data/workspace/viewport.cljs new file mode 100644 index 0000000000..aad315aa1d --- /dev/null +++ b/frontend/src/app/main/data/workspace/viewport.cljs @@ -0,0 +1,148 @@ +;; 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.viewport + (:require + [app.common.data :as d] + [app.common.geom.align :as gal] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.math :as mth] + [app.common.pages.helpers :as cph] + [app.common.spec :as us] + [app.main.data.workspace.state-helpers :as wsh] + [app.main.streams :as ms] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defn initialize-viewport + [{:keys [width height] :as size}] + (letfn [(update* [{:keys [vport] :as local}] + (let [wprop (/ (:width vport) width) + hprop (/ (:height vport) height)] + (-> local + (assoc :vport size) + (update :vbox (fn [vbox] + (-> vbox + (update :width #(/ % wprop)) + (update :height #(/ % hprop)))))))) + + (initialize [state local] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + shapes (cph/get-immediate-children objects) + srect (gsh/selection-rect shapes) + local (assoc local :vport size :zoom 1)] + (cond + (or (not (d/num? (:width srect))) + (not (d/num? (:height srect)))) + (assoc local :vbox (assoc size :x 0 :y 0)) + + (or (> (:width srect) width) + (> (:height srect) height)) + (let [srect (gal/adjust-to-viewport size srect {:padding 40}) + zoom (/ (:width size) (:width srect))] + (-> local + (assoc :zoom zoom) + (update :vbox merge srect))) + + :else + (assoc local :vbox (assoc size + :x (+ (:x srect) (/ (- (:width srect) width) 2)) + :y (+ (:y srect) (/ (- (:height srect) height) 2))))))) + + (setup [state local] + (if (and (:vbox local) (:vport local)) + (update* local) + (initialize state local)))] + + (ptk/reify ::initialize-viewport + ptk/UpdateEvent + (update [_ state] + (update state :workspace-local + (fn [local] + (setup state local))))))) + +(defn update-viewport-position + [{:keys [x y] :or {x identity y identity}}] + (us/assert fn? x) + (us/assert fn? y) + (ptk/reify ::update-viewport-position + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-local :vbox] + (fn [vbox] + (-> vbox + (update :x x) + (update :y y))))))) + +(defn update-viewport-size + [resize-type {:keys [width height] :as size}] + (ptk/reify ::update-viewport-size + ptk/UpdateEvent + (update [_ state] + (update state :workspace-local + (fn [{:keys [vport] :as local}] + (if (or (nil? vport) + (mth/almost-zero? width) + (mth/almost-zero? height)) + ;; If we have a resize to zero just keep the old value + local + (let [wprop (/ (:width vport) width) + hprop (/ (:height vport) height) + + vbox (:vbox local) + vbox-x (:x vbox) + vbox-y (:y vbox) + vbox-width (:width vbox) + vbox-height (:height vbox) + + vbox-width' (/ vbox-width wprop) + vbox-height' (/ vbox-height hprop) + + vbox-x' + (case resize-type + :left (+ vbox-x (- vbox-width vbox-width')) + :right vbox-x + (+ vbox-x (/ (- vbox-width vbox-width') 2))) + + vbox-y' + (case resize-type + :top (+ vbox-y (- vbox-height vbox-height')) + :bottom vbox-y + (+ vbox-y (/ (- vbox-height vbox-height') 2)))] + (-> local + (assoc :vport size) + (assoc-in [:vbox :x] vbox-x') + (assoc-in [:vbox :y] vbox-y') + (assoc-in [:vbox :width] vbox-width') + (assoc-in [:vbox :height] vbox-height'))))))))) + +(defn start-panning [] + (ptk/reify ::start-panning + ptk/WatchEvent + (watch [_ state stream] + (let [stopper (->> stream (rx/filter (ptk/type? ::finish-panning))) + zoom (-> (get-in state [:workspace-local :zoom]) gpt/point)] + (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 + ptk/UpdateEvent + (update [_ state] + (-> state + (update :workspace-local dissoc :panning))))) diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index 674dfb3ea0..abfde90017 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -21,6 +21,7 @@ [app.util.router :as rt] [app.util.storage :refer [storage]] [app.util.timers :as ts] + [cuerdas.core :as str] [potok.core :as ptk])) (defn on-error @@ -45,6 +46,14 @@ ;; Set the main potok error handler (reset! st/on-error on-error) +(defmethod ptk/handle-error :default + [error] + (let [hint (str/concat "Unexpected error: " (:hint error))] + (ts/schedule #(st/emit! (rt/assign-exception error))) + (js/console.group hint) + (ex/ignoring (js/console.error (pr-str error))) + (js/console.groupEnd hint))) + ;; We receive a explicit authentication error; this explicitly clears ;; all profile data and redirect the user to the login page. This is ;; here and not in app.main.errors because of circular dependency. @@ -105,7 +114,6 @@ (js/console.groupEnd msg))) - ;; Error on parsing an SVG ;; TODO: looks unused and deprecated (defmethod ptk/handle-error :svg-parser @@ -187,14 +195,20 @@ (defn on-unhandled-error [error] - (if (instance? ExceptionInfo error) - (-> error ex-data ptk/handle-error) - (let [hint (ex-message error) - msg (dm/str "Unhandled Internal Error: " hint)] - (ts/schedule #(st/emit! (rt/assign-exception error))) - (js/console.group msg) - (ex/ignoring (js/console.error error)) - (js/console.groupEnd msg)))) + (letfn [(is-ignorable-exception? [cause] + (let [message (ex-message cause)] + (or (= message "Possible side-effect in debug-evaluate") + (= message "Unexpected end of input") true + (str/starts-with? message "Unexpected token "))))] + (if (instance? ExceptionInfo error) + (-> error ex-data ptk/handle-error) + (when-not (is-ignorable-exception? error) + (let [hint (ex-message error) + msg (dm/str "Unhandled Internal Error: " hint)] + (ts/schedule #(st/emit! (rt/assign-exception error))) + (js/console.group msg) + (ex/ignoring (js/console.error error)) + (js/console.groupEnd msg)))))) (defonce uncaught-error-handler (letfn [(on-error [event] diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 585f5d50ee..9040ee8312 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -209,7 +209,7 @@ (def workspace-recent-fonts (l/derived (fn [data] - (get data :workspace-data [])) + (get data :recent-fonts [])) workspace-data)) (def workspace-file-typography @@ -270,6 +270,14 @@ (into [] (keep (d/getf objects)) children-ids))) workspace-page-objects =)) +(defn all-children-objects + [id] + (l/derived + (fn [objects] + (let [children-ids (cph/get-children-ids objects id)] + (into [] (keep (d/getf objects)) children-ids))) + workspace-page-objects =)) + (def workspace-page-options (l/derived :options workspace-page)) @@ -306,8 +314,11 @@ (fn [{:keys [modifiers objects]}] (let [keys (->> modifiers (keys) - (filter #(or (= frame-id %) - (= frame-id (get-in objects [% :frame-id])))))] + (filter (fn [id] + (let [shape (get objects id)] + (or (= frame-id id) + (and (= frame-id (:frame-id shape)) + (not (= :frame (:type shape)))))))))] (select-keys modifiers keys))) workspace-modifiers-with-objects =)) @@ -376,6 +387,9 @@ (def users (l/derived :users st/state)) +(def current-file-comments-users + (l/derived :current-file-comments-users st/state)) + (def viewer-fullscreen? (l/derived (fn [state] (dm/get-in state [:viewer-local :fullscreen?])) @@ -396,3 +410,7 @@ (defn workspace-text-modifier-by-id [id] (l/derived #(get % id) workspace-text-modifier =)) + +(def colorpicker + (l/derived :colorpicker st/state)) + diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 74729dbdf6..240a442018 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -14,11 +14,11 @@ (:require ["react-dom/server" :as rds] [app.common.colors :as clr] - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.bounds :as gsb] [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.config :as cfg] @@ -28,7 +28,6 @@ [app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.embed :as embed] [app.main.ui.shapes.export :as export] - [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.group :as group] [app.main.ui.shapes.image :as image] @@ -61,9 +60,11 @@ (defn- calculate-dimensions [objects] - (let [shapes (cph/get-immediate-children objects) - rect (gsh/selection-rect shapes)] - (-> rect + (let [bounds + (->> (cph/get-root-objects objects) + (map (partial gsb/get-object-bounds objects)) + (gsh/join-rects))] + (-> bounds (update :x mth/finite 0) (update :y mth/finite 0) (update :width mth/finite 100000) @@ -77,10 +78,13 @@ frame-shape (frame/frame-shape shape-wrapper)] (mf/fnc frame-wrapper [{:keys [shape] :as props}] - (let [childs (mapv #(get objects %) (:shapes shape)) + + (let [render-thumbnails? (mf/use-ctx muc/render-thumbnails) + childs (mapv #(get objects %) (:shapes shape)) shape (gsh/transform-shape shape)] - [:> shape-container {:shape shape} - [:& frame-shape {:shape shape :childs childs}]])))) + (if (and render-thumbnails? (some? (:thumbnail shape))) + [:& frame/frame-thumbnail {:shape shape :bounds (:children-bounds shape)}] + [:& frame-shape {:shape shape :childs childs}]))))) (defn group-wrapper-factory [objects] @@ -169,7 +173,7 @@ [objects object-id] (let [object (get objects object-id) object (cond->> object - (cph/root-frame? object) + (cph/root? object) (adapt-root-frame objects)) ;; Replace the previous object with the new one @@ -186,136 +190,114 @@ (reduce updt-fn objects mod-ids))) -(defn get-object-bounds - [objects object-id] - (let [object (get objects object-id) - padding (filters/calculate-padding object true) - bounds (-> (filters/get-filters-bounds object) - (update :x - (:horizontal padding)) - (update :y - (:vertical padding)) - (update :width + (* 2 (:horizontal padding))) - (update :height + (* 2 (:vertical padding))))] - - (if (cph/group-shape? object) - (if (:masked-group? object) - (get-object-bounds objects (-> object :shapes first)) - (->> (:shapes object) - (into [bounds] (map (partial get-object-bounds objects))) - (gsh/join-rects))) - bounds))) - (mf/defc page-svg {::mf/wrap [mf/memo]} [{:keys [data thumbnails? render-embed? include-metadata?] :as props :or {render-embed? false include-metadata? false}}] (let [objects (:objects data) shapes (cph/get-immediate-children objects) - dim (calculate-dimensions objects) vbox (format-viewbox dim) bgcolor (dm/get-in data [:options :background] default-color) - frame-wrapper - (mf/use-memo - (mf/deps objects) - #(frame-wrapper-factory objects)) - shape-wrapper (mf/use-memo (mf/deps objects) #(shape-wrapper-factory objects))] - [:& (mf/provider embed/context) {:value render-embed?} - [:& (mf/provider export/include-metadata-ctx) {:value include-metadata?} - [:svg {:view-box vbox - :version "1.1" - :xmlns "http://www.w3.org/2000/svg" - :xmlnsXlink "http://www.w3.org/1999/xlink" - :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns") - :style {:width "100%" - :height "100%" - :background bgcolor} - :fill "none"} + [:& (mf/provider muc/render-thumbnails) {:value thumbnails?} + [:& (mf/provider embed/context) {:value render-embed?} + [:& (mf/provider export/include-metadata-ctx) {:value include-metadata?} + [:svg {:view-box vbox + :version "1.1" + :xmlns "http://www.w3.org/2000/svg" + :xmlnsXlink "http://www.w3.org/1999/xlink" + :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns") + :style {:width "100%" + :height "100%" + :background bgcolor} + :fill "none"} - (when include-metadata? - [:& export/export-page {:options (:options data)}]) + (when include-metadata? + [:& export/export-page {:options (:options data)}]) + (let [shapes (->> shapes + (remove cph/frame-shape?) + (mapcat #(cph/get-children-with-self objects (:id %)))) + fonts (ff/shapes->fonts shapes)] + [:& ff/fontfaces-style {:fonts fonts}]) - (let [shapes (->> shapes - (remove cph/frame-shape?) - (mapcat #(cph/get-children-with-self objects (:id %)))) - fonts (ff/shapes->fonts shapes)] - [:& ff/fontfaces-style {:fonts fonts}]) - - (for [item shapes] - (let [frame? (= (:type item) :frame)] - (cond - (and frame? thumbnails? (some? (:thumbnail item))) - [:> shape-container {:shape item} - [:& frame/frame-thumbnail {:shape item}]] - - frame? - [:& frame-wrapper {:shape item - :key (:id item)}] - :else - [:& shape-wrapper {:shape item - :key (:id item)}])))]]])) + (for [item shapes] + [:& shape-wrapper {:shape item + :key (:id item)}])]]]])) ;; Component that serves for render frame thumbnails, mainly used in ;; the viewer and handoff - (mf/defc frame-svg {::mf/wrap [mf/memo]} [{:keys [objects frame zoom show-thumbnails?] :or {zoom 1} :as props}] (let [frame-id (:id frame) include-metadata? (mf/use-ctx export/include-metadata-ctx) - modifier - (mf/with-memo [(:x frame) (:y frame)] - (-> (gpt/point (:x frame) (:y frame)) - (gpt/negate) - (gmt/translate-matrix))) + bounds (gsb/get-object-bounds objects frame) + + ;; Bounds without shadows/blur will be the bounds of the thumbnail + bounds2 (gsb/get-object-bounds objects (dissoc frame :shadow :blur)) + + delta-bounds (gpt/point (:x bounds) (:y bounds)) + + modifier (gmt/translate-matrix (gpt/negate delta-bounds)) + + children-ids + (cph/get-children-ids objects frame-id) objects (mf/with-memo [frame-id objects modifier] (let [update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)] - (->> (cph/get-children-ids objects frame-id) + (->> children-ids (into [frame-id]) (reduce update-fn objects)))) frame (mf/with-memo [modifier] - (assoc-in frame [:modifiers :displacement] modifier)) + (-> frame + (assoc-in [:modifiers :displacement] modifier) + (gsh/transform-shape))) - wrapper - (mf/with-memo [objects] - (frame-wrapper-factory objects)) + frame + (cond-> frame + (and (some? bounds) (nil? (:children-bounds bounds))) + (assoc :children-bounds bounds2)) - width (* (:width frame) zoom) - height (* (:height frame) zoom) - vbox (format-viewbox {:width (:width frame 0) :height (:height frame 0)})] + frame (-> frame + (update-in [:children-bounds :x] - (:x delta-bounds)) + (update-in [:children-bounds :y] - (:y delta-bounds))) - [:svg {:view-box vbox - :width (ust/format-precision width viewbox-decimal-precision) - :height (ust/format-precision height viewbox-decimal-precision) - :version "1.1" - :xmlns "http://www.w3.org/2000/svg" - :xmlnsXlink "http://www.w3.org/1999/xlink" - :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns") - :fill "none"} - (if (or (not show-thumbnails?) (nil? (:thumbnail frame))) - [:& wrapper {:shape frame :view-box vbox}] + shape-wrapper + (mf/use-memo + (mf/deps objects) + #(shape-wrapper-factory objects)) - ;; Render the frame thumbnail - (let [frame (gsh/transform-shape frame)] - [:> shape-container {:shape frame} - [:& frame/frame-thumbnail {:shape frame}]]))])) + width (* (:width bounds) zoom) + height (* (:height bounds) zoom) + vbox (format-viewbox {:width (:width bounds 0) :height (:height bounds 0)})] + + [:& (mf/provider muc/render-thumbnails) {:value show-thumbnails?} + [:svg {:view-box vbox + :width (ust/format-precision width viewbox-decimal-precision) + :height (ust/format-precision height viewbox-decimal-precision) + :version "1.1" + :xmlns "http://www.w3.org/2000/svg" + :xmlnsXlink "http://www.w3.org/1999/xlink" + :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns") + :fill "none"} + + [:& shape-wrapper {:shape frame}]]])) ;; Component for rendering a thumbnail of a single componenent. Mainly ;; used to render thumbnails on assets panel. - (mf/defc component-svg {::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]} [{:keys [objects group zoom] :or {zoom 1} :as props}] @@ -363,7 +345,7 @@ (mf/defc object-svg {::mf/wrap [mf/memo]} - [{:keys [objects object-id render-texts? render-embed?] + [{:keys [objects object-id render-embed?] :or {render-embed? false} :as props}] (let [object (get objects object-id) @@ -371,60 +353,31 @@ (:hide-fill-on-export object) (assoc :fills [])) - {:keys [x y width height]} (get-object-bounds objects object-id) - vbox (dm/str x " " y " " width " " height) - frame-wrapper - (mf/with-memo [objects] - (frame-wrapper-factory objects)) - - group-wrapper - (mf/with-memo [objects] - (group-wrapper-factory objects)) + {:keys [width height] :as bounds} (gsb/get-object-bounds objects object) + vbox (format-viewbox bounds) + fonts (ff/shape->fonts object objects) shape-wrapper (mf/with-memo [objects] - (shape-wrapper-factory objects)) + (shape-wrapper-factory objects))] - text-shapes (sequence (filter cph/text-shape?) (vals objects)) - render-texts? (and render-texts? (d/seek (comp nil? :position-data) text-shapes))] + [:& (mf/provider export/include-metadata-ctx) {:value false} + [:& (mf/provider embed/context) {:value render-embed?} + [:svg {:id (dm/str "screenshot-" object-id) + :view-box vbox + :width (ust/format-precision width viewbox-decimal-precision) + :height (ust/format-precision height viewbox-decimal-precision) + :version "1.1" + :xmlns "http://www.w3.org/2000/svg" + :xmlnsXlink "http://www.w3.org/1999/xlink" + ;; Fix Chromium bug about color of html texts + ;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5 + :style {:-webkit-print-color-adjust :exact} + :fill "none"} - [:& (mf/provider embed/context) {:value render-embed?} - [:svg {:id (dm/str "screenshot-" object-id) - :view-box vbox - :width width - :height height - :version "1.1" - :xmlns "http://www.w3.org/2000/svg" - :xmlnsXlink "http://www.w3.org/1999/xlink" - ;; Fix Chromium bug about color of html texts - ;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5 - :style {:-webkit-print-color-adjust :exact} - :fill "none"} - - (let [fonts (ff/shape->fonts object objects)] - [:& ff/fontfaces-style {:fonts fonts}]) - - (case (:type object) - :frame [:& frame-wrapper {:shape object :view-box vbox}] - :group [:> shape-container {:shape object} - [:& group-wrapper {:shape object}]] - [:& shape-wrapper {:shape object}])] - - ;; Auxiliary SVG for rendering text-shapes - (when render-texts? - (for [object text-shapes] - [:& (mf/provider muc/text-plain-colors-ctx) {:value true} - [:svg - {:id (dm/str "screenshot-text-" (:id object)) - :view-box (dm/str "0 0 " (:width object) " " (:height object)) - :width (:width object) - :height (:height object) - :version "1.1" - :xmlns "http://www.w3.org/2000/svg" - :xmlnsXlink "http://www.w3.org/1999/xlink" - :fill "none"} - [:& shape-wrapper {:shape (assoc object :x 0 :y 0)}]]]))])) + [:& ff/fontfaces-style {:fonts fonts}] + [:& shape-wrapper {:shape object}]]]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SPRITES (DEBUG) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index ee068f205a..a5e0b115a1 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -73,10 +73,23 @@ (rx/map http/conditional-decode-transit) (rx/mapcat handle-response))) +(defn- send-command! + "A simple helper for a common case of sending and receiving transit + data to the penpot mutation api." + [id params {:keys [response-type form-data?]}] + (->> (http/send! {:method :post + :uri (u/join base-uri "api/rpc/command/" (name id)) + :credentials "include" + :body (if form-data? (http/form-data params) (http/transit-data params)) + :response-type (or response-type :text)}) + (rx/map http/conditional-decode-transit) + (rx/mapcat handle-response))) + (defn- dispatch [& args] (first args)) (defmulti query dispatch) (defmulti mutation dispatch) +(defmulti command dispatch) (defmethod query :default [id params] @@ -90,6 +103,18 @@ [id params] (send-mutation! id params)) +(defmethod command :default + [id params] + (send-command! id params nil)) + +(defmethod command :export-binfile + [id params] + (send-command! id params {:response-type :blob})) + +(defmethod command :import-binfile + [id params] + (send-command! id params {:form-data? true})) + (defn query! ([id] (query id {})) ([id params] (query id params))) @@ -98,7 +123,15 @@ ([id] (mutation id {})) ([id params] (mutation id params))) -(defmethod mutation :login-with-oauth +(defn command! + ([id] (command id {})) + ([id params] (command id params))) + +(defn cmd! + ([id] (command id {})) + ([id params] (command id params))) + +(defmethod command :login-with-oidc [_ {:keys [provider] :as params}] (let [uri (u/join base-uri "api/auth/oauth/" (d/name provider)) params (dissoc params :provider)] @@ -109,7 +142,7 @@ (rx/map http/conditional-decode-transit) (rx/mapcat handle-response)))) -(defmethod mutation :send-feedback +(defmethod command :send-feedback [_ params] (->> (http/send! {:method :post :uri (u/join base-uri "api/feedback") @@ -128,7 +161,7 @@ (rx/map http/conditional-decode-transit) (rx/mapcat handle-response))) -(defmethod query :exporter +(defmethod command :export [_ params] (let [default {:wait false :blob? false}] (send-export (merge default params)))) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index fd65d7569f..768a86f1fe 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -29,6 +29,7 @@ (mf/defc on-main-error [{:keys [error] :as props}] (mf/with-effect + (js/console.log error) (st/emit! (rt/assign-exception error))) [:span "Internal application error"]) diff --git a/frontend/src/app/main/ui/auth/login.cljs b/frontend/src/app/main/ui/auth/login.cljs index 8c9cc7b0a7..a1b849bdbd 100644 --- a/frontend/src/app/main/ui/auth/login.cljs +++ b/frontend/src/app/main/ui/auth/login.cljs @@ -23,10 +23,11 @@ [rumext.alpha :as mf])) (def show-alt-login-buttons? - (or cf/google-client-id - cf/gitlab-client-id - cf/github-client-id - cf/oidc-client-id)) + (some (partial contains? @cf/flags) + [:login-with-google + :login-with-github + :login-with-gitlab + :login-with-oidc])) (s/def ::email ::us/email) (s/def ::password ::us/not-empty-string) @@ -36,19 +37,27 @@ (s/keys :req-un [::email ::password] :opt-un [::invitation-token])) -(defn- login-with-oauth +(defn- login-with-oidc [event provider params] (dom/prevent-default event) - (->> (rp/mutation! :login-with-oauth (assoc params :provider provider)) + (->> (rp/command! :login-with-oidc (assoc params :provider provider)) (rx/subs (fn [{:keys [redirect-uri] :as rsp}] - (.replace js/location redirect-uri))))) + (.replace js/location redirect-uri)) + (fn [{:keys [type code] :as error}] + (cond + (and (= type :restriction) + (= code :provider-not-configured)) + (st/emit! (dm/error (tr "errors.auth-provider-not-configured"))) + + :else + (st/emit! (dm/error (tr "errors.generic")))))))) (defn- login-with-ldap [event params] (dom/prevent-default event) (dom/stop-propagation event) (let [{:keys [on-error]} (meta params)] - (->> (rp/mutation! :login-with-ldap params) + (->> (rp/command! :login-with-ldap params) (rx/subs (fn [profile] (if-let [token (:invitation-token profile)] (st/emit! (rt/nav :auth-verify-token {} {:token token})) @@ -56,14 +65,18 @@ (fn [{:keys [type code] :as error}] (cond (and (= type :restriction) - (= code :ldap-disabled)) + (= code :ldap-not-initialized)) (st/emit! (dm/error (tr "errors.ldap-disabled"))) (fn? on-error) - (on-error error))))))) + (on-error error) + + :else + (st/emit! (dm/error (tr "errors.generic"))))))))) + (mf/defc login-form - [{:keys [params] :as props}] + [{:keys [params on-success-callback] :as props}] (let [initial (mf/use-memo (mf/deps params) (constantly params)) error (mf/use-state false) @@ -73,10 +86,17 @@ (fn [_] (reset! error (tr "errors.wrong-credentials"))) - on-succes + on-success-default (fn [data] (when-let [token (:invitation-token data)] (st/emit! (rt/nav :auth-verify-token {} {:token token})))) + + on-success + (fn [data] + (if (nil? on-success-callback) + (on-success-default data) + (on-success-callback) + )) on-submit (mf/use-callback @@ -84,7 +104,7 @@ (reset! error nil) (let [params (with-meta (:clean-data @form) {:on-error on-error - :on-success on-succes})] + :on-success on-success})] (st/emit! (du/login params))))) on-submit-ldap @@ -95,7 +115,7 @@ (let [params (:clean-data @form)] (login-with-ldap event (with-meta params {:on-error on-error - :on-success on-succes})))))] + :on-success on-success})))))] [:* (when-let [message @error] [:& msgs/inline-banner @@ -134,63 +154,68 @@ (mf/defc login-buttons [{:keys [params] :as props}] [:div.auth-buttons - (when cf/google-client-id + (when (contains? @cf/flags :login-with-google) [:a.btn-primary.btn-large.btn-google-auth - {:on-click #(login-with-oauth % :google params)} + {:on-click #(login-with-oidc % :google params)} [:span.logo i/brand-google] (tr "auth.login-with-google-submit")]) - (when cf/github-client-id + (when (contains? @cf/flags :login-with-github) [:a.btn-primary.btn-large.btn-github-auth - {:on-click #(login-with-oauth % :github params)} + {:on-click #(login-with-oidc % :github params)} [:span.logo i/brand-github] (tr "auth.login-with-github-submit")]) - (when cf/gitlab-client-id + (when (contains? @cf/flags :login-with-gitlab) [:a.btn-primary.btn-large.btn-gitlab-auth - {:on-click #(login-with-oauth % :gitlab params)} + {:on-click #(login-with-oidc % :gitlab params)} [:span.logo i/brand-gitlab] (tr "auth.login-with-gitlab-submit")]) - (when cf/oidc-client-id + (when (contains? @cf/flags :login-with-oidc) [:a.btn-primary.btn-large.btn-github-auth - {:on-click #(login-with-oauth % :oidc params)} + {:on-click #(login-with-oidc % :oidc params)} [:span.logo i/brand-openid] (tr "auth.login-with-oidc-submit")])]) (mf/defc login-button-oidc [{:keys [params] :as props}] - (when cf/oidc-client-id + (when (contains? @cf/flags :login-with-oidc) [:div.link-entry.link-oidc - [:a {:on-click #(login-with-oauth % :oidc params)} + [:a {:on-click #(login-with-oidc % :oidc params)} (tr "auth.login-with-oidc-submit")]])) +(mf/defc login-methods + [{:keys [params on-success-callback] :as props}] + [:* + (when show-alt-login-buttons? + [:* + [:span.separator + [:span.line] + [:span.text (tr "labels.continue-with")] + [:span.line]] + + [:div.buttons + [:& login-buttons {:params params}]] + + (when (or (contains? @cf/flags :login) + (contains? @cf/flags :login-with-ldap)) + [:span.separator + [:span.line] + [:span.text (tr "labels.or")] + [:span.line]])]) + + (when (or (contains? @cf/flags :login) + (contains? @cf/flags :login-with-ldap)) + [:& login-form {:params params :on-success-callback on-success-callback}])]) + (mf/defc login-page [{:keys [params] :as props}] [:div.generic-form.login-form [:div.form-container [:h1 {:data-test "login-title"} (tr "auth.login-title")] - (when show-alt-login-buttons? - [:* - [:span.separator - [:span.line] - [:span.text (tr "labels.continue-with")] - [:span.line]] - - [:div.buttons - [:& login-buttons {:params params}]] - - (when (or (contains? @cf/flags :login) - (contains? @cf/flags :login-with-ldap)) - [:span.separator - [:span.line] - [:span.text (tr "labels.or")] - [:span.line]])]) - - (when (or (contains? @cf/flags :login) - (contains? @cf/flags :login-with-ldap)) - [:& login-form {:params params}]) + [:& login-methods {:params params}] [:div.links (when (contains? @cf/flags :login) diff --git a/frontend/src/app/main/ui/auth/recovery_request.cljs b/frontend/src/app/main/ui/auth/recovery_request.cljs index 6477cfb32d..436f7cded8 100644 --- a/frontend/src/app/main/ui/auth/recovery_request.cljs +++ b/frontend/src/app/main/ui/auth/recovery_request.cljs @@ -22,15 +22,19 @@ (s/def ::recovery-request-form (s/keys :req-un [::email])) (mf/defc recovery-form - [] + [{:keys [on-success-callback] :as props}] (let [form (fm/use-form :spec ::recovery-request-form :initial {}) submitted (mf/use-state false) + default-success-finish #(st/emit! (dm/info (tr "auth.notifications.recovery-token-sent"))) + on-success (mf/use-callback - (fn [_ _] + (fn [cdata _] (reset! submitted false) - (st/emit! (dm/info (tr "auth.notifications.recovery-token-sent"))))) + (if (nil? on-success-callback) + (default-success-finish) + (on-success-callback (:email cdata))))) on-error (mf/use-callback @@ -74,15 +78,17 @@ ;; --- Recovery Request Page (mf/defc recovery-request-page - [] - [:section.generic-form - [:div.form-container - [:h1 (tr "auth.recovery-request-title")] - [:div.subtitle (tr "auth.recovery-request-subtitle")] - [:& recovery-form] + [{:keys [params on-success-callback go-back-callback] :as props}] + (let [default-go-back #(st/emit! (rt/nav :auth-login)) + go-back (or go-back-callback default-go-back)] + [:section.generic-form + [:div.form-container + [:h1 (tr "auth.recovery-request-title")] + [:div.subtitle (tr "auth.recovery-request-subtitle")] + [:& recovery-form {:params params :on-success-callback on-success-callback}] - [:div.links - [:div.link-entry - [:a {:on-click #(st/emit! (rt/nav :auth-login)) - :data-test "go-back-link"} - (tr "labels.go-back")]]]]]) + [:div.links + [:div.link-entry + [:a {:on-click go-back + :data-test "go-back-link"} + (tr "labels.go-back")]]]]])) diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 739b7ad1da..f2e3a4fd09 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -68,28 +68,34 @@ (st/emit! (dm/error (tr "errors.generic"))))) (defn- handle-prepare-register-success - [_ params] + [params] (st/emit! (rt/nav :auth-register-validate {} params))) + (mf/defc register-form - [{:keys [params] :as props}] + [{:keys [params on-success-callback] :as props}] (let [initial (mf/use-memo (mf/deps params) (constantly params)) form (fm/use-form :spec ::register-form :validators [validate] :initial initial) submitted? (mf/use-state false) + on-success (fn [p] + (if (nil? on-success-callback) + (handle-prepare-register-success p) + (on-success-callback p))) + on-submit (mf/use-callback (fn [form _event] (reset! submitted? true) (let [cdata (:clean-data @form)] - (->> (rp/mutation :prepare-register-profile cdata) + (->> (rp/command! :prepare-register-profile cdata) (rx/map #(merge % params)) (rx/finalize #(reset! submitted? false)) - (rx/subs (partial handle-prepare-register-success form) - (partial handle-prepare-register-error form)))))) - ] + (rx/subs + on-success + (partial handle-prepare-register-error form))))))] [:& fm/form {:on-submit on-submit @@ -113,15 +119,10 @@ :disabled @submitted? :data-test "register-form-submit"}]])) -(mf/defc register-page - [{:keys [params] :as props}] - [:div.form-container - [:h1 {:data-test "registration-title"} (tr "auth.register-title")] - [:div.subtitle (tr "auth.register-subtitle")] - - (when (contains? @cf/flags :demo-warning) - [:& demo-warning]) +(mf/defc register-methods + [{:keys [params on-success-callback] :as props}] + [:* (when login/show-alt-login-buttons? [:* [:span.separator @@ -139,7 +140,19 @@ [:span.text (tr "labels.or")] [:span.line]])]) - [:& register-form {:params params}] + [:& register-form {:params params :on-success-callback on-success-callback}]]) + +(mf/defc register-page + [{:keys [params] :as props}] + [:div.form-container + + [:h1 {:data-test "registration-title"} (tr "auth.register-title")] + [:div.subtitle (tr "auth.register-subtitle")] + + (when (contains? @cf/flags :demo-warning) + [:& demo-warning]) + + [:& register-methods {:params params}] [:div.links [:div.link-entry @@ -170,7 +183,7 @@ (st/emit! (dm/error (tr "errors.generic")))))) (defn- handle-register-success - [_form data] + [data] (cond (some? (:invitation-token data)) (let [token (:invitation-token data)] @@ -197,21 +210,25 @@ ::accept-newsletter-subscription]))) (mf/defc register-validate-form - [{:keys [params] :as props}] + [{:keys [params on-success-callback] :as props}] (let [form (fm/use-form :spec ::register-validate-form :initial params) submitted? (mf/use-state false) + on-success (fn [p] + (if (nil? on-success-callback) + (handle-register-success p) + (on-success-callback (:email p)))) + on-submit (mf/use-callback (fn [form _event] (reset! submitted? true) (let [params (:clean-data @form)] - (->> (rp/mutation :register-profile params) + (->> (rp/command! :register-profile params) (rx/finalize #(reset! submitted? false)) - (rx/subs (partial handle-register-success form) - (partial handle-register-error form)))))) - ] + (rx/subs on-success + (partial handle-register-error form))))))] [:& fm/form {:on-submit on-submit :form form} diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index a7019d3627..31fe477678 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -6,9 +6,11 @@ (ns app.main.ui.comments (:require + [app.common.geom.point :as gpt] [app.config :as cfg] [app.main.data.comments :as dcm] [app.main.data.modal :as modal] + [app.main.data.workspace.comments :as dwcm] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] @@ -109,8 +111,10 @@ [:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]])])) (mf/defc draft-thread - [{:keys [draft zoom on-cancel on-submit] :as props}] - (let [position (:position draft) + [{:keys [draft zoom on-cancel on-submit position-modifier]}] + (let [position (cond-> (:position draft) + (some? position-modifier) + (gpt/transform position-modifier)) content (:content draft) pos-x (* (:x position) zoom) pos-y (* (:y position) zoom) @@ -183,7 +187,7 @@ [:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]]])) (mf/defc comment-item - [{:keys [comment thread users] :as props}] + [{:keys [comment thread users origin] :as props}] (let [owner (get users (:owner-id comment)) profile (mf/deref refs/profile) options (mf/use-state false) @@ -210,7 +214,9 @@ (mf/use-callback (mf/deps thread) #(st/emit! (dcm/close-thread) - (dcm/delete-comment-thread thread))) + (if (= origin :viewer) + (dcm/delete-comment-thread-on-viewer thread) + (dcm/delete-comment-thread-on-workspace thread)))) on-delete-thread @@ -278,9 +284,13 @@ (l/derived (l/in [:comments id]) st/state)) (mf/defc thread-comments - [{:keys [thread zoom users]}] + {::mf/wrap [mf/memo]} + [{:keys [thread zoom users origin position-modifier]}] (let [ref (mf/use-ref) - pos (:position thread) + pos (cond-> (:position thread) + (some? position-modifier) + (gpt/transform position-modifier)) + pos-x (+ (* (:x pos) zoom) 14) pos-y (- (* (:y pos) zoom) 14) @@ -313,39 +323,146 @@ [:div.comments [:& comment-item {:comment comment :users users - :thread thread}] + :thread thread + :origin origin}] (for [item (rest comments)] [:* [:hr] - [:& comment-item {:comment item :users users}]]) + [:& comment-item {:comment item + :users users + :origin origin}]]) [:div {:ref ref}]] [:& reply-form {:thread thread}]]))) +(defn use-buble + [zoom {:keys [position frame-id]}] + (let [dragging-ref (mf/use-ref false) + start-ref (mf/use-ref nil) + + state (mf/use-state {:hover false + :new-position-x nil + :new-position-y nil + :new-frame-id frame-id}) + + on-pointer-down + (mf/use-callback + (fn [event] + (dom/capture-pointer event) + (mf/set-ref-val! dragging-ref true) + (mf/set-ref-val! start-ref (dom/get-client-position event)))) + + on-pointer-up + (mf/use-callback + (mf/deps (select-keys @state [:new-position-x :new-position-y :new-frame-id])) + (fn [_ thread] + (when (and + (some? (:new-position-x @state)) + (some? (:new-position-y @state))) + (st/emit! (dwcm/update-comment-thread-position thread [(:new-position-x @state) (:new-position-y @state)]))))) + + on-lost-pointer-capture + (mf/use-callback + (fn [event] + (dom/release-pointer event) + (mf/set-ref-val! dragging-ref false) + (mf/set-ref-val! start-ref nil) + (swap! state assoc :new-position-x nil) + (swap! state assoc :new-position-y nil))) + + on-mouse-move + (mf/use-callback + (mf/deps position zoom) + (fn [event] + (when-let [_ (mf/ref-val dragging-ref)] + (let [start-pt (mf/ref-val start-ref) + current-pt (dom/get-client-position event) + delta-x (/ (- (:x current-pt) (:x start-pt)) zoom) + delta-y (/ (- (:y current-pt) (:y start-pt)) zoom)] + (swap! state assoc + :new-position-x (+ (:x position) delta-x) + :new-position-y (+ (:y position) delta-y))))))] + + {:on-pointer-down on-pointer-down + :on-pointer-up on-pointer-up + :on-mouse-move on-mouse-move + :on-lost-pointer-capture on-lost-pointer-capture + :state state})) + (mf/defc thread-bubble {::mf/wrap [mf/memo]} - [{:keys [thread zoom on-click] :as params}] - (let [pos (:position thread) - pos-x (* (:x pos) zoom) - pos-y (* (:y pos) zoom) - on-click* (fn [event] - (dom/stop-propagation event) - (on-click thread))] + [{:keys [thread zoom open? on-click origin position-modifier]}] + (let [pos (cond-> (:position thread) + (some? position-modifier) + (gpt/transform position-modifier)) + + drag? (mf/use-ref nil) + was-open? (mf/use-ref nil) + + {:keys [on-pointer-down + on-pointer-up + on-mouse-move + state + on-lost-pointer-capture]} (use-buble zoom thread) + + pos-x (* (or (:new-position-x @state) (:x pos)) zoom) + pos-y (* (or (:new-position-y @state) (:y pos)) zoom) + + on-pointer-down* + (mf/use-callback + (mf/deps origin was-open? open? drag? on-pointer-down) + (fn [event] + (when (not= origin :viewer) + (mf/set-ref-val! was-open? open?) + (when open? (st/emit! (dcm/close-thread))) + (mf/set-ref-val! drag? false) + (dom/stop-propagation event) + (on-pointer-down event)))) + + on-pointer-up* + (mf/use-callback + (mf/deps origin thread was-open? drag? on-pointer-up) + (fn [event] + (when (not= origin :viewer) + (dom/stop-propagation event) + (on-pointer-up event thread) + + (when (or (and (mf/ref-val was-open?) (mf/ref-val drag?)) + (and (not (mf/ref-val was-open?)) (not (mf/ref-val drag?)))) + (st/emit! (dcm/open-thread thread)))))) + + on-mouse-move* + (mf/use-callback + (mf/deps origin drag? on-mouse-move) + (fn [event] + (when (not= origin :viewer) + (mf/set-ref-val! drag? true) + (dom/stop-propagation event) + (on-mouse-move event)))) + + on-click* + (mf/use-callback + (mf/deps origin thread on-click) + (fn [event] + (dom/stop-propagation event) + (when (= origin :viewer) + (on-click thread))))] [:div.thread-bubble {:style {:top (str pos-y "px") :left (str pos-x "px")} - :on-mouse-down (fn [event] - (dom/prevent-default event)) + :on-pointer-down on-pointer-down* + :on-pointer-up on-pointer-up* + :on-mouse-move on-mouse-move* + :on-click on-click* + :on-lost-pointer-capture on-lost-pointer-capture :class (dom/classnames :resolved (:is-resolved thread) - :unread (pos? (:count-unread-comments thread))) - :on-click on-click*} + :unread (pos? (:count-unread-comments thread)))} [:span (:seqn thread)]])) (mf/defc comment-thread - [{:keys [item users on-click] :as props}] + [{:keys [item users on-click]}] (let [owner (get users (:owner-id item)) - on-click* (mf/use-callback (mf/deps item) diff --git a/frontend/src/app/main/ui/components/color_bullet.cljs b/frontend/src/app/main/ui/components/color_bullet.cljs index aa177e0947..5fd3acd0fc 100644 --- a/frontend/src/app/main/ui/components/color_bullet.cljs +++ b/frontend/src/app/main/ui/components/color_bullet.cljs @@ -17,23 +17,31 @@ :radial (tr "workspace.gradients.radial") nil)) -(mf/defc color-bullet [{:keys [color on-click]}] - (if (uc/multiple? color) - [:div.color-bullet.multiple {:on-click #(when on-click (on-click %))}] +(mf/defc color-bullet + {::mf/wrap [mf/memo]} + [{:keys [color on-click]}] + (let [on-click (mf/use-fn + (mf/deps color on-click) + (fn [event] + (when (fn? on-click) + (^function on-click color event))))] - ;; No multiple selection - (let [color (if (string? color) {:color color :opacity 1} color)] - [:div.color-bullet.tooltip.tooltip-right - {:class (dom/classnames :is-library-color (some? (:id color)) - :is-not-library-color (nil? (:id color)) - :is-gradient (some? (:gradient color))) - :on-click #(when on-click (on-click %)) - :alt (or (:name color) (:color color) (gradient-type->string (:type (:gradient color))))} - (if (:gradient color) - [:div.color-bullet-wrapper {:style {:background (uc/color->background color)}}] - [:div.color-bullet-wrapper - [:div.color-bullet-left {:style {:background (uc/color->background (assoc color :opacity 1))}}] - [:div.color-bullet-right {:style {:background (uc/color->background color)}}]])]))) + (if (uc/multiple? color) + [:div.color-bullet.multiple {:on-click on-click}] + + ;; No multiple selection + (let [color (if (string? color) {:color color :opacity 1} color)] + [:div.color-bullet.tooltip.tooltip-right + {:class (dom/classnames :is-library-color (some? (:id color)) + :is-not-library-color (nil? (:id color)) + :is-gradient (some? (:gradient color))) + :on-click on-click + :alt (or (:name color) (:color color) (gradient-type->string (:type (:gradient color))))} + (if (:gradient color) + [:div.color-bullet-wrapper {:style {:background (uc/color->background color)}}] + [:div.color-bullet-wrapper + [:div.color-bullet-left {:style {:background (uc/color->background (assoc color :opacity 1))}}] + [:div.color-bullet-right {:style {:background (uc/color->background color)}}]])])))) (mf/defc color-name [{:keys [color size on-click on-double-click]}] (let [color (if (string? color) {:color color :opacity 1} color) diff --git a/frontend/src/app/main/ui/components/context_menu.cljs b/frontend/src/app/main/ui/components/context_menu.cljs index 6054fd505b..498974a88f 100644 --- a/frontend/src/app/main/ui/components/context_menu.cljs +++ b/frontend/src/app/main/ui/components/context_menu.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.components.context-menu (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.main.refs :as refs] [app.main.ui.components.dropdown :refer [dropdown']] [app.main.ui.icons :as i] @@ -110,10 +111,10 @@ (for [[index [option-name option-handler sub-options data-test]] (d/enumerate (:options level))] (when option-name (if (= option-name :separator) - [:li.separator] + [:li.separator {:key (dm/str "context-item-" index)}] [:li.context-menu-item {:class (dom/classnames :is-selected (and selected (= option-name selected))) - :key index} + :key (dm/str "context-item-" index)} (if-not sub-options [:a.context-menu-action {:on-click #(do (dom/stop-propagation %) (on-close) diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs index 8b4723a286..24720a645f 100644 --- a/frontend/src/app/main/ui/components/forms.cljs +++ b/frontend/src/app/main/ui/components/forms.cljs @@ -243,7 +243,7 @@ (into [] (distinct) (conj coll item))) (mf/defc multi-input - [{:keys [form label class name trim valid-item-fn] :as props}] + [{:keys [form label class name trim valid-item-fn on-submit] :as props}] (let [form (or form (mf/use-ctx form-ctx)) input-name (get props :name) touched? (get-in @form [:touched input-name]) @@ -297,8 +297,11 @@ (dom/prevent-default event) (dom/stop-propagation event) (let [val (cond-> @value trim str/trim)] - (reset! value "") - (swap! items conj-dedup {:text val :valid (valid-item-fn val)}))) + (when (and (kbd/enter? event) (str/empty? @value) (not-empty @items)) + (on-submit form)) + (when (not (str/empty? @value)) + (reset! value "") + (swap! items conj-dedup {:text val :valid (valid-item-fn val)})))) (and (kbd/backspace? event) (str/empty? @value)) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 634739944f..92a01bb6b4 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -8,12 +8,7 @@ (:require [rumext.alpha :as mf])) -(def render-ctx (mf/create-context nil)) -(def def-ctx (mf/create-context false)) - -;; This content is used to replace complex colors to simple ones -;; for text shapes in the export process -(def text-plain-colors-ctx (mf/create-context false)) +(def render-id (mf/create-context nil)) (def current-route (mf/create-context nil)) (def current-profile (mf/create-context nil)) @@ -21,4 +16,8 @@ (def current-project-id (mf/create-context nil)) (def current-page-id (mf/create-context nil)) (def current-file-id (mf/create-context nil)) -(def scroll-ctx (mf/create-context nil)) \ No newline at end of file +(def current-scroll (mf/create-context nil)) +(def current-zoom (mf/create-context nil)) + +(def active-frames (mf/create-context nil)) +(def render-thumbnails (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/dashboard/comments.cljs b/frontend/src/app/main/ui/dashboard/comments.cljs index 582ea47ac4..11d3ef7011 100644 --- a/frontend/src/app/main/ui/dashboard/comments.cljs +++ b/frontend/src/app/main/ui/dashboard/comments.cljs @@ -31,7 +31,7 @@ show-dropdown (mf/use-fn #(reset! show-dropdown? true)) hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) threads-map (mf/deref refs/comment-threads) - users (mf/deref refs/users) + users (mf/deref refs/current-file-comments-users) tgroups (->> (vals threads-map) (sort-by :modified-at) diff --git a/frontend/src/app/main/ui/dashboard/export.cljs b/frontend/src/app/main/ui/dashboard/export.cljs index 5a1425ba6b..58f8d957d5 100644 --- a/frontend/src/app/main/ui/dashboard/export.cljs +++ b/frontend/src/app/main/ui/dashboard/export.cljs @@ -51,7 +51,7 @@ (mf/defc export-dialog {::mf/register modal/components ::mf/register-as :export} - [{:keys [team-id files has-libraries?]}] + [{:keys [team-id files has-libraries? binary?]}] (let [state (mf/use-state {:status :prepare :files (->> files (mapv #(assoc % :loading? true)))}) selected-option (mf/use-state :all) @@ -60,10 +60,11 @@ (fn [] (swap! state assoc :status :exporting) (->> (uw/ask-many! - {:cmd :export-file + {:cmd (if binary? :export-binary-file :export-standard-file) :team-id team-id :export-type @selected-option - :files (->> files (mapv :id))}) + :files files + }) (rx/delay-emit 1000) (rx/subs (fn [msg] @@ -73,6 +74,7 @@ (when (= :finish (:type msg)) (swap! state update :files mark-file-success (:file-id msg)) (dom/trigger-download-uri (:filename msg) (:mtype msg) (:uri msg))))))) + cancel-fn (mf/use-callback (fn [event] diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index bbcdb74e05..7903f591ae 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -158,26 +158,38 @@ :on-accept del-shared}))) on-export-files + (fn [event-name binary?] + (st/emit! (ptk/event ::ev/event {::ev/name event-name + ::ev/origin "dashboard" + :num-files (count files)})) + + (->> (rx/from files) + (rx/flat-map + (fn [file] + (->> (rp/query :file-libraries {:file-id (:id file)}) + (rx/map #(assoc file :has-libraries? (d/not-empty? %)))))) + (rx/reduce conj []) + (rx/subs + (fn [files] + (st/emit! + (modal/show + {:type :export + :team-id current-team-id + :has-libraries? (->> files (some :has-libraries?)) + :files files + :binary? binary?})))))) + + on-export-binary-files (mf/use-callback (mf/deps files current-team-id) (fn [_] - (st/emit! (ptk/event ::ev/event {::ev/name "export-files" - ::ev/origin "dashboard" - :num-files (count files)})) - (->> (rx/from files) - (rx/flat-map - (fn [file] - (->> (rp/query :file-libraries {:file-id (:id file)}) - (rx/map #(assoc file :has-libraries? (d/not-empty? %)))))) - (rx/reduce conj []) - (rx/subs - (fn [files] - (st/emit! - (modal/show - {:type :export - :team-id current-team-id - :has-libraries? (->> files (some :has-libraries?)) - :files files}))))))) + (on-export-files "export-binary-files" true))) + + on-export-standard-files + (mf/use-callback + (mf/deps files current-team-id) + (fn [_] + (on-export-files "export-standard-files" false))) ;; NOTE: this is used for detect if component is still mounted mounted-ref (mf/use-ref true)] @@ -210,7 +222,8 @@ [[(tr "dashboard.duplicate-multi" file-count) on-duplicate nil "duplicate-multi"] (when (or (seq current-projects) (seq other-teams)) [(tr "dashboard.move-to-multi" file-count) nil sub-options "move-to-multi"]) - [(tr "dashboard.export-multi" file-count) on-export-files] + [(tr "dashboard.export-binary-multi" file-count) on-export-binary-files] + [(tr "dashboard.export-standard-multi" file-count) on-export-standard-files] [:separator] [(tr "labels.delete-multi-files" file-count) on-delete nil "delete-multi-files"]] @@ -222,7 +235,9 @@ (if (:is-shared file) [(tr "dashboard.remove-shared") on-del-shared nil "file-del-shared"] [(tr "dashboard.add-shared") on-add-shared nil "file-add-shared"]) - [(tr "dashboard.export-single") on-export-files nil "file-export"] + [:separator] + [(tr "dashboard.download-binary-file") on-export-binary-files nil "download-binary-file"] + [(tr "dashboard.download-standard-file") on-export-standard-files nil "download-standard-file"] [:separator] [(tr "labels.delete") on-delete nil "file-delete"]])] diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index 6fa2f4697b..565dda8fb3 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.dashboard.import (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.logging :as log] [app.main.data.events :as ev] [app.main.data.modal :as modal] @@ -49,7 +50,7 @@ (let [on-file-selected (use-import-file project-id on-finish-import)] [:form.import-file - [:& file-uploader {:accept ".penpot" + [:& file-uploader {:accept ".penpot,.zip" :multi true :ref external-ref :on-selected on-file-selected}]])) @@ -78,19 +79,20 @@ (= uri (:uri file)) (assoc :status :analyze-error)))))) -(defn set-analyze-result [files uri data] +(defn set-analyze-result [files uri type data] (let [existing-files? (into #{} (->> files (map :file-id) (filter some?))) replace-file (fn [file] - (if (and (= uri (:uri file) ) + (if (and (= uri (:uri file)) (= (:status file) :analyzing)) (->> (:files data) - (remove (comp existing-files? first) ) + (remove (comp existing-files? first)) (mapv (fn [[file-id file-data]] (-> file-data (assoc :file-id file-id :status :ready - :uri uri))))) + :uri uri + :type type))))) [file]))] (into [] (mapcat replace-file) files))) @@ -139,7 +141,7 @@ (str message))) (mf/defc import-entry - [{:keys [state file editing?]}] + [{:keys [state file editing? can-be-deleted?]}] (let [loading? (or (= :analyzing (:status file)) (= :importing (:status file))) @@ -206,9 +208,11 @@ [:div.file-name-label (:name file) (when is-shared? i/library)]) - [:div.edit-entry-buttons - [:button {:on-click handle-edit-entry} i/pencil] - [:button {:on-click handle-remove-entry} i/trash]]] + [:div.edit-entry-buttons + (when (= "application/zip" (:type file)) + [:button {:on-click handle-edit-entry} i/pencil]) + (when can-be-deleted? + [:button {:on-click handle-remove-entry} i/trash])]] (cond analyze-error? @@ -245,21 +249,20 @@ (fn [files] (->> (uw/ask-many! {:cmd :analyze-import - :files (->> files (mapv :uri))}) + :files files}) (rx/delay-emit emit-delay) (rx/subs - (fn [{:keys [uri data error] :as msg}] + (fn [{:keys [uri data error type] :as msg}] (log/debug :uri uri :data data :error error) (if (some? error) (swap! state update :files set-analyze-error uri) - (swap! state update :files set-analyze-result uri data))))))) + (swap! state update :files set-analyze-result uri type data))))))) import-files (mf/use-callback (fn [project-id files] (st/emit! (ptk/event ::ev/event {::ev/name "import-files" :num-files (count files)})) - (->> (uw/ask-many! {:cmd :import-files :project-id project-id @@ -281,7 +284,7 @@ (mf/deps project-id (:files @state)) (fn [event] (dom/prevent-default event) - (let [files (->> @state :files (filterv #(= :ready (:status %))))] + (let [files (->> @state :files (filterv #(and (= :ready (:status %)) (not (:deleted? %)))))] (import-files project-id files)) (swap! state @@ -300,7 +303,8 @@ warning-files (->> @state :files (filter #(and (= (:status %) :import-finish) (d/not-empty? (:errors %)))) count) success-files (->> @state :files (filter #(and (= (:status %) :import-finish) (empty? (:errors %)))) count) pending-analysis? (> (->> @state :files (filter #(= (:status %) :analyzing)) count) 0) - pending-import? (> (->> @state :files (filter #(= (:status %) :importing)) count) 0)] + pending-import? (> (->> @state :files (filter #(= (:status %) :importing)) count) 0) + files (->> (:files @state) (filterv (comp not :deleted?)))] (mf/use-effect (fn [] @@ -333,12 +337,14 @@ [:div.icon i/checkbox-checked] [:div.message (tr "dashboard.import.import-message" success-files)]])) - (for [file (->> (:files @state) (filterv (comp not :deleted?)))] - (let [editing? (and (some? (:file-id file)) - (= (:file-id file) (:editing @state)))] + (for [file files] + (let [editing? (and (some? (:file-id file)) + (= (:file-id file) (:editing @state)))] [:& import-entry {:state state + :key (dm/str (:id file)) :file file - :editing? editing?}]))] + :editing? editing? + :can-be-deleted? (> (count files) 1)}]))] [:div.modal-footer [:div.action-buttons diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 939b6e2d1a..98c92857f2 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -77,7 +77,7 @@ ] (filterv identity))) -(s/def ::emails (s/and ::us/set-of-emails d/not-empty?)) +(s/def ::emails (s/and ::us/set-of-valid-emails d/not-empty?)) (s/def ::role ::us/keyword) (s/def ::team-id ::us/uuid) @@ -142,7 +142,8 @@ :auto-focus? true :trim true :valid-item-fn us/parse-email - :label (tr "modals.invite-member.emails")}] + :label (tr "modals.invite-member.emails") + :on-submit on-submit}] [:& fm/select {:name :role :options roles}]] [:div.action-buttons @@ -605,7 +606,7 @@ [:div.label (tr "dashboard.team-info")] [:div.name (:name team)] [:div.icon - [:span.update-overlay {:on-click on-image-click} i/exit] + [:span.update-overlay {:on-click on-image-click} i/image] [:img {:src (cfg/resolve-team-photo-url team)}] [:& file-uploader {:accept "image/jpeg,image/png" :multi false diff --git a/frontend/src/app/main/ui/features.cljs b/frontend/src/app/main/ui/features.cljs new file mode 100644 index 0000000000..30f0ebb596 --- /dev/null +++ b/frontend/src/app/main/ui/features.cljs @@ -0,0 +1,62 @@ +;; 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.features + (:require + [app.common.data :as d] + [app.common.logging :as log] + [app.main.store :as st] + [okulary.core :as l] + [potok.core :as ptk] + [rumext.alpha :as mf])) + +(log/set-level! :debug) + +(def features-list #{:auto-layout}) + +(defn toggle-feature + [feature] + (ptk/reify ::toggle-feature + ptk/UpdateEvent + (update [_ state] + (log/debug :msg "toggle-feature" + :feature (d/name feature) + :result (if (not (contains? (:features state) feature)) + "enabled" + "disabled")) + + (-> state + (update :features + (fn [features] + (let [features (or features #{})] + (if (contains? features feature) + (disj features feature) + (conj features feature))))))))) + +(defn toggle-feature! + [feature] + (assert (contains? features-list feature) "Not supported feature") + (st/emit! (toggle-feature feature))) + +(def features + (l/derived :features st/state)) + +(defn active-feature + [feature] + (l/derived #(contains? % feature) features)) + +(defn use-feature + [feature] + (assert (contains? features-list feature) "Not supported feature") + (let [active-feature-ref (mf/use-memo (mf/deps feature) #(active-feature feature)) + active-feature? (mf/deref active-feature-ref)] + active-feature?)) + +;; By default the features are active in local environments +(when *assert* + ;; Activate all features in local environment + (doseq [f features-list] + (toggle-feature! f))) diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index d9c2b72bae..6fc2c6850b 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -7,16 +7,26 @@ (ns app.main.ui.hooks "A collection of general purpose react hooks." (:require + [app.common.data.macros :as dm] [app.common.pages :as cp] + [app.common.uuid :as uuid] + [app.main.broadcast :as mbc] [app.main.data.shortcuts :as dsc] [app.main.refs :as refs] [app.main.store :as st] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] + [app.util.storage :refer [storage]] [app.util.timers :as ts] [beicon.core :as rx] + [goog.functions :as f] [rumext.alpha :as mf])) +(defn use-id + "Get a stable id value across rerenders." + [] + (mf/use-memo #(dm/str (uuid/next)))) + (defn use-rxsub [ob] (let [[state reset-state!] (mf/useState @ob)] @@ -191,7 +201,6 @@ [(deref state) ref])) - (defn use-stream "Wraps the subscription to a stream into a `use-effect` call" ([stream on-subscribe] @@ -205,6 +214,7 @@ ;; https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state (defn use-previous + "Returns the value from previuous render cycle." [value] (let [ref (mf/use-ref value)] (mf/use-effect @@ -214,13 +224,27 @@ (mf/ref-val ref))) (defn use-update-var + "Returns a var pointer what automatically updates with latest values." [value] - (let [ref (mf/use-var value)] - (mf/use-effect - (mf/deps value) - (fn [] - (reset! ref value))) - ref)) + (let [ptr (mf/use-var value)] + (mf/with-effect [value] + (reset! ptr value)) + ptr)) + +(defn use-ref-callback + "Returns a stable callback pointer what calls the interned + callback. The interned callback will be automatically updated on + each reander if the reference changes and works as noop if the + pointer references to nil value." + [f] + (let [ptr (mf/use-ref nil)] + (mf/with-effect [f] + (mf/set-ref-val! ptr #js {:f f})) + (mf/use-fn + (fn [& args] + (let [obj (mf/ref-val ptr)] + (when ^boolean obj + (apply (.-f obj) args))))))) (defn use-equal-memo [val] @@ -258,4 +282,34 @@ #(cp/focus-objects objects focus))] objects))) +(defn use-debounce + [ms value] + (let [[state update-state-fn] (mf/useState value) + update-fn (mf/use-memo (mf/deps ms) #(f/debounce update-state-fn ms))] + (mf/with-effect [value] + (update-fn value)) + state)) +(defn use-shared-state + "A specialized hook that adds persistence and inter-context reactivity + to the default mf/use-state hook. + + The state is automatically persisted under the provided key on + localStorage. And it will keep watching events with type equals to + `key` for new values." + [key default] + (let [id (use-id) + state (mf/use-state (get @storage key default)) + stream (mf/with-memo [] + (->> mbc/stream + (rx/filter #(= (:type %) key)) + (rx/filter #(not= (:id %) id)) + (rx/map deref)))] + + (mf/with-effect [@state key] + (mbc/emit! id key @state) + (swap! storage assoc key @state)) + + (use-stream stream (partial reset! state)) + + state)) diff --git a/frontend/src/app/main/ui/hooks/resize.cljs b/frontend/src/app/main/ui/hooks/resize.cljs index bd72680709..101d84b219 100644 --- a/frontend/src/app/main/ui/hooks/resize.cljs +++ b/frontend/src/app/main/ui/hooks/resize.cljs @@ -8,7 +8,9 @@ (:require [app.common.geom.point :as gpt] [app.common.logging :as log] + [app.common.spec :as us] [app.main.ui.context :as ctx] + [app.main.ui.hooks :as hooks] [app.util.dom :as dom] [app.util.storage :refer [storage]] [rumext.alpha :as mf])) @@ -72,38 +74,38 @@ (defn use-resize-observer [callback] - (assert (some? callback)) + (us/assert! (some? callback) "the `callback` is mandatory") (let [prev-val-ref (mf/use-ref nil) - current-observer-ref (mf/use-ref nil) + observer-ref (mf/use-ref nil) + callback (hooks/use-ref-callback callback) ;; We use the ref as a callback when the dom node is ready (or change) - node-ref - (mf/use-callback - (mf/deps callback) - (fn [^js node] - (let [^js current-observer (mf/ref-val current-observer-ref) - ^js prev-val (mf/ref-val prev-val-ref)] + node-ref (mf/use-fn + (fn [^js node] + (when (some? node) + (let [^js observer (mf/ref-val observer-ref) + ^js prev-val (mf/ref-val prev-val-ref)] - (when (and (not= prev-val node) (some? current-observer)) - (log/debug :action "disconnect" :js/prev-val prev-val :js/node node) - (.disconnect current-observer) - (mf/set-ref-val! current-observer-ref nil)) + (when (and (not= prev-val node) (some? observer)) + (log/debug :action "disconnect" :js/prev-val prev-val :js/node node) + (.disconnect observer) + (mf/set-ref-val! observer-ref nil)) - (when (and (not= prev-val node) (some? node)) - (let [^js observer - (js/ResizeObserver. #(callback last-resize-type (dom/get-client-size node)))] - (mf/set-ref-val! current-observer-ref observer) - (log/debug :action "observe" :js/node node :js/observer observer) - (.observe observer node)))) - (mf/set-ref-val! prev-val-ref node)))] + (when (and (not= prev-val node) (some? node)) + (let [^js observer (js/ResizeObserver. + #(callback last-resize-type (dom/get-client-size node)))] + (mf/set-ref-val! observer-ref observer) + (log/debug :action "observe" :js/node node :js/observer observer) + (.observe observer node)))) + + (mf/set-ref-val! prev-val-ref node))))] + + (mf/with-effect [] + ;; On dismount we need to disconnect the current observer + (fn [] + (when-let [observer (mf/ref-val observer-ref)] + (log/debug :action "disconnect") + (.disconnect ^js observer)))) - (mf/use-effect - (fn [] - ;; On dismount we need to disconnect the current observer - (fn [] - (let [current-observer (mf/ref-val current-observer-ref)] - (when (some? current-observer) - (log/debug :action "disconnect") - (.disconnect current-observer)))))) node-ref)) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 078d767bdc..b8232b043e 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -26,8 +26,17 @@ (def arrow-slide (icon-xref :arrow-slide)) (def artboard (icon-xref :artboard)) (def at (icon-xref :at)) +(def auto-direction (icon-xref :auto-direction)) +(def auto-fill (icon-xref :auto-fill)) (def auto-fix (icon-xref :auto-fix)) +(def auto-fix-layout (icon-xref :auto-fix-layout)) +(def auto-gap (icon-xref :auto-gap)) (def auto-height (icon-xref :auto-height)) +(def auto-hug (icon-xref :auto-hug)) +(def auto-margin-side (icon-xref :auto-margin-side)) +(def auto-margin (icon-xref :auto-margin)) +(def auto-padding (icon-xref :auto-padding)) +(def auto-padding-side (icon-xref :auto-padding-side)) (def auto-width (icon-xref :auto-width)) (def bool-difference (icon-xref :boolean-difference)) (def bool-exclude (icon-xref :boolean-exclude)) @@ -67,6 +76,8 @@ (def full-screen-off (icon-xref :full-screen-off)) (def grid (icon-xref :grid)) (def grid-snap (icon-xref :grid-snap)) +(def go-next (icon-xref :go-next)) +(def go-prev (icon-xref :go-prev)) (def help (icon-xref :help)) (def icon-empty (icon-xref :icon-empty)) (def icon-filter (icon-xref :filter)) @@ -134,6 +145,7 @@ (def radius-4 (icon-xref :radius-4)) (def recent (icon-xref :recent)) (def redo (icon-xref :redo)) +(def reset (icon-xref :reset)) (def rotate (icon-xref :rotate)) (def ruler (icon-xref :ruler)) (def ruler-tool (icon-xref :ruler-tool)) @@ -152,6 +164,8 @@ (def size-vert (icon-xref :size-vert)) (def sort-ascending (icon-xref :sort-ascending)) (def sort-descending (icon-xref :sort-descending)) +(def space-around (icon-xref :space-around)) +(def space-between (icon-xref :space-between)) (def strikethrough (icon-xref :strikethrough)) (def stroke (icon-xref :stroke)) (def switch (icon-xref :switch)) diff --git a/frontend/src/app/main/ui/measurements.cljs b/frontend/src/app/main/ui/measurements.cljs index f879e2345d..6571b3c58c 100644 --- a/frontend/src/app/main/ui/measurements.cljs +++ b/frontend/src/app/main/ui/measurements.cljs @@ -240,16 +240,20 @@ (when (seq selected-shapes) [:g.measurement-feedback {:pointer-events "none"} - [:& selection-guides {:selrect selected-selrect :bounds bounds :zoom zoom}] + [:& selection-guides {:selrect selected-selrect + :bounds bounds + :zoom zoom}] [:& size-display {:selrect selected-selrect :zoom zoom}] (if (or (not hover-shape) (not hover-selected-shape?)) (when (and frame (not= uuid/zero (:id frame))) - [:g.hover-shapes - [:& distance-display {:from (:selrect frame) - :to selected-selrect - :zoom zoom - :bounds bounds-selrect}]]) + (let [frame-bb (-> (:points frame) (gsh/points->selrect))] + [:g.hover-shapes + [:& selection-rect {:type :hover :selrect frame-bb :zoom zoom}] + [:& distance-display {:from frame-bb + :to selected-selrect + :zoom zoom + :bounds bounds-selrect}]])) [:g.hover-shapes [:& selection-rect {:type :hover :selrect hover-selrect :zoom zoom}] diff --git a/frontend/src/app/main/ui/onboarding/team_choice.cljs b/frontend/src/app/main/ui/onboarding/team_choice.cljs index 77cc59f0fd..2247fe9e31 100644 --- a/frontend/src/app/main/ui/onboarding/team_choice.cljs +++ b/frontend/src/app/main/ui/onboarding/team_choice.cljs @@ -102,7 +102,7 @@ [{:value "editor" :label (tr "labels.editor")} {:value "admin" :label (tr "labels.admin")}]) -(s/def ::emails (s/and ::us/set-of-emails d/not-empty?)) +(s/def ::emails (s/and ::us/set-of-valid-emails d/not-empty?)) (s/def ::role ::us/keyword) (s/def ::invite-form (s/keys :req-un [::role ::emails])) @@ -171,7 +171,8 @@ :auto-focus? true :trim true :valid-item-fn us/parse-email - :label (tr "modals.invite-member.emails")}] + :label (tr "modals.invite-member.emails") + :on-submit on-submit}] [:& fm/select {:name :role :options roles}]] [:div.buttons diff --git a/frontend/src/app/main/ui/releases.cljs b/frontend/src/app/main/ui/releases.cljs index 19822f7967..68d645030b 100644 --- a/frontend/src/app/main/ui/releases.cljs +++ b/frontend/src/app/main/ui/releases.cljs @@ -15,6 +15,7 @@ [app.main.ui.releases.v1-12] [app.main.ui.releases.v1-13] [app.main.ui.releases.v1-14] + [app.main.ui.releases.v1-15] [app.main.ui.releases.v1-4] [app.main.ui.releases.v1-5] [app.main.ui.releases.v1-6] @@ -84,4 +85,4 @@ (defmethod rc/render-release-notes "0.0" [params] - (rc/render-release-notes (assoc params :version "1.14"))) + (rc/render-release-notes (assoc params :version "1.15"))) diff --git a/frontend/src/app/main/ui/releases/v1_11.cljs b/frontend/src/app/main/ui/releases/v1_11.cljs index 56e8dacef8..451005ab0e 100644 --- a/frontend/src/app/main/ui/releases/v1_11.cljs +++ b/frontend/src/app/main/ui/releases/v1_11.cljs @@ -25,7 +25,7 @@ [:span.release "Beta version " version] [:div.modal-content [:p "Penpot continues growing with new features that improve performance, user experience and visual design."] - [:p "We are happy to show you a sneak peak of the most important stuff that the Beta 1.11 version brings."]] + [:p "We are happy to show you a sneak peek of the most important stuff that the Beta 1.11 version brings."]] [:div.modal-navigation [:button.btn-secondary {:on-click next} "Continue"]]] [:img.deco {:src "images/deco-left.png" :border "0"}] diff --git a/frontend/src/app/main/ui/releases/v1_12.cljs b/frontend/src/app/main/ui/releases/v1_12.cljs index b494cb568a..d3bc515cc1 100644 --- a/frontend/src/app/main/ui/releases/v1_12.cljs +++ b/frontend/src/app/main/ui/releases/v1_12.cljs @@ -25,7 +25,7 @@ [:span.release "Beta version " version] [:div.modal-content [:p "Penpot continues growing with new features that improve performance, user experience and visual design."] - [:p "We are happy to show you a sneak peak of the most important stuff that the Beta 1.12 version brings."]] + [:p "We are happy to show you a sneak peek of the most important stuff that the Beta 1.12 version brings."]] [:div.modal-navigation [:button.btn-secondary {:on-click next} "Continue"]]] [:img.deco {:src "images/deco-left.png" :border "0"}] diff --git a/frontend/src/app/main/ui/releases/v1_13.cljs b/frontend/src/app/main/ui/releases/v1_13.cljs index 9f73b57298..6cf873b858 100644 --- a/frontend/src/app/main/ui/releases/v1_13.cljs +++ b/frontend/src/app/main/ui/releases/v1_13.cljs @@ -25,7 +25,7 @@ [:span.release "Beta version " version] [:div.modal-content [:p "Penpot continues to grow with new features that improve performance, user experience and visual design."] - [:p "We are happy to show you a sneak peak of the most important stuff that the Beta 1.13 version brings."]] + [:p "We are happy to show you a sneak peek of the most important stuff that the Beta 1.13 version brings."]] [:div.modal-navigation [:button.btn-secondary {:on-click next} "Continue"]]] [:img.deco {:src "images/deco-left.png" :border "0"}] diff --git a/frontend/src/app/main/ui/releases/v1_14.cljs b/frontend/src/app/main/ui/releases/v1_14.cljs index f0e4e459ef..77a714a5a7 100644 --- a/frontend/src/app/main/ui/releases/v1_14.cljs +++ b/frontend/src/app/main/ui/releases/v1_14.cljs @@ -25,7 +25,7 @@ [:span.release "Beta version " version] [:div.modal-content [:p "Penpot continues to grow with new features that improve performance, user experience and visual design."] - [:p "We are happy to show you a sneak peak of the most important stuff that the Beta 1.14 version brings."]] + [:p "We are happy to show you a sneak peek of the most important stuff that the Beta 1.14 version brings."]] [:div.modal-navigation [:button.btn-secondary {:on-click next} "Continue"]]] [:img.deco {:src "images/deco-left.png" :border "0"}] diff --git a/frontend/src/app/main/ui/releases/v1_15.cljs b/frontend/src/app/main/ui/releases/v1_15.cljs new file mode 100644 index 0000000000..369a1dd838 --- /dev/null +++ b/frontend/src/app/main/ui/releases/v1_15.cljs @@ -0,0 +1,108 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.ui.releases.v1-15 + (:require + [app.main.ui.releases.common :as c] + [rumext.alpha :as mf])) + +(defmethod c/render-release-notes "1.15" + [{:keys [slide klass next finish navigate version]}] + (mf/html + (case @slide + :start + [:div.modal-overlay + [:div.animated {:class @klass} + [:div.modal-container.onboarding.feature + [:div.modal-left + [:img {:src "images/login-on.jpg" :border "0" :alt "What's new Beta release 1.15"}]] + [:div.modal-right + [:div.modal-title + [:h2 "What's new?"]] + [:span.release "Beta version " version] + [:div.modal-content + [:p "Penpot continues to grow with new features that improve performance, user experience and visual design."] + [:p "We are happy to show you a sneak peek of the most important stuff that the Beta 1.15 version brings."]] + [:div.modal-navigation + [:button.btn-secondary {:on-click next} "Continue"]]] + [:img.deco {:src "images/deco-left.png" :border "0"}] + [:img.deco.right {:src "images/deco-right.png" :border "0"}]]]] + + 0 + [:div.modal-overlay + [:div.animated {:class @klass} + [:div.modal-container.onboarding.feature + [:div.modal-left + [:img {:src "images/features/1.15-nested-boards.gif" :border "0" :alt "Nested boards"}]] + [:div.modal-right + [:div.modal-title + [:h2 "Nested boards"]] + [:div.modal-content + [:p "Unlike its predecessors (the artboards) boards can contain other boards and offer the options to clip content and show them or not at the View Mode, opening up a ton of possibilities when creating and organizing your designs."] + [:p "Say goodbye to Artboards and hello to Boards!"]] + [:div.modal-navigation + [:button.btn-secondary {:on-click next} "Continue"] + [:& c/navigation-bullets + {:slide @slide + :navigate navigate + :total 4}]]]]]] + + 1 + [:div.modal-overlay + [:div.animated {:class @klass} + [:div.modal-container.onboarding.feature + [:div.modal-left + [:img {:src "images/features/1.15-share.gif" :border "0" :alt "Share prototype options"}]] + [:div.modal-right + [:div.modal-title + [:h2 "Share prototype options"]] + [:div.modal-content + [:p "Have you ever wanted to share a Penpot file and get feedback from people that are not in your Penpot team?"] + [:p "Now you can thanks to new permissions that allow you to decide who can comment and/or inspect the code at a shared prototype link."]] + [:div.modal-navigation + [:button.btn-secondary {:on-click next} "Continue"] + [:& c/navigation-bullets + {:slide @slide + :navigate navigate + :total 4}]]]]]] + + 2 + [:div.modal-overlay + [:div.animated {:class @klass} + [:div.modal-container.onboarding.feature + [:div.modal-left + [:img {:src "images/features/1.15-comments.gif" :border "0" :alt "Comments positioning"}]] + [:div.modal-right + [:div.modal-title + [:h2 "Comments poitioning"]] + [:div.modal-content + [:p "They live! Now you can move existing comments wherever you want by dragging them."] + [:p "Also, comments inside boards will be associated with it, so that if you move a board its comments will maintain its place inside it."]] + [:div.modal-navigation + [:button.btn-secondary {:on-click next} "Continue"] + [:& c/navigation-bullets + {:slide @slide + :navigate navigate + :total 4}]]]]]] + + 3 + [:div.modal-overlay + [:div.animated {:class @klass} + [:div.modal-container.onboarding.feature + [:div.modal-left + [:img {:src "images/features/1.15-view-mode.gif" :border "0" :alt "View Mode improvements"}]] + [:div.modal-right + [:div.modal-title + [:h2 "View Mode improvements"]] + [:div.modal-content + [:p "The View Mode, used for presenting designs, is now easier to use thanks to new navigation buttons and microinteractions."] + [:p "We’ve also made some adjustments to ensure the access to the options from small screens."]] + [:div.modal-navigation + [:button.btn-secondary {:on-click finish} "Start!"] + [:& c/navigation-bullets + {:slide @slide + :navigate navigate + :total 4}]]]]]]))) diff --git a/frontend/src/app/main/ui/releases/v1_4.cljs b/frontend/src/app/main/ui/releases/v1_4.cljs index da5bb480b4..42032d87b9 100644 --- a/frontend/src/app/main/ui/releases/v1_4.cljs +++ b/frontend/src/app/main/ui/releases/v1_4.cljs @@ -25,7 +25,7 @@ [:span.release "Alpha version " version] [:div.modal-content [:p "Penpot continues growing with new features that improve performance, user experience and visual design."] - [:p "We are happy to show you a sneak peak of the most important stuff that the Alpha 1.4.0 version brings."]] + [:p "We are happy to show you a sneak peek of the most important stuff that the Alpha 1.4.0 version brings."]] [:div.modal-navigation [:button.btn-secondary {:on-click next} "Continue"]]] [:img.deco {:src "images/deco-left.png" :border "0"}] diff --git a/frontend/src/app/main/ui/releases/v1_5.cljs b/frontend/src/app/main/ui/releases/v1_5.cljs index 64f83de777..3fc9dd01e1 100644 --- a/frontend/src/app/main/ui/releases/v1_5.cljs +++ b/frontend/src/app/main/ui/releases/v1_5.cljs @@ -25,7 +25,7 @@ [:span.release "Alpha version " version] [:div.modal-content [:p "Penpot continues growing with new features that improve performance, user experience and visual design."] - [:p "We are happy to show you a sneak peak of the most important stuff that the Alpha 1.5.0 version brings."]] + [:p "We are happy to show you a sneak peek of the most important stuff that the Alpha 1.5.0 version brings."]] [:div.modal-navigation [:button.btn-secondary {:on-click next} "Continue"]]] [:img.deco {:src "images/deco-left.png" :border "0"}] diff --git a/frontend/src/app/main/ui/releases/v1_6.cljs b/frontend/src/app/main/ui/releases/v1_6.cljs index 6402875d58..19b539363c 100644 --- a/frontend/src/app/main/ui/releases/v1_6.cljs +++ b/frontend/src/app/main/ui/releases/v1_6.cljs @@ -25,7 +25,7 @@ [:span.release "Alpha version " version] [:div.modal-content [:p "Penpot continues growing with new features that improve performance, user experience and visual design."] - [:p "We are happy to show you a sneak peak of the most important stuff that the Alpha 1.6.0 version brings."]] + [:p "We are happy to show you a sneak peek of the most important stuff that the Alpha 1.6.0 version brings."]] [:div.modal-navigation [:button.btn-secondary {:on-click next} "Continue"]]] [:img.deco {:src "images/deco-left.png" :border "0"}] diff --git a/frontend/src/app/main/ui/releases/v1_7.cljs b/frontend/src/app/main/ui/releases/v1_7.cljs index 4a61adc388..9024266e07 100644 --- a/frontend/src/app/main/ui/releases/v1_7.cljs +++ b/frontend/src/app/main/ui/releases/v1_7.cljs @@ -25,7 +25,7 @@ [:span.release "Alpha version " version] [:div.modal-content [:p "Penpot continues growing with new features that improve performance, user experience and visual design."] - [:p "We are happy to show you a sneak peak of the most important stuff that the Alpha 1.7 version brings."]] + [:p "We are happy to show you a sneak peek of the most important stuff that the Alpha 1.7 version brings."]] [:div.modal-navigation [:button.btn-secondary {:on-click next} "Continue"]]] [:img.deco {:src "images/deco-left.png" :border "0"}] diff --git a/frontend/src/app/main/ui/releases/v1_8.cljs b/frontend/src/app/main/ui/releases/v1_8.cljs index 7d88f71c1c..aba61525d6 100644 --- a/frontend/src/app/main/ui/releases/v1_8.cljs +++ b/frontend/src/app/main/ui/releases/v1_8.cljs @@ -25,7 +25,7 @@ [:span.release "Alpha version " version] [:div.modal-content [:p "Penpot continues growing with new features that improve performance, user experience and visual design."] - [:p "We are happy to show you a sneak peak of the most important stuff that the Alpha 1.8 version brings."]] + [:p "We are happy to show you a sneak peek of the most important stuff that the Alpha 1.8 version brings."]] [:div.modal-navigation [:button.btn-secondary {:on-click next} "Continue"]]] [:img.deco {:src "images/deco-left.png" :border "0"}] diff --git a/frontend/src/app/main/ui/releases/v1_9.cljs b/frontend/src/app/main/ui/releases/v1_9.cljs index 7bf95fadbc..febb043ad1 100644 --- a/frontend/src/app/main/ui/releases/v1_9.cljs +++ b/frontend/src/app/main/ui/releases/v1_9.cljs @@ -25,7 +25,7 @@ [:span.release "Alpha version " version] [:div.modal-content [:p "Penpot continues growing with new features that improve performance, user experience and visual design."] - [:p "We are happy to show you a sneak peak of the most important stuff that the Alpha 1.9 version brings."]] + [:p "We are happy to show you a sneak peek of the most important stuff that the Alpha 1.9 version brings."]] [:div.modal-navigation [:button.btn-secondary {:on-click next} "Continue"]]] [:img.deco {:src "images/deco-left.png" :border "0"}] diff --git a/frontend/src/app/main/ui/settings/feedback.cljs b/frontend/src/app/main/ui/settings/feedback.cljs index 1bd671406a..4b5055d85d 100644 --- a/frontend/src/app/main/ui/settings/feedback.cljs +++ b/frontend/src/app/main/ui/settings/feedback.cljs @@ -55,7 +55,7 @@ (fn [form _] (reset! loading true) (let [data (:clean-data @form)] - (->> (rp/mutation! :send-feedback data) + (->> (rp/command! :send-feedback data) (rx/subs on-succes on-error)))))] [:& fm/form {:class "feedback-form" diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index 3eb2d13771..f94871a5f5 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -10,8 +10,8 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] - [app.common.spec.radius :as ctr] - [app.common.spec.shape :refer [stroke-caps-line stroke-caps-marker]] + [app.common.types.shape :refer [stroke-caps-line stroke-caps-marker]] + [app.common.types.shape.radius :as ctsr] [app.main.ui.context :as muc] [app.util.object :as obj] [app.util.svg :as usvg] @@ -31,7 +31,7 @@ (defn add-border-radius [attrs {:keys [x y width height] :as shape}] - (case (ctr/radius-mode shape) + (case (ctsr/radius-mode shape) :radius-1 (let [radius (gsh/shape-corners-1 shape)] (obj/merge! attrs #js {:rx radius :ry radius})) @@ -159,7 +159,7 @@ (defn add-style-attrs ([props shape] - (let [render-id (mf/use-ctx muc/render-ctx)] + (let [render-id (mf/use-ctx muc/render-id)] (add-style-attrs props shape render-id))) ([props shape render-id] @@ -169,7 +169,7 @@ [svg-attrs svg-styles] (extract-svg-attrs render-id svg-defs svg-attrs) - styles (-> (obj/get props "style" (obj/new)) + styles (-> (obj/get props "style" (obj/create)) (obj/merge! svg-styles) (add-layer-props shape)) @@ -211,24 +211,24 @@ (defn extract-style-attrs [shape] - (-> (obj/new) + (-> (obj/create) (add-style-attrs shape))) (defn extract-fill-attrs [fill-data render-id index type] - (let [fill-styles (-> (obj/get fill-data "style" (obj/new)) + (let [fill-styles (-> (obj/get fill-data "style" (obj/create)) (add-fill fill-data render-id index type))] - (-> (obj/new) + (-> (obj/create) (obj/set! "style" fill-styles)))) (defn extract-stroke-attrs [stroke-data index render-id] - (let [stroke-styles (-> (obj/get stroke-data "style" (obj/new)) + (let [stroke-styles (-> (obj/get stroke-data "style" (obj/create)) (add-stroke stroke-data render-id index))] - (-> (obj/new) + (-> (obj/create) (obj/set! "style" stroke-styles)))) (defn extract-border-radius-attrs [shape] - (-> (obj/new) + (-> (obj/create) (add-border-radius shape))) diff --git a/frontend/src/app/main/ui/shapes/circle.cljs b/frontend/src/app/main/ui/shapes/circle.cljs index 5924765404..f5db9bf3ed 100644 --- a/frontend/src/app/main/ui/shapes/circle.cljs +++ b/frontend/src/app/main/ui/shapes/circle.cljs @@ -6,7 +6,7 @@ (ns app.main.ui.shapes.circle (:require - [app.common.geom.shapes :as geom] + [app.common.geom.shapes :as gsh] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.custom-stroke :refer [shape-custom-strokes]] [app.util.object :as obj] @@ -17,7 +17,7 @@ [props] (let [shape (unchecked-get props "shape") {:keys [x y width height]} shape - transform (geom/transform-matrix shape) + transform (gsh/transform-str shape) cx (+ x (/ width 2)) cy (+ y (/ height 2)) diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index 5c5782b027..7753c3f328 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.bounds :as gsb] [app.common.pages.helpers :as cph] [app.main.ui.context :as muc] [app.main.ui.shapes.attrs :as attrs] @@ -45,7 +46,7 @@ :center (/ (:stroke-width shape 0) 2) :outer (:stroke-width shape 0) 0) - margin (gsh/shape-stroke-margin shape stroke-width) + margin (gsb/shape-stroke-margin shape stroke-width) bounding-box (-> (gsh/points->selrect (:points shape)) (update :x - (+ stroke-width margin)) (update :y - (+ stroke-width margin)) @@ -210,7 +211,7 @@ {::mf/wrap-props false} [props] - (let [render-id (mf/use-ctx muc/render-ctx) + (let [render-id (mf/use-ctx muc/render-id) child (obj/get props "children") base-props (obj/get child "props") elem-name (obj/get child "type") @@ -252,7 +253,7 @@ (mf/defc inner-stroke {::mf/wrap-props false} [props] - (let [render-id (mf/use-ctx muc/render-ctx) + (let [render-id (mf/use-ctx muc/render-id) child (obj/get props "children") base-props (obj/get child "props") elem-name (obj/get child "type") @@ -291,7 +292,7 @@ (let [child (obj/get props "children") shape (obj/get props "shape") - render-id (mf/use-ctx muc/render-ctx) + render-id (mf/use-ctx muc/render-id) index (obj/get props "index") stroke-width (:stroke-width shape 0) stroke-style (:stroke-style shape :none) @@ -416,7 +417,7 @@ shape (obj/get props "shape") elem-name (obj/get child "type") position (or (obj/get props "position") 0) - render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-ctx))] + render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id))] [:g {:id (dm/fmt "fills-%" (:id shape))} [:> elem-name (build-fill-props shape child position render-id)]])) @@ -426,9 +427,9 @@ (let [child (obj/get props "children") shape (obj/get props "shape") elem-name (obj/get child "type") - render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-ctx)) + render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id)) stroke-id (dm/fmt "strokes-%" (:id shape)) - stroke-props (-> (obj/new) + stroke-props (-> (obj/create) (obj/set! "id" stroke-id) (cond-> ;; There is a blur diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index c5b548ccf1..b2453f7e04 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -95,6 +95,10 @@ (add! :constraints-v) (add! :fixed-scroll) + (cond-> frame? + (-> (add! :show-content) + (add! :hide-in-viewer))) + (cond-> (and (or rect? image? frame?) (some? (:r1 shape))) (-> (add! :r1) (add! :r2) @@ -262,7 +266,7 @@ (when (= (:type shape) :svg-raw) (let [shape (-> shape (d/update-in-when [:content :attrs :style] str->style)) props - (-> (obj/new) + (-> (obj/create) (obj/set! "penpot:x" (:x shape)) (obj/set! "penpot:y" (:y shape)) (obj/set! "penpot:width" (:width shape)) @@ -282,7 +286,7 @@ (for [[index fill] (d/enumerate fills)] [:> "penpot:fill" #js {:penpot:fill-color (if (some? (:fill-color-gradient fill)) - (str/format "url(#%s)" (str "fill-color-gradient_" (mf/use-ctx muc/render-ctx) "_" index)) + (str/format "url(#%s)" (str "fill-color-gradient_" (mf/use-ctx muc/render-id) "_" index)) (d/name (:fill-color fill))) :penpot:fill-color-ref-file (d/name (:fill-color-ref-file fill)) :penpot:fill-color-ref-id (d/name (:fill-color-ref-id fill)) @@ -295,7 +299,7 @@ (for [[index stroke] (d/enumerate strokes)] [:> "penpot:stroke" #js {:penpot:stroke-color (if (some? (:stroke-color-gradient stroke)) - (str/format "url(#%s)" (str "stroke-color-gradient_" (mf/use-ctx muc/render-ctx) "_" index)) + (str/format "url(#%s)" (str "stroke-color-gradient_" (mf/use-ctx muc/render-id) "_" index)) (d/name (:stroke-color stroke))) :penpot:stroke-color-ref-file (d/name (:stroke-color-ref-file stroke)) :penpot:stroke-color-ref-id (d/name (:stroke-color-ref-id stroke)) @@ -328,7 +332,7 @@ (mf/defc export-data [{:keys [shape]}] - (let [props (-> (obj/new) (add-data shape) (add-library-refs shape))] + (let [props (-> (obj/create) (add-data shape) (add-library-refs shape))] [:> "penpot:shape" props (export-shadow-data shape) (export-blur-data shape) diff --git a/frontend/src/app/main/ui/shapes/fills.cljs b/frontend/src/app/main/ui/shapes/fills.cljs index 788a433153..36cc2c24b1 100644 --- a/frontend/src/app/main/ui/shapes/fills.cljs +++ b/frontend/src/app/main/ui/shapes/fills.cljs @@ -41,7 +41,7 @@ (cfg/resolve-file-media (:fill-image shape))) embed (embed/use-data-uris [uri]) - transform (gsh/transform-matrix shape) + transform (gsh/transform-str shape) ;; When true the image has not loaded yet loading? (and (some? uri) (not (contains? embed uri))) diff --git a/frontend/src/app/main/ui/shapes/filters.cljs b/frontend/src/app/main/ui/shapes/filters.cljs index c3b9574019..a9cc7a264f 100644 --- a/frontend/src/app/main/ui/shapes/filters.cljs +++ b/frontend/src/app/main/ui/shapes/filters.cljs @@ -8,8 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.geom.shapes :as gsh] - [app.common.math :as mth] + [app.common.geom.shapes.bounds :as gsb] [app.common.uuid :as uuid] [app.util.color :as color] [cuerdas.core :as str] @@ -108,34 +107,6 @@ :in2 filter-in :result filter-id}]) -(defn filter-bounds [shape filter-entry] - (let [{:keys [x y width height]} (:selrect shape) - {:keys [offset-x offset-y blur spread] :or {offset-x 0 offset-y 0 blur 0 spread 0}} (:params filter-entry) - filter-x (min x (+ x offset-x (- spread) (- blur) -5)) - filter-y (min y (+ y offset-y (- spread) (- blur) -5)) - filter-width (+ width (mth/abs offset-x) (* spread 2) (* blur 2) 10) - filter-height (+ height (mth/abs offset-y) (* spread 2) (* blur 2) 10)] - {:x1 filter-x - :y1 filter-y - :x2 (+ filter-x filter-width) - :y2 (+ filter-y filter-height)})) - -(defn blur-filters [type value] - (->> [value] - (remove :hidden) - (filter #(= (:type %) type)) - (map #(hash-map :id (str "filter_" (:id %)) - :type (:type %) - :params %)))) - -(defn shadow-filters [type filters] - (->> filters - (remove :hidden) - (filter #(= (:style %) type)) - (map #(hash-map :id (str "filter_" (:id %)) - :type (:style %) - :params %)))) - (mf/defc filter-entry [{:keys [entry]}] (let [props #js {:filter-id (:id entry) :filter-in (:filter-in entry) @@ -148,84 +119,6 @@ :image-fix [:> image-fix-filter props] :blend-filters [:> blend-filters props]))) -(defn shape->filters - [shape] - (d/concat-vec - [{:id "BackgroundImageFix" :type :image-fix}] - - ;; Background blur won't work in current SVG specification - ;; We can revisit this in the future - #_(->> shape :blur (blur-filters :background-blur)) - - (->> shape :shadow (shadow-filters :drop-shadow)) - [{:id "shape" :type :blend-filters}] - (->> shape :shadow (shadow-filters :inner-shadow)) - (->> shape :blur (blur-filters :layer-blur)))) - -(defn get-filters-bounds - ([shape] - (let [filters (shape->filters shape) - blur-value (or (-> shape :blur :value) 0)] - (get-filters-bounds shape filters blur-value))) - - ([shape filters blur-value] - - (let [svg-root? (and (= :svg-raw (:type shape)) (not= :svg (get-in shape [:content :tag]))) - {:keys [x y width height]} (:selrect shape)] - (if svg-root? - ;; When is a raw-svg but not the root we use the whole svg as bound for the filter. Is the maximum - ;; we're allowed to display - {:x x :y y :width width :height height} - - ;; Otherwise we calculate the bound - (let [filter-bounds (->> filters - (filter #(= :drop-shadow (:type %))) - (map (partial filter-bounds shape))) - ;; We add the selrect so the minimum size will be the selrect - filter-bounds (conj filter-bounds (-> shape :points gsh/points->selrect)) - - x1 (apply min (map :x1 filter-bounds)) - y1 (apply min (map :y1 filter-bounds)) - x2 (apply max (map :x2 filter-bounds)) - y2 (apply max (map :y2 filter-bounds)) - - x1 (- x1 (* blur-value 2)) - x2 (+ x2 (* blur-value 2)) - y1 (- y1 (* blur-value 2)) - y2 (+ y2 (* blur-value 2))] - - ;; We should move the frame filter coordinates because they should be - ;; relative with the frame. By default they come as absolute - {:x x1 - :y y1 - :width (- x2 x1) - :height (- y2 y1)}))))) - -(defn calculate-padding - ([shape] - (calculate-padding shape false)) - - ([shape ignore-margin?] - (let [stroke-width (apply max 0 (map #(case (:stroke-alignment % :center) - :center (/ (:stroke-width % 0) 2) - :outer (:stroke-width % 0) - 0) (:strokes shape))) - - margin (if ignore-margin? - 0 - (apply max 0 (map #(gsh/shape-stroke-margin % stroke-width) (:strokes shape)))) - - shadow-width (apply max 0 (map #(case (:style % :drop-shadow) - :drop-shadow (+ (mth/abs (:offset-x %)) (* (:spread %) 2) (* (:blur %) 2) 10) - 0) (:shadow shape))) - - shadow-height (apply max 0 (map #(case (:style % :drop-shadow) - :drop-shadow (+ (mth/abs (:offset-y %)) (* (:spread %) 2) (* (:blur %) 2) 10) - 0) (:shadow shape)))] - - {:horizontal (+ stroke-width margin shadow-width) - :vertical (+ stroke-width margin shadow-height)}))) - (defn change-filter-in "Adds the previous filter as `filter-in` parameter" [filters] @@ -234,9 +127,9 @@ (mf/defc filters [{:keys [filter-id shape]}] - (let [filters (-> shape shape->filters change-filter-in) - bounds (get-filters-bounds shape filters (or (-> shape :blur :value) 0)) - padding (calculate-padding shape) + (let [filters (-> shape gsb/shape->filters change-filter-in) + bounds (gsb/get-rect-filter-bounds (:selrect shape) filters (or (-> shape :blur :value) 0)) + padding (gsb/calculate-padding shape) selrect (:selrect shape) filter-x (/ (- (:x bounds) (:x selrect) (:horizontal padding)) (:width selrect)) filter-y (/ (- (:y bounds) (:y selrect) (:vertical padding)) (:height selrect)) diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index 8e4d078662..ba821a5d72 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -28,95 +28,95 @@ [{:keys [shape render-id]}] (when (= :frame (:type shape)) (let [{:keys [x y width height]} shape + transform (gsh/transform-str shape) props (-> (attrs/extract-style-attrs shape) (obj/merge! #js {:x x :y y :width width - :height height})) + :height height + :transform transform})) path? (some? (.-d props))] [:clipPath {:id (frame-clip-id shape render-id) :class "frame-clip"} (if path? [:> :path props] [:> :rect props])]))) +;; Wrapper around the frame that will handle things such as strokes and other properties +;; we wrap the proper frames and also the thumbnails +(mf/defc frame-container + {::mf/wrap-props false} + [props] + + (let [shape (obj/get props "shape") + children (obj/get props "children") + + {:keys [x y width height show-content]} shape + transform (gsh/transform-str shape) + props (-> (attrs/extract-style-attrs shape) + (obj/merge! + #js {:x x + :y y + :transform transform + :width width + :height height + :className "frame-background"})) + path? (some? (.-d props)) + render-id (mf/use-ctx muc/render-id)] + + [:* + [:g {:clip-path (when (not show-content) (frame-clip-url shape render-id))} + (when (not show-content) + [:& frame-clip-def {:shape shape :render-id render-id}]) + + [:& shape-fills {:shape shape} + (if path? + [:> :path props] + [:> :rect props])] + + children] + + [:& shape-strokes {:shape shape} + (if path? + [:> :path props] + [:> :rect props])]])) + + +(mf/defc frame-thumbnail-image + {::mf/wrap-props false} + [props] + + (let [shape (obj/get props "shape") + bounds (or (obj/get props "bounds") (gsh/points->selrect (:points shape)))] + + (when (:thumbnail shape) + [:image.frame-thumbnail + {:id (dm/str "thumbnail-" (:id shape)) + :href (:thumbnail shape) + :x (:x bounds) + :y (:y bounds) + :width (:width bounds) + :height (:height bounds) + ;; DEBUG + :style {:filter (when (debug? :thumbnails) "sepia(1)")}}]))) + (mf/defc frame-thumbnail {::mf/wrap-props false} [props] (let [shape (obj/get props "shape")] (when (:thumbnail shape) - (let [{:keys [x y width height]} shape - transform (gsh/transform-matrix shape) - props (-> (attrs/extract-style-attrs shape) - (obj/merge! - #js {:x x - :y y - :transform (str transform) - :width width - :height height - :className "frame-background"})) - path? (some? (.-d props)) - render-id (mf/use-ctx muc/render-ctx)] - - [:* - [:g {:clip-path (frame-clip-url shape render-id)} - [:& frame-clip-def {:shape shape :render-id render-id}] - [:& shape-fills {:shape shape} - (if path? - [:> :path props] - [:> :rect props])] - - [:image.frame-thumbnail - {:id (dm/str "thumbnail-" (:id shape)) - :href (:thumbnail shape) - :x (:x shape) - :y (:y shape) - :width (:width shape) - :height (:height shape) - ;; DEBUG - :style {:filter (when (debug? :thumbnails) "sepia(1)")}}]] - - [:& shape-strokes {:shape shape} - (if path? - [:> :path props] - [:> :rect props])]])))) + [:> frame-container props + [:> frame-thumbnail-image props]]))) (defn frame-shape [shape-wrapper] (mf/fnc frame-shape {::mf/wrap-props false} [props] - (let [childs (unchecked-get props "childs") - shape (unchecked-get props "shape") - {:keys [x y width height]} shape - transform (gsh/transform-matrix shape) - - props (-> (attrs/extract-style-attrs shape) - (obj/merge! - #js {:x x - :y y - :transform (str transform) - :width width - :height height - :className "frame-background"})) - path? (some? (.-d props)) - render-id (mf/use-ctx muc/render-ctx)] - - [:* - [:g {:clip-path (frame-clip-url shape render-id)} - [:& shape-fills {:shape shape} - (if path? - [:> :path props] - [:> :rect props])] - - [:g.frame-children - (for [item childs] - [:& shape-wrapper {:shape item - :key (dm/str (:id item))}])]] - - [:& shape-strokes {:shape shape} - (if path? - [:> :path props] - [:> :rect props])]]))) + (let [childs (unchecked-get props "childs")] + [:> frame-container props + [:g.frame-children + (for [item childs] + [:& shape-wrapper {:key (dm/str (:id item)) :shape item}])]]))) diff --git a/frontend/src/app/main/ui/shapes/gradients.cljs b/frontend/src/app/main/ui/shapes/gradients.cljs index aaee2d5291..9d45482f4c 100644 --- a/frontend/src/app/main/ui/shapes/gradients.cljs +++ b/frontend/src/app/main/ui/shapes/gradients.cljs @@ -27,13 +27,15 @@ (obj/set! "penpot:width" (:width gradient)))) (mf/defc linear-gradient [{:keys [id gradient shape]}] - (let [transform (when (= :path (:type shape)) (gsh/transform-matrix shape nil (gpt/point 0.5 0.5))) + (let [transform (when (= :path (:type shape)) + (gsh/transform-matrix shape nil (gpt/point 0.5 0.5))) + base-props #js {:id id :x1 (:start-x gradient) :y1 (:start-y gradient) :x2 (:end-x gradient) :y2 (:end-y gradient) - :gradientTransform transform} + :gradientTransform (dm/str transform)} include-metadata? (mf/use-ctx ed/include-metadata-ctx) @@ -102,7 +104,7 @@ (let [attr (obj/get props "attr") shape (obj/get props "shape") id (obj/get props "id") - id' (mf/use-ctx muc/render-ctx) + id' (mf/use-ctx muc/render-id) id (or id (dm/str (name attr) "_" id')) gradient (get shape attr) gradient-props #js {:id id diff --git a/frontend/src/app/main/ui/shapes/group.cljs b/frontend/src/app/main/ui/shapes/group.cljs index 403a9f2a0d..664c4e8344 100644 --- a/frontend/src/app/main/ui/shapes/group.cljs +++ b/frontend/src/app/main/ui/shapes/group.cljs @@ -21,7 +21,7 @@ (let [shape (unchecked-get props "shape") childs (unchecked-get props "childs") objects (unchecked-get props "objects") - render-id (mf/use-ctx muc/render-ctx) + render-id (mf/use-ctx muc/render-id) masked-group? (:masked-group? shape) [mask childs] (if masked-group? @@ -34,18 +34,18 @@ ; Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1734805 [clip-wrapper clip-props] (if masked-group? - ["g" (-> (obj/new) + ["g" (-> (obj/create) (obj/set! "clipPath" (clip-url render-id mask)))] [mf/Fragment nil]) [mask-wrapper mask-props] (if masked-group? - ["g" (-> (obj/new) - (obj/set! "mask" (mask-url render-id mask)))] + ["g" (-> (obj/create) + (obj/set! "mask" (mask-url render-id mask)))] [mf/Fragment nil]) ;; This factory is generic, it's used for viewer, workspace and handoff. - ;; These props are generated in viewer wrappers only, in the rest of the + ;; These props are generated in viewer wrappers only, in the rest of the ;; cases these props will be nil, not affecting the code. delta (unchecked-get props "delta") fixed? (unchecked-get props "fixed?")] diff --git a/frontend/src/app/main/ui/shapes/image.cljs b/frontend/src/app/main/ui/shapes/image.cljs index 5a80f78444..c13fa3a205 100644 --- a/frontend/src/app/main/ui/shapes/image.cljs +++ b/frontend/src/app/main/ui/shapes/image.cljs @@ -18,7 +18,7 @@ (let [shape (unchecked-get props "shape") {:keys [x y width height]} shape - transform (gsh/transform-matrix shape) + transform (gsh/transform-str shape) props (-> (attrs/extract-style-attrs shape) (obj/merge! (attrs/extract-border-radius-attrs shape)) (obj/merge! diff --git a/frontend/src/app/main/ui/shapes/mask.cljs b/frontend/src/app/main/ui/shapes/mask.cljs index 5c353d1205..c43b12a609 100644 --- a/frontend/src/app/main/ui/shapes/mask.cljs +++ b/frontend/src/app/main/ui/shapes/mask.cljs @@ -47,18 +47,18 @@ {::mf/wrap-props false} [props] (let [mask (unchecked-get props "mask") - render-id (mf/use-ctx muc/render-ctx) + render-id (mf/use-ctx muc/render-id) svg-text? (and (= :text (:type mask)) (some? (:position-data mask))) ;; This factory is generic, it's used for viewer, workspace and handoff. - ;; These props are generated in viewer wrappers only, in the rest of the - ;; cases these props will be nil, not affecting the code. + ;; These props are generated in viewer wrappers only, in the rest of the + ;; cases these props will be nil, not affecting the code. fixed? (unchecked-get props "fixed?") delta (unchecked-get props "delta") mask-bb (-> (gsh/transform-shape mask) (cond-> fixed? (gsh/move delta)) (:points)) - + mask-bb-rect (gsh/points->rect mask-bb)] [:defs [:filter {:id (filter-id render-id mask)} diff --git a/frontend/src/app/main/ui/shapes/rect.cljs b/frontend/src/app/main/ui/shapes/rect.cljs index 8c8a24e792..e75a804eeb 100644 --- a/frontend/src/app/main/ui/shapes/rect.cljs +++ b/frontend/src/app/main/ui/shapes/rect.cljs @@ -17,7 +17,7 @@ [props] (let [shape (unchecked-get props "shape") {:keys [x y width height]} shape - transform (gsh/transform-matrix shape) + transform (gsh/transform-str shape) props (-> (attrs/extract-style-attrs shape) (obj/merge! diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index 6ec138194c..a457534a7c 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -8,8 +8,9 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.uuid :as uuid] + [app.common.pages.helpers :as cph] [app.main.ui.context :as muc] + [app.main.ui.hooks :as h] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.export :as ed] [app.main.ui.shapes.fills :as fills] @@ -48,18 +49,19 @@ {::mf/forward-ref true ::mf/wrap-props false} [props ref] - (let [shape (obj/get props "shape") - children (obj/get props "children") - pointer-events (obj/get props "pointer-events") - type (:type shape) - render-id (mf/use-memo #(str (uuid/next))) - filter-id (str "filter_" render-id) - styles (-> (obj/new) - (obj/set! "pointerEvents" pointer-events) + (let [shape (unchecked-get props "shape") + children (unchecked-get props "children") + pointer-events (unchecked-get props "pointer-events") + disable-shadows? (unchecked-get props "disable-shadows?") - (cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal)) - (obj/set! "mixBlendMode" (d/name (:blend-mode shape))))) + type (:type shape) + render-id (h/use-id) + filter-id (dm/str "filter_" render-id) + styles (-> (obj/create) + (obj/set! "pointerEvents" pointer-events) + (cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal)) + (obj/set! "mixBlendMode" (d/name (:blend-mode shape))))) include-metadata? (mf/use-ctx ed/include-metadata-ctx) @@ -68,20 +70,21 @@ wrapper-props (-> (obj/clone props) - (obj/without ["shape" "children"]) + (obj/without ["shape" "children" "disable-shadows?"]) (obj/set! "ref" ref) (obj/set! "id" (dm/fmt "shape-%" (:id shape))) (obj/set! "style" styles)) - wrapper-props - (cond-> wrapper-props - (some #(= (:type shape) %) [:group :svg-raw :frame]) - (obj/set! "filter" (filters/filter-str filter-id shape))) - wrapper-props (cond-> wrapper-props (= :group type) - (attrs/add-style-attrs shape render-id)) + (attrs/add-style-attrs shape render-id) + + (and (or (cph/group-shape? shape) + (cph/frame-shape? shape) + (cph/svg-raw-shape? shape)) + (not disable-shadows?)) + (obj/set! "filter" (filters/filter-str filter-id shape))) svg-group? (and (contains? shape :svg-attrs) (= :group type)) @@ -89,7 +92,7 @@ svg-group? (propagate-wrapper-styles wrapper-props))] - [:& (mf/provider muc/render-ctx) {:value render-id} + [:& (mf/provider muc/render-id) {:value render-id} [:> :g wrapper-props (when include-metadata? [:& ed/export-data {:shape shape}]) diff --git a/frontend/src/app/main/ui/shapes/svg_defs.cljs b/frontend/src/app/main/ui/shapes/svg_defs.cljs index d127361cb3..7f69b9b262 100644 --- a/frontend/src/app/main/ui/shapes/svg_defs.cljs +++ b/frontend/src/app/main/ui/shapes/svg_defs.cljs @@ -10,8 +10,7 @@ [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] - [app.main.ui.shapes.filters :as f] - [app.util.object :as obj] + [app.common.geom.shapes.bounds :as gsb] [app.util.svg :as usvg] [rumext.alpha :as mf])) @@ -68,7 +67,7 @@ [wrapper wrapper-props] (if (= tag :mask) ["g" #js {:className "svg-mask-wrapper" :transform (str transform)}] - [mf/Fragment (obj/new)])] + [mf/Fragment #js {}])] [:> (name tag) (clj->js attrs) [:> wrapper wrapper-props @@ -88,7 +87,7 @@ (d/parse-double (get-in svg-def [:attrs :width])) (d/parse-double (get-in svg-def [:attrs :height]))) (gsh/transform-rect transform)) - (f/get-filters-bounds shape)))) + (gsb/get-shape-filter-bounds shape)))) (mf/defc svg-defs [{:keys [shape render-id]}] (let [svg-defs (:svg-defs shape) diff --git a/frontend/src/app/main/ui/shapes/svg_raw.cljs b/frontend/src/app/main/ui/shapes/svg_raw.cljs index 690ee60220..188390cac5 100644 --- a/frontend/src/app/main/ui/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/shapes/svg_raw.cljs @@ -60,7 +60,7 @@ (obj/set! "preserveAspectRatio" "none"))] [:& (mf/provider svg-ids-ctx) {:value ids-mapping} - [:g.svg-raw {:transform (dm/str (gsh/transform-matrix shape))} + [:g.svg-raw {:transform (gsh/transform-str shape)} [:> "svg" attrs children]]])) (mf/defc svg-element diff --git a/frontend/src/app/main/ui/shapes/text/fo_text.cljs b/frontend/src/app/main/ui/shapes/text/fo_text.cljs index 1e6a259d19..eb5908c689 100644 --- a/frontend/src/app/main/ui/shapes/text/fo_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/fo_text.cljs @@ -8,8 +8,7 @@ (:require [app.common.colors :as clr] [app.common.data :as d] - [app.common.geom.shapes :as geom] - [app.main.ui.context :as muc] + [app.common.geom.shapes :as gsh] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.text.styles :as sts] [app.util.color :as uc] @@ -91,23 +90,6 @@ (recur (uc/next-rgb current-rgb)) current-hex)))) -(defn- remap-colors - "Returns a new content replacing the original colors by their mapped 'simple color'" - [content color-mapping] - - (cond-> content - (and (:fill-opacity content) (< (:fill-opacity content) 1.0)) - (-> (assoc :fill-color (get color-mapping [(:fill-color content) (:fill-opacity content)])) - (assoc :fill-opacity 1.0)) - - (some? (:fill-color-gradient content)) - (-> (assoc :fill-color (get color-mapping (:fill-color-gradient content))) - (assoc :fill-opacity 1.0) - (dissoc :fill-color-gradient)) - - (contains? content :children) - (update :children #(mapv (fn [node] (remap-colors node color-mapping)) %)))) - (defn- fill->color "Given a content node returns the information about that node fill color" [{:keys [fill-color fill-opacity fill-color-gradient]}] @@ -192,31 +174,25 @@ ::mf/forward-ref true} [props ref] (let [shape (obj/get props "shape") - transform (str (geom/transform-matrix shape)) + transform (gsh/transform-str shape) {:keys [id x y width height content]} shape grow-type (obj/get props "grow-type") ;; This is only needed in workspace ;; We add 8px to add a padding for the exporter ;; width (+ width 8) - [colors color-mapping color-mapping-inverse] (retrieve-colors shape) - - plain-colors? (mf/use-ctx muc/text-plain-colors-ctx) - - content (cond-> content - plain-colors? - (remap-colors color-mapping))] + [colors _color-mapping color-mapping-inverse] (retrieve-colors shape)] [:foreignObject {:x x :y y :id id :data-colors (->> colors (str/join ",")) - :data-mapping (-> color-mapping-inverse (clj->js) (js/JSON.stringify)) + :data-mapping (-> color-mapping-inverse clj->js js/JSON.stringify) :transform transform :width (if (#{:auto-width} grow-type) 100000 width) :height (if (#{:auto-height :auto-width} grow-type) 100000 height) - :style (-> (obj/new) (attrs/add-layer-props shape)) + :style (-> (obj/create) (attrs/add-layer-props shape)) :ref ref} ;; We use a class here because react has a bug that won't use the appropriate selector for ;; `background-clip` diff --git a/frontend/src/app/main/ui/shapes/text/html_text.cljs b/frontend/src/app/main/ui/shapes/text/html_text.cljs new file mode 100644 index 0000000000..d7bfc78c11 --- /dev/null +++ b/frontend/src/app/main/ui/shapes/text/html_text.cljs @@ -0,0 +1,105 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.ui.shapes.text.html-text + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.main.ui.shapes.text.styles :as sts] + [app.util.object :as obj] + [rumext.alpha :as mf])) + +(mf/defc render-text + {::mf/wrap-props false} + [props] + (let [node (obj/get props "node") + parent (obj/get props "parent") + shape (obj/get props "shape") + text (:text node) + style (if (= text "") + (sts/generate-text-styles shape parent) + (sts/generate-text-styles shape node))] + [:span.text-node {:style style} + (if (= text "") "\u00A0" text)])) + +(mf/defc render-root + {::mf/wrap-props false} + [props] + (let [node (obj/get props "node") + children (obj/get props "children") + shape (obj/get props "shape") + style (sts/generate-root-styles shape node)] + [:div.root.rich-text + {:style style + :xmlns "http://www.w3.org/1999/xhtml"} + children])) + +(mf/defc render-paragraph-set + {::mf/wrap-props false} + [props] + (let [children (obj/get props "children") + shape (obj/get props "shape") + style (sts/generate-paragraph-set-styles shape)] + [:div.paragraph-set {:style style} children])) + +(mf/defc render-paragraph + {::mf/wrap-props false} + [props] + (let [node (obj/get props "node") + shape (obj/get props "shape") + children (obj/get props "children") + style (sts/generate-paragraph-styles shape node) + dir (:text-direction node "auto")] + [:p.paragraph {:style style :dir dir} children])) + +;; -- Text nodes +(mf/defc render-node + {::mf/wrap-props false} + [props] + (let [{:keys [type text children] :as parent} (obj/get props "node")] + (if (string? text) + [:> render-text props] + (let [component (case type + "root" render-root + "paragraph-set" render-paragraph-set + "paragraph" render-paragraph + nil)] + (when component + [:> component props + (for [[index node] (d/enumerate children)] + (let [props (-> (obj/clone props) + (obj/set! "node" node) + (obj/set! "parent" parent) + (obj/set! "index" index) + (obj/set! "key" index))] + [:> render-node props]))]))))) + +(mf/defc text-shape + {::mf/wrap-props false + ::mf/forward-ref true} + [props ref] + (let [shape (obj/get props "shape") + grow-type (obj/get props "grow-type") + {:keys [id x y width height content]} shape] + + [:div.text-node-html + {:id (dm/str "html-text-node-" id) + :ref ref + :data-x x + :data-y y + :style {:position "fixed" + :left 0 + :top 0 + :background "white" + :width (if (#{:auto-width} grow-type) 100000 width) + :height (if (#{:auto-height :auto-width} grow-type) 100000 height)}} + ;; We use a class here because react has a bug that won't use the appropriate selector for + ;; `background-clip` + [:style ".text-node { background-clip: text; + -webkit-background-clip: text;" ] + [:& render-node {:index 0 + :shape shape + :node content}]])) diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 8082d00ff9..44d23ee397 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -63,7 +63,6 @@ (let [letter-spacing (:letter-spacing data 0) text-decoration (:text-decoration data) text-transform (:text-transform data) - line-height (:line-height data 1.2) font-id (:font-id data (:font-id txt/default-text-attrs)) font-variant-id (:font-variant-id data) @@ -80,13 +79,12 @@ base #js {:textDecoration text-decoration :textTransform text-transform - :lineHeight (or line-height "1.2") :color (if show-text? text-color "transparent") :caretColor (or text-color "black") :overflowWrap "initial" :lineBreak "auto" - :whiteSpace "break-spaces"} - + :whiteSpace "break-spaces" + :textRendering "geometricPrecision"} fills (cond (some? (:fills data)) diff --git a/frontend/src/app/main/ui/shapes/text/svg_text.cljs b/frontend/src/app/main/ui/shapes/text/svg_text.cljs index 5ee933815b..3f90c6cd34 100644 --- a/frontend/src/app/main/ui/shapes/text/svg_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/svg_text.cljs @@ -53,13 +53,13 @@ ::mf/wrap [mf/memo]} [props] - (let [render-id (mf/use-ctx muc/render-ctx) + (let [render-id (mf/use-ctx muc/render-id) shape (obj/get props "shape") shape (cond-> shape (:is-mask? shape) set-white-fill) {:keys [x y width height position-data]} shape - transform (str (gsh/transform-matrix shape {:no-flip true})) + transform (gsh/transform-str shape {:no-flip true}) ;; These position attributes are not really necesary but they are convenient for for the export group-props (-> #js {:transform transform @@ -106,7 +106,6 @@ (obj/set! "fill" (str "url(#fill-" index "-" render-id ")")))}) shape (assoc shape :fills (:fills data))] - [:& (mf/provider muc/render-ctx) {:key index :value (str render-id "_" (:id shape) "_" index)} + [:& (mf/provider muc/render-id) {:key index :value (str render-id "_" (:id shape) "_" index)} [:& shape-custom-strokes {:shape shape :position index :render-id render-id} [:> :text props (:text data)]]]))]])) - diff --git a/frontend/src/app/main/ui/share_link.cljs b/frontend/src/app/main/ui/share_link.cljs index 52438b3de7..48accd3694 100644 --- a/frontend/src/app/main/ui/share_link.cljs +++ b/frontend/src/app/main/ui/share_link.cljs @@ -10,6 +10,7 @@ [app.common.logging :as log] [app.config :as cf] [app.main.data.common :as dc] + [app.main.data.events :as ev] [app.main.data.messages :as dm] [app.main.data.modal :as modal] [app.main.refs :as refs] @@ -19,71 +20,89 @@ [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.webapi :as wapi] + [potok.core :as ptk] [rumext.alpha :as mf])) (log/set-level! :warn) (defn prepare-params - [{:keys [sections pages pages-mode]}] - {:pages pages - :flags (-> #{} - (into (map #(str "section-" %)) sections) - (into (map #(str "pages-" %)) [pages-mode]))}) + [{:keys [pages who-comment who-inspect]}] + + {:pages pages + :who-comment who-comment + :who-inspect who-inspect}) (mf/defc share-link-dialog {::mf/register modal/components ::mf/register-as :share-link} [{:keys [file page]}] - (let [slinks (mf/deref refs/share-links) - router (mf/deref refs/router) - route (mf/deref refs/route) + (let [current-page page + slinks (mf/deref refs/share-links) + router (mf/deref refs/router) + route (mf/deref refs/route) - link (mf/use-state nil) - confirm (mf/use-state false) + link (mf/use-state nil) + confirm (mf/use-state false) + open-ops (mf/use-state false) + + opts (mf/use-state + {:pages-mode "current" + :all-pages false + :pages #{(:id page)} + :who-comment "team" + :who-inspect "team"}) - opts (mf/use-state - {:sections #{"viewer"} - :pages-mode "current" - :pages #{(:id page)}}) close (fn [event] (dom/prevent-default event) - (st/emit! (modal/hide))) + (st/emit! (modal/hide)) + (modal/disallow-click-outside!)) - select-pages-mode - (fn [mode] + toggle-all + (fn [] (reset! confirm false) (swap! opts (fn [state] - (-> state - (assoc :pages-mode mode) - (cond-> (= mode "current") (assoc :pages #{(:id page)})) - (cond-> (= mode "all") (assoc :pages (into #{} (get-in file [:data :pages])))))))) + (if (= true (:all-pages state)) + (-> state + (assoc :all-pages false) + (assoc :pages #{(:id page)})) + (-> state + (assoc :all-pages true) + (assoc :pages (into #{} (get-in file [:data :pages])))))))) mark-checked-page (fn [event id] (let [target (dom/get-target event) - checked? (.-checked ^js target)] - (reset! confirm false) - (swap! opts update :pages - (fn [pages] - (if checked? - (conj pages id) - (disj pages id)))))) + checked? (.-checked ^js target) + dif-pages? (not= id (first (:pages @opts))) + no-one-page (< 1 (count (:pages @opts))) + should-change (or no-one-page dif-pages?)] + (when should-change + (reset! confirm false) + (swap! opts update :pages + (fn [pages] + (if checked? + (conj pages id) + (disj pages id))))))) create-link (fn [_] (let [params (prepare-params @opts) params (assoc params :file-id (:id file))] - (st/emit! (dc/create-share-link params)))) + (st/emit! (dc/create-share-link params) + (ptk/event ::ev/event {::ev/name "create-shared-link" + ::ev/origin "viewer" + :can-comment (:who-comment params) + :can-inspect-code (:who-inspect params)})))) copy-link (fn [_] (wapi/write-to-clipboard @link) (st/emit! (dm/show {:type :info :content (tr "common.share-link.link-copied-success") - :timeout 3000}))) + :timeout 1000}))) try-delete-link (fn [_] @@ -94,17 +113,27 @@ (let [params (prepare-params @opts) slink (d/seek #(= (:flags %) (:flags params)) slinks)] (reset! confirm false) - (st/emit! (dc/delete-share-link slink) - (dm/show {:type :info - :content (tr "common.share-link.link-deleted-success") - :timeout 3000})))) - ] + (st/emit! (dc/delete-share-link slink)))) + + manage-open-ops + (fn [_] + (swap! open-ops not)) + + on-who-change + (fn [type event] + (let [target (dom/get-target event) + value (dom/get-value target) + value (keyword value)] + (reset! confirm false) + (if (= type :comment) + (swap! opts assoc :who-comment (d/name value)) + (swap! opts assoc :who-inspect (d/name value)))))] (mf/use-effect (mf/deps file slinks @opts) (fn [] - (let [{:keys [flags pages] :as params} (prepare-params @opts) - slink (d/seek #(and (= (:flags %) flags) (= (:pages %) pages)) slinks) + (let [{:keys [pages who-comment who-inspect] :as params} (prepare-params @opts) + slink (d/seek #(and (= (:who-inspect %) who-inspect) (= (:who-comment %) who-comment) (= (:pages %) pages)) slinks) href (when slink (let [pparams (:path-params route) qparams (-> (:query-params route) @@ -114,123 +143,123 @@ (assoc cf/public-uri :fragment href)))] (reset! link (some-> href str))))) - [:div.modal-overlay + [:div.modal-overlay.share-modal [:div.modal-container.share-link-dialog - [:div.modal-content + [:div.modal-content.initial [:div.title [:h2 (tr "common.share-link.title")] [:div.modal-close-button {:on-click close :title (tr "labels.close")} - i/close]] - - [:div.share-link-section - [:label (tr "labels.link")] - [:div.custom-input.with-icon - [:input {:type "text" - :value (or @link "") - :placeholder (tr "common.share-link.placeholder") - :read-only true}] - (when (some? @link) - [:div.help-icon {:title (tr "labels.copy") - :on-click copy-link} - i/copy])] - - [:div.hint (tr "common.share-link.permissions-hint")]]] - + i/close]]] [:div.modal-content - (let [sections (:sections @opts)] - [:div.access-mode - [:div.title (tr "common.share-link.permissions-can-access")] - [:div.items - [:div.input-checkbox.check-primary.disabled - [:input.check-primary.input-checkbox {:type "checkbox" :disabled true}] - [:label (tr "labels.workspace")]] + [:div.share-link-section + (when (and (not @confirm) (some? @link)) + [:div.custom-input.with-icon + [:input {:type "text" + :value (or @link "") + :placeholder (tr "common.share-link.placeholder") + :read-only true}] + [:div.help-icon {:title (tr "viewer.header.share.copy-link") + :on-click copy-link} + i/copy]]) + [:div.hint-wrapper + (when (not @confirm) [:div.hint (tr "common.share-link.permissions-hint")]) + (cond + (true? @confirm) + [:div.confirm-dialog + [:div.description (tr "common.share-link.confirm-deletion-link-description")] + [:div.actions + [:input.btn-secondary + {:type "button" + :on-click #(reset! confirm false) + :value (tr "labels.cancel")}] + [:input.btn-warning + {:type "button" + :on-click delete-link + :value (tr "common.share-link.destroy-link")}]]] - [:div.input-checkbox.check-primary - [:input {:type "checkbox" - :default-checked (contains? sections "viewer")}] - [:label (tr "labels.viewer") - [:span.hint "(" (tr "labels.default") ")"]]] - - ;; [:div.input-checkbox.check-primary - ;; [:input.check-primary.input-checkbox {:type "checkbox"}] - ;; [:label "Handoff" ]] - ]]) - - (let [mode (:pages-mode @opts)] - [:* - [:div.view-mode - [:div.title (tr "common.share-link.permissions-can-view")] - [:div.items - [:div.input-radio.radio-primary - [:input {:type "radio" - :id "view-all" - :checked (= "all" mode) - :name "pages-mode" - :on-change #(select-pages-mode "all")}] - [:label {:for "view-all"} (tr "common.share-link.view-all-pages")]] - - [:div.input-radio.radio-primary - [:input {:type "radio" - :id "view-current" - :name "pages-mode" - :checked (= "current" mode) - :on-change #(select-pages-mode "current")}] - [:label {:for "view-current"} (tr "common.share-link.view-current-page")]] - - [:div.input-radio.radio-primary - [:input {:type "radio" - :id "view-selected" - :name "pages-mode" - :checked (= "selected" mode) - :on-change #(select-pages-mode "selected")}] - [:label {:for "view-selected"} (tr "common.share-link.view-selected-pages")]]]] - - (when (= "selected" mode) - (let [pages (->> (get-in file [:data :pages]) - (map #(get-in file [:data :pages-index %]))) - selected (:pages @opts)] - [:ul.pages-selection - (for [page pages] - [:li.input-checkbox.check-primary {:key (str (:id page))} - [:input {:type "checkbox" - :id (str "page-" (:id page)) - :on-change #(mark-checked-page % (:id page)) - :checked (contains? selected (:id page))}] - [:label {:for (str "page-" (:id page))} (:name page)]])]))])] - - [:div.modal-footer - (cond - (true? @confirm) - [:div.confirm-dialog - [:div.description (tr "common.share-link.confirm-deletion-link-description")] - [:div.actions + (some? @link) [:input.btn-secondary {:type "button" - :on-click #(reset! confirm false) - :value (tr "labels.cancel")}] - [:input.btn-warning + :class "primary" + :on-click try-delete-link + :value (tr "common.share-link.destroy-link")}] + + :else + [:input.btn-primary {:type "button" - :on-click delete-link - :value (tr "common.share-link.remove-link") - }]]] + :class "primary" + :on-click create-link + :value (tr "common.share-link.get-link")}])]]] + [:div.modal-content.ops-section + [:div.manage-permissions + {:on-click manage-open-ops} + [:span.icon i/picker-hsv] + [:div.title (tr "common.share-link.manage-ops")]] + (when @open-ops + [:* + (let [all-selected? (:all-pages @opts) + pages (->> (get-in file [:data :pages]) + (map #(get-in file [:data :pages-index %]))) + selected (:pages @opts)] - (some? @link) - [:input.btn-secondary - {:type "button" - :class "primary" - :on-click try-delete-link - :value (tr "common.share-link.remove-link")}] + [:* + [:div.view-mode + [:div.subtitle + [:span.icon i/play] + (tr "common.share-link.permissions-pages")] + [:div.items + (if (= 1 (count pages)) + [:div.input-checkbox.check-primary + [:input {:type "checkbox" + :id (str "page-" (:id current-page)) + :on-change #(mark-checked-page % (:id current-page)) + :checked true}] + [:label {:for (str "page-" (:id current-page))} (:name current-page)] + [:span (str " " (tr "common.share-link.current-tag"))]] - :else - [:input.btn-primary - {:type "button" - :class "primary" - :on-click create-link - :value (tr "common.share-link.get-link")}])] + [:* + [:div.row + [:div.input-checkbox.check-primary + [:input {:type "checkbox" + :id "view-all" + :checked all-selected? + :name "pages-mode" + :on-change toggle-all}] + [:label {:for "view-all"} (tr "common.share-link.view-all")]] + [:span.count-pages (tr "common.share-link.page-shared" (i18n/c (count selected)))]] - ]])) + [:ul.pages-selection + (for [page pages] + [:li.input-checkbox.check-primary {:key (str (:id page))} + [:input {:type "checkbox" + :id (str "page-" (:id page)) + :on-change #(mark-checked-page % (:id page)) + :checked (contains? selected (:id page))}] + (if (= (:id current-page) (:id page)) + [:* + [:label {:for (str "page-" (:id page))} (:name page)] + [:span.current-tag (str " " (tr "common.share-link.current-tag"))]] + [:label {:for (str "page-" (:id page))} (:name page)])])]])]]]) + [:div.access-mode + [:div.subtitle + [:span.icon i/chat] + (tr "common.share-link.permissions-can-comment")] + [:div.items + [:select.input-select {:on-change (partial on-who-change :comment) + :value (:who-comment @opts)} + [:option {:value "team"} (tr "common.share-link.team-members")] + [:option {:value "all"} (tr "common.share-link.all-users")]]]] + [:div.inspect-mode + [:div.subtitle + [:span.icon i/code] + (tr "common.share-link.permissions-can-inspect")] + [:div.items + [:select.input-select {:on-change (partial on-who-change :inspect) + :value (:who-inspect @opts)} + [:option {:value "team"} (tr "common.share-link.team-members")] + [:option {:value "all"} (tr "common.share-link.all-users")]]]]])]]])) diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index b94028defd..ced7b3c6ab 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -8,8 +8,10 @@ (:require [app.common.colors :as clr] [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.geom.point :as gpt] + [app.common.geom.shapes.bounds :as gsb] [app.common.pages.helpers :as cph] [app.common.text :as txt] [app.main.data.comments :as dcm] @@ -21,33 +23,38 @@ [app.main.ui.context :as ctx] [app.main.ui.hooks :as hooks] [app.main.ui.icons :as i] - [app.main.ui.shapes.filters :as filters] [app.main.ui.share-link] [app.main.ui.static :as static] - [app.main.ui.viewer.comments :refer [comments-layer]] + [app.main.ui.viewer.comments :refer [comments-layer comments-sidebar]] [app.main.ui.viewer.handoff :as handoff] - [app.main.ui.viewer.header :refer [header]] + [app.main.ui.viewer.header :as header] [app.main.ui.viewer.interactions :as interactions] + [app.main.ui.viewer.login] [app.main.ui.viewer.thumbnails :refer [thumbnails-panel]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.webapi :as wapi] + [cuerdas.core :as str] [goog.events :as events] + [okulary.core :as l] [rumext.alpha :as mf])) +(def current-animation-ref + (l/derived :viewer-animation st/state)) + +(def current-overlays-ref + (l/derived :viewer-overlays st/state)) + (defn- calculate-size - [frame zoom] - (let [{:keys [_ _ width height]} (filters/get-filters-bounds frame) - padding (filters/calculate-padding frame) - x (- (:horizontal padding)) - y (- (:vertical padding)) - width (+ width (* 2 (:horizontal padding))) - height (+ height (* 2 (:vertical padding)))] + [objects frame zoom] + (let [{:keys [x y width height]} (gsb/get-object-bounds objects frame)] {:base-width width :base-height height + :x x + :y y :width (* width zoom) :height (* height zoom) - :vbox (str x " " y " " width " " height)})) + :vbox (dm/fmt "% % % %" 0 0 width height)})) (defn- calculate-wrapper [size1 size2 zoom] @@ -60,19 +67,172 @@ :height (* height zoom) :vbox (str "0 0 " width " " height)}))) +(mf/defc viewer-pagination + [{:keys [index num-frames left-bar right-bar] :as props}] + [:* + (when (pos? index) + [:div.viewer-go-prev {:class (when left-bar "left-bar")} + [:div.arrow {:on-click #(st/emit! dv/select-prev-frame)} i/go-prev]]) + (when (< (+ index 1) num-frames) + [:div.viewer-go-next {:class (when right-bar "right-bar")} + [:div.arrow {:on-click #(st/emit! dv/select-next-frame)} i/go-next]]) + [:div.viewer-bottom {:class (when left-bar "left-bar")} + [:div.reset {:on-click #(st/emit! dv/select-first-frame)} i/reset] + [:div.counter (str/join " / " [(+ index 1) num-frames])] + [:span]]]) + +(mf/defc viewer-pagination-and-sidebar + {::mf/wrap [mf/memo]} + [{:keys [section index users frame page]}] + (let [comments-local (mf/deref refs/comments-local) + show-sidebar? (and (= section :comments) (:show-sidebar? comments-local))] + [:* + [:& viewer-pagination + {:index index + :num-frames (count (:frames page)) + :right-bar show-sidebar?}] + + (when show-sidebar? + [:& comments-sidebar + {:users users + :frame frame + :page page}])])) + +(mf/defc viewer-overlay + [{:keys [overlay page frame zoom wrapper-size close-overlay interactions-mode]}] + (let [close-click-outside? (:close-click-outside overlay) + background-overlay? (:background-overlay overlay) + overlay-frame (:frame overlay) + overlay-position (:position overlay) + + size + (mf/with-memo [page overlay zoom] + (calculate-size (:objects page) (:frame overlay) zoom)) + + on-click + (mf/use-fn + (mf/deps overlay close-overlay close-click-outside?) + (fn [_] + (when close-click-outside? + (close-overlay (:frame overlay)))))] + + [:* + (when (or close-click-outside? background-overlay?) + [:div.viewer-overlay-background + {:class (dom/classnames :visible background-overlay?) + :style {:width (:width wrapper-size) + :height (:height wrapper-size) + :position "absolute" + :left 0 + :top 0} + :on-click on-click}]) + + [:div.viewport-container.viewer-overlay + {:id (dm/str "overlay-" (:id overlay-frame)) + :style {:width (:width size) + :height (:height size) + :left (* (:x overlay-position) zoom) + :top (* (:y overlay-position) zoom)}} + + [:& interactions/viewport + {:frame overlay-frame + :base-frame frame + :frame-offset overlay-position + :size size + :page page + :interactions-mode interactions-mode}]]])) + + +(mf/defc viewer-wrapper + [{:keys [wrapper-size orig-frame orig-viewport-ref orig-size page file users current-viewport-ref + size frame interactions-mode overlays zoom close-overlay section index] :as props}] + [:* + [:& viewer-pagination-and-sidebar + {:section section + :index index + :page page + :users users + :frame frame}] + + [:div.viewer-wrapper + {:style {:width (:width wrapper-size) + :height (:height wrapper-size)}} + [:div.viewer-clipper + (when orig-frame + [:div.viewport-container + {:ref orig-viewport-ref + :style {:width (:width orig-size) + :height (:height orig-size) + :position "relative"}} + + [:& interactions/viewport + {:frame orig-frame + :base-frame orig-frame + :frame-offset (gpt/point 0 0) + :size orig-size + :page page + :users users + :interactions-mode :hide}]]) + + [:div.viewport-container + {:ref current-viewport-ref + :style {:width (:width size) + :height (:height size) + :position "relative"}} + + [:& interactions/viewport + {:frame frame + :base-frame frame + :frame-offset (gpt/point 0 0) + :size size + :page page + :interactions-mode interactions-mode}] + + (for [overlay overlays] + [:& viewer-overlay {:overlay overlay + :key (dm/str (:id overlay)) + :page page + :frame frame + :zoom zoom + :wrapper-size wrapper-size + :close-overlay close-overlay + :interactions-mode interactions-mode}]) + + ]] + + + (when (= section :comments) + [:& comments-layer {:file file + :users users + :frame frame + :page page + :zoom zoom}])]]) + (mf/defc viewer [{:keys [params data]}] (let [{:keys [page-id section index]} params {:keys [file users project permissions]} data + allowed (or + (= section :interactions) + (and (= section :comments) + (or (:can-edit permissions) + (and (true? (:is-logged permissions)) + (= (:who-comment permissions) "all")))) + (and (= section :handoff) + (or (:can-edit permissions) + (and (true? (:is-logged permissions)) + (= (:who-inspect permissions) "all"))))) + local (mf/deref refs/viewer-local) nav-scroll (:nav-scroll local) - orig-viewport-ref (mf/use-ref nil) + orig-viewport-ref (mf/use-ref nil) current-viewport-ref (mf/use-ref nil) - viewer-section-ref (mf/use-ref nil) - current-animation (:current-animation local) + viewer-section-ref (mf/use-ref nil) + + current-animation (mf/deref current-animation-ref) page-id (or page-id (-> file :data :pages first)) @@ -90,57 +250,59 @@ zoom (:zoom local) frames (:frames page) frame (get frames index) - fullscreen? (mf/deref refs/viewer-fullscreen?) - overlays (:overlays local) - scroll (mf/use-state nil) + + fullscreen? (mf/deref header/fullscreen-ref) + overlays (mf/deref current-overlays-ref) + scroll (mf/use-state nil) orig-frame (when (:orig-frame-id current-animation) (d/seek #(= (:id %) (:orig-frame-id current-animation)) frames)) - size (mf/use-memo - (mf/deps frame zoom) - (fn [] (calculate-size frame zoom))) + size + (mf/with-memo [frame zoom] + (calculate-size (:objects page) frame zoom)) - orig-size (mf/use-memo - (mf/deps orig-frame zoom) - (fn [] (when orig-frame (calculate-size orig-frame zoom)))) + orig-size + (mf/with-memo [orig-frame zoom] + (when orig-frame + (calculate-size (:objects page) orig-frame zoom))) - wrapper-size (mf/use-memo - (mf/deps size orig-size zoom) - (fn [] (calculate-wrapper size orig-size zoom))) + wrapper-size + (mf/with-memo [size orig-size zoom] + (calculate-wrapper size orig-size zoom)) interactions-mode (:interactions-mode local) on-click - (mf/use-callback + (mf/use-fn (mf/deps section) (fn [_] (when (= section :comments) (st/emit! (dcm/close-thread))))) - close-overlay - (mf/use-callback - (fn [frame] - (st/emit! (dv/close-overlay (:id frame))))) - set-up-new-size - (mf/use-callback + (mf/use-fn (fn [_] (let [viewer-section (dom/get-element "viewer-section") size (dom/get-client-size viewer-section)] (st/emit! (dv/set-viewport-size {:size size}))))) on-scroll - (fn [event] - (reset! scroll (dom/get-target-scroll event)))] + (mf/use-fn + (fn [event] + (reset! scroll (dom/get-target-scroll event))))] (hooks/use-shortcuts ::viewer sc/shortcuts) (when (nil? page) (ex/raise :type :not-found)) + (mf/with-effect [] + (when (not allowed) + (st/emit! (dv/go-to-section :interactions)))) + ;; Set the page title (mf/use-effect (mf/deps (:name file)) @@ -148,14 +310,13 @@ (let [name (:name file)] (dom/set-html-title (str "\u25b6 " (tr "title.viewer" name)))))) - (mf/use-effect - (fn [] - (dom/set-html-theme-color clr/gray-50 "dark") - (let [key1 (events/listen js/window "click" on-click) - key2 (events/listen (mf/ref-val viewer-section-ref) "scroll" on-scroll)] - (fn [] - (events/unlistenByKey key1) - (events/unlistenByKey key2))))) + (mf/with-effect [] + (dom/set-html-theme-color clr/gray-50 "dark") + (let [key1 (events/listen js/window "click" on-click) + key2 (events/listen (mf/ref-val viewer-section-ref) "scroll" on-scroll)] + (fn [] + (events/unlistenByKey key1) + (events/unlistenByKey key2)))) (mf/use-layout-effect (fn [] @@ -212,7 +373,7 @@ (let [overlay-viewport (dom/get-element (str "overlay-" (str (:overlay-id current-animation)))) overlay (d/seek #(= (:id (:frame %)) (:overlay-id current-animation)) overlays) - overlay-size (calculate-size (:frame overlay) zoom) + overlay-size (calculate-size (:objects page) (:frame overlay) zoom) overlay-position {:x (* (:x (:position overlay)) zoom) :y (* (:y (:position overlay)) zoom)}] (interactions/animate-open-overlay @@ -226,7 +387,7 @@ (let [overlay-viewport (dom/get-element (str "overlay-" (str (:overlay-id current-animation)))) overlay (d/seek #(= (:id (:frame %)) (:overlay-id current-animation)) overlays) - overlay-size (calculate-size (:frame overlay) zoom) + overlay-size (calculate-size (:objects page) (:frame overlay) zoom) overlay-position {:x (* (:x (:position overlay)) zoom) :y (* (:y (:position overlay)) zoom)}] (interactions/animate-close-overlay @@ -246,20 +407,22 @@ fonts (into #{} (keep :font-id) text-nodes)] (run! fonts/ensure-loaded! fonts)))) - [:div#viewer-layout {:class (dom/classnames - :force-visible (:show-thumbnails local) - :viewer-layout (not= section :handoff) - :handoff-layout (= section :handoff) - :fullscreen fullscreen?)} + [:div#viewer-layout + {:class (dom/classnames + :force-visible (:show-thumbnails local) + :viewer-layout (not= section :handoff) + :handoff-layout (= section :handoff) + :fullscreen fullscreen?)} - [:& header {:project project - :index index - :file file - :page page - :frame frame - :permissions permissions - :zoom zoom - :section section}] + [:& header/header + {:project project + :index index + :file file + :page page + :frame frame + :permissions permissions + :zoom zoom + :section section}] [:div.viewer-content [:div.thumbnail-close {:on-click #(st/emit! dv/close-thumbnails-panel) @@ -289,85 +452,29 @@ :page page :file file :section section - :local local}] + :local local + :size size + :index index + :viewer-pagination viewer-pagination}] - [:* - [:div.viewer-wrapper - {:style {:width (:width wrapper-size) - :height (:height wrapper-size)}} - [:& (mf/provider ctx/scroll-ctx) {:value @scroll} - [:div.viewer-clipper - [:* - (when orig-frame - [:div.viewport-container - {:ref orig-viewport-ref - :style {:width (:width orig-size) - :height (:height orig-size) - :position "relative"}} - - [:& interactions/viewport - {:frame orig-frame - :base-frame orig-frame - :frame-offset (gpt/point 0 0) - :size orig-size - :page page - :file file - :users users - :interactions-mode :hide}]]) - - [:div.viewport-container - {:ref current-viewport-ref - :style {:width (:width size) - :height (:height size) - :position "relative"}} - - [:& interactions/viewport - {:frame frame - :base-frame frame - :frame-offset (gpt/point 0 0) - :size size - :page page - :file file - :users users - :interactions-mode interactions-mode}] - - (for [overlay overlays] - (let [size-over (calculate-size (:frame overlay) zoom)] - [:* - (when (or (:close-click-outside overlay) - (:background-overlay overlay)) - [:div.viewer-overlay-background - {:class (dom/classnames - :visible (:background-overlay overlay)) - :style {:width (:width wrapper-size) - :height (:height wrapper-size) - :position "absolute" - :left 0 - :top 0} - :on-click #(when (:close-click-outside overlay) - (close-overlay (:frame overlay)))}]) - [:div.viewport-container.viewer-overlay - {:id (str "overlay-" (str (:id (:frame overlay)))) - :style {:width (:width size-over) - :height (:height size-over) - :left (* (:x (:position overlay)) zoom) - :top (* (:y (:position overlay)) zoom)}} - [:& interactions/viewport - {:frame (:frame overlay) - :base-frame frame - :frame-offset (:position overlay) - :size size-over - :page page - :file file - :users users - :interactions-mode interactions-mode}]]]))]] - - (when (= section :comments) - [:& comments-layer {:file file - :users users - :frame frame - :page page - :zoom zoom}])]]]]))]]])) + [:& (mf/provider ctx/current-scroll) {:value @scroll} + [:& (mf/provider ctx/current-zoom) {:value zoom} + [:& viewer-wrapper + {:wrapper-size wrapper-size + :orig-frame orig-frame + :orig-viewport-ref orig-viewport-ref + :orig-size orig-size + :page page + :file file + :users users + :current-viewport-ref current-viewport-ref + :size size + :frame frame + :interactions-mode interactions-mode + :overlays overlays + :zoom zoom + :section section + :index index}]]]))]]])) ;; --- Component: Viewer Page diff --git a/frontend/src/app/main/ui/viewer/comments.cljs b/frontend/src/app/main/ui/viewer/comments.cljs index c813485fbd..96c44b2625 100644 --- a/frontend/src/app/main/ui/viewer/comments.cljs +++ b/frontend/src/app/main/ui/viewer/comments.cljs @@ -6,8 +6,10 @@ (ns app.main.ui.viewer.comments (: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.main.data.comments :as dcm] [app.main.data.events :as ev] [app.main.refs :as refs] @@ -15,144 +17,192 @@ [app.main.ui.comments :as cmt] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.icons :as i] + [app.main.ui.workspace.comments :as wc] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [okulary.core :as l] [rumext.alpha :as mf])) (mf/defc comments-menu + {::mf/wrap [mf/memo] + ::mf/wrap-props false} [] - (let [{cmode :mode cshow :show} (mf/deref refs/comments-local) + (let [local (mf/deref refs/comments-local) + owner-filter (:owner-filter local) + status-filter (:status-filter local) + show-sidebar? (:show-sidebar? local) show-dropdown? (mf/use-state false) toggle-dropdown (mf/use-fn #(swap! show-dropdown? not)) hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) - update-mode - (mf/use-callback - (fn [mode] - (st/emit! (dcm/update-filters {:mode mode})))) - - update-show - (mf/use-callback - (fn [mode] - (st/emit! (dcm/update-filters {:show mode}))))] + update-option (mf/use-fn + (fn [event] + (let [target (dom/get-current-target event) + key (d/read-string (dom/get-attribute target "data-key")) + val (d/read-string (dom/get-attribute target "data-val"))] + (st/emit! (dcm/update-options {key val})))))] [:div.view-options {:on-click toggle-dropdown} [:span.label (tr "labels.comments")] [:span.icon i/arrow-down] [:& dropdown {:show @show-dropdown? :on-close hide-dropdown} + [:ul.dropdown.with-check - [:li {:class (dom/classnames :selected (= :all cmode)) - :on-click #(update-mode :all)} + [:li {:class (dom/classnames :selected (= :all owner-filter)) + :data-key ":owner-filter" + :data-val ":all" + :on-click update-option} [:span.icon i/tick] [:span.label (tr "labels.show-all-comments")]] - [:li {:class (dom/classnames :selected (= :yours cmode)) - :on-click #(update-mode :yours)} + [:li {:class (dom/classnames :selected (= :yours owner-filter)) + :data-key ":owner-filter" + :data-val ":yours" + :on-click update-option} [:span.icon i/tick] [:span.label (tr "labels.show-your-comments")]] [:hr] - [:li {:class (dom/classnames :selected (= :pending cshow)) - :on-click #(update-show (if (= :pending cshow) :all :pending))} + [:li {:class (dom/classnames :selected (= :pending status-filter)) + :data-key ":status-filter" + :data-val (if (= :pending status-filter) ":all" ":pending") + :on-click update-option} [:span.icon i/tick] - [:span.label (tr "labels.hide-resolved-comments")]]]]])) + [:span.label (tr "labels.hide-resolved-comments")]] + + [:hr] + [:li {:class (dom/classnames :selected show-sidebar?) + :data-key ":show-sidebar?" + :data-val (if show-sidebar? "false" "true") + :on-click update-option} + [:span.icon i/tick] + [:span.label (tr "labels.show-comments-list")]]]]])) -(defn- frame-contains? - [{:keys [x y width height]} {px :x py :y}] - (let [x2 (+ x width) - y2 (+ y height)] - (and (<= x px x2) - (<= y py y2)))) - -(def threads-ref - (l/derived :comment-threads st/state)) - -(def comments-local-ref - (l/derived :comments-local st/state)) +(defn- update-thread-position [positions {:keys [id] :as thread}] + (if-let [data (get positions id)] + (-> thread + (assoc :position (:position data)) + (assoc :frame-id (:frame-id data))) + thread)) (mf/defc comments-layer [{:keys [zoom file users frame page] :as props}] - (let [profile (mf/deref refs/profile) - threads-map (mf/deref threads-ref) + (prn "comments-layer") + (let [profile (mf/deref refs/profile) + local (mf/deref refs/comments-local) - modifier1 (-> (gpt/point (:x frame) (:y frame)) - (gpt/negate) - (gmt/translate-matrix)) + open-thread-id (:open local) + page-id (:id page) + file-id (:id file) + frame-id (:id frame) - modifier2 (-> (gpt/point (:x frame) (:y frame)) - (gmt/translate-matrix)) + tpos-ref (mf/with-memo [page-id] + (-> (l/in [:pages page-id :options :comment-threads-position]) + (l/derived refs/viewer-data))) - cstate (mf/deref refs/comments-local) + positions (mf/deref tpos-ref) + threads-map (mf/deref refs/comment-threads) + + frame-corner (mf/with-memo [frame] + (-> frame :points gsh/points->selrect gpt/point)) + + modifier1 (mf/with-memo [frame-corner] + (-> (gmt/matrix) + (gmt/translate (gpt/negate frame-corner)))) + modifier2 (mf/with-memo [frame-corner] + (-> (gpt/point frame-corner) + (gmt/translate-matrix))) + + + threads (mf/with-memo [threads-map positions] + (->> (vals threads-map) + (map (partial update-thread-position positions)) + (filter #(= (:frame-id %) (:id frame))) + (dcm/apply-filters local profile) + (filter (fn [{:keys [position]}] + (gsh/has-point? frame position))))) - threads (->> (vals threads-map) - (dcm/apply-filters cstate profile) - (filter (fn [{:keys [position]}] - (frame-contains? frame position)))) on-bubble-click - (mf/use-callback - (mf/deps cstate) - (fn [thread] - (if (= (:open cstate) (:id thread)) - (st/emit! (dcm/close-thread)) - (st/emit! (-> (dcm/open-thread thread) + (mf/use-fn + (mf/deps open-thread-id) + (fn [{:keys [id] :as thread}] + (st/emit! (if (= open-thread-id id) + (dcm/close-thread) + (-> (dcm/open-thread thread) (with-meta {::ev/origin "viewer"})))))) on-click - (mf/use-callback - (mf/deps cstate frame page file zoom) + (mf/use-fn + (mf/deps open-thread-id zoom page-id file-id modifier2) (fn [event] (dom/stop-propagation event) - (if (some? (:open cstate)) + (if (some? open-thread-id) (st/emit! (dcm/close-thread)) - (let [event (.-nativeEvent ^js event) - viewport-point (dom/get-offset-position event) - viewport-point (-> viewport-point (update :x #(/ % zoom)) (update :y #(/ % zoom))) - position (gpt/transform viewport-point modifier2) + (let [event (dom/event->native-event event) + position (-> (dom/get-offset-position event) + (update :x #(/ % zoom)) + (update :y #(/ % zoom)) + (gpt/transform modifier2)) params {:position position :page-id (:id page) :file-id (:id file)}] (st/emit! (dcm/create-draft params)))))) on-draft-cancel - (mf/use-callback - (mf/deps cstate) - #(st/emit! (dcm/close-thread))) + (mf/use-fn #(st/emit! (dcm/close-thread))) on-draft-submit - (mf/use-callback - (mf/deps frame) + (mf/use-fn + (mf/deps frame-id modifier2) (fn [draft] - (let [params (update draft :position gpt/transform modifier2)] - (st/emit! (dcm/create-thread params) + (let [params (-> draft + (update :position gpt/transform modifier2) + (assoc :frame-id frame-id))] + (st/emit! (dcm/create-thread-on-viewer params) (dcm/close-thread)))))] [:div.comments-section {:on-click on-click} [:div.viewer-comments-container [:div.threads (for [item threads] - (let [item (update item :position gpt/transform modifier1)] - [:& cmt/thread-bubble {:thread item - :zoom zoom - :on-click on-bubble-click - :open? (= (:id item) (:open cstate)) - :key (:seqn item)}])) + [:& cmt/thread-bubble + {:thread item + :position-modifier modifier1 + :zoom zoom + :on-click on-bubble-click + :open? (= (:id item) (:open local)) + :key (:seqn item) + :origin :viewer}]) - (when-let [id (:open cstate)] - (when-let [thread (as-> (get threads-map id) $ - (when (some? $) - (update $ :position gpt/transform modifier1)))] - [:& cmt/thread-comments {:thread thread - :users users - :zoom zoom}])) + (when-let [thread (get threads-map open-thread-id)] + [:& cmt/thread-comments + {:thread thread + :position-modifier modifier1 + :users users + :zoom zoom}]) - (when-let [draft (:draft cstate)] - [:& cmt/draft-thread {:draft (update draft :position gpt/transform modifier1) - :on-cancel on-draft-cancel - :on-submit on-draft-submit - :zoom zoom}])]]])) + (when-let [draft (:draft local)] + [:& cmt/draft-thread + {:draft draft + :position-modifier modifier1 + :on-cancel on-draft-cancel + :on-submit on-draft-submit + :zoom zoom}])]]])) + +(mf/defc comments-sidebar + [{:keys [users frame page]}] + (let [profile (mf/deref refs/profile) + local (mf/deref refs/comments-local) + threads-map (mf/deref refs/comment-threads) + threads (->> (vals threads-map) + (dcm/apply-filters local profile) + (filter (fn [{:keys [position]}] + (gsh/has-point? frame position))))] + [:aside.settings-bar.settings-bar-right.comments-right-sidebar + [:div.settings-bar-inside + [:& wc/comments-sidebar {:users users :threads threads :page-id (:id page)}]]])) diff --git a/frontend/src/app/main/ui/viewer/handoff.cljs b/frontend/src/app/main/ui/viewer/handoff.cljs index 4a00631ea6..2eb108de6b 100644 --- a/frontend/src/app/main/ui/viewer/handoff.cljs +++ b/frontend/src/app/main/ui/viewer/handoff.cljs @@ -25,7 +25,7 @@ (st/emit! (dv/select-shape (:id frame))))) (mf/defc viewport - [{:keys [local file page frame]}] + [{:keys [local file page frame index viewer-pagination size]}] (let [on-mouse-wheel (fn [event] (when (kbd/mod? event) @@ -58,8 +58,9 @@ :local local :page page}] [:div.handoff-svg-wrapper {:on-click (handle-select-frame frame)} + [:& viewer-pagination {:index index :num-frames (count (:frames page)) :left-bar true :right-bar true}] [:div.handoff-svg-container - [:& render-frame-svg {:frame frame :page page :local local}]]] + [:& render-frame-svg {:frame frame :page page :local local :size size}]]] [:& right-sidebar {:frame frame :selected (:selected local) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs index f2fca42b8c..ccf89070f0 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs @@ -6,7 +6,7 @@ (ns app.main.ui.viewer.handoff.attributes.layout (:require - [app.common.spec.radius :as ctr] + [app.common.types.shape.radius :as ctsr] [app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.formats :as fmt] [app.util.code-gen :as cg] @@ -59,13 +59,13 @@ [:div.attributes-value (fmt/format-pixels y)] [:& copy-button {:data (copy-data selrect :y)}]]) - (when (ctr/radius-1? shape) + (when (ctsr/radius-1? shape) [:div.attributes-unit-row [:div.attributes-label (tr "handoff.attributes.layout.radius")] [:div.attributes-value (fmt/format-pixels (:rx shape 0))] [:& copy-button {:data (copy-data shape :rx)}]]) - (when (ctr/radius-4? shape) + (when (ctsr/radius-4? shape) [:div.attributes-unit-row [:div.attributes-label (tr "handoff.attributes.layout.radius")] [:div.attributes-value diff --git a/frontend/src/app/main/ui/viewer/handoff/code.cljs b/frontend/src/app/main/ui/viewer/handoff/code.cljs index 030a443b45..265b2eba54 100644 --- a/frontend/src/app/main/ui/viewer/handoff/code.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/code.cljs @@ -72,12 +72,7 @@ [:div.element-options [:div.code-block - [:div.code-row-lang - [:select.code-selection - [:option {:value "css"} "CSS"] - #_[:option {:value "sass"} "SASS"] - #_[:option {:value "less"} "Less"] - #_[:option {:value "stylus"} "Stylus"]] + [:div.code-row-lang "CSS" [:button.expand-button {:on-click on-expand } @@ -91,10 +86,7 @@ :code style-code}]]] [:div.code-block - [:div.code-row-lang - [:select.code-selection - [:option "SVG"] - [:option "HTML"]] + [:div.code-row-lang "SVG" [:button.expand-button {:on-click on-expand} diff --git a/frontend/src/app/main/ui/viewer/handoff/render.cljs b/frontend/src/app/main/ui/viewer/handoff/render.cljs index a016077cc8..61d9829108 100644 --- a/frontend/src/app/main/ui/viewer/handoff/render.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/render.cljs @@ -7,13 +7,12 @@ (ns app.main.ui.viewer.handoff.render "The main container for a frame in handoff mode" (:require - [app.common.geom.shapes :as geom] + [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] [app.main.data.viewer :as dv] [app.main.store :as st] [app.main.ui.shapes.bool :as bool] [app.main.ui.shapes.circle :as circle] - [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.group :as group] [app.main.ui.shapes.image :as image] @@ -31,24 +30,26 @@ (declare shape-container-factory) (defn handle-hover-shape - [{:keys [type id]} hover?] + [shape hover?] (fn [event] - (when-not (#{:group :frame} type) + (when-not (or (cph/group-shape? shape) + (cph/root-frame? shape)) (dom/prevent-default event) (dom/stop-propagation event) - (st/emit! (dv/hover-shape id hover?))))) + (st/emit! (dv/hover-shape (:id shape) hover?))))) -(defn select-shape [{:keys [type id]}] +(defn select-shape [shape] (fn [event] - (when-not (#{:group :frame} type) + (when-not (or (cph/group-shape? shape) + (cph/root-frame? shape)) (dom/stop-propagation event) (dom/prevent-default event) (cond (.-shiftKey ^js event) - (st/emit! (dv/toggle-selection id)) + (st/emit! (dv/toggle-selection (:id shape))) :else - (st/emit! (dv/select-shape id)))))) + (st/emit! (dv/select-shape (:id shape))))))) (defn shape-wrapper-factory [component] @@ -87,9 +88,9 @@ [props] (let [shape (unchecked-get props "shape") childs (mapv #(get objects %) (:shapes shape)) - shape (geom/transform-shape shape) + shape (gsh/transform-shape shape) - props (-> (obj/new) + props (-> (obj/create) (obj/merge! props) (obj/merge! #js {:shape shape :childs childs}))] @@ -105,7 +106,7 @@ [props] (let [shape (unchecked-get props "shape") childs (mapv #(get objects %) (:shapes shape)) - props (-> (obj/new) + props (-> (obj/create) (obj/merge! props) (obj/merge! #js {:childs childs}))] [:> group-wrapper props])))) @@ -121,7 +122,7 @@ (let [shape (unchecked-get props "shape") children (->> (cph/get-children-ids objects (:id shape)) (select-keys objects)) - props (-> (obj/new) + props (-> (obj/create) (obj/merge! props) (obj/merge! #js {:childs children}))] [:> bool-wrapper props])))) @@ -136,7 +137,7 @@ [props] (let [shape (unchecked-get props "shape") childs (mapv #(get objects %) (:shapes shape)) - props (-> (obj/new) + props (-> (obj/create) (obj/merge! props) (obj/merge! #js {:childs childs}))] [:> svg-raw-wrapper props])))) @@ -154,6 +155,10 @@ (let [shape (unchecked-get props "shape") frame (unchecked-get props "frame") + frame-container + (mf/use-memo (mf/deps objects) + #(frame-container-factory objects)) + group-container (mf/use-memo (mf/deps objects) #(group-container-factory objects)) @@ -166,11 +171,12 @@ (mf/use-memo (mf/deps objects) #(svg-raw-container-factory objects))] (when (and shape (not (:hidden shape))) - (let [shape (-> (geom/transform-shape shape) - (geom/translate-to-frame frame)) + (let [shape (-> (gsh/transform-shape shape) + (gsh/translate-to-frame frame)) opts #js {:shape shape :frame frame}] (case (:type shape) + :frame [:> frame-container opts] :text [:> text-wrapper opts] :rect [:> rect-wrapper opts] :path [:> path-wrapper opts] @@ -181,46 +187,30 @@ :svg-raw [:> svg-raw-container opts]))))))) (mf/defc render-frame-svg - [{:keys [page frame local]}] - (let [objects (mf/use-memo - (mf/deps page frame) - (prepare-objects page frame)) - + [{:keys [page frame local size]}] + (let [objects (mf/with-memo [page frame size] + (prepare-objects page frame size)) ;; Retrieve frame again with correct modifier frame (get objects (:id frame)) - - zoom (:zoom local 1) - - {:keys [_ _ width height]} (filters/get-filters-bounds frame) - padding (filters/calculate-padding frame) - x (- (:horizontal padding)) - y (- (:vertical padding)) - width (+ width (* 2 (:horizontal padding))) - height (+ height (* 2 (:vertical padding))) - - vbox (str x " " y " " width " " height) - - width (* width zoom) - height (* height zoom) - render (mf/use-memo (mf/deps objects) #(frame-container-factory objects))] [:svg {:id "svg-frame" - :view-box vbox - :width width - :height height + :view-box (:vbox size) + :width (:width size) + :height (:height size) :version "1.1" :xmlnsXlink "http://www.w3.org/1999/xlink" :xmlns "http://www.w3.org/2000/svg" :fill "none"} - [:& render {:shape frame :view-box vbox}] + [:& render {:shape frame :view-box (:vbox size)}] [:& selection-feedback {:frame frame :objects objects - :local local}]])) + :local local + :size size}]])) diff --git a/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs index 4445eee30a..ba5f7b24d1 100644 --- a/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs @@ -8,7 +8,7 @@ (:require [app.common.data :as d] [app.common.geom.shapes :as gsh] - [app.main.ui.measurements :refer [selection-guides size-display measurement]] + [app.main.ui.measurements :refer [size-display measurement]] [rumext.alpha :as mf])) ;; ------------------------------------------------ @@ -52,24 +52,23 @@ :stroke-width selection-rect-width}}]])) (mf/defc selection-feedback - [{:keys [frame local objects]}] + [{:keys [frame local objects size]}] (let [{:keys [hover selected zoom]} local - hover-shape (-> (or (first (resolve-shapes objects [hover])) frame) - (gsh/translate-to-frame frame)) - selected-shapes (->> (resolve-shapes objects selected)) - selrect (gsh/selection-rect selected-shapes) - bounds (frame->bounds frame)] + shapes (resolve-shapes objects [hover]) + hover-shape (or (first shapes) frame) + hover-shape (gsh/translate-to-frame hover-shape size) + selected-shapes (resolve-shapes objects selected) + selrect (gsh/selection-rect selected-shapes)] - (when (seq selected-shapes) + (when (d/not-empty? selected-shapes) [:g.selection-feedback {:pointer-events "none"} [:g.selected-shapes - [:& selection-guides {:bounds bounds :selrect selrect :zoom zoom}] [:& selection-rect {:selrect selrect :zoom zoom}] [:& size-display {:selrect selrect :zoom zoom}]] - [:& measurement {:bounds bounds + [:& measurement {:bounds (assoc size :x 0 :y 0) :selected-shapes selected-shapes :hover-shape hover-shape :zoom zoom}]]))) diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index ad5f2e49cf..2fbf99e512 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -6,10 +6,10 @@ (ns app.main.ui.viewer.header (:require + [app.common.data.macros :as dm] [app.main.data.modal :as modal] [app.main.data.viewer :as dv] [app.main.data.viewer.shortcuts :as sc] - [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.export :refer [export-progress-widget]] @@ -19,8 +19,18 @@ [app.main.ui.viewer.interactions :refer [flows-menu interactions-menu]] [app.util.dom :as dom] [app.util.i18n :refer [tr]] + [okulary.core :as l] [rumext.alpha :as mf])) +(def fullscreen-ref + (l/derived (fn [state] + (dm/get-in state [:viewer-local :fullscreen?])) + st/state)) + +(defn open-login-dialog + [] + (modal/show! :login-register {})) + (mf/defc zoom-widget {::mf/wrap [mf/memo]} [{:keys [zoom @@ -61,7 +71,7 @@ (mf/defc header-options [{:keys [section zoom page file index permissions]}] - (let [fullscreen? (mf/deref refs/viewer-fullscreen?) + (let [fullscreen? (mf/deref fullscreen-ref) toggle-fullscreen (mf/use-callback @@ -77,7 +87,8 @@ (mf/use-callback (mf/deps page) (fn [] - (modal/show! :share-link {:page page :file file})))] + (modal/show! :share-link {:page page :file file}) + (modal/allow-click-outside!)))] [:div.options-zone (case section @@ -107,18 +118,20 @@ i/full-screen)] (when (:is-admin permissions) - [:span.btn-primary {:on-click open-share-dialog} (tr "labels.share-prototype")]) + [:span.btn-primary {:on-click open-share-dialog} i/export [:span (tr "labels.share-prototype")]]) (when (:can-edit permissions) - [:span.btn-text-dark {:on-click go-to-workspace} (tr "labels.edit-file")])])) + [:span.btn-text-dark {:on-click go-to-workspace} (tr "labels.edit-file")]) + + (when-not (:is-logged permissions) + [:span.btn-text-dark {:on-click open-login-dialog} (tr "labels.log-or-sign")])])) (mf/defc header-sitemap - [{:keys [project file page frame index] :as props}] + [{:keys [project file page frame] :as props}] (let [project-name (:name project) file-name (:name file) page-name (:name page) frame-name (:name frame) - total (count (:frames page)) show-dropdown? (mf/use-state false) toggle-thumbnails @@ -151,22 +164,23 @@ [:span "/"] [:span.page-name page-name] - [:span.icon i/arrow-down] + [:& dropdown {:show @show-dropdown? :on-close close-dropdown} [:ul.dropdown (for [id (get-in file [:data :pages])] [:li {:id (str id) + :key (str id) :on-click (partial navigate-to id)} (get-in file [:data :pages-index id :name])])]]] + [:span.icon {:on-click open-dropdown} i/arrow-down] [:div.current-frame {:on-click toggle-thumbnails} [:span.label "/"] - [:span.label frame-name] - [:span.icon i/arrow-down] - [:span.counters (str (inc index) " / " total)]]])) + [:span.label frame-name]] + [:span.icon {:on-click toggle-thumbnails} i/arrow-down]])) (mf/defc header @@ -175,19 +189,23 @@ #(st/emit! (dv/go-to-dashboard)) go-to-handoff - (fn [] - (st/emit! dv/close-thumbnails-panel (dv/go-to-section :handoff))) + (fn[] + (if (:is-logged permissions) + (st/emit! dv/close-thumbnails-panel (dv/go-to-section :handoff)) + (open-login-dialog))) navigate (fn [section] - (st/emit! (dv/go-to-section section)))] + (if (or (= section :interactions) (:is-logged permissions)) + (st/emit! (dv/go-to-section section)) + (open-login-dialog)))] [:header.viewer-header [:div.nav-zone [:div.main-icon [:a {:on-click go-to-dashboard ;; If the user doesn't have permission we disable the link - :style {:pointer-events (when-not permissions "none")}} i/logo-icon]] + :style {:pointer-events (when-not (:can-edit permissions) "none")}} i/logo-icon]] [:& header-sitemap {:project project :file file :page page :frame frame :index index}]] @@ -198,7 +216,8 @@ :alt (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions))} i/play] - (when (:can-edit permissions) + (when (or (:can-edit permissions) + (= (:who-comment permissions) "all")) [:button.mode-zone-button.tooltip.tooltip-bottom {:on-click #(navigate :comments) :class (dom/classnames :active (= section :comments)) @@ -207,7 +226,7 @@ (when (or (= (:type permissions) :membership) (and (= (:type permissions) :share-link) - (contains? (:flags permissions) :section-handoff))) + (= (:who-inspect permissions) "all"))) [:button.mode-zone-button.tooltip.tooltip-bottom {:on-click go-to-handoff :class (dom/classnames :active (= section :handoff)) diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index c7a8a7ce30..aa479c9cb4 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -7,15 +7,17 @@ (ns app.main.ui.viewer.interactions (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] - [app.common.spec.page :as csp] + [app.common.types.page :as ctp] [app.main.data.comments :as dcm] [app.main.data.viewer :as dv] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.hooks :as h] [app.main.ui.icons :as i] [app.main.ui.viewer.shapes :as shapes] [app.util.dom :as dom] @@ -25,89 +27,114 @@ [rumext.alpha :as mf])) (defn prepare-objects - [page frame] - (fn [] - (let [objects (:objects page) - frame-id (:id frame) - modifier (-> (gpt/point (:x frame) (:y frame)) - (gpt/negate) - (gmt/translate-matrix)) + [page frame size] + (let [objects (:objects page) + frame-id (:id frame) + modifier (-> (gpt/point (:x size) (:y size)) + (gpt/negate) + (gmt/translate-matrix)) - update-fn #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)] + update-fn #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)] - (->> (cph/get-children-ids objects frame-id) - (into [frame-id]) - (reduce update-fn objects))))) + (->> (cph/get-children-ids objects frame-id) + (into [frame-id]) + (reduce update-fn objects)))) -(mf/defc viewport - {::mf/wrap [mf/memo]} - [{:keys [page interactions-mode frame base-frame frame-offset size]}] - (let [objects (mf/use-memo - (mf/deps page frame) - (prepare-objects page frame)) +(mf/defc viewport-svg + {::mf/wrap [mf/memo] + ::mf/wrap-props false} + [props] + (let [page (unchecked-get props "page") + frame (unchecked-get props "frame") + base (unchecked-get props "base") + offset (unchecked-get props "offset") + size (unchecked-get props "size") - wrapper (mf/use-memo - (mf/deps objects) - #(shapes/frame-container-factory objects)) + vbox (:vbox size) + + objects (mf/with-memo [page frame size] + (prepare-objects page frame size)) + + wrapper (mf/with-memo [objects] + (shapes/frame-container-factory objects)) ;; Retrieve frames again with correct modifier - frame (get objects (:id frame)) - base-frame (get objects (:id base-frame)) + frame (get objects (:id frame)) + base (get objects (:id base))] - on-click - (fn [_] - (when (= interactions-mode :show-on-click) - (st/emit! dv/flash-interactions))) - - on-mouse-wheel - (fn [event] - (when (kbd/mod? event) - (dom/prevent-default event) - (let [event (.getBrowserEvent ^js event) - delta (+ (.-deltaY ^js event) (.-deltaX ^js event))] - (if (pos? delta) - (st/emit! dv/decrease-zoom) - (st/emit! dv/increase-zoom))))) - - on-key-down - (fn [event] - (when (kbd/esc? event) - (st/emit! (dcm/close-thread))))] - - (mf/use-effect - (mf/deps interactions-mode) ;; on-click event depends on interactions-mode - (fn [] - ;; bind with passive=false to allow the event to be cancelled - ;; https://stackoverflow.com/a/57582286/3219895 - (let [key1 (events/listen goog/global "wheel" on-mouse-wheel #js {"passive" false}) - key2 (events/listen js/window "keydown" on-key-down) - key3 (events/listen js/window "click" on-click)] - (fn [] - (events/unlistenByKey key1) - (events/unlistenByKey key2) - (events/unlistenByKey key3))))) - - [:& (mf/provider shapes/base-frame-ctx) {:value base-frame} - [:& (mf/provider shapes/frame-offset-ctx) {:value frame-offset} - [:svg {:view-box (:vbox size) + [:& (mf/provider shapes/base-frame-ctx) {:value base} + [:& (mf/provider shapes/frame-offset-ctx) {:value offset} + [:svg {:view-box vbox :width (:width size) :height (:height size) :version "1.1" :xmlnsXlink "http://www.w3.org/1999/xlink" :xmlns "http://www.w3.org/2000/svg" :fill "none"} - [:& wrapper {:shape frame - :view-box (:vbox size)}]]]])) + [:& wrapper {:shape frame :view-box vbox}]]]])) +(mf/defc viewport + {::mf/wrap [mf/memo] + ::mf/wrap-props false} + [props] + (let [;; NOTE: with `use-equal-memo` hook we ensure that all values + ;; conserves the reference identity for avoid unnecesary dummy + ;; rerenders. + mode (h/use-equal-memo (unchecked-get props "interactions-mode")) + offset (h/use-equal-memo (unchecked-get props "frame-offset")) + size (h/use-equal-memo (unchecked-get props "size")) + + page (unchecked-get props "page") + frame (unchecked-get props "frame") + base (unchecked-get props "base-frame")] + + (mf/with-effect [mode] + (let [on-click + (fn [_] + (when (= mode :show-on-click) + (st/emit! (dv/flash-interactions)))) + + on-mouse-wheel + (fn [event] + (when (kbd/mod? event) + (dom/prevent-default event) + (let [event (dom/event->browser-event event) + delta (+ (.-deltaY ^js event) + (.-deltaX ^js event))] + (if (pos? delta) + (st/emit! dv/decrease-zoom) + (st/emit! dv/increase-zoom))))) + + on-key-down + (fn [event] + (when (kbd/esc? event) + (st/emit! (dcm/close-thread)))) + + + ;; bind with passive=false to allow the event to be cancelled + ;; https://stackoverflow.com/a/57582286/3219895 + key1 (events/listen goog/global "wheel" on-mouse-wheel #js {"passive" false}) + key2 (events/listen goog/global "keydown" on-key-down) + key3 (events/listen goog/global "click" on-click)] + (fn [] + (events/unlistenByKey key1) + (events/unlistenByKey key2) + (events/unlistenByKey key3)))) + + [:& viewport-svg {:page page + :frame frame + :base base + :offset offset + :size size}])) (mf/defc flows-menu {::mf/wrap [mf/memo]} [{:keys [page index]}] - (let [flows (get-in page [:options :flows]) + (let [flows (dm/get-in page [:options :flows]) frames (:frames page) frame (get frames index) current-flow (mf/use-state - (csp/get-frame-flow flows (:id frame))) + (ctp/get-frame-flow flows (:id frame))) show-dropdown? (mf/use-state false) toggle-dropdown (mf/use-fn #(swap! show-dropdown? not)) @@ -127,13 +154,13 @@ [:& dropdown {:show @show-dropdown? :on-close hide-dropdown} [:ul.dropdown.with-check - (for [flow flows] - [:li {:class (dom/classnames :selected (= (:id flow) (:id @current-flow))) + (for [[index flow] (d/enumerate flows)] + [:li {:key (dm/str "flow-" (:id flow) "-" index) + :class (dom/classnames :selected (= (:id flow) (:id @current-flow))) :on-click #(select-flow flow)} [:span.icon i/tick] [:span.label (:name flow)]])]]]))) - (mf/defc interactions-menu [] (let [local (mf/deref refs/viewer-local) diff --git a/frontend/src/app/main/ui/viewer/login.cljs b/frontend/src/app/main/ui/viewer/login.cljs new file mode 100644 index 0000000000..0745a3c5f1 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/login.cljs @@ -0,0 +1,106 @@ +;; 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.viewer.login + (:require + [app.common.logging :as log] + [app.main.data.modal :as modal] + [app.main.store :as st] + [app.main.ui.auth :refer [terms-login]] + [app.main.ui.auth.login :refer [login-methods]] + [app.main.ui.auth.recovery-request :refer [recovery-request-page]] + [app.main.ui.auth.register :refer [register-methods register-validate-form register-success-page]] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [app.util.storage :refer [storage]] + [rumext.alpha :as mf])) + +(log/set-level! :warn) + +(mf/defc login-register-modal + {::mf/register modal/components + ::mf/register-as :login-register} + [_] + (let [uri (. (. js/document -location) -href) + user-email (mf/use-state "") + register-token (mf/use-state "") + current-section (mf/use-state :login) + set-current-section (mf/use-fn #(reset! current-section %)) + main-section (or + (= @current-section :login) + (= @current-section :register) + (= @current-section :register-validate)) + close + (fn [event] + (dom/prevent-default event) + (st/emit! (modal/hide))) + success-email-sent + (fn [email] + (reset! user-email email) + (set-current-section :email-sent)) + success-login + (fn [] + (.reload js/window.location true)) + success-register + (fn [data] + (reset! register-token (:token data)) + (set-current-section :register-validate))] + (mf/with-effect [] + (swap! storage assoc :redirect-url uri)) + + [:div.modal-overlay + [:div.modal-container.login-register + [:div.title + [:div.modal-close-button {:on-click close :title (tr "labels.close")} + i/close] + (when main-section + [:h2 (tr "labels.continue-with-penpot")])] + + [:div.modal-bottom.auth-content + + (case @current-section + :login + [:div.generic-form.login-form + [:div.form-container + [:& login-methods {:on-success-callback success-login}] + [:div.links + [:div.link-entry + [:a {:on-click #(set-current-section :recovery-request)} + (tr "auth.forgot-password")]] + [:div.link-entry + [:span (tr "auth.register") " "] + [:a {:on-click #(set-current-section :register)} + (tr "auth.register-submit")]]]]] + + :register + [:div.form-container + [:& register-methods {:on-success-callback success-register}] + [:div.links + [:div.link-entry + [:span (tr "auth.already-have-account") " "] + [:a {:on-click #(set-current-section :login)} + (tr "auth.login-here")]]]] + + :register-validate + [:div.form-container + [:& register-validate-form {:params {:token @register-token} + :on-success-callback success-email-sent}] + [:div.links + [:div.link-entry + [:a {:on-click #(set-current-section :register)} + (tr "labels.go-back")]]]] + + :recovery-request + [:& recovery-request-page {:go-back-callback #(set-current-section :login) + :on-success-callback success-email-sent}] + :email-sent + [:div.form-container + [:& register-success-page {:params {:email @user-email}}]])] + + (when main-section + [:div.modal-footer.links + [:& terms-login]])]])) diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index 4e28186b72..ae280e2152 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -8,9 +8,9 @@ "The main container for a frame in viewer mode" (:require [app.common.data :as d] - [app.common.geom.shapes :as geom] + [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] - [app.common.spec.interactions :as cti] + [app.common.types.shape.interactions :as ctsi] [app.main.data.viewer :as dv] [app.main.refs :as refs] [app.main.store :as st] @@ -56,10 +56,10 @@ background-overlay (:background-overlay interaction) dest-frame (get objects dest-frame-id) - position (cti/calc-overlay-position interaction - base-frame - dest-frame - frame-offset)] + position (ctsi/calc-overlay-position interaction + base-frame + dest-frame + frame-offset)] (when dest-frame-id (st/emit! (dv/open-overlay dest-frame-id position @@ -123,10 +123,10 @@ background-overlay (:background-overlay interaction) dest-frame (get objects dest-frame-id) - position (cti/calc-overlay-position interaction - base-frame - dest-frame - frame-offset)] + position (ctsi/calc-overlay-position interaction + base-frame + dest-frame + frame-offset)] (when dest-frame-id (st/emit! (dv/open-overlay dest-frame-id position @@ -204,7 +204,10 @@ :stroke-width (if interactions-show? 1 0) :fill-opacity (if interactions-show? 0.2 0) :style {:pointer-events (when frame? "none")} - :transform (geom/transform-matrix shape)}]))) + :transform (gsh/transform-str shape)}]))) + + +;; TODO: use-memo use-fn (defn generic-wrapper-factory "Wrap some svg shape and add interaction controls" @@ -212,13 +215,13 @@ (mf/fnc generic-wrapper {::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - childs (unchecked-get props "childs") - frame (unchecked-get props "frame") - objects (unchecked-get props "objects") - fixed? (unchecked-get props "fixed?") - delta (unchecked-get props "delta") - base-frame (mf/use-ctx base-frame-ctx) + (let [shape (unchecked-get props "shape") + childs (unchecked-get props "childs") + frame (unchecked-get props "frame") + objects (unchecked-get props "objects") + fixed? (unchecked-get props "fixed?") + delta (unchecked-get props "delta") + base-frame (mf/use-ctx base-frame-ctx) frame-offset (mf/use-ctx frame-offset-ctx) interactions-show? (mf/deref viewer-interactions-show?) @@ -226,20 +229,37 @@ interactions (:interactions shape) svg-element? (and (= :svg-raw (:type shape)) - (not= :svg (get-in shape [:content :tag])))] + (not= :svg (get-in shape [:content :tag]))) - (mf/use-effect - (fn [] - (let [sems (on-load shape base-frame frame-offset objects)] - #(run! tm/dispose! sems)))) + + on-mouse-down + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-down % shape base-frame frame-offset objects)) + + on-mouse-up + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-up % shape base-frame frame-offset objects)) + + on-mouse-enter + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-enter % shape base-frame frame-offset objects)) + + on-mouse-leave + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-leave % shape base-frame frame-offset objects))] + + + (mf/with-effect [] + (let [sems (on-load shape base-frame frame-offset objects)] + (partial run! tm/dispose! sems))) (if-not svg-element? [:> shape-container {:shape shape - :cursor (when (cti/actionable? interactions) "pointer") - :on-mouse-down #(on-mouse-down % shape base-frame frame-offset objects) - :on-mouse-up #(on-mouse-up % shape base-frame frame-offset objects) - :on-mouse-enter #(on-mouse-enter % shape base-frame frame-offset objects) - :on-mouse-leave #(on-mouse-leave % shape base-frame frame-offset objects)} + :cursor (when (ctsi/actionable? interactions) "pointer") + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up + :on-mouse-enter on-mouse-enter + :on-mouse-leave on-mouse-leave} [:& component {:shape shape :frame frame @@ -306,11 +326,12 @@ [props] (let [shape (obj/get props "shape") childs (mapv #(get objects %) (:shapes shape)) - shape (geom/transform-shape shape) + shape (gsh/transform-shape shape) props (obj/merge! #js {} props #js {:shape shape :childs childs :objects objects})] + [:> frame-wrapper props])))) (defn group-container-factory @@ -362,36 +383,52 @@ image-wrapper (image-wrapper) circle-wrapper (circle-wrapper)] (mf/fnc shape-container - {::mf/wrap-props false} + {::mf/wrap-props false + ::mf/wrap [mf/memo]} [props] - (let [scroll (mf/use-ctx ctx/scroll-ctx) - local (mf/deref refs/viewer-local) - zoom (:zoom local) - shape (unchecked-get props "shape") - parents (map (d/getf objects) (cph/get-parent-ids objects (:id shape))) - fixed? (or (:fixed-scroll shape) (some :fixed-scroll parents)) + (let [shape (unchecked-get props "shape") frame (unchecked-get props "frame") - delta {:x (/ (:scroll-left scroll) zoom) :y (/ (:scroll-top scroll) zoom)} + + ;; TODO: this watch of scroll position is killing + ;; performance of the viewer. + scroll (mf/use-ctx ctx/current-scroll) + zoom (mf/use-ctx ctx/current-zoom) + + fixed? (mf/with-memo [shape objects] + (->> (cph/get-parent-ids objects (:id shape)) + (map (d/getf objects)) + (concat [shape]) + (some :fixed-scroll))) + + delta {:x (/ (:scroll-left scroll) zoom) + :y (/ (:scroll-top scroll) zoom)} + group-container - (mf/use-memo (mf/deps objects) - #(group-container-factory objects)) + (mf/with-memo [objects] + (group-container-factory objects)) + + frame-container + (mf/with-memo [objects] + (frame-container-factory objects)) bool-container - (mf/use-memo (mf/deps objects) - #(bool-container-factory objects)) + (mf/with-memo [objects] + (bool-container-factory objects)) svg-raw-container - (mf/use-memo (mf/deps objects) - #(svg-raw-container-factory objects))] + (mf/with-memo [objects] + (svg-raw-container-factory objects)) + + ] (when (and shape (not (:hidden shape))) - (let [shape (-> (geom/transform-shape shape) - (geom/translate-to-frame frame) - (cond-> fixed? (geom/move delta))) + (let [shape (-> (gsh/transform-shape shape) + (gsh/translate-to-frame frame) + (cond-> fixed? (gsh/move delta))) opts #js {:shape shape :objects objects}] (case (:type shape) - :frame [:g.empty] + :frame [:> frame-container opts] :text [:> text-wrapper opts] :rect [:> rect-wrapper opts] :path [:> path-wrapper opts] @@ -400,4 +437,3 @@ :group [:> group-container {:shape shape :frame frame :objects objects :fixed? fixed? :delta delta}] :bool [:> bool-container {:shape shape :frame frame :objects objects}] :svg-raw [:> svg-raw-container {:shape shape :frame frame :objects objects}]))))))) - diff --git a/frontend/src/app/main/ui/viewer/thumbnails.cljs b/frontend/src/app/main/ui/viewer/thumbnails.cljs index 7d205c6180..afd76945f7 100644 --- a/frontend/src/app/main/ui/viewer/thumbnails.cljs +++ b/frontend/src/app/main/ui/viewer/thumbnails.cljs @@ -8,6 +8,8 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.shapes :as gsh] + [app.common.pages.helpers :as cph] [app.main.data.viewer :as dv] [app.main.render :as render] [app.main.store :as st] @@ -77,15 +79,18 @@ #(mf/deferred % ts/idle-then-raf)]} [{:keys [selected? frame on-click index objects page-id thumbnail-data]}] - [:div.thumbnail-item {:on-click #(on-click % index)} - [:div.thumbnail-preview - {:class (dom/classnames :selected selected?)} - [:& render/frame-svg {:frame (-> frame - (assoc :thumbnail (get thumbnail-data (dm/str page-id (:id frame))))) - :objects objects - :show-thumbnails? true}]] - [:div.thumbnail-info - [:span.name {:title (:name frame)} (:name frame)]]]) + (let [children-ids (cph/get-children-ids objects (:id frame)) + children-bounds (gsh/selection-rect (concat [frame] (->> children-ids (keep (d/getf objects)))))] + [:div.thumbnail-item {:on-click #(on-click % index)} + [:div.thumbnail-preview + {:class (dom/classnames :selected selected?)} + [:& render/frame-svg {:frame (-> frame + (assoc :thumbnail (get thumbnail-data (dm/str page-id (:id frame)))) + (assoc :children-bounds children-bounds)) + :objects objects + :show-thumbnails? true}]] + [:div.thumbnail-info + [:span.name {:title (:name frame)} (:name frame)]]])) (mf/defc thumbnails-panel [{:keys [frames page index show? thumbnail-data] :as props}] @@ -118,6 +123,7 @@ :total (count frames)} (for [[i frame] (d/enumerate frames)] [:& thumbnail-item {:index i + :key (dm/str (:id frame) "-" i) :frame frame :page-id (:id page) :objects objects diff --git a/frontend/src/app/main/ui/workspace/colorpalette.cljs b/frontend/src/app/main/ui/workspace/colorpalette.cljs index c468632309..39b0e17bbb 100644 --- a/frontend/src/app/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/app/main/ui/workspace/colorpalette.cljs @@ -6,11 +6,13 @@ (ns app.main.ui.workspace.colorpalette (:require + [app.common.data.macros :as dm] [app.main.data.workspace.colors :as mdc] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.color-bullet :as cb] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.hooks :as h] [app.main.ui.hooks.resize :refer [use-resize-hook]] [app.main.ui.icons :as i] [app.util.dom :as dom] @@ -19,36 +21,21 @@ [app.util.object :as obj] [cuerdas.core :as str] [goog.events :as events] - [okulary.core :as l] [rumext.alpha :as mf])) -;; --- Refs - -(def palettes-ref - (-> (l/in [:library :palettes]) - (l/derived st/state))) - -(def selected-palette-ref - (-> (l/in [:workspace-global :selected-palette]) - (l/derived st/state))) - -(def selected-palette-size-ref - (-> (l/in [:workspace-global :selected-palette-size]) - (l/derived st/state))) - ;; --- Components -(mf/defc palette-item - [{:keys [color]}] - (let [select-color - (fn [event] - (st/emit! (mdc/apply-color-from-palette color (kbd/alt? event))))] +(mf/defc palette-item + {::mf/wrap [mf/memo]} + [{:keys [color]}] + (letfn [(select-color [event] + (st/emit! (mdc/apply-color-from-palette color (kbd/alt? event))))] [:div.color-cell {:on-click select-color} [:& cb/color-bullet {:color color}] [:& cb/color-name {:color color}]])) (mf/defc palette - [{:keys [current-colors recent-colors file-colors shared-libs selected]}] + [{:keys [current-colors recent-colors file-colors shared-libs selected on-select]}] (let [state (mf/use-state {:show-menu false}) width (:width @state 0) @@ -97,54 +84,66 @@ (fn [_] (let [dom (mf/ref-val container) width (obj/get dom "clientWidth")] - (swap! state assoc :width width))))] + (swap! state assoc :width width)))) + on-select-palette + (mf/use-fn + (mf/deps on-select) + (fn [event] + (let [node (dom/get-current-target event) + value (dom/get-attribute node "data-palette")] + (on-select (if (or (= "file" value) (= "recent" value)) + (keyword value) + (parse-uuid value))))))] (mf/use-layout-effect #(let [dom (mf/ref-val container) width (obj/get dom "clientWidth")] (swap! state assoc :width width))) - (mf/use-effect - #(let [key1 (events/listen js/window "resize" on-resize)] - (fn [] - (events/unlistenByKey key1)))) + (mf/with-effect [] + (let [key1 (events/listen js/window "resize" on-resize)] + #(events/unlistenByKey key1))) [:div.color-palette {:ref parent-ref :class (dom/classnames :no-text (< size 72)) - :style #js {"--height" (str size "px") - "--bullet-size" (str (if (< size 72) (- size 15) (- size 30)) "px")}} + :style #js {"--height" (dm/str size "px") + "--bullet-size" (dm/str (if (< size 72) (- size 15) (- size 30)) "px")}} [:div.resize-area {:on-pointer-down on-pointer-down :on-lost-pointer-capture on-lost-pointer-capture :on-mouse-move on-mouse-move}] [:& dropdown {:show (:show-menu @state) :on-close #(swap! state assoc :show-menu false)} [:ul.workspace-context-menu.palette-menu - (for [[idx cur-library] (map-indexed vector (vals shared-libs))] - (let [colors (-> cur-library (get-in [:data :colors]) vals)] + (for [{:keys [data id] :as library} (vals shared-libs)] + (let [colors (-> data :colors vals)] [:li.palette-library - {:key (str "library-" idx) - :on-click #(st/emit! (mdc/change-palette-selected (:id cur-library)))} - (when (= selected (:id cur-library)) i/tick) - [:div.library-name (str (:name cur-library) " " (str/format "(%s)" (count colors)))] + {:key (dm/str "library-" id) + :on-click on-select-palette + :data-palette (dm/str id)} + (when (= selected id) i/tick) + [:div.library-name (str (:name library) " " (str/ffmt "(%)" (count colors)))] [:div.color-sample - (for [[idx {:keys [color]}] (map-indexed vector (take 7 colors))] - [:& cb/color-bullet {:key (str "color-" idx) + (for [[i {:keys [color]}] (map-indexed vector (take 7 colors))] + [:& cb/color-bullet {:key (dm/str "color-" i) :color color}])]])) [:li.palette-library - {:on-click #(st/emit! (mdc/change-palette-selected :file))} + {:on-click on-select-palette + :data-palette "file"} (when (= selected :file) i/tick) - [:div.library-name (str (tr "workspace.libraries.colors.file-library") - (str/format " (%s)" (count file-colors)))] + [:div.library-name (dm/str + (tr "workspace.libraries.colors.file-library") + (str/ffmt " (%)" (count file-colors)))] [:div.color-sample - (for [[idx color] (map-indexed vector (take 7 (vals file-colors))) ] - [:& cb/color-bullet {:key (str "color-" idx) + (for [[i color] (map-indexed vector (take 7 (vals file-colors))) ] + [:& cb/color-bullet {:key (dm/str "color-" i) :color color}])]] [:li.palette-library - {:on-click #(st/emit! (mdc/change-palette-selected :recent))} + {:on-click on-select-palette + :data-palette "recent"} (when (= selected :recent) i/tick) [:div.library-name (str (tr "workspace.libraries.colors.recent-colors") (str/format " (%s)" (count recent-colors)))] @@ -178,34 +177,32 @@ (let [recent-colors (mf/deref refs/workspace-recent-colors) file-colors (mf/deref refs/workspace-file-colors) shared-libs (mf/deref refs/workspace-libraries) - selected (or (mf/deref selected-palette-ref) :recent) - current-library-colors (mf/use-state [])] + selected (h/use-shared-state mdc/colorpalette-selected-broadcast-key :recent) - (mf/use-effect - (mf/deps selected) - (fn [] - (reset! current-library-colors - (into [] - (cond - (= selected :recent) (reverse recent-colors) - (= selected :file) (->> (vals file-colors) (sort-by :name)) - :else (->> (library->colors shared-libs selected) (sort-by :name))))))) + colors (mf/use-state []) + on-select (mf/use-fn #(reset! selected %))] - (mf/use-effect - (mf/deps recent-colors) - (fn [] - (when (= selected :recent) - (reset! current-library-colors (reverse recent-colors))))) + (mf/with-effect [@selected] + (fn [] + (reset! colors + (into [] + (cond + (= @selected :recent) (reverse recent-colors) + (= @selected :file) (->> (vals file-colors) (sort-by :name)) + :else (->> (library->colors shared-libs @selected) (sort-by :name))))))) - (mf/use-effect - (mf/deps file-colors) - (fn [] - (when (= selected :file) - (reset! current-library-colors (into [] (->> (vals file-colors) - (sort-by :name))))))) + (mf/with-effect [recent-colors @selected] + (when (= @selected :recent) + (reset! colors (reverse recent-colors)))) - [:& palette {:current-colors @current-library-colors + (mf/with-effect [file-colors @selected] + (when (= @selected :file) + (reset! colors (into [] (->> (vals file-colors) + (sort-by :name)))))) + + [:& palette {:current-colors @colors :recent-colors recent-colors :file-colors file-colors :shared-libs shared-libs - :selected selected}])) + :selected @selected + :on-select on-select}])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index 12829dd128..da975ea1d0 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -6,7 +6,6 @@ (ns app.main.ui.workspace.colorpicker (:require - [app.common.colors :as clr] [app.main.data.modal :as modal] [app.main.data.workspace.colors :as dc] [app.main.data.workspace.libraries :as dwl] @@ -41,247 +40,102 @@ (def viewport (l/derived :vport refs/workspace-local)) -(def editing-spot-state-ref - (l/derived :editing-stop refs/workspace-global)) - -(def current-gradient-ref - (l/derived :current-gradient refs/workspace-global)) - ;; --- Color Picker Modal -(defn color->components [value opacity] - (let [value (if (uc/hex? value) value clr/black) - [r g b] (uc/hex->rgb value) - [h s v] (uc/hex->hsv value)] - - {:hex (or value "000000") - :alpha (or opacity 1) - :r r :g g :b b - :h h :s s :v v})) - -(defn data->state [{:keys [color opacity gradient]}] - (let [type (cond - (nil? gradient) :color - (= :linear (:type gradient)) :linear-gradient - (= :radial (:type gradient)) :radial-gradient) - - parse-stop (fn [{:keys [offset color opacity]}] - (vector offset (color->components color opacity))) - - stops (when gradient - (map parse-stop (:stops gradient))) - - current-color (if (nil? gradient) - (color->components color opacity) - (-> stops first second)) - - gradient-data (select-keys gradient [:start-x :start-y - :end-x :end-y - :width])] - - (cond-> {:type type - :current-color current-color} - gradient (assoc :gradient-data gradient-data) - stops (assoc :stops (into {} stops)) - stops (assoc :editing-stop (-> stops first first))))) - -(defn state->data [{:keys [type current-color stops gradient-data]}] - (if (= type :color) - {:color (:hex current-color) - :opacity (:alpha current-color)} - - (let [gradient-type (case type - :linear-gradient :linear - :radial-gradient :radial) - parse-stop (fn [[offset {:keys [hex alpha]}]] - (hash-map :offset offset - :color hex - :opacity alpha))] - {:gradient (-> {:type gradient-type - :stops (mapv parse-stop stops)} - (merge gradient-data))}))) - -(defn create-gradient-data [type] - {:start-x 0.5 - :start-y (if (= type :linear-gradient) 0.0 0.5) - :end-x 0.5 - :end-y 1 - :width 1.0}) - (mf/defc colorpicker [{:keys [data disable-gradient disable-opacity on-change on-accept]}] - (let [state (mf/use-state (data->state data)) - active-tab (mf/use-state :ramp #_:harmony #_:hsva) + (let [state (mf/deref refs/colorpicker) + node-ref (mf/use-ref) - ref-picker (mf/use-ref) - - dirty? (mf/use-var false) - last-color (mf/use-var data) - - picking-color? (mf/deref picking-color?) - picked-color (mf/deref picked-color) + ;; TODO: I think we need to put all this picking state under + ;; the same object for avoid creating adhoc refs for each + ;; value + picking-color? (mf/deref picking-color?) + picked-color (mf/deref picked-color) picked-color-select (mf/deref picked-color-select) - editing-spot-state (mf/deref editing-spot-state-ref) - current-gradient (mf/deref current-gradient-ref) + current-color (:current-color state) - current-color (:current-color @state) - - change-tab - (fn [tab] - #(reset! active-tab tab)) + active-tab (mf/use-state :ramp #_:harmony #_:hsva) + set-ramp-tab! (mf/use-fn #(reset! active-tab :ramp)) + set-harmony-tab! (mf/use-fn #(reset! active-tab :harmony)) + set-hsva-tab! (mf/use-fn #(reset! active-tab :hsva)) handle-change-color - (fn [changes] - (let [editing-stop (:editing-stop @state)] - (swap! state #(cond-> % - :always - (update :current-color merge changes) - - (not editing-stop) - (-> (assoc :type :color) - (dissoc :gradient-data :stops :editing-stops)) - - editing-stop - (update-in [:stops editing-stop] merge changes))) - (reset! dirty? true))) + (mf/use-fn #(st/emit! (dc/update-colorpicker-color %))) handle-click-picker - (fn [] - (if picking-color? - (do (modal/disallow-click-outside!) - (st/emit! (dc/stop-picker))) - (do (modal/allow-click-outside!) - (st/emit! (dc/start-picker))))) + (mf/use-fn + (mf/deps picking-color?) + (fn [] + (if picking-color? + (do (modal/disallow-click-outside!) + (st/emit! (dc/stop-picker))) + (do (modal/allow-click-outside!) + (st/emit! (dc/start-picker)))))) handle-change-stop - (fn [offset] - (when-let [offset-color (get-in @state [:stops offset])] - (swap! state assoc - :current-color offset-color - :editing-stop offset) - - (st/emit! (dc/select-gradient-stop offset)))) + (mf/use-fn + (fn [offset] + (st/emit! (dc/select-colorpicker-gradient-stop offset)))) on-select-library-color - (fn [color] - (let [editing-stop (:editing-stop @state) - is-gradient? (some? (:gradient color))] - - (if is-gradient? - (st/emit! (dc/start-gradient (:gradient color))) - (st/emit! (dc/stop-gradient))) - - (if (and (some? editing-stop) (not is-gradient?)) - (handle-change-color (color->components (:color color) (:opacity color))) - (do (reset! dirty? false) - (reset! state (-> (data->state color) - (assoc :editing-stop nil))) - (on-change color))))) - + (mf/use-fn + (fn [color] + (on-change color))) on-add-library-color - (fn [_] - (st/emit! (dwl/add-color (state->data @state)))) + (mf/use-fn + (mf/deps state) + (fn [_] + (st/emit! (dwl/add-color (dc/get-color-from-colorpicker-state state))))) - on-activate-gradient - (fn [type] - (fn [] - (reset! dirty? true) - (if (= type (:type @state)) - (do - (swap! state assoc :type :color) - (swap! state dissoc :editing-stop :stops :gradient-data) - (st/emit! (dc/stop-gradient))) - (let [gradient-data (create-gradient-data type)] - (swap! state assoc :type type :gradient-data gradient-data) - (when (not (:stops @state)) - (swap! state assoc - :editing-stop 0 - :stops {0 (:current-color @state) - 1 (-> (:current-color @state) - (assoc :alpha 0))}))))))] + on-activate-linear-gradient + (mf/use-fn #(st/emit! (dc/activate-colorpicker-gradient :linear-gradient))) + + on-activate-radial-gradient + (mf/use-fn #(st/emit! (dc/activate-colorpicker-gradient :radial-gradient)))] + + ;; Initialize colorpicker state + (mf/with-effect [] + (st/emit! (dc/initialize-colorpicker on-change)) + (partial st/emit! (dc/finalize-colorpicker))) + + ;; Update colorpicker with external color changes + (mf/with-effect [data] + (st/emit! (dc/update-colorpicker data))) ;; Updates the CSS color variable when there is a change in the color - (mf/use-effect - (mf/deps current-color) - (fn [] (let [node (mf/ref-val ref-picker) - {:keys [r g b h v]} current-color - rgb [r g b] - hue-rgb (uc/hsv->rgb [h 1.0 255]) - hsl-from (uc/hsv->hsl [h 0.0 v]) - hsl-to (uc/hsv->hsl [h 1.0 v]) + (mf/with-effect [current-color] + (let [node (mf/ref-val node-ref) + {:keys [r g b h v]} current-color + rgb [r g b] + hue-rgb (uc/hsv->rgb [h 1.0 255]) + hsl-from (uc/hsv->hsl [h 0.0 v]) + hsl-to (uc/hsv->hsl [h 1.0 v]) - format-hsl (fn [[h s l]] - (str/fmt "hsl(%s, %s, %s)" - h - (str (* s 100) "%") - (str (* l 100) "%")))] - (dom/set-css-property! node "--color" (str/join ", " rgb)) - (dom/set-css-property! node "--hue-rgb" (str/join ", " hue-rgb)) - (dom/set-css-property! node "--saturation-grad-from" (format-hsl hsl-from)) - (dom/set-css-property! node "--saturation-grad-to" (format-hsl hsl-to))))) - - ;; When closing the modal we update the recent-color list - (mf/use-effect - #(fn [] - (st/emit! (dc/stop-picker)) - (when @last-color - (st/emit! (dwl/add-recent-color @last-color))))) + format-hsl (fn [[h s l]] + (str/fmt "hsl(%s, %s, %s)" + h + (str (* s 100) "%") + (str (* l 100) "%")))] + (dom/set-css-property! node "--color" (str/join ", " rgb)) + (dom/set-css-property! node "--hue-rgb" (str/join ", " hue-rgb)) + (dom/set-css-property! node "--saturation-grad-from" (format-hsl hsl-from)) + (dom/set-css-property! node "--saturation-grad-to" (format-hsl hsl-to)))) ;; Updates color when used el pixel picker - (mf/use-effect - (mf/deps picking-color? picked-color picked-color-select) - (fn [] - (when (and picking-color? picked-color picked-color-select) - (let [[r g b alpha] picked-color - hex (uc/rgb->hex [r g b]) - [h s v] (uc/hex->hsv hex)] - (handle-change-color {:hex hex - :r r :g g :b b - :h h :s s :v v - :alpha (/ alpha 255)}))))) + (mf/with-effect [picking-color? picked-color picked-color-select] + (when (and picking-color? picked-color picked-color-select) + (let [[r g b alpha] picked-color + hex (uc/rgb->hex [r g b]) + [h s v] (uc/hex->hsv hex)] + (handle-change-color {:hex hex + :r r :g g :b b + :h h :s s :v v + :alpha (/ alpha 255)})))) - ;; Changes when another gradient handler is selected - (mf/use-effect - (mf/deps editing-spot-state) - #(when (not= editing-spot-state (:editing-stop @state)) - (handle-change-stop (or editing-spot-state 0)))) - - ;; Changes on the viewport when moving a gradient handler - (mf/use-effect - (mf/deps current-gradient) - (fn [] - (when current-gradient - (let [gradient-data (select-keys current-gradient [:start-x :start-y - :end-x :end-y - :width])] - (when (not= (:gradient-data @state) gradient-data) - (reset! dirty? true) - (swap! state assoc :gradient-data gradient-data)))))) - - ;; Check if we've opened a color with gradient - (mf/use-effect - (fn [] - (when (:gradient data) - (st/emit! (dc/start-gradient (:gradient data)))) - - ;; on-unmount we stop the handlers - #(st/emit! (dc/stop-gradient)))) - - ;; Send the properties to the store - (mf/use-effect - (mf/deps @state) - (fn [] - (when @dirty? - (let [color (state->data @state)] - (reset! dirty? false) - (reset! last-color color) - (when (:gradient color) - (st/emit! (dc/start-gradient (:gradient color)))) - (on-change color))))) - - [:div.colorpicker {:ref ref-picker} + [:div.colorpicker {:ref node-ref} [:div.colorpicker-content [:div.top-actions [:button.picker-btn @@ -292,70 +146,81 @@ (when (not disable-gradient) [:div.gradients-buttons [:button.gradient.linear-gradient - {:on-click (on-activate-gradient :linear-gradient) - :class (when (= :linear-gradient (:type @state)) "active")}] + {:on-click on-activate-linear-gradient + :class (when (= :linear-gradient (:type state)) "active")}] [:button.gradient.radial-gradient - {:on-click (on-activate-gradient :radial-gradient) - :class (when (= :radial-gradient (:type @state)) "active")}]])] + {:on-click on-activate-radial-gradient + :class (when (= :radial-gradient (:type state)) "active")}]])] - [:& gradients {:type (:type @state) - :stops (:stops @state) - :editing-stop (:editing-stop @state) - :on-select-stop handle-change-stop}] + + (when (or (= (:type state) :linear-gradient) + (= (:type state) :radial-gradient)) + [:& gradients + {:stops (:stops state) + :editing-stop (:editing-stop state) + :on-select-stop handle-change-stop}]) [:div.colorpicker-tabs [:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand {:class (when (= @active-tab :ramp) "active") :alt (tr "workspace.libraries.colors.rgba") - :on-click (change-tab :ramp)} i/picker-ramp] + :on-click set-ramp-tab!} i/picker-ramp] [:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand {:class (when (= @active-tab :harmony) "active") :alt (tr "workspace.libraries.colors.rgb-complementary") - :on-click (change-tab :harmony)} i/picker-harmony] + :on-click set-harmony-tab!} i/picker-harmony] [:div.colorpicker-tab.tooltip.tooltip-bottom.tooltip-expand {:class (when (= @active-tab :hsva) "active") :alt (tr "workspace.libraries.colors.hsv") - :on-click (change-tab :hsva)} i/picker-hsv]] + :on-click set-hsva-tab!} i/picker-hsv]] (if picking-color? [:div.picker-detail-wrapper [:div.center-circle] [:canvas#picker-detail {:width 200 :height 160}]] (case @active-tab - :ramp [:& ramp-selector {:color current-color - :disable-opacity disable-opacity - :on-change handle-change-color - :on-start-drag #(st/emit! (dwu/start-undo-transaction)) - :on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}] - :harmony [:& harmony-selector {:color current-color - :disable-opacity disable-opacity - :on-change handle-change-color - :on-start-drag #(st/emit! (dwu/start-undo-transaction)) - :on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}] - :hsva [:& hsva-selector {:color current-color - :disable-opacity disable-opacity - :on-change handle-change-color - :on-start-drag #(st/emit! (dwu/start-undo-transaction)) - :on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}] + :ramp + [:& ramp-selector + {:color current-color + :disable-opacity disable-opacity + :on-change handle-change-color + :on-start-drag #(st/emit! (dwu/start-undo-transaction)) + :on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}] + :harmony + [:& harmony-selector + {:color current-color + :disable-opacity disable-opacity + :on-change handle-change-color + :on-start-drag #(st/emit! (dwu/start-undo-transaction)) + :on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}] + :hsva + [:& hsva-selector + {:color current-color + :disable-opacity disable-opacity + :on-change handle-change-color + :on-start-drag #(st/emit! (dwu/start-undo-transaction)) + :on-finish-drag #(st/emit! (dwu/commit-undo-transaction))}] nil)) - [:& color-inputs {:type (if (= @active-tab :hsva) :hsv :rgb) - :disable-opacity disable-opacity - :color current-color - :on-change handle-change-color}] + [:& color-inputs + {:type (if (= @active-tab :hsva) :hsv :rgb) + :disable-opacity disable-opacity + :color current-color + :on-change handle-change-color}] - [:& libraries {:current-color current-color - :disable-gradient disable-gradient - :disable-opacity disable-opacity - :on-select-color on-select-library-color - :on-add-library-color on-add-library-color}] + [:& libraries + {:current-color current-color + :disable-gradient disable-gradient + :disable-opacity disable-opacity + :on-select-color on-select-library-color + :on-add-library-color on-add-library-color}] (when on-accept [:div.actions [:button.btn-primary.btn-large {:on-click (fn [] - (on-accept (state->data @state)) + (on-accept (dc/get-color-from-colorpicker-state state)) (modal/hide!))} (tr "workspace.libraries.colors.save-color")]])]])) 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 b1645d3f64..d2d58ae509 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs @@ -11,13 +11,17 @@ [app.util.dom :as dom] [rumext.alpha :as mf])) +(defn parse-hex + [val] + (if (= (first val) \#) + val + (str \# val))) + (mf/defc color-inputs [{:keys [type color disable-opacity on-change]}] (let [{red :r green :g blue :b hue :h saturation :s value :v hex :hex alpha :alpha} color - parse-hex (fn [val] (if (= (first val) \#) val (str \# val))) - refs {:hex (mf/use-ref nil) :r (mf/use-ref nil) :g (mf/use-ref nil) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs index 56c3ef0509..78072f564c 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs @@ -6,32 +6,31 @@ (ns app.main.ui.workspace.colorpicker.gradients (:require + [app.common.data.macros :as dm] [cuerdas.core :as str] [rumext.alpha :as mf])) -(defn gradient->string [stops] - (let [format-stop - (fn [[offset {:keys [r g b alpha]}]] - (str/fmt "rgba(%s, %s, %s, %s) %s" - r g b alpha (str (* offset 100) "%"))) +(defn- format-rgba + [{:keys [r g b alpha offset]}] + (str/ffmt "rgba(%1, %2, %3, %4) %5%%" r g b alpha (* offset 100))) - gradient-css (str/join "," (map format-stop stops))] - (str/fmt "linear-gradient(90deg, %s)" gradient-css))) +(defn- gradient->string [stops] + (let [gradient-css (str/join ", " (map format-rgba stops))] + (str/ffmt "linear-gradient(90deg, %1)" gradient-css))) -(mf/defc gradients [{:keys [type stops editing-stop on-select-stop]}] - (when (#{:linear-gradient :radial-gradient} type) - [:div.gradient-stops - [:div.gradient-background-wrapper - [:div.gradient-background {:style {:background (gradient->string stops)}}]] +(mf/defc gradients + [{:keys [stops editing-stop on-select-stop]}] + [:div.gradient-stops + [:div.gradient-background-wrapper + [:div.gradient-background {:style {:background (gradient->string stops)}}]] - [:div.gradient-stop-wrapper - (for [[offset value] stops] - [:div.gradient-stop - {:class (when (= editing-stop offset) "active") - :on-click (partial on-select-stop offset) - :style {:left (str (* offset 100) "%")}} + [:div.gradient-stop-wrapper + (for [{:keys [offset hex r g b alpha] :as value} stops] + [:div.gradient-stop + {:class (when (= editing-stop offset) "active") + :on-click (partial on-select-stop offset) + :style {:left (dm/str (* offset 100) "%")} + :key (dm/str offset)} - (let [{:keys [hex r g b alpha]} value] - [:* - [:div.gradient-stop-color {:style {:background-color hex}}] - [:div.gradient-stop-alpha {:style {:background-color (str/format "rgba(%s, %s, %s, %s)" r g b alpha)}}]])])]])) + [:div.gradient-stop-color {:style {:background-color hex}}] + [:div.gradient-stop-alpha {:style {:background-color (str/ffmt "rgba(%1, %2, %3, %4)" r g b alpha)}}]])]]) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs index 79b4becb6c..8e3b6e3925 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs @@ -6,90 +6,85 @@ (ns app.main.ui.workspace.colorpicker.libraries (:require - [app.common.uuid :refer [uuid]] + [app.common.data.macros :as dm] [app.main.data.workspace.colors :as dc] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.color-bullet :refer [color-bullet]] + [app.main.ui.hooks :as h] [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] - [okulary.core :as l] [rumext.alpha :as mf])) -(def selected-palette-ref - (-> (l/in [:workspace-global :selected-palette-colorpicker]) - (l/derived st/state))) - (mf/defc libraries [{:keys [on-select-color on-add-library-color disable-gradient disable-opacity]}] - (let [selected-library (or (mf/deref selected-palette-ref) :recent) - current-library-colors (mf/use-state []) + (let [selected (h/use-shared-state dc/colorpicker-selected-broadcast-key :recent) + current-colors (mf/use-state []) shared-libs (mf/deref refs/workspace-libraries) file-colors (mf/deref refs/workspace-file-colors) recent-colors (mf/deref refs/workspace-recent-colors) - parse-selected - (fn [selected-str] - (if (#{"recent" "file"} selected-str) - (keyword selected-str) - (uuid selected-str))) + on-library-change + (mf/use-fn + (fn [event] + (let [val (dom/get-target-val event)] + (reset! selected + (if (or (= val "recent") + (= val "file")) + (keyword val) + (parse-uuid val)))))) - check-valid-color? (fn [color] - (and (or (not disable-gradient) (not (:gradient color))) - (or (not disable-opacity) (= 1 (:opacity color)))))] + check-valid-color? + (fn [color] + (and (or (not disable-gradient) (not (:gradient color))) + (or (not disable-opacity) (= 1 (:opacity color)))))] ;; Load library colors when the select is changed - (mf/use-effect - (mf/deps selected-library) - (fn [] - (let [mapped-colors - (cond - (= selected-library :recent) - ;; The `map?` check is to keep backwards compatibility. We transform from string to map - (map #(if (map? %) % (hash-map :color %)) (reverse (or recent-colors []))) + (mf/with-effect [@selected recent-colors file-colors] + (let [colors (cond + (= @selected :recent) + ;; The `map?` check is to keep backwards compatibility. We transform from string to map + (map #(if (map? %) % {:color %}) (reverse (or recent-colors []))) - (= selected-library :file) - (vals file-colors) + (= @selected :file) + (vals file-colors) - :else ;; Library UUID - (->> (get-in shared-libs [selected-library :data :colors]) - (vals) - (map #(merge % {:file-id selected-library}))))] + :else ;; Library UUID + (as-> @selected file-id + (->> (get-in shared-libs [file-id :data :colors]) + (vals) + (map #(assoc % :file-id file-id)))))] - (reset! current-library-colors (into [] (filter check-valid-color?) mapped-colors))))) + (reset! current-colors (into [] (filter check-valid-color?) colors)))) ;; If the file colors change and the file option is selected updates the state - (mf/use-effect - (mf/deps file-colors) - (fn [] (when (= selected-library :file) - (let [colors (vals file-colors)] - (reset! current-library-colors (into [] (filter check-valid-color?) colors)))))) + (mf/with-effect [file-colors] + (when (= @selected :file) + (let [colors (vals file-colors)] + (reset! current-colors (into [] (filter check-valid-color?) colors))))) [:div.libraries - [:select {:on-change (fn [e] - (when-let [val (parse-selected (dom/get-target-val e))] - (st/emit! (dc/change-palette-selected-colorpicker val)))) - :value (name selected-library)} + [:select {:on-change on-library-change :value (name @selected)} [:option {:value "recent"} (tr "workspace.libraries.colors.recent-colors")] [:option {:value "file"} (tr "workspace.libraries.colors.file-library")] (for [[_ {:keys [name id]}] shared-libs] - [:option {:key id - :value id} name])] + [:option {:key id :value id} name])] [:div.selected-colors - (when (= selected-library :file) + (when (= @selected :file) [:div.color-bullet.button.plus-button {:style {:background-color "var(--color-white)"} :on-click on-add-library-color} i/plus]) [:div.color-bullet.button {:style {:background-color "var(--color-white)"} - :on-click #(st/emit! (dc/show-palette selected-library))} + :on-click #(st/emit! (dc/show-palette @selected))} i/palette] - (for [[idx color] (map-indexed vector @current-library-colors)] - [:& color-bullet {:key (str "color-" idx) - :color color - :on-click #(on-select-color color)}])]])) + (for [[idx color] (map-indexed vector @current-colors)] + [:& color-bullet + {:key (dm/str "color-" idx) + :color color + :on-click on-select-color}])]])) diff --git a/frontend/src/app/main/ui/workspace/comments.cljs b/frontend/src/app/main/ui/workspace/comments.cljs index 322aa473c5..e66781a01a 100644 --- a/frontend/src/app/main/ui/workspace/comments.cljs +++ b/frontend/src/app/main/ui/workspace/comments.cljs @@ -57,20 +57,23 @@ [:span.label (tr "labels.hide-resolved-comments")]]])) (mf/defc comments-sidebar - [] + [{:keys [users threads page-id]}] (let [threads-map (mf/deref refs/threads-ref) profile (mf/deref refs/profile) - users (mf/deref refs/users) + users-refs (mf/deref refs/current-file-comments-users) + users (or users users-refs) local (mf/deref refs/comments-local) options? (mf/use-state false) + threads (if (nil? threads) + (->> (vals threads-map) + (sort-by :modified-at) + (reverse) + (dcm/apply-filters local profile)) + threads) + tgroups (->> threads + (dcm/group-threads-by-page)) - tgroups (->> (vals threads-map) - (sort-by :modified-at) - (reverse) - (dcm/apply-filters local profile) - (dcm/group-threads-by-page)) - - page-id (mf/use-ctx ctx/current-page-id) + page-id (or page-id (mf/use-ctx ctx/current-page-id)) on-thread-click (mf/use-callback @@ -87,7 +90,7 @@ [:div.comments-section.comment-threads-section [:div.workspace-comment-threads-sidebar-header - [:div.label "Comments"] + [:div.label (tr "labels.comments")] [:div.options {:on-click #(reset! options? true)} [:div.label (case (:mode local) (nil :all) (tr "labels.all") diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 669e5cf91d..54a440d12d 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -8,8 +8,9 @@ "A workspace specific context menu (mouse right click)." (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] - [app.common.spec.page :as csp] + [app.common.types.page :as ctp] [app.main.data.events :as ev] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] @@ -123,17 +124,25 @@ [:& menu-separator]])) (mf/defc context-menu-layer-position - [{:keys [hover-objs shapes]}] - (let [do-bring-forward #(st/emit! (dw/vertical-order-selected :up)) - do-bring-to-front #(st/emit! (dw/vertical-order-selected :top)) - do-send-backward #(st/emit! (dw/vertical-order-selected :down)) - do-send-to-back #(st/emit! (dw/vertical-order-selected :bottom)) - select-shapes (fn [id] #(st/emit! (dws/select-shape id)))] + [{:keys [shapes]}] + (let [do-bring-forward (mf/use-fn #(st/emit! (dw/vertical-order-selected :up))) + do-bring-to-front (mf/use-fn #(st/emit! (dw/vertical-order-selected :top))) + do-send-backward (mf/use-fn #(st/emit! (dw/vertical-order-selected :down))) + do-send-to-back (mf/use-fn #(st/emit! (dw/vertical-order-selected :bottom))) + select-shapes (fn [id] #(st/emit! (dws/select-shape id))) + + ;; NOTE: we use deref instead of mf/deref on objects because + ;; we really don't want rerender on object changes + hover-ids (deref refs/current-hover-ids) + objects (deref refs/workspace-page-objects) + hover-objs (into [] (keep (d/getf objects)) hover-ids)] + [:* (when (> (count hover-objs) 1) [:& menu-entry {:title (tr "workspace.shape.menu.select-layer")} (for [object hover-objs] [:& menu-entry {:title (:name object) + :key (dm/str (:id object)) :selected? (some #(= object %) shapes) :on-click (select-shapes (:id object)) :icon (si/element-icon {:shape object})}])]) @@ -191,7 +200,6 @@ has-group? (->> shapes (d/seek #(= :group (:type %)))) has-bool? (->> shapes (d/seek #(= :bool (:type %)))) has-mask? (->> shapes (d/seek :masked-group?)) - has-frame? (->> shapes (d/seek #(= :frame (:type %)))) is-group? (and single? has-group?) is-bool? (and single? has-bool?) @@ -207,10 +215,9 @@ :shortcut (sc/get-tooltip :ungroup) :on-click do-remove-group}]) - (when (not has-frame?) - [:& menu-entry {:title (tr "workspace.shape.menu.group") - :shortcut (sc/get-tooltip :group) - :on-click do-create-group}]) + [:& menu-entry {:title (tr "workspace.shape.menu.group") + :shortcut (sc/get-tooltip :group) + :on-click do-create-group}] (when (or multiple? (and is-group? (not has-mask?)) is-bool?) [:& menu-entry {:title (tr "workspace.shape.menu.mask") @@ -222,12 +229,10 @@ :shortcut (sc/get-tooltip :unmask) :on-click do-unmask-group}]) - (when (not has-frame?) - [:* - [:& menu-entry {:title (tr "workspace.shape.menu.create-artboard-from-selection") - :shortcut (sc/get-tooltip :artboard-selection) - :on-click do-create-artboard-from-selection}] - [:& menu-separator]])])) + [:& menu-entry {:title (tr "workspace.shape.menu.create-artboard-from-selection") + :shortcut (sc/get-tooltip :artboard-selection) + :on-click do-create-artboard-from-selection}] + [:& menu-separator]])) (mf/defc context-focus-mode-menu [{:keys []}] @@ -333,7 +338,7 @@ is-frame? (and single? has-frame?)] (when (and prototype? is-frame?) - (let [flow (csp/get-frame-flow flows (-> shapes first :id))] + (let [flow (ctp/get-frame-flow flows (-> shapes first :id))] (if (some? flow) [:& menu-entry {:title (tr "workspace.shape.menu.delete-flow-start") :on-click (do-remove-flow flow)}] @@ -439,14 +444,11 @@ :on-click do-delete}])) (mf/defc shape-context-menu + {::mf/wrap [mf/memo]} [{:keys [mdata] :as props}] (let [{:keys [disable-booleans? disable-flatten?]} mdata shapes (mf/deref refs/selected-objects) - hover-ids (mf/deref refs/current-hover-ids) - hover-objs (mf/deref (refs/objects-by-id hover-ids)) - props #js {:shapes shapes - :hover-objs hover-objs :disable-booleans? disable-booleans? :disable-flatten? disable-flatten?}] (when-not (empty? shapes) diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 2b4fd97288..5f93e4def3 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -121,26 +121,26 @@ (mf/use-fn (mf/deps file) #(st/emit! (modal/show - {:type :confirm - :message "" - :title (tr "modals.add-shared-confirm.message" (:name file)) - :hint (tr "modals.add-shared-confirm.hint") - :cancel-label :omit - :accept-label (tr "modals.add-shared-confirm.accept") - :accept-style :primary - :on-accept add-shared-fn}))) + {:type :confirm + :message "" + :title (tr "modals.add-shared-confirm.message" (:name file)) + :hint (tr "modals.add-shared-confirm.hint") + :cancel-label :omit + :accept-label (tr "modals.add-shared-confirm.accept") + :accept-style :primary + :on-accept add-shared-fn}))) on-remove-shared (mf/use-fn (mf/deps file) #(st/emit! (modal/show - {:type :confirm - :message "" - :title (tr "modals.remove-shared-confirm.message" (:name file)) - :hint (tr "modals.remove-shared-confirm.hint") - :cancel-label :omit - :accept-label (tr "modals.remove-shared-confirm.accept") - :on-accept del-shared-fn}))) + {:type :confirm + :message "" + :title (tr "modals.remove-shared-confirm.message" (:name file)) + :hint (tr "modals.remove-shared-confirm.hint") + :cancel-label :omit + :accept-label (tr "modals.remove-shared-confirm.accept") + :on-accept del-shared-fn}))) handle-blur (fn [_] (let [value (-> edit-input-ref mf/ref-val dom/get-value)] @@ -160,27 +160,38 @@ (st/emit! (de/show-workspace-export-dialog)))) on-export-file + (fn [event-name binary?] + (st/emit! (ptk/event ::ev/event {::ev/name event-name + ::ev/origin "workspace" + :num-files 1})) + + (->> (rx/of file) + (rx/flat-map + (fn [file] + (->> (rp/query :file-libraries {:file-id (:id file)}) + (rx/map #(assoc file :has-libraries? (d/not-empty? %)))))) + (rx/reduce conj []) + (rx/subs + (fn [files] + (st/emit! + (modal/show + {:type :export + :team-id team-id + :has-libraries? (->> files (some :has-libraries?)) + :files files + :binary? binary?})))))) + + on-export-binary-file (mf/use-callback (mf/deps file team-id) (fn [_] - (st/emit! (ptk/event ::ev/event {::ev/name "export-files" - ::ev/origin "workspace" - :num-files 1})) + (on-export-file "export-binary-files" true))) - (->> (rx/of file) - (rx/flat-map - (fn [file] - (->> (rp/query :file-libraries {:file-id (:id file)}) - (rx/map #(assoc file :has-libraries? (d/not-empty? %)))))) - (rx/reduce conj []) - (rx/subs - (fn [files] - (st/emit! - (modal/show - {:type :export - :team-id team-id - :has-libraries? (->> files (some :has-libraries?)) - :files files}))))))) + on-export-standard-file + (mf/use-callback + (mf/deps file team-id) + (fn [_] + (on-export-file "export-standard-files" false))) on-export-frames (mf/use-callback @@ -274,10 +285,12 @@ [:li.export-file {:on-click on-export-shapes} [:span (tr "dashboard.export-shapes")] [:span.shortcut (sc/get-tooltip :export-shapes)]] - [:li.export-file {:on-click on-export-file} - [:span (tr "dashboard.export-single")]] + [:li.separator.export-file {:on-click on-export-binary-file} + [:span (tr "dashboard.download-binary-file")]] + [:li.export-file {:on-click on-export-standard-file} + [:span (tr "dashboard.download-standard-file")]] (when (seq frames) - [:li.export-file {:on-click on-export-frames} + [:li.separator.export-file {:on-click on-export-frames} [:span (tr "dashboard.export-frames")]])]] [:& dropdown {:show (= @show-sub-menu? :edit) diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 7905df9518..0c93d33eda 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -13,6 +13,7 @@ common." (:require [app.common.pages.helpers :as cph] + [app.main.ui.context :as ctx] [app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.image :as image] [app.main.ui.shapes.rect :as rect] @@ -52,7 +53,8 @@ (mf/use-memo (mf/deps objects) #(cph/objects-by-frame objects))] - [:* + + [:& (mf/provider ctx/active-frames) {:value active-frames} ;; Render font faces only for shapes that are part of the root ;; frame but don't belongs to any other frame. (let [xform (comp @@ -75,23 +77,30 @@ ::mf/wrap-props false} [props] (let [shape (obj/get props "shape") - opts #js {:shape shape}] + + active-frames + (when (cph/root-frame? shape) (mf/use-ctx ctx/active-frames)) + + thumbnail? + (and (some? active-frames) + (not (contains? active-frames (:id shape)))) + + opts #js {:shape shape :thumbnail? thumbnail?}] (when (and (some? shape) (not (:hidden shape))) - [:* - (case (:type shape) - :path [:> path/path-wrapper opts] - :text [:> text/text-wrapper opts] - :group [:> group-wrapper opts] - :rect [:> rect-wrapper opts] - :image [:> image-wrapper opts] - :circle [:> circle-wrapper opts] - :svg-raw [:> svg-raw-wrapper opts] - :bool [:> bool-wrapper opts] + (case (:type shape) + :path [:> path/path-wrapper opts] + :text [:> text/text-wrapper opts] + :group [:> group-wrapper opts] + :rect [:> rect-wrapper opts] + :image [:> image-wrapper opts] + :circle [:> circle-wrapper opts] + :svg-raw [:> svg-raw-wrapper opts] + :bool [:> bool-wrapper opts] - ;; Only used when drawing a new frame. - :frame [:> frame-wrapper opts] + ;; Only used when drawing a new frame. + :frame [:> frame-wrapper opts] - nil)]))) + nil)))) (def group-wrapper (group/group-wrapper-factory shape-wrapper)) (def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper)) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index ec70154f7e..420eaaa442 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -8,10 +8,12 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.uuid :as uuid] + [app.common.pages.helpers :as cph] + [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] [app.main.fonts :as fonts] [app.main.refs :as refs] + [app.main.store :as st] [app.main.ui.context :as ctx] [app.main.ui.hooks :as hooks] [app.main.ui.shapes.embed :as embed] @@ -38,15 +40,14 @@ childs (mf/deref childs-ref)] [:& (mf/provider embed/context) {:value true} - [:& shape-container {:shape shape :ref ref} + [:& shape-container {:shape shape :ref ref :disable-shadows? true} [:& frame-shape {:shape shape :childs childs} ]]])))) (defn check-props [new-props old-props] (and (= (unchecked-get new-props "thumbnail?") (unchecked-get old-props "thumbnail?")) - (= (unchecked-get new-props "shape") (unchecked-get old-props "shape")) - (= (unchecked-get new-props "objects") (unchecked-get old-props "objects")))) + (= (unchecked-get new-props "shape") (unchecked-get old-props "shape")))) (defn frame-wrapper-factory [shape-wrapper] @@ -57,77 +58,81 @@ ::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - thumbnail? (unchecked-get props "thumbnail?") - objects (unchecked-get props "objects") - - render-id (mf/use-memo #(str (uuid/next))) - fonts (mf/use-memo (mf/deps shape objects) #(ff/shape->fonts shape objects)) - fonts (-> fonts (hooks/use-equal-memo)) - - force-render (mf/use-state false) - - ;; Thumbnail data + (let [shape (unchecked-get props "shape") frame-id (:id shape) - page-id (mf/use-ctx ctx/current-page-id) ;; References to the current rendered node and the its parentn node-ref (mf/use-var nil) - ;; when `true` we've called the mount for the frame - rendered? (mf/use-var false) + objects (wsh/lookup-page-objects @st/state) ;; Modifiers modifiers-ref (mf/use-memo (mf/deps frame-id) #(refs/workspace-modifiers-by-frame-id frame-id)) - modifiers (mf/deref modifiers-ref) - - disable-thumbnail? (d/not-empty? (dm/get-in modifiers [(:id shape) :modifiers])) - - [on-load-frame-dom render-frame? thumbnail-renderer] - (ftr/use-render-thumbnail page-id shape node-ref rendered? disable-thumbnail? @force-render) - - on-frame-load - (fns/use-node-store thumbnail? node-ref rendered? render-frame?)] + modifiers (mf/deref modifiers-ref)] (fdm/use-dynamic-modifiers objects @node-ref modifiers) - (mf/use-effect - (mf/deps fonts) - (fn [] - (->> (rx/from fonts) - (rx/merge-map fonts/fetch-font-css) - (rx/ignore)))) + (if-not (cph/root-frame? shape) + [:& frame-shape {:shape shape :ref node-ref}] - (mf/use-effect - (fn [] - ;; When a change in the data is received a "force-render" event is emited - ;; that will force the component to be mounted in memory - (let [sub - (->> (dwt/force-render-stream (:id shape)) - (rx/take-while #(not @rendered?)) - (rx/subs #(reset! force-render true)))] - #(when sub - (rx/dispose! sub))))) + ;; If the current shape is root we handle its thumbnail and the dynamic modifiers + (let [thumbnail? (unchecked-get props "thumbnail?") + fonts (mf/use-memo (mf/deps shape objects) #(ff/shape->fonts shape objects)) + fonts (-> fonts (hooks/use-equal-memo)) - (mf/use-effect - (mf/deps shape fonts thumbnail? on-load-frame-dom @force-render render-frame?) - (fn [] - (when (and (some? @node-ref) (or @rendered? (not thumbnail?) @force-render render-frame?)) - (mf/mount - (mf/element frame-shape - #js {:ref on-load-frame-dom :shape shape :fonts fonts}) + force-render (mf/use-state false) - @node-ref) - (when (not @rendered?) (reset! rendered? true))))) + ;; Thumbnail data + page-id (mf/use-ctx ctx/current-page-id) - [:& (mf/provider ctx/render-ctx) {:value render-id} - [:g.frame-container {:id (dm/str "frame-container-" (:id shape)) - :key "frame-container" - :ref on-frame-load - :opacity (when (:hidden shape) 0)} - [:& ff/fontfaces-style {:fonts fonts}] - [:g.frame-thumbnail-wrapper - {:id (dm/str "thumbnail-container-" (:id shape)) - ;; Hide the thumbnail when not displaying - :opacity (when (and @rendered? (not thumbnail?) (not render-frame?)) 0)} - thumbnail-renderer]]])))) + ;; when `true` we've called the mount for the frame + rendered? (mf/use-var false) + + disable-thumbnail? (d/not-empty? (dm/get-in modifiers [(:id shape) :modifiers])) + + [on-load-frame-dom render-frame? thumbnail-renderer] + (ftr/use-render-thumbnail page-id shape node-ref rendered? disable-thumbnail? @force-render) + + on-frame-load + (fns/use-node-store thumbnail? node-ref rendered? render-frame?)] + + (mf/use-effect + (mf/deps fonts) + (fn [] + (->> (rx/from fonts) + (rx/merge-map fonts/fetch-font-css) + (rx/ignore)))) + + (mf/use-effect + (fn [] + ;; When a change in the data is received a "force-render" event is emited + ;; that will force the component to be mounted in memory + (let [sub + (->> (dwt/force-render-stream (:id shape)) + (rx/take-while #(not @rendered?)) + (rx/subs #(reset! force-render true)))] + #(when sub + (rx/dispose! sub))))) + + (mf/use-effect + (mf/deps shape fonts thumbnail? on-load-frame-dom @force-render render-frame?) + (fn [] + (when (and (some? @node-ref) (or @rendered? (not thumbnail?) @force-render render-frame?)) + (mf/mount + (mf/element frame-shape + #js {:ref on-load-frame-dom :shape shape :fonts fonts}) + + @node-ref) + (when (not @rendered?) (reset! rendered? true))))) + + [:& shape-container {:shape shape} + [:g.frame-container {:id (dm/str "frame-container-" (:id shape)) + :key "frame-container" + :ref on-frame-load + :opacity (when (:hidden shape) 0)} + [:& ff/fontfaces-style {:fonts fonts}] + [:g.frame-thumbnail-wrapper + {:id (dm/str "thumbnail-container-" (:id shape)) + ;; Hide the thumbnail when not displaying + :opacity (when (and @rendered? (not thumbnail?) (not render-frame?)) 0)} + thumbnail-renderer]]])))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index ae25c9177b..dc46bf2412 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -77,39 +77,42 @@ (defn get-nodes "Retrieve the DOM nodes to apply the matrix transformation" - [base-node {:keys [id type masked-group?]}] - (let [shape-node (dom/query base-node (str "#shape-" id)) + [base-node {:keys [id type masked-group?] :as shape}] + (when (some? base-node) + (let [shape-node (if (= (.-id base-node) (dm/str "shape-" id)) + base-node + (dom/query base-node (dm/str "#shape-" id))) - frame? (= :frame type) - group? (= :group type) - text? (= :text type) - mask? (and group? masked-group?)] + frame? (= :frame type) + group? (= :group type) + text? (= :text type) + mask? (and group? masked-group?)] + (cond + frame? + [shape-node + (dom/query shape-node ".frame-children") + (dom/query (dm/str "#thumbnail-container-" id)) + (dom/query (dm/str "#thumbnail-" id)) + (dom/query (dm/str "#frame-title-" id))] - (cond - frame? - [shape-node - (dom/query shape-node ".frame-children") - (dom/query (str "#thumbnail-container-" id)) - (dom/query (str "#thumbnail-" id))] + ;; For groups we don't want to transform the whole group but only + ;; its filters/masks + mask? + [(dom/query shape-node ".mask-clip-path") + (dom/query shape-node ".mask-shape")] - ;; For groups we don't want to transform the whole group but only - ;; its filters/masks - mask? - [(dom/query shape-node ".mask-clip-path") - (dom/query shape-node ".mask-shape")] + group? + (let [shape-defs (dom/query shape-node "defs")] + (d/concat-vec + (dom/query-all shape-defs ".svg-def") + (dom/query-all shape-defs ".svg-mask-wrapper"))) - group? - (let [shape-defs (dom/query shape-node "defs")] - (d/concat-vec - (dom/query-all shape-defs ".svg-def") - (dom/query-all shape-defs ".svg-mask-wrapper"))) + text? + [shape-node + (dom/query shape-node ".text-container")] - text? - [shape-node - (dom/query shape-node ".text-container")] - - :else - [shape-node]))) + :else + [shape-node])))) (defn transform-region! [node modifiers] diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs index c61ca1d84b..28c72adf2f 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.main.data.workspace.thumbnails :as dwt] [app.main.refs :as refs] @@ -32,7 +33,6 @@ (.clearRect canvas-context 0 0 canvas-width canvas-height) (.drawImage canvas-context img-node 0 0 canvas-width canvas-height) - (.removeAttribute canvas-node "data-empty") true)) (catch :default err (.error js/console err) @@ -54,7 +54,7 @@ (defn use-render-thumbnail "Hook that will create the thumbnail thata" - [page-id {:keys [id x y width height] :as shape} node-ref rendered? disable? force-render] + [page-id {:keys [id] :as shape} node-ref rendered? disable? force-render] (let [frame-canvas-ref (mf/use-ref nil) frame-image-ref (mf/use-ref nil) @@ -63,23 +63,35 @@ regenerate-thumbnail (mf/use-var false) - fixed-width (mth/clamp (:width shape) 250 2000) - fixed-height (/ (* (:height shape) fixed-width) (:width shape)) + all-children-ref (mf/use-memo (mf/deps id) #(refs/all-children-objects id)) + all-children (mf/deref all-children-ref) + + {:keys [x y width height] :as shape-bb} + (if (:show-content shape) + (gsh/selection-rect (concat [shape] all-children)) + (-> shape :points gsh/points->selrect)) + + fixed-width (mth/clamp width 250 2000) + fixed-height (/ (* height fixed-width) width) image-url (mf/use-state nil) observer-ref (mf/use-var nil) - shape-ref (hooks/use-update-var shape) + shape-bb-ref (hooks/use-update-var shape-bb) - updates-str (mf/use-memo #(rx/subject)) + updates-str (mf/use-memo #(rx/subject)) thumbnail-data-ref (mf/use-memo (mf/deps page-id id) #(refs/thumbnail-frame-data page-id id)) thumbnail-data (mf/deref thumbnail-data-ref) prev-thumbnail-data (hooks/use-previous thumbnail-data) + ;; State to indicate to the parent that should render the frame render-frame? (mf/use-state (not thumbnail-data)) + ;; State variable to select whether we show the image thumbnail or the canvas thumbnail + show-frame-thumbnail (mf/use-state (some? thumbnail-data)) + on-image-load (mf/use-callback (fn [] @@ -89,6 +101,8 @@ (when (draw-thumbnail-canvas! canvas-node img-node) (reset! image-url nil) + (when @show-frame-thumbnail + (reset! show-frame-thumbnail false)) ;; If we don't have the thumbnail data saved (normaly the first load) we update the data ;; when available (when (not @thumbnail-data-ref) @@ -101,7 +115,8 @@ (fn [] (let [node @node-ref frame-html (dom/node->xml node) - {:keys [x y width height]} @shape-ref + + {:keys [x y width height]} @shape-bb-ref style-node (dom/query (dm/str "#frame-container-" (:id shape) " style")) style-str (or (-> style-node dom/node->xml) "") @@ -199,18 +214,26 @@ [on-load-frame-dom @render-frame? (mf/html - [:* - [:> frame/frame-thumbnail {:key (dm/str (:id shape)) - :shape (cond-> shape - (some? thumbnail-data) - (assoc :thumbnail thumbnail-data))}] + [:& frame/frame-container {:bounds shape-bb + :shape (cond-> shape + (some? thumbnail-data) + (assoc :thumbnail thumbnail-data))} + + (when @show-frame-thumbnail + [:> frame/frame-thumbnail-image + {:key (dm/str (:id shape)) + :bounds shape-bb + :shape (cond-> shape + (some? thumbnail-data) + (assoc :thumbnail thumbnail-data))}]) + [:foreignObject {:x x :y y :width width :height height} [:canvas.thumbnail-canvas {:key (dm/str "thumbnail-canvas-" (:id shape)) :ref frame-canvas-ref :data-object-id (dm/str page-id (:id shape)) - :data-empty true + :data-empty @show-frame-thumbnail :width fixed-width :height fixed-height ;; DEBUG @@ -220,9 +243,9 @@ (when (some? @image-url) [:image {:ref frame-image-ref - :x (:x shape) - :y (:y shape) + :x x + :y y :href @image-url - :width (:width shape) - :height (:height shape) + :width width + :height height :on-load on-image-load}])])])) 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 b124d82e17..052a8f7d68 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -152,12 +152,9 @@ (fn [state] (let [old-state (mf/ref-val prev-value)] (if (and (some? state) (some? old-state)) - (let [block-changes (ted/get-content-changes old-state state) - - prev-data (-> (ted/get-editor-current-inline-styles old-state) - (dissoc :text-align :text-direction)) - - block-to-setup (get-blocks-to-setup block-changes) + (let [block-changes (ted/get-content-changes old-state state) + prev-data (ted/get-editor-current-inline-styles old-state) + block-to-setup (get-blocks-to-setup block-changes) block-to-add-styles (get-blocks-to-add-styles block-changes)] (-> state (ted/setup-block-styles block-to-setup prev-data) @@ -211,11 +208,12 @@ handle-pasted-text (fn [text _ _] - (let [style (ted/get-editor-current-inline-styles state) - state (-> (ted/insert-text state text style) - (handle-change))] + (let [current-block-styles (ted/get-editor-current-block-data state) + inline-styles (ted/get-editor-current-inline-styles state) + style (merge current-block-styles inline-styles) + state (-> (ted/insert-text state text style) + (handle-change))] (st/emit! (dwt/update-editor-state shape state))) - "handled")] (mf/use-layout-effect on-mount) @@ -288,7 +286,7 @@ :height (or height (:height shape)) :fill "red"}]]] - [:foreignObject {:x (:x shape) :y (:y shape) :width "100%" :height "100%"} + [:foreignObject {:x (:x shape) :y (:y shape) :width width :height height} [:div {:style {:position "absolute" :left 0 :top 0 diff --git a/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs b/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs index ba2bfa059e..ad76c2f65a 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs @@ -23,7 +23,7 @@ (some? text-modifier) (dwt/apply-text-modifier text-modifier)) - transform (gsh/transform-matrix shape {:no-flip true}) + transform (gsh/transform-str shape {:no-flip true}) {:keys [x y width height]} shape] [:rect.main.viewport-selrect @@ -31,7 +31,7 @@ :y y :width width :height height - :transform (str transform) + :transform transform :style {:stroke "var(--color-select)" :stroke-width (/ 1 zoom) :fill "none"}}])) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs index 3aca3072c9..b0aa93524b 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs @@ -32,8 +32,8 @@ (defn strip-modifier [modifier] - (if (or (some? (get-in modifier [:modifiers :resize-vector])) - (some? (get-in modifier [:modifiers :resize-vector-2]))) + (if (or (some? (dm/get-in modifier [:modifiers :resize-vector])) + (some? (dm/get-in modifier [:modifiers :resize-vector-2]))) modifier (d/update-when modifier :modifiers dissoc :displacement :rotation))) @@ -66,7 +66,7 @@ [{:keys [grow-type id migrate] :as shape} node] ;; Check if we need to update the size because it's auto-width or auto-height ;; Update the position-data of every text fragment - (p/let [position-data (tsp/calc-position-data node)] + (p/let [position-data (tsp/calc-position-data id)] ;; At least one paragraph needs to be inside the bounding box (when (gsht/overlaps-position-data? shape position-data) (st/emit! (dwt/update-position-data id position-data))) @@ -85,7 +85,7 @@ (defn- update-text-modifier [{:keys [grow-type id]} node] - (p/let [position-data (tsp/calc-position-data node) + (p/let [position-data (tsp/calc-position-data id) props {:position-data position-data} props diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs new file mode 100644 index 0000000000..a8892489a7 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -0,0 +1,258 @@ +;; 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.text.viewport-texts-html + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.text :as gsht] + [app.common.math :as mth] + [app.common.pages.helpers :as cph] + [app.common.text :as txt] + [app.main.data.workspace.texts :as dwt] + [app.main.fonts :as fonts] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.hooks :as hooks] + [app.main.ui.shapes.text.html-text :as html] + [app.util.dom :as dom] + [app.util.object :as obj] + [app.util.text-editor :as ted] + [app.util.text-svg-position :as tsp] + [app.util.timers :as ts] + [promesa.core :as p] + [rumext.alpha :as mf])) + +(defn strip-position-data [shape] + (dissoc shape :position-data :transform :transform-inverse)) + +(defn strip-modifier + [modifier] + (if (or (some? (dm/get-in modifier [:modifiers :resize-vector])) + (some? (dm/get-in modifier [:modifiers :resize-vector-2]))) + modifier + (d/update-when modifier :modifiers dissoc :displacement :rotation))) + +(defn process-shape [modifiers {:keys [id] :as shape}] + (let [modifier (-> (get modifiers id) strip-modifier) + shape (cond-> shape + (not (gsh/empty-modifiers? (:modifiers modifier))) + (-> (assoc :grow-type :fixed) + (merge modifier) gsh/transform-shape))] + (-> shape + (cond-> (nil? (:position-data shape)) + (assoc :migrate true)) + strip-position-data))) + +(defn- update-with-editor-state + "Updates the shape with the current state in the editor" + [shape editor-state] + (let [content (:content shape) + editor-content + (when editor-state + (-> editor-state + (ted/get-editor-current-content) + (ted/export-content)))] + + (cond-> shape + (and (some? shape) (some? editor-content)) + (assoc :content (d/txt-merge content editor-content))))) + +(defn- update-text-shape + [{:keys [grow-type id migrate] :as shape} node] + ;; Check if we need to update the size because it's auto-width or auto-height + ;; Update the position-data of every text fragment + (p/let [position-data (tsp/calc-position-data id)] + ;; At least one paragraph needs to be inside the bounding box + (when (gsht/overlaps-position-data? shape position-data) + (st/emit! (dwt/update-position-data id position-data))) + + (when (contains? #{:auto-height :auto-width} grow-type) + (let [{:keys [width height]} + (-> (dom/query node ".paragraph-set") + (dom/get-client-size)) + width (mth/ceil width) + height (mth/ceil height)] + (when (and (not (mth/almost-zero? width)) + (not (mth/almost-zero? height)) + (not migrate)) + (st/emit! (dwt/resize-text id width height))))) + (st/emit! (dwt/clean-text-modifier id)))) + +(defn- update-text-modifier + [{:keys [grow-type id]} node] + (p/let [position-data (tsp/calc-position-data id) + props {:position-data position-data} + + props + (if (contains? #{:auto-height :auto-width} grow-type) + (let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size)) + width (mth/ceil width) + height (mth/ceil height)] + (if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height))) + (assoc props :width width :height height) + props)) + props)] + + (st/emit! (dwt/update-text-modifier id props)))) + +(mf/defc text-container + {::mf/wrap-props false + ::mf/wrap [mf/memo]} + [props] + (let [shape (obj/get props "shape") + on-update (obj/get props "on-update") + + handle-update + (mf/use-callback + (mf/deps shape on-update) + (fn [node] + (when (some? node) + (on-update shape node))))] + + [:& html/text-shape {:key (str "shape-" (:id shape)) + :ref handle-update + :shape shape + :grow-type (:grow-type shape)}])) + +(mf/defc viewport-texts-wrapper + {::mf/wrap-props false + ::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]} + [props] + (let [text-shapes (obj/get props "text-shapes") + modifiers (obj/get props "modifiers") + prev-modifiers (hooks/use-previous modifiers) + prev-text-shapes (hooks/use-previous text-shapes) + + ;; A change in position-data won't be a "real" change + text-change? + (fn [id] + (let [old-shape (get prev-text-shapes id) + new-shape (get text-shapes id) + old-modifiers (-> (get prev-modifiers id) strip-modifier) + new-modifiers (-> (get modifiers id) strip-modifier)] + + (or (and (not (identical? old-shape new-shape)) + (not= (dissoc old-shape :migrate :position-data) + (dissoc new-shape :migrate :position-data))) + (not= new-modifiers old-modifiers) + + ;; When the position data is nil we force to recalculate + (:migrate new-shape)))) + + changed-texts + (mf/use-memo + (mf/deps text-shapes modifiers) + #(->> (keys text-shapes) + (filter text-change?) + (map (d/getf text-shapes)))) + + handle-update-modifier (mf/use-callback update-text-modifier) + handle-update-shape (mf/use-callback update-text-shape)] + + [:* + (for [{:keys [id] :as shape} changed-texts] + [:& text-container {:shape (gsh/transform-shape shape) + :on-update (if (some? (get modifiers (:id shape))) + handle-update-modifier + handle-update-shape) + :key (str (dm/str "text-container-" id))}])])) + +(mf/defc viewport-text-editing + {::mf/wrap-props false} + [props] + + (let [shape (obj/get props "shape") + + ;; Join current objects with the state of the editor + editor-state + (-> (mf/deref refs/workspace-editor-state) + (get (:id shape))) + + text-modifier-ref + (mf/use-memo (mf/deps (:id shape)) #(refs/workspace-text-modifier-by-id (:id shape))) + + text-modifier + (mf/deref text-modifier-ref) + + shape (cond-> shape + (some? editor-state) + (update-with-editor-state editor-state)) + + ;; When we have a text with grow-type :auto-height we need to check the correct height + ;; otherwise the center alignment will break + shape + (if (or (not= :auto-height (:grow-type shape)) (empty? text-modifier)) + shape + (let [tr-shape (dwt/apply-text-modifier shape text-modifier)] + (cond-> shape + ;; we only change the height otherwise could cause problems with the other fields + (some? text-modifier) + (assoc :height (:height tr-shape))))) + + shape (hooks/use-equal-memo shape) + + handle-update-shape (mf/use-callback update-text-modifier)] + + (mf/use-effect + (mf/deps (:id shape)) + (fn [] + #(st/emit! (dwt/remove-text-modifier (:id shape))))) + + [:& text-container {:shape shape + :on-update handle-update-shape}])) + +(defn check-props + [new-props old-props] + (and (identical? (unchecked-get new-props "objects") + (unchecked-get old-props "objects")) + (identical? (unchecked-get new-props "modifiers") + (unchecked-get old-props "modifiers")) + (= (unchecked-get new-props "edition") + (unchecked-get old-props "edition")))) + +(mf/defc viewport-texts + {::mf/wrap-props false + ::mf/wrap [#(mf/memo' % check-props)]} + [props] + (let [objects (obj/get props "objects") + edition (obj/get props "edition") + modifiers (obj/get props "modifiers") + + text-shapes + (mf/use-memo + (mf/deps objects) + #(into {} (filter (comp cph/text-shape? second)) objects)) + + text-shapes + (mf/use-memo + (mf/deps text-shapes modifiers) + #(d/update-vals text-shapes (partial process-shape modifiers))) + + editing-shape (get text-shapes edition) + + ;; This memo is necesary so the viewport-text-wrapper memoize its props correctly + text-shapes-wrapper + (mf/use-memo + (mf/deps text-shapes edition) + (fn [] + (dissoc text-shapes edition)))] + + ;; We only need the effect to run on "mount" because the next fonts will be changed when the texts are + ;; edited + (mf/use-effect + (fn [] + (let [text-nodes (->> text-shapes (vals)(mapcat #(txt/node-seq txt/is-text-node? (:content %)))) + fonts (into #{} (keep :font-id) text-nodes)] + (run! fonts/ensure-loaded! fonts)))) + + [:* + (when editing-shape + [:& viewport-text-editing {:shape editing-shape}]) + + [:& viewport-texts-wrapper {:text-shapes text-shapes-wrapper + :modifiers modifiers}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 1987dca1ae..aab1c95a69 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -47,14 +47,10 @@ [potok.core :as ptk] [rumext.alpha :as mf])) -;; TODO: refactor to remove duplicate code and less parameter passing. -;; - Move all state to [:workspace-local :assets-bar file-id :open-boxes {} -;; :open-groups {} -;; :reverse-sort? -;; :listing-thumbs? -;; :selected-assets {}] -;; - Move selection code to independent functions that receive the state as a parameter. -;; +;; NOTE: TODO: for avoid too many arguments, I think we can use react +;; context variables for pass to the down tree all the common +;; variables that are defined on the MAIN container/box component. + ;; TODO: change update operations to admit multiple ids, thus avoiding the need of ;; emitting many events and opening an undo transaction. Also move the logic ;; of grouping, deleting, etc. to events in the data module, since now the @@ -205,8 +201,6 @@ create-typed-assets-group (partial create-typed-assets-group components-to-group)] (modal/show! :name-group-dialog {:accept create-typed-assets-group})))))) - - (defn- on-drag-enter-asset [event asset dragging? selected-assets selected-assets-paths] (when (and @@ -275,8 +269,6 @@ (:id target-asset) (cph/merge-path-item prefix (:name target-asset)))))))) - - ;; ---- Common blocks ---- (def auto-pos-menu-state {:open? false @@ -1090,6 +1082,7 @@ :else (:value color)) ;; TODO: looks like the first argument is not necessary + ;; TODO: this code should be out of this UI component apply-color (fn [_ event] (let [objects (wsh/lookup-page-objects @st/state) @@ -1629,6 +1622,17 @@ local-data (mf/deref typography-data) menu-state (mf/use-state auto-pos-menu-state) + + extract-path-if-missing + (fn [typography] + (let [[path name] (cph/parse-path-name (:name typography))] + (if (= (:name typography) name) + typography + (assoc typography :path path :name name)))) + + typographies (->> typographies + (map extract-path-if-missing)) + groups (group-assets typographies reverse-sort?) selected-typographies (:typographies selected-assets) @@ -1937,13 +1941,11 @@ (fn [asset-type asset-groups asset-id] (letfn [(flatten-groups [groups] - (concat - (get groups "" []) - (reduce concat - (into [] - (->> (filter #(seq (first %)) groups) - (map second) - (mapcat flatten-groups))))))] + (reduce concat [(get groups "" []) + (into [] + (->> (filter #(seq (first %)) groups) + (map second) + (mapcat flatten-groups)))]))] (let [selected-assets-type (get selected-assets asset-type) count-assets (count selected-assets-type)] (if (<= count-assets 0) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 6b3132217e..05ad9bdfd5 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -11,7 +11,7 @@ [app.common.pages.helpers :as cph] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] - [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.collapse :as dwc] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.shape-icon :as si] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs index e6a3f463d0..7e1d748643 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs @@ -10,7 +10,7 @@ [app.common.data.macros :as dm] [app.common.text :as txt] [app.main.data.workspace.colors :as dc] - [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.selection :as dws] [app.main.store :as st] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] @@ -185,7 +185,7 @@ (fn [color] (let [shapes-by-color (get @grouped-colors* color) ids (into (d/ordered-set) (map :shape-id) shapes-by-color)] - (st/emit! (dwc/select-shapes ids)))))] + (st/emit! (dws/select-shapes ids)))))] (mf/with-effect [grouped-colors] (reset! grouped-colors* grouped-colors)) 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 fdd9da5369..0a28635ced 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 @@ -6,11 +6,9 @@ (ns app.main.ui.workspace.sidebar.options.menus.component (:require - [app.common.pages.helpers :as cph] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.libraries :as dwl] - [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.context :as ctx] @@ -22,7 +20,7 @@ (def component-attrs [:component-id :component-file :shape-ref]) (mf/defc component-menu - [{:keys [ids values] :as props}] + [{:keys [ids values shape-name] :as props}] (let [current-file-id (mf/use-ctx ctx/current-file-id) id (first ids) @@ -30,15 +28,6 @@ component-id (:component-id values) library-id (:component-file values) - - local-file (deref refs/workspace-local-library) - libraries (deref refs/workspace-libraries) - - ;; NOTE: this is necessary because the `cph/get-component` - ;; expects a map of all libraries, including the local one. - libraries (assoc libraries (:id local-file) {:data local-file}) - - component (cph/get-component libraries library-id component-id) show? (some? component-id) on-menu-click @@ -63,14 +52,14 @@ do-update-remote-component #(st/emit! (modal/show - {:type :confirm - :message "" - :title (tr "modals.update-remote-component.message") - :hint (tr "modals.update-remote-component.hint") - :cancel-label (tr "modals.update-remote-component.cancel") - :accept-label (tr "modals.update-remote-component.accept") - :accept-style :primary - :on-accept do-update-component})) + {:type :confirm + :message "" + :title (tr "modals.update-remote-component.message") + :hint (tr "modals.update-remote-component.hint") + :cancel-label (tr "modals.update-remote-component.cancel") + :accept-label (tr "modals.update-remote-component.accept") + :accept-style :primary + :on-accept do-update-component})) do-show-component #(st/emit! (dw/go-to-component component-id)) do-navigate-component-file #(st/emit! (dwl/nav-to-component-file library-id))] @@ -81,7 +70,7 @@ [:div.element-set-content [:div.row-flex.component-row i/component - (:name component) + shape-name [:div.row-actions {:on-click on-menu-click} i/actions 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 0f99378352..6569802d95 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 @@ -245,7 +245,7 @@ handle-create-grid (mf/use-fn (mf/deps id) #(st/emit! (dw/add-frame-grid id)))] [:div.element-set [:div.element-set-title - [:span (tr "workspace.options.grid.title")] + [:span (tr "workspace.options.grid.grid-title")] [:div.add-page {:on-click handle-create-grid} i/close]] (when (seq (:grids shape)) 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 e8ba4e9b9a..18035e283e 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 @@ -7,9 +7,10 @@ (ns app.main.ui.workspace.sidebar.options.menus.interactions (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] - [app.common.spec.interactions :as csi] - [app.common.spec.page :as csp] + [app.common.types.page :as ctp] + [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] [app.main.data.workspace.interactions :as dwi] @@ -79,7 +80,7 @@ {:dissolve (tr "workspace.options.interaction-animation-dissolve") :slide (tr "workspace.options.interaction-animation-slide")} - (csi/allow-push? (:action-type interaction)) + (ctsi/allow-push? (:action-type interaction)) (assoc :push (tr "workspace.options.interaction-animation-push")))) (defn- easing-names @@ -165,7 +166,7 @@ (mf/defc shape-flows [{:keys [flows shape]}] (when (= (:type shape) :frame) - (let [flow (csp/get-frame-flow flows (:id shape))] + (let [flow (ctp/get-frame-flow flows (:id shape))] [:div.element-set.interactions-options [:div.element-set-title [:span (tr "workspace.options.flows.flow-start")]] @@ -178,10 +179,10 @@ (mf/defc interaction-entry [{:keys [index shape interaction update-interaction remove-interaction]}] - (let [objects (deref refs/workspace-page-objects) - destination (get objects (:destination interaction)) - frames (mf/with-memo [objects] - (cph/get-frames objects)) + (let [objects (deref refs/workspace-page-objects) + destination (get objects (:destination interaction)) + + frames (mf/with-memo [objects] (cph/get-viewer-frames objects {:all-frames? (not= :navigate (:action-type interaction))})) overlay-pos-type (:overlay-pos-type interaction) close-click-outside? (:close-click-outside interaction false) @@ -201,27 +202,27 @@ change-event-type (fn [event] (let [value (-> event dom/get-target dom/get-value d/read-string)] - (update-interaction index #(csi/set-event-type % value shape)))) + (update-interaction index #(ctsi/set-event-type % value shape)))) change-action-type (fn [event] (let [value (-> event dom/get-target dom/get-value d/read-string)] - (update-interaction index #(csi/set-action-type % value)))) + (update-interaction index #(ctsi/set-action-type % value)))) change-delay (fn [value] - (update-interaction index #(csi/set-delay % value))) + (update-interaction index #(ctsi/set-delay % value))) change-destination (fn [event] (let [value (-> event dom/get-target dom/get-value) value (when (not= value "") (uuid/uuid value))] - (update-interaction index #(csi/set-destination % value)))) + (update-interaction index #(ctsi/set-destination % value)))) change-preserve-scroll (fn [event] (let [value (-> event dom/get-target dom/checked?)] - (update-interaction index #(csi/set-preserve-scroll % value)))) + (update-interaction index #(ctsi/set-preserve-scroll % value)))) change-url (fn [event] @@ -237,55 +238,55 @@ (if (dom/valid? target) (do (dom/remove-class! target "error") - (update-interaction index #(csi/set-url % value))) + (update-interaction index #(ctsi/set-url % value))) (dom/add-class! target "error")))) change-overlay-pos-type (fn [event] (let [value (-> event dom/get-target dom/get-value d/read-string)] - (update-interaction index #(csi/set-overlay-pos-type % value shape objects)))) + (update-interaction index #(ctsi/set-overlay-pos-type % value shape objects)))) toggle-overlay-pos-type (fn [pos-type] - (update-interaction index #(csi/toggle-overlay-pos-type % pos-type shape objects))) + (update-interaction index #(ctsi/toggle-overlay-pos-type % pos-type shape objects))) change-close-click-outside (fn [event] (let [value (-> event dom/get-target dom/checked?)] - (update-interaction index #(csi/set-close-click-outside % value)))) + (update-interaction index #(ctsi/set-close-click-outside % value)))) change-background-overlay (fn [event] (let [value (-> event dom/get-target dom/checked?)] - (update-interaction index #(csi/set-background-overlay % value)))) + (update-interaction index #(ctsi/set-background-overlay % value)))) change-animation-type (fn [event] (let [value (-> event dom/get-target dom/get-value d/read-string)] - (update-interaction index #(csi/set-animation-type % value)))) + (update-interaction index #(ctsi/set-animation-type % value)))) change-duration (fn [value] - (update-interaction index #(csi/set-duration % value))) + (update-interaction index #(ctsi/set-duration % value))) change-easing (fn [event] (let [value (-> event dom/get-target dom/get-value d/read-string)] - (update-interaction index #(csi/set-easing % value)))) + (update-interaction index #(ctsi/set-easing % value)))) change-way (fn [event] (let [value (-> event dom/get-target dom/get-value d/read-string)] - (update-interaction index #(csi/set-way % value)))) + (update-interaction index #(ctsi/set-way % value)))) change-direction (fn [value] - (update-interaction index #(csi/set-direction % value))) + (update-interaction index #(ctsi/set-direction % value))) change-offset-effect (fn [event] (let [value (-> event dom/get-target dom/checked?)] - (update-interaction index #(csi/set-offset-effect % value)))) + (update-interaction index #(ctsi/set-offset-effect % value)))) ] [:* @@ -313,10 +314,11 @@ (for [[value name] (event-type-names)] (when-not (and (= value :after-delay) (not= (:type shape) :frame)) - [:option {:value (str value)} name]))]] + [:option {:key (dm/str value) + :value (dm/str value)} name]))]] ; Delay - (when (csi/has-delay interaction) + (when (ctsi/has-delay interaction) [:div.interactions-element [:span.element-set-subtitle.wide (tr "workspace.options.interaction-delay")] [:div.input-element {:title (tr "workspace.options.interaction-ms")} @@ -334,10 +336,11 @@ {:value (str (:action-type interaction)) :on-change change-action-type} (for [[value name] (action-type-names)] - [:option {:value (str value)} name])]] + [:option {:key (dm/str "action-" value) + :value (str value)} name])]] ; Destination - (when (csi/has-destination interaction) + (when (ctsi/has-destination interaction) [:div.interactions-element [:span.element-set-subtitle.wide (tr "workspace.options.interaction-destination")] [:select.input-select @@ -349,10 +352,11 @@ (for [frame frames] (when (and (not= (:id frame) (:id shape)) ; A frame cannot navigate to itself (not= (:id frame) (:frame-id shape))) ; nor a shape to its container frame - [:option {:value (str (:id frame))} (:name frame)]))]]) + [:option {:key (dm/str "destination-" (:id frame)) + :value (str (:id frame))} (:name frame)]))]]) ; Preserve scroll - (when (csi/has-preserve-scroll interaction) + (when (ctsi/has-preserve-scroll interaction) [:div.interactions-element [:div.input-checkbox [:input {:type "checkbox" @@ -363,7 +367,7 @@ (tr "workspace.options.interaction-preserve-scroll")]]]) ; URL - (when (csi/has-url interaction) + (when (ctsi/has-url interaction) [:div.interactions-element [:span.element-set-subtitle.wide (tr "workspace.options.interaction-url")] [:input.input-text {:type "url" @@ -371,7 +375,7 @@ :default-value (str (:url interaction)) :on-blur change-url}]]) - (when (csi/has-overlay-opts interaction) + (when (ctsi/has-overlay-opts interaction) [:* ; Overlay position (select) [:div.interactions-element @@ -433,7 +437,7 @@ [:label {:for (str "background-" index)} (tr "workspace.options.interaction-background")]]]]) - (when (csi/has-animation? interaction) + (when (ctsi/has-animation? interaction) [:* ; Animation select [:div.interactions-element.separator @@ -446,7 +450,7 @@ [:option {:value (str value)} name])]] ; Direction - (when (csi/has-way? interaction) + (when (ctsi/has-way? interaction) [:div.interactions-element.interactions-way-buttons [:div.input-radio [:input {:type "radio" @@ -466,7 +470,7 @@ [:label {:for "way-out"} (tr "workspace.options.interaction-out")]]]) ; Direction - (when (csi/has-direction? interaction) + (when (ctsi/has-direction? interaction) [:div.interactions-element.interactions-direction-buttons [:div.element-set-actions-button {:class (dom/classnames :active (= direction :right)) @@ -486,7 +490,7 @@ i/animate-up]]) ; Duration - (when (csi/has-duration? interaction) + (when (ctsi/has-duration? interaction) [:div.interactions-element [:span.element-set-subtitle.wide (tr "workspace.options.interaction-duration")] [:div.input-element {:title (tr "workspace.options.interaction-ms")} @@ -498,7 +502,7 @@ [:span.after (tr "workspace.options.interaction-ms")]]]) ; Easing - (when (csi/has-easing? interaction) + (when (ctsi/has-easing? interaction) [:div.interactions-element [:span.element-set-subtitle.wide (tr "workspace.options.interaction-easing")] [:select.input-select @@ -515,7 +519,7 @@ :ease-in-out i/easing-ease-in-out)]]) ; Offset effect - (when (csi/has-offset-effect? interaction) + (when (ctsi/has-offset-effect? interaction) [:div.interactions-element [:div.input-checkbox [:input {:type "checkbox" @@ -568,7 +572,8 @@ [:div.interactions-help (tr "workspace.options.use-play-button")]])] [:div.groups (for [[index interaction] (d/enumerate interactions)] - [:& interaction-entry {:index index + [:& interaction-entry {:key (dm/str (:id shape) "-" index) + :index index :shape shape :interaction interaction :update-interaction update-interaction diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout.cljs new file mode 100644 index 0000000000..54745a0edb --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -0,0 +1,353 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.ui.workspace.sidebar.options.menus.layout + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.main.ui.components.numeric-input :refer [numeric-input]] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [cuerdas.core :as str] + [rumext.alpha :as mf])) + +(def layout-attrs + [:layout ;; true if active, false if not + :layout-dir ;; :right, :left, :top, :bottom + :gap ;; number could be negative + :layout-type ;; :packed, :space-between, :space-around + :wrap-type ;; :wrap, :no-wrap + :padding-type ;; :simple, :multiple + :padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative + :h-orientation ;; :top, :center, :bottom + :v-orientation]) ;; :left, :center, :right + +(def grid-pos [[:top :left] + [:top :center] + [:top :right] + [:center :left] + [:center :center] + [:center :right] + [:bottom :left] + [:bottom :center] + [:bottom :right]]) +(def grid-rows [:top :center :bottom]) +(def grid-cols [:left :center :right]) + +(mf/defc direction-row + [{:keys [dir saved-dir set-direction] :as props}] + [:button.dir.tooltip.tooltip-bottom + {:class (dom/classnames :active (= saved-dir dir) + :left (= :left dir) + :right (= :right dir) + :top (= :top dir) + :bottom (= :bottom dir)) + :key (dm/str "direction-" dir) + :alt (tr (dm/str "workspace.options.layout.direction." (d/name dir))) + :on-click #(set-direction dir)} + i/auto-direction]) + +(mf/defc orientation-grid + [{:keys [manage-orientation test-values get-icon] :as props}] + (let [dir (:layout-dir @test-values) + type (:layout-type @test-values) + is-col? (or (= dir :top) + (= dir :bottom)) + saved-pos [(:h-orientation @test-values) (:v-orientation @test-values)]] + (if (= type :packed) + [:div.orientation-grid + [:div.button-wrapper + (for [[pv ph] grid-pos] + [:button.orientation + {:on-click (partial manage-orientation pv ph type) + :class (dom/classnames + :active (= [pv ph] saved-pos) + :top (= :top pv) + :center (= :center pv) + :bottom (= :bottom pv) + :left (= :left ph) + :center (= :center ph) + :right (= :right ph)) + :key (dm/str pv ph)} + [:span.icon + {:class (dom/classnames + :rotated is-col?)} + (get-icon dir type pv ph)]])]] + + (if is-col? + [:div.orientation-grid.col + [:div.button-wrapper + (for [col grid-cols] + [:button.orientation + {:on-click (partial manage-orientation :top col type) + :class (dom/classnames + :active (= col (second saved-pos)) + :top (= :left col) + :centered (= :center col) + :bottom (= :right col))} + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-icon dir type nil col)] + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-icon dir type nil col)] + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-icon dir type nil col)]])]] + + [:div.orientation-grid.row + [:div.button-wrapper + (for [row grid-rows] + [:button.orientation + {:on-click (partial manage-orientation row :left type) + :class (dom/classnames + :active (= row (first saved-pos)) + :top (= :top row) + :centered (= :center row) + :bottom (= :bottom row))} + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-icon dir type row nil)] + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-icon dir type row nil)] + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-icon dir type row nil)]])]])))) + +(mf/defc padding-section + [{:keys [test-values change-padding-style select-all on-padding-change] :as props}] + + (let [padding-type (:padding-type @test-values)] + + [:div.row-flex + [:div.padding-options + [:div.padding-icon.tooltip.tooltip-bottom + {:class (dom/classnames :selected (= padding-type :simple)) + :alt (tr "workspace.options.layout.padding-simple") + :on-click #(change-padding-style :simple)} + i/auto-padding] + [:div.padding-icon.tooltip.tooltip-bottom + {:class (dom/classnames :selected (= padding-type :multiple)) + :alt (tr "workspace.options.layout.padding") + :on-click #(change-padding-style :multiple)} + i/auto-padding-side]] + + (cond + (= padding-type :simple) + [:div.tooltip.tooltip-bottom + {:alt (tr "workspace.options.layout.padding-all")} + [:div.input-element.mini + + [:> numeric-input + {:placeholder "--" + :on-click select-all + :on-change (partial on-padding-change :simple) + :value (:p1 (:padding @test-values))}]]] + + (= padding-type :multiple) + (for [num [:p1 :p2 :p3 :p4]] + [:div.tooltip.tooltip-bottom + {:key (dm/str "padding-" (d/name num)) + :alt (case num + :p1 (tr "workspace.options.layout.top") + :p2 (tr "workspace.options.layout.right") + :p3 (tr "workspace.options.layout.bottom") + :p4 (tr "workspace.options.layout.left"))} + [:div.input-element.mini + [:> numeric-input + {:placeholder "--" + :on-click select-all + :on-change (partial on-padding-change num) + :value (num (:padding @test-values))}]]]))])) + + + +(mf/defc layout-menu + {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type"]))]} + [{:keys [_ids _type _values] :as props}] + (let [test-values (mf/use-state {:layout false + :layout-dir nil + :gap 0 + :layout-type nil + :wrap-type nil + :padding-type nil + :padding {:p1 0 :p2 0 :p3 0 :p4 0} + :h-orientation nil + :v-orientation nil}) + + open? (mf/use-state false) + gap-selected? (mf/use-state false) + toggle-open (fn [] (swap! open? not)) + + on-add-layout + (fn [_] + (reset! test-values {:layout true + :layout-dir :left + :gap 0 + :layout-type :packed + :wrap-type :wrap + :padding-type :simple + :padding {:p1 0 :p2 0 :p3 0 :p4 0} + :h-orientation :top + :v-orientation :left})) + on-remove-layout + (fn [_] + (reset! test-values {:layout false + :layout-dir nil + :gap 0 + :layout-type nil + :wrap-type nil + :padding-type nil + :padding {:p1 0 :p2 0 :p3 0 :p4 0} + :h-orientation nil + :v-orientation nil}) + (reset! open? false)) + + set-direction + (fn [dir] + (swap! test-values assoc :layout-dir dir)) + + set-gap + (fn [event] + (swap! test-values assoc :gap event)) + + change-padding-style + (fn [type] + (swap! test-values assoc :padding-type type)) + + select-all #(dom/select-target %) + + select-all-gap #(do (reset! gap-selected? true) + (dom/select-target %)) + + on-padding-change + (fn [type val] + (if (= type :simple) + (swap! test-values assoc :padding {:p1 val :p2 val :p3 val :p4 val}) + (swap! test-values assoc-in [:padding type] val))) + + handle-change-type + (fn [event] + (let [target (dom/get-target event) + value (dom/get-value target) + value (keyword value)] + (swap! test-values assoc :layout-type value))) + + handle-wrap-type + (mf/use-callback + (fn [event] + (let [target (dom/get-target event) + value (dom/get-value target) + value (keyword value)] + (swap! test-values assoc :wrap-type value)))) + + manage-orientation + (fn [h v] + (swap! test-values assoc :h-orientation h :v-orientation v)) + + get-icon + (fn [dir layout-type v h] + (let [col? (= dir (or :left :right)) + manage-text-icon + (if col? + (case h + :left i/text-align-left + :center i/text-align-center + :right i/text-align-right + i/text-align-center) + + (case v + :top i/text-align-left + :center i/text-align-center + :bottom i/text-align-right + i/text-align-center))] + (case layout-type + :packed manage-text-icon + :space-around i/space-around + :space-between i/space-between))) + + layout-info + (fn [] + (let [type (:layout-type @test-values) + dir (:layout-dir @test-values) + is-col? (or (= dir :top) + (= dir :bottom)) + h (:v-orientation @test-values) + v (:h-orientation @test-values) + wrap (:wrap-type @test-values) + orientation (if (= type :packed) + (dm/str (tr (dm/str "workspace.options.layout.v." (d/name v))) ", " (tr (dm/str "workspace.options.layout.h." (d/name h))) ", ") + (if is-col? + (dm/str (tr (dm/str "workspace.options.layout.h." (d/name h))) ", ") + (dm/str (tr (dm/str "workspace.options.layout.v." (d/name v))) ", ")))] + + (dm/str orientation + (str/replace (tr (dm/str "workspace.options.layout." (d/name type))) "-" " ") ", " + (str/replace (tr (dm/str "workspace.options.layout." (d/name wrap))) "-" " "))))] + + [:div.element-set + [:div.element-set-title + [:* + [:span (tr "workspace.options.layout.title")] + (if (= true (:layout @test-values)) + [:div.add-page {:on-click on-remove-layout} i/minus] + [:div.add-page {:on-click on-add-layout} i/close])]] + + (when (= true (:layout @test-values)) + [:div.element-set-content.layout-menu + ;; DIRECTION-GAP + [:div.direction-gap + [:div.direction + [:* + (for [dir [:left :right :bottom :top]] + [:& direction-row {:dir dir + :saved-dir (:layout-dir @test-values) + :set-direction set-direction}])]] + [:div.gap.tooltip.tooltip-bottom-left + {:alt (tr "workspace.options.layout.gap")} + [:span.icon + {:class (dom/classnames + :rotated (or (= (:layout-dir @test-values) :top) + (= (:layout-dir @test-values) :bottom)) + :activated (= @gap-selected? true))} + i/auto-gap] + [:> numeric-input {:no-validate true + :placeholder "--" + :on-click select-all-gap + :on-change set-gap + :on-blur #(reset! gap-selected? false) + :value (:gap @test-values)}]]] + + ;; LAYOUT FLEX + [:div.layout-container + [:div.layout-entry.tooltip.tooltip-bottom + {:on-click toggle-open + :alt (layout-info)} + [:div.element-set-actions-button i/actions] + [:div.layout-info + (layout-info)]] + (when (= true @open?) + [:div.layout-body + [:& orientation-grid {:manage-orientation manage-orientation :test-values test-values :get-icon get-icon}] + + [:div.selects-wrapper + [:select.input-select {:value (d/name (:layout-type @test-values)) + :on-change handle-change-type} + [:option {:value "packed" :label (tr "workspace.options.layout.packed")}] + [:option {:value "space-between" :label (tr "workspace.options.layout.space-between")}] + [:option {:value "space-around" :label (tr "workspace.options.layout.space-around")}]] + + [:select.input-select {:value (d/name (:wrap-type @test-values)) + :on-change handle-wrap-type} + [:option {:value "wrap" :label (tr "workspace.options.layout.wrap")}] + [:option {:value "no-wrap" :label (tr "workspace.options.layout.no-wrap")}]]]])] + + [:& padding-section {:test-values test-values + :change-padding-style change-padding-style + :select-all select-all + :on-padding-change on-padding-change}]])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs new file mode 100644 index 0000000000..448211b1fa --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -0,0 +1,190 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.main.ui.workspace.sidebar.options.menus.layout-item + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.main.ui.components.numeric-input :refer [numeric-input]] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [rumext.alpha :as mf])) + +(def layout-item-attrs + [:margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} + :margin-type ;; :simple :multiple + :h-behavior ;; :fill :fix :auto + :v-behavior ;; :fill :fix :auto + :max-h ;; num + :min-h ;; num + :max-w ;; num + :min-w ]) ;; num +(mf/defc margin-section + [{:keys [test-values change-margin-style select-all on-margin-change] :as props}] + + (let [margin-type (:margin-type @test-values)] + + [:div.row-flex + [:div.margin-options + [:div.margin-icon.tooltip.tooltip-bottom + {:class (dom/classnames :selected (= margin-type :simple)) + :alt (tr "workspace.options.layout.margin-simple") + :on-click #(change-margin-style :simple)} + i/auto-margin] + [:div.margin-icon.tooltip.tooltip-bottom + {:class (dom/classnames :selected (= margin-type :multiple)) + :alt (tr "workspace.options.layout.margin") + :on-click #(change-margin-style :multiple)} + i/auto-margin-side]] + + (cond + (= margin-type :simple) + [:div.tooltip.tooltip-bottom + {:alt (tr "workspace.options.layout.margin-all")} + [:div.input-element.mini + + [:> numeric-input + {:placeholder "--" + :on-click select-all + :on-change (partial on-margin-change :simple) + :value (:m1 (:margin @test-values))}]]] + + (= margin-type :multiple) + [:* + (for [num [:m1 :m2 :m3 :m4]] + [:div.tooltip.tooltip-bottom + {:class (dm/str "margin-" (d/name num)) + :key (dm/str "margin-" (d/name num)) + :alt (case num + :m1 (tr "workspace.options.layout.top") + :m2 (tr "workspace.options.layout.right") + :m3 (tr "workspace.options.layout.bottom") + :m4 (tr "workspace.options.layout.left"))} + [:div.input-element.mini + [:> numeric-input + {:placeholder "--" + :on-click select-all + :on-change (partial on-margin-change num) + :value (num (:margin @test-values))}]]])])])) + +(mf/defc element-behavior + [{:keys [is-layout-container? is-layout-item? h-behavior v-behavior on-change-behavior] :as props}] + (let [auto? is-layout-container? + fill? (and (= true is-layout-item?) (not= true is-layout-container?))] + + [:div.layout-behavior + [:div.button-wrapper.horizontal + [:button.behavior-btn.tooltip.tooltip-bottom + {:alt "horizontal fix" + :class (dom/classnames :activated (= h-behavior :fix)) + :on-click #(on-change-behavior :h :fix)} + [:span.icon i/auto-fix-layout]] + (when fill? + [:button.behavior-btn.tooltip.tooltip-bottom + {:alt "horizontal fill" + :class (dom/classnames :activated (= h-behavior :fill)) + :on-click #(on-change-behavior :h :fill)} + [:span.icon i/auto-fill]]) + (when auto? + [:button.behavior-btn.tooltip.tooltip-bottom + {:alt "horizontal auto" + :class (dom/classnames :activated (= h-behavior :auto)) + :on-click #(on-change-behavior :h :auto)} + [:span.icon i/auto-hug]])] + [:div.button-wrapper + [:button.behavior-btn.tooltip.tooltip-bottom + {:alt "vertical fix" + :class (dom/classnames :activated (= v-behavior :fix)) + :on-click #(on-change-behavior :v :fix)} + [:span.icon i/auto-fix-layout]] + (when fill? + [:button.behavior-btn.tooltip.tooltip-bottom + {:alt "vertical fill" + :class (dom/classnames :activated (= v-behavior :fill)) + :on-click #(on-change-behavior :v :fill)} + [:span.icon i/auto-fill]]) + (when auto? + [:button.behavior-btn.tooltip.tooltip-bottom + {:alt "vertical auto" + :class (dom/classnames :activated (= v-behavior :auto)) + :on-click #(on-change-behavior :v :auto)} + [:span.icon i/auto-hug]])]])) + +(mf/defc layout-item-menu + {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type"]))]} + [{:keys [_ids _type _values] :as props}] + (let [test-values (mf/use-state {:margin {:m1 0 :m2 0 :m3 0 :m4 0} + :margin-type :simple + :h-behavior :fill + :v-behavior :fill + :max-h 100 + :min-h 100 + :max-w 100 + :min-w 100}) + open? (mf/use-state false) + toggle-open (fn [] (swap! open? not)) + is-layout-container? true + is-layout-item? true + change-margin-style + (fn [type] + (swap! test-values assoc :margin-type type)) + + select-all #(dom/select-target %) + + on-margin-change + (fn [type val] + (if (= type :simple) + (swap! test-values assoc :margin {:m1 val :m2 val :m3 val :m4 val}) + (swap! test-values assoc-in [:margin type] val))) + + on-change-behavior + (fn [dir value] + (if (= dir :h) + (swap! test-values assoc :h-behavior value) + (swap! test-values assoc :v-behavior value))) + + on-size-change + (fn [measure value] + (swap! test-values assoc measure value))] + [:div.element-set + [:div.element-set-title + [:span (tr "workspace.options.layout-item.title")]] + [:div.element-set-content.layout-item-menu + [:& element-behavior {:is-layout-container? is-layout-container? + :is-layout-item? is-layout-item? + :v-behavior (:v-behavior @test-values) + :h-behavior (:h-behavior @test-values) + :on-change-behavior on-change-behavior}] + [:div.margin [:& margin-section {:test-values test-values + :change-margin-style change-margin-style + :select-all select-all + :on-margin-change on-margin-change}]] + [:div.advanced-ops-container + [:div.advanced-ops.toltip.tooltip-bottom + {:on-click toggle-open + :alt (tr "workspace.options.layout-item.advanced-ops")} + [:div.element-set-actions-button i/actions] + [:span (tr "workspace.options.layout-item.advanced-ops")]]] + (when (= true @open?) + [:div.advanced-ops-body + (for [item [:max-h :min-h :max-w :min-w]] + [:div.input-element + {:alt (tr (dm/str "workspace.options.layout-item." (d/name item))) + :title (tr (dm/str "workspace.options.layout-item." (d/name item))) + :class (dom/classnames "maxH" (= item :max-h) + "minH" (= item :min-h) + "maxW" (= item :max-w) + "minW" (= item :min-w)) + :key item} + [:> numeric-input + {:no-validate true + :min 0 + :data-wrap true + :placeholder "--" + :on-click select-all + :on-change (partial on-size-change item) + :value (item @test-values)}]])])]])) 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 0977b1a632..5a44676bae 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 @@ -8,7 +8,8 @@ (:require [app.common.data :as d] [app.common.geom.shapes :as gsh] - [app.common.spec.radius :as ctr] + [app.common.types.shape.radius :as ctsr] + [app.main.constants :refer [size-presets]] [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dch] [app.main.refs :as refs] @@ -18,7 +19,7 @@ [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] - [clojure.set :refer [union]] + [clojure.set :refer [rename-keys union]] [rumext.alpha :as mf])) (def measure-attrs @@ -30,12 +31,14 @@ :rx :ry :r1 :r2 :r3 :r4 :selrect - :points]) + :points + :show-content + :hide-in-viewer]) (def ^:private type->options {:bool #{:size :position :rotation} :circle #{:size :position :rotation} - :frame #{:presets :size :position :radius} + :frame #{:presets :size :position :rotation :radius :clip-content :show-in-viewer} :group #{:size :position :rotation} :image #{:size :position :rotation :radius} :path #{:size :position :rotation} @@ -43,12 +46,26 @@ :svg-raw #{:size :position :rotation} :text #{:size :position :rotation}}) -(declare +size-presets+) +(defn select-measure-keys + "Consider some shapes can be drawn from bottom to top or from left to right" + [shape] + (let [shape (cond + (and (:flip-x shape) (:flip-y shape)) + (rename-keys shape {:r1 :r3 :r2 :r4 :r3 :r1 :r4 :r2}) + + (:flip-x shape) + (rename-keys shape {:r1 :r2 :r2 :r1 :r3 :r4 :r4 :r3}) + + (:flip-y shape) + (rename-keys shape {:r1 :r4 :r2 :r3 :r3 :r2 :r4 :r1}) + + :else + shape)] + (select-keys shape measure-attrs))) ;; -- User/drawing coords (mf/defc measures-menu [{:keys [ids ids-with-children values type all-types shape] :as props}] - (let [options (if (= type :multiple) (reduce #(union %1 %2) (map #(get type->options %) all-types)) (get type->options type)) @@ -77,8 +94,10 @@ ;; In case of multiple selection, the origin point has been already ;; calculated and given in the fake :ox and :oy attributes. See ;; common/src/app/common/attrs.cljc - (some? (:ox values)) (assoc :x (:ox values)) - (some? (:oy values)) (assoc :y (:oy values)))) + (and (= (:x values) :multiple) + (some? (:ox values))) (assoc :x (:ox values)) + (and (= (:y values) :multiple) + (some? (:oy values))) (assoc :y (:oy values)))) ;; For :height and :width we take those in the :selrect attribute, because ;; not all shapes have an own :width and :height (e. g. paths). Here the @@ -98,11 +117,14 @@ show-presets-dropdown? (mf/use-state false) - radius-mode (ctr/radius-mode values) - all-equal? (ctr/all-equal? values) + radius-mode (ctsr/radius-mode values) + all-equal? (ctsr/all-equal? values) radius-multi? (mf/use-state nil) radius-input-ref (mf/use-ref nil) + clip-content-ref (mf/use-ref nil) + show-in-viewer-ref (mf/use-ref nil) + on-preset-selected (fn [width height] (st/emit! (udw/update-dimensions ids :width width) @@ -146,34 +168,36 @@ change-radius (mf/use-callback - (mf/deps ids-with-children) - (fn [update-fn] - (dch/update-shapes ids-with-children - (fn [shape] - (if (ctr/has-radius? shape) - (update-fn shape) - shape))))) + (mf/deps ids-with-children) + (fn [update-fn] + (dch/update-shapes ids-with-children + (fn [shape] + (if (ctsr/has-radius? shape) + (update-fn shape) + shape)) + {:reg-objects? true + :attrs [:rx :ry :r1 :r2 :r3 :r4]}))) on-switch-to-radius-1 (mf/use-callback (mf/deps ids) (fn [_value] (if all-equal? - (st/emit! (change-radius ctr/switch-to-radius-1)) + (st/emit! (change-radius ctsr/switch-to-radius-1)) (reset! radius-multi? true)))) on-switch-to-radius-4 (mf/use-callback (mf/deps ids) (fn [_value] - (st/emit! (change-radius ctr/switch-to-radius-4)) + (st/emit! (change-radius ctsr/switch-to-radius-4)) (reset! radius-multi? false))) on-radius-1-change (mf/use-callback (mf/deps ids) (fn [value] - (st/emit! (change-radius #(ctr/set-radius-1 % value))))) + (st/emit! (change-radius #(ctsr/set-radius-1 % value))))) on-radius-multi-change (mf/use-callback @@ -181,15 +205,15 @@ (fn [event] (let [value (-> event dom/get-target dom/get-value d/parse-integer)] (when (some? value) - (st/emit! (change-radius ctr/switch-to-radius-1) - (change-radius #(ctr/set-radius-1 % value))) + (st/emit! (change-radius ctsr/switch-to-radius-1) + (change-radius #(ctsr/set-radius-1 % value))) (reset! radius-multi? false))))) on-radius-4-change (mf/use-callback (mf/deps ids) (fn [value attr] - (st/emit! (change-radius #(ctr/set-radius-4 % attr value))))) + (st/emit! (change-radius #(ctsr/set-radius-4 % attr value))))) on-width-change #(on-size-change % :width) on-height-change #(on-size-change % :height) @@ -200,17 +224,31 @@ on-radius-r3-change #(on-radius-4-change % :r3) on-radius-r4-change #(on-radius-4-change % :r4) + on-change-clip-content + (mf/use-callback + (mf/deps ids) + (fn [event] + (let [value (-> event dom/get-target dom/checked?)] + (st/emit! (dch/update-shapes ids (fn [shape] (assoc shape :show-content (not value)))))))) + + on-change-show-in-viewer + (mf/use-callback + (mf/deps ids) + (fn [event] + (let [value (-> event dom/get-target dom/checked?)] + (st/emit! (dch/update-shapes ids (fn [shape] (assoc shape :hide-in-viewer (not value)))))))) + select-all #(-> % (dom/get-target) (.select))] - - (mf/use-layout-effect - (mf/deps radius-mode @radius-multi?) - (fn [] - (when (and (= radius-mode :radius-1) - (= @radius-multi? false)) - ;; when going back from radius-multi to normal radius-1, - ;; restore focus to the newly created numeric-input - (let [radius-input (mf/ref-val radius-input-ref)] - (dom/focus! radius-input))))) + + (mf/use-layout-effect + (mf/deps radius-mode @radius-multi?) + (fn [] + (when (and (= radius-mode :radius-1) + (= @radius-multi? false)) + ;; when going back from radius-multi to normal radius-1, + ;; restore focus to the newly created numeric-input + (let [radius-input (mf/ref-val radius-input-ref)] + (dom/focus! radius-input))))) [:* [:div.element-set @@ -226,7 +264,7 @@ [:& dropdown {:show @show-presets-dropdown? :on-close #(reset! show-presets-dropdown? false)} [:ul.custom-select-dropdown - (for [size-preset +size-presets+] + (for [size-preset size-presets] (if-not (:width size-preset) [:li.dropdown-label {:key (:name size-preset)} [:span (:name size-preset)]] @@ -367,172 +405,30 @@ :min 0 :on-click select-all :on-change on-radius-r4-change - :value (:r4 values)}]]])])]]])) + :value (:r4 values)}]]])]) - (def +size-presets+ - [{:name "APPLE"} - {:name "iPhone 12/12 Pro" - :width 390 - :height 844} - {:name "iPhone 12 Mini" - :width 360 - :height 780} - {:name "iPhone 12 Pro Max" - :width 428 - :height 926} - {:name "iPhone X/XS/11 Pro" - :width 375 - :height 812} - {:name "iPhone XS Max/XR/11" - :width 414 - :height 896} - {:name "iPhone 6/7/8 Plus" - :width 414 - :height 736} - {:name "iPhone 6/7/8/SE2" - :width 375 - :height 667} - {:name "iPhone 5/SE" - :width 320 - :height 568} - {:name "iPad" - :width 768 - :height 1024} - {:name "iPad Pro 10.5in" - :width 834 - :height 1112} - {:name "iPad Pro 12.9in" - :width 1024 - :height 1366} - {:name "Watch 44mm" - :width 368 - :height 448} - {:name "Watch 42mm" - :width 312 - :height 390} - {:name "Watch 40mm" - :width 324 - :height 394} - {:name "Watch 38mm" - :width 272 - :height 340} + (when (options :clip-content) + [:div.input-checkbox + [:input {:type "checkbox" + :id "clip-content" + :ref clip-content-ref + :checked (not (:show-content values)) + :on-change on-change-clip-content}] - {:name "ANDROID"} - {:name "Mobile" - :width 360 - :height 640} - {:name "Tablet" - :width 768 - :height 1024} - {:name "Google Pixel 4a/5" - :width 393 - :height 851} - {:name "Samsung Galaxy S20+" - :width 384 - :height 854} - {:name "Samsung Galaxy A71/A51" - :width 412 - :height 914} + [:label {:for "clip-content"} + (tr "workspace.options.clip-content")]]) - {:name "MICROSOFT"} - {:name "Surface Pro 3" - :width 1440 - :height 960} - {:name "Surface Pro 4/5/6/7" - :width 1368 - :height 912} + (when (options :show-in-viewer) + [:div.input-checkbox + [:input {:type "checkbox" + :id "show-in-viewer" + :ref show-in-viewer-ref + :checked (not (:hide-in-viewer values)) + :on-change on-change-show-in-viewer}] - {:name "ReMarkable"} - {:name "Remarkable 2" - :width 840 - :height 1120} + [:label {:for "show-in-viewer"} + (tr "workspace.options.show-in-viewer")]]) - {:name "WEB"} - {:name "Web 1280" - :width 1280 - :height 800} - {:name "Web 1366" - :width 1366 - :height 768} - {:name "Web 1024" - :width 1024 - :height 768} - {:name "Web 1920" - :width 1920 - :height 1080} + ]]])) - {:name "PRINT (96dpi)"} - {:name "A0" - :width 3179 - :height 4494} - {:name "A1" - :width 2245 - :height 3179} - {:name "A2" - :width 1587 - :height 2245} - {:name "A3" - :width 1123 - :height 1587} - {:name "A4" - :width 794 - :height 1123} - {:name "A5" - :width 559 - :height 794} - {:name "A6" - :width 397 - :height 559} - {:name "Letter" - :width 816 - :height 1054} - {:name "DIN Lang" - :width 835 - :height 413} - {:name "SOCIAL MEDIA"} - {:name "Instagram profile" - :width 320 - :height 320} - {:name "Instagram post" - :width 1080 - :height 1080} - {:name "Instagram story" - :width 1080 - :height 1920} - {:name "Facebook profile" - :width 720 - :height 720} - {:name "Facebook cover" - :width 820 - :height 312} - {:name "Facebook post" - :width 1200 - :height 630} - {:name "LinkedIn profile" - :width 400 - :height 400} - {:name "LinkedIn cover" - :width 1584 - :height 396} - {:name "LinkedIn post" - :width 1200 - :height 627} - {:name "Twitter profile" - :width 400 - :height 400} - {:name "Twitter header" - :width 1500 - :height 500} - {:name "Twitter post" - :width 1024 - :height 512} - {:name "YouTube profile" - :width 800 - :height 800} - {:name "YouTube banner" - :width 2560 - :height 1440} - {:name "YouTube thumb" - :width 1280 - :height 720}]) 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 c8e6da475c..145ce30b9f 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 @@ -10,7 +10,6 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.exceptions :as ex] - [app.common.pages.helpers :as cph] [app.common.text :as txt] [app.main.data.fonts :as fts] [app.main.data.shortcuts :as dsc] @@ -559,7 +558,7 @@ [:input.element-name.adv-typography-name {:type "text" :ref name-input-ref - :default-value (cph/merge-path-item (:path typography) (:name typography)) + :default-value (:name typography) :on-blur on-name-blur}] [:div.element-set-actions-button 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 fd608efd18..3f2200139a 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 @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.rows.color-row (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.pages :as cp] [app.main.data.modal :as modal] [app.main.refs :as refs] @@ -22,34 +23,8 @@ [app.util.i18n :as i18n :refer [tr]] [rumext.alpha :as mf])) -(defn color-picker-callback - [color disable-gradient disable-opacity handle-change-color handle-open handle-close] - (fn [event] - (let [color - (cond - (uc/multiple? color) - {:color cp/default-color - :opacity 1} - - (= :multiple (:opacity color)) - (assoc color :opacity 1) - - :else - color) - - x (.-clientX event) - y (.-clientY event) - props {:x x - :y y - :disable-gradient disable-gradient - :disable-opacity disable-opacity - :on-change handle-change-color - :on-close handle-close - :data color}] - (handle-open color) - (modal/show! :colorpicker props)))) - -(defn opacity->string [opacity] +(defn opacity->string + [opacity] (if (= opacity :multiple) "" (str (-> opacity @@ -57,7 +32,8 @@ (* 100) (fmt/format-number))))) -(defn remove-multiple [v] +(defn remove-multiple + [v] (if (= v :multiple) nil v)) (mf/defc color-row @@ -68,64 +44,88 @@ file-colors (mf/deref refs/workspace-file-colors) shared-libs (mf/deref refs/workspace-libraries) hover-detach (mf/use-state false) + on-change (h/use-ref-callback on-change) + src-colors (if (= (:file-id color) current-file-id) + file-colors + (dm/get-in shared-libs [(:file-id color) :data :colors])) - on-change-var (h/use-update-var {:fn on-change}) + color-name (dm/get-in src-colors [(:id color) :name]) - src-colors (if (= (:file-id color) current-file-id) - file-colors - (get-in shared-libs [(:file-id color) :data :colors])) + parse-color + (mf/use-fn + (fn [color] + (update color :color #(or % (:value color))))) - color-name (get-in src-colors [(:id color) :name]) + detach-value + (mf/use-fn + (mf/deps on-detach color) + (fn [] + (when on-detach + (on-detach color)))) - parse-color (fn [color] - (-> color - (update :color #(or % (:value color))))) + handle-select + (mf/use-fn + (mf/deps select-only color) + (fn [] + (select-only color))) - detach-value (fn [] - (when on-detach (on-detach color))) + handle-value-change + (mf/use-fn + (mf/deps color on-change) + (fn [new-value] + (on-change (-> color + (assoc :color new-value) + (dissoc :gradient))))) - change-value (fn [new-value] - (when (:fn @on-change-var) ((:fn @on-change-var) (-> color - (assoc :color new-value) - (dissoc :gradient))))) + handle-opacity-change + (mf/use-fn + (mf/deps color on-change) + (fn [value] + (on-change (assoc color + :opacity (/ value 100) + :id nil + :file-id nil)))) - change-opacity (fn [new-opacity] - (when (:fn @on-change-var) ((:fn @on-change-var) (assoc color - :opacity new-opacity - :id nil - :file-id nil)))) + handle-click-color + (mf/use-fn + (mf/deps disable-gradient disable-opacity on-change on-close on-open) + (fn [color event] + (let [color (cond + (uc/multiple? color) + {:color cp/default-color + :opacity 1} - handle-pick-color (fn [color] - (when (:fn @on-change-var) ((:fn @on-change-var) (merge uc/empty-color color)))) + (= :multiple (:opacity color)) + (assoc color :opacity 1) - handle-select (fn [] - (select-only color)) + :else + color) - handle-open (fn [color] - (when on-open (on-open (merge uc/empty-color color)))) + {:keys [x y]} (dom/get-client-position event) - handle-close (fn [value opacity id file-id] - (when on-close (on-close value opacity id file-id))) + props {:x x + :y y + :disable-gradient disable-gradient + :disable-opacity disable-opacity + :on-change #(on-change (merge uc/empty-color %)) + :on-close (fn [value opacity id file-id] + (when on-close + (on-close value opacity id file-id))) + :data color}] - handle-value-change (fn [new-value] - (-> new-value - change-value)) + (when on-open + (on-open (merge uc/empty-color color))) - handle-opacity-change (fn [value] - (change-opacity (/ value 100))) + (modal/show! :colorpicker props)))) - handle-click-color (color-picker-callback color - disable-gradient - disable-opacity - handle-pick-color - handle-open - handle-close) prev-color (h/use-previous color) on-drop - (fn [_ data] - (on-reorder (:index data))) + (mf/use-fn + (mf/deps on-reorder) + (fn [_ data] + (on-reorder (:index data)))) [dprops dref] (if (some? on-reorder) (h/use-sortable @@ -138,11 +138,9 @@ :name (str "Color row" index)}) [nil nil])] - (mf/use-effect - (mf/deps color prev-color) - (fn [] - (when (not= prev-color color) - (modal/update-props! :colorpicker {:data (parse-color color)})))) + (mf/with-effect [color prev-color] + (when (not= prev-color color) + (modal/update-props! :colorpicker {:data (parse-color color)}))) [:div.row-flex.color-data {:title title :class (dom/classnames @@ -182,7 +180,7 @@ (when select-only [:div.element-set-actions-button {:on-click handle-select} i/pointer-inner])] - + ;; Rendering a plain color/opacity :else diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs index 23367cdda4..69517bad68 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs @@ -6,10 +6,12 @@ (ns app.main.ui.workspace.sidebar.options.shapes.bool (:require + [app.main.constants :refer [has-layout-item]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] @@ -22,12 +24,18 @@ measure-values (select-keys shape measure-attrs) stroke-values (select-keys shape stroke-attrs) layer-values (select-keys shape layer-attrs) - constraint-values (select-keys shape constraint-attrs)] + constraint-values (select-keys shape constraint-attrs) + layout-item-values (select-keys shape layout-item-attrs)] [:* [:& measures-menu {:ids ids :type type :values measure-values :shape shape}] + (when has-layout-item + [:& layout-item-menu {:ids ids + :type type + :values layout-item-values + :shape shape}]) [:& constraints-menu {:ids ids :values constraint-values}] [:& layer-menu {:ids ids 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 5dbeada465..845db69616 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 @@ -6,10 +6,12 @@ (ns app.main.ui.workspace.sidebar.options.shapes.circle (:require + [app.main.constants :refer [has-layout-item]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] @@ -23,12 +25,18 @@ measure-values (select-keys shape measure-attrs) stroke-values (select-keys shape stroke-attrs) layer-values (select-keys shape layer-attrs) - constraint-values (select-keys shape constraint-attrs)] + constraint-values (select-keys shape constraint-attrs) + layout-item-values (select-keys shape layout-item-attrs)] [:* [:& measures-menu {:ids ids :type type :values measure-values :shape shape}] + (when has-layout-item + [:& layout-item-menu {:ids ids + :type type + :values layout-item-values + :shape shape}]) [:& constraints-menu {:ids ids :values constraint-values}] [:& layer-menu {:ids ids 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 18ef3950d8..13aa47a60e 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 @@ -6,10 +6,14 @@ (ns app.main.ui.workspace.sidebar.options.shapes.frame (:require + [app.main.constants :refer [has-layout-item]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] + [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]] [app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout :refer [layout-attrs layout-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] @@ -21,12 +25,25 @@ type (:type shape) stroke-values (select-keys shape stroke-attrs) layer-values (select-keys shape layer-attrs) - measure-values (select-keys shape measure-attrs)] + measure-values (select-keys shape measure-attrs) + constraint-values (select-keys shape constraint-attrs) + layout-values (select-keys shape layout-attrs) + layout-item-values (select-keys shape layout-item-attrs)] [:* [:& measures-menu {:ids [(:id shape)] :values measure-values :type type :shape shape}] + [:& constraints-menu {:ids ids + :values constraint-values}] + (when has-layout-item + [:& layout-menu {:type type :ids [(:id shape)] :values layout-values}]) + + (when has-layout-item + [:& layout-item-menu {:ids ids + :type type + :values layout-item-values + :shape shape}]) [:& layer-menu {:ids ids :type type :values layer-values}] 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 e3ae1c9229..3a4e92055a 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 @@ -7,12 +7,15 @@ (ns app.main.ui.workspace.sidebar.options.shapes.group (:require [app.common.data :as d] + [app.main.constants :refer [has-layout-item]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]] [app.main.ui.workspace.sidebar.options.menus.component :refer [component-attrs component-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout :refer [layout-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-menu]] @@ -32,20 +35,27 @@ file-id (unchecked-get props "file-id") type :group - [measure-ids measure-values] (get-attrs [shape] objects :measure) - [layer-ids layer-values] (get-attrs [shape] objects :layer) - [constraint-ids constraint-values] (get-attrs [shape] objects :constraint) - [fill-ids fill-values] (get-attrs [shape] objects :fill) - [shadow-ids shadow-values] (get-attrs [shape] objects :shadow) - [blur-ids blur-values] (get-attrs [shape] objects :blur) - [stroke-ids stroke-values] (get-attrs [shape] objects :stroke) - [text-ids text-values] (get-attrs [shape] objects :text) - [svg-ids svg-values] [[(:id shape)] (select-keys shape [:svg-attrs])] - [comp-ids comp-values] [[(:id shape)] (select-keys shape component-attrs)]] + [measure-ids measure-values] (get-attrs [shape] objects :measure) + [layer-ids layer-values] (get-attrs [shape] objects :layer) + [constraint-ids constraint-values] (get-attrs [shape] objects :constraint) + [fill-ids fill-values] (get-attrs [shape] objects :fill) + [shadow-ids shadow-values] (get-attrs [shape] objects :shadow) + [blur-ids blur-values] (get-attrs [shape] objects :blur) + [stroke-ids stroke-values] (get-attrs [shape] objects :stroke) + [text-ids text-values] (get-attrs [shape] objects :text) + [svg-ids svg-values] [[(:id shape)] (select-keys shape [:svg-attrs])] + [comp-ids comp-values] [[(:id shape)] (select-keys shape component-attrs)] + [layout-ids layout-values] (get-attrs [shape] objects :layout) + [layout-item-ids layout-item-values] (get-attrs [shape] objects :layout-item) + ] [:div.options [:& measures-menu {:type type :ids measure-ids :values measure-values :shape shape}] - [:& component-menu {:ids comp-ids :values comp-values}] + [:& component-menu {:ids comp-ids :values comp-values :shape-name (:name shape)}] + (when-not (empty? layout-ids) + [:& layout-menu {:type type :ids layout-ids :values layout-values}]) + (when has-layout-item + [:& layout-item-menu {:type type :ids layout-item-ids :values layout-item-values}]) [:& constraints-menu {:ids constraint-ids :values constraint-values}] [:& layer-menu {:type type :ids layer-ids :values layer-values}] 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 077ecda693..b6d37e3a53 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 @@ -6,10 +6,12 @@ (ns app.main.ui.workspace.sidebar.options.shapes.image (:require + [app.main.constants :refer [has-layout-item]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] @@ -23,12 +25,18 @@ layer-values (select-keys shape layer-attrs) constraint-values (select-keys shape constraint-attrs) fill-values (select-keys shape fill-attrs) - stroke-values (select-keys shape stroke-attrs)] + stroke-values (select-keys shape stroke-attrs) + layout-item-values (select-keys shape layout-item-attrs)] [:* [:& measures-menu {:ids ids :type type :values measure-values :shape shape}] + (when has-layout-item + [:& layout-item-menu {:ids ids + :type type + :values layout-item-values + :shape shape}]) [:& constraints-menu {:ids ids :values constraint-values}] 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 adbddb5748..e3edc9d2ea 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 @@ -11,6 +11,7 @@ [app.common.geom.shapes :as gsh] [app.common.pages.common :as cpc] [app.common.text :as txt] + [app.main.constants :refer [has-layout-item]] [app.main.ui.hooks :as hooks] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-attrs blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]] @@ -18,11 +19,13 @@ [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-attrs exports-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] - [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout :refer [layout-attrs layout-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] + [app.main.ui.workspace.sidebar.options.menus.measures :refer [select-measure-keys measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-attrs shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.menus.text :as ot] - [rumext.alpha :as mf])) + [rumext.alpha :as mf])) ;; Define how to read each kind of attribute depending on the shape type: ;; - shape: read the attribute directly from the shape. @@ -130,15 +133,17 @@ :exports :shape}}) (def group->attrs - {:measure measure-attrs - :layer layer-attrs - :constraint constraint-attrs - :fill fill-attrs - :shadow shadow-attrs - :blur blur-attrs - :stroke stroke-attrs - :text ot/attrs - :exports exports-attrs}) + {:measure measure-attrs + :layer layer-attrs + :constraint constraint-attrs + :fill fill-attrs + :shadow shadow-attrs + :blur blur-attrs + :stroke stroke-attrs + :text ot/attrs + :exports exports-attrs + :layout layout-attrs + :layout-item layout-item-attrs}) (def shadow-keys [:style :color :offset-x :offset-y :blur :spread]) @@ -192,8 +197,10 @@ :shape (let [;; Get the editable attrs from the shape, ensuring that all attributes ;; are present, with value nil if they are not present in the shape. shape-values (merge - (into {} (map #(vector % nil)) editable-attrs) - (select-keys shape editable-attrs))] + (into {} (map #(vector % nil)) editable-attrs) + (cond + (= attr-group :measure) (select-measure-keys shape) + :else (select-keys shape editable-attrs)))] [(conj ids id) (merge-attrs values shape-values)]) @@ -243,17 +250,20 @@ all-types (into #{} (map :type shapes)) has-text? (contains? all-types :text) - + [measure-ids measure-values] (get-attrs shapes objects :measure) - [layer-ids layer-values - constraint-ids constraint-values - fill-ids fill-values - shadow-ids shadow-values - blur-ids blur-values - stroke-ids stroke-values - text-ids text-values - exports-ids exports-values] + + [layer-ids layer-values + constraint-ids constraint-values + fill-ids fill-values + shadow-ids shadow-values + blur-ids blur-values + stroke-ids stroke-values + text-ids text-values + exports-ids exports-values + layout-ids layout-values + layout-item-ids layout-item-values] (mf/use-memo (mf/deps objects-no-measures) (fn [] @@ -267,12 +277,21 @@ (get-attrs shapes objects-no-measures :blur) (get-attrs shapes objects-no-measures :stroke) (get-attrs shapes objects-no-measures :text) - (get-attrs shapes objects-no-measures :exports)])))] + (get-attrs shapes objects-no-measures :exports) + (get-attrs shapes objects-no-measures :layout) + (get-attrs shapes objects-no-measures :layout-item) + ])))] [:div.options (when-not (empty? measure-ids) [:& measures-menu {:type type :all-types all-types :ids measure-ids :values measure-values :shape shapes}]) + (when-not (empty? layout-ids) + [:& layout-menu {:type type :ids layout-ids :values layout-values}]) + + (when has-layout-item + [:& layout-item-menu {:type type :ids layout-item-ids :values layout-item-values}]) + (when-not (empty? constraint-ids) [:& constraints-menu {:ids constraint-ids :values constraint-values}]) @@ -285,7 +304,7 @@ (when-not (empty? stroke-ids) [:& stroke-menu {:type type :ids stroke-ids :show-caps show-caps :values stroke-values :disable-stroke-style has-text?}]) - + (when-not (empty? shapes) [:& color-selection-menu {:file-id file-id :type type :shapes (vals objects-no-measures) :shared-libs shared-libs}]) 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 be8bb8e1e8..b7f4265fae 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 @@ -6,10 +6,12 @@ (ns app.main.ui.workspace.sidebar.options.shapes.path (:require + [app.main.constants :refer [has-layout-item]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] @@ -23,12 +25,18 @@ measure-values (select-keys shape measure-attrs) stroke-values (select-keys shape stroke-attrs) layer-values (select-keys shape layer-attrs) - constraint-values (select-keys shape constraint-attrs)] + constraint-values (select-keys shape constraint-attrs) + layout-item-values (select-keys shape layout-item-attrs)] [:* [:& measures-menu {:ids ids :type type :values measure-values :shape shape}] + (when has-layout-item + [:& layout-item-menu {:ids ids + :type type + :values layout-item-values + :shape shape}]) [:& constraints-menu {:ids ids :values constraint-values}] [:& layer-menu {:ids ids 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 825e8e3eba..723050df70 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 @@ -6,11 +6,13 @@ (ns app.main.ui.workspace.sidebar.options.shapes.rect (:require + [app.main.constants :refer [has-layout-item]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] - [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] + [app.main.ui.workspace.sidebar.options.menus.measures :refer [select-measure-keys measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]] @@ -21,17 +23,22 @@ [{:keys [shape] :as props}] (let [ids [(:id shape)] type (:type shape) - measure-values (select-keys shape measure-attrs) + measure-values (select-measure-keys shape) layer-values (select-keys shape layer-attrs) constraint-values (select-keys shape constraint-attrs) fill-values (select-keys shape fill-attrs) - stroke-values (select-keys shape stroke-attrs)] + stroke-values (select-keys shape stroke-attrs) + layout-item-values (select-keys shape layout-item-attrs)] [:* [:& measures-menu {:ids ids :type type :values measure-values :shape shape}] - + (when has-layout-item + [:& layout-item-menu {:ids ids + :type type + :values layout-item-values + :shape shape}]) [:& constraints-menu {:ids ids :values constraint-values}] 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 c21726de83..331cc6d558 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 @@ -8,9 +8,11 @@ (:require [app.common.colors :as clr] [app.common.data :as d] + [app.main.constants :refer [has-layout-item]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] @@ -97,7 +99,8 @@ measure-values (select-keys shape measure-attrs) constraint-values (select-keys shape constraint-attrs) fill-values (get-fill-values shape) - stroke-values (get-stroke-values shape)] + stroke-values (get-stroke-values shape) + layout-item-values (select-keys shape layout-item-attrs)] (when (contains? svg-elements tag) [:* @@ -105,6 +108,11 @@ :type type :values measure-values :shape shape}] + (when has-layout-item + [:& layout-item-menu {:ids ids + :type type + :values layout-item-values + :shape shape}]) [:& constraints-menu {:ids ids :values constraint-values}] 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 5cdf25e46f..98e2e7849a 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 @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.shapes.text (:require [app.common.data :as d] + [app.main.constants :refer [has-layout-item]] [app.main.data.workspace.texts :as dwt] [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] @@ -14,6 +15,7 @@ [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu fill-attrs]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] @@ -58,7 +60,8 @@ (dwt/current-text-values {:editor-state editor-state :shape shape - :attrs text-attrs}))] + :attrs text-attrs})) + layout-item-values (select-keys shape layout-item-attrs)] [:* [:& measures-menu @@ -66,7 +69,11 @@ :type type :values (select-keys shape measure-attrs) :shape shape}] - + (when has-layout-item + [:& layout-item-menu {:ids ids + :type type + :values layout-item-values + :shape shape}]) [:& constraints-menu {:ids ids :values (select-keys shape constraint-attrs)}] @@ -74,7 +81,7 @@ [:& layer-menu {:ids ids :type type :values layer-values}] - + [:& text-menu {:ids ids :type type @@ -99,6 +106,4 @@ [:& blur-menu {:ids ids - :values (select-keys shape [:blur])}] - - ])) + :values (select-keys shape [:blur])}]])) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 88ee7e26b4..3dfbe8cb88 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -10,6 +10,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] + [app.common.pages.helpers :as cph] [app.main.refs :as refs] [app.main.ui.context :as ctx] [app.main.ui.hooks :as ui-hooks] @@ -19,7 +20,7 @@ [app.main.ui.workspace.shapes :as shapes] [app.main.ui.workspace.shapes.text.editor :as editor] [app.main.ui.workspace.shapes.text.text-edition-outline :refer [text-edition-outline]] - [app.main.ui.workspace.shapes.text.viewport-texts :as stv] + [app.main.ui.workspace.shapes.text.viewport-texts-html :as stvh] [app.main.ui.workspace.viewport.actions :as actions] [app.main.ui.workspace.viewport.comments :as comments] [app.main.ui.workspace.viewport.drawarea :as drawarea] @@ -155,7 +156,13 @@ show-draw-area? drawing-obj show-gradient-handlers? (= (count selected) 1) show-grids? (contains? layout :display-grid) - show-outlines? (and (nil? transform) (not edition) (not drawing-obj) (not (#{:comments :path :curve} drawing-tool))) + + show-frame-outline? (= transform :move) + show-outlines? (and (nil? transform) + (not edition) + (not drawing-obj) + (not (#{:comments :path :curve} drawing-tool))) + show-pixel-grid? (and (contains? layout :show-pixel-grid) (>= zoom 8)) show-text-editor? (and editing-shape (= :text (:type editing-shape))) @@ -187,6 +194,13 @@ [:div.viewport [:div.viewport-overlays {:ref overlays-ref} + [:div {:style {:pointer-events "none" :opacity 0}} + [:& stvh/viewport-texts + {:key (dm/str "texts-" page-id) + :page-id page-id + :objects objects + :modifiers modifiers + :edition edition}]] (when show-comments? [:& comments/comments-layer {:vbox vbox @@ -229,23 +243,6 @@ :objects base-objects :active-frames @active-frames}]]]] - [:svg.render-shapes - {:id "text-position-layer" - :xmlns "http://www.w3.org/2000/svg" - :xmlnsXlink "http://www.w3.org/1999/xlink" - :preserveAspectRatio "xMidYMid meet" - :key (str "text-position-layer" page-id) - :width (:width vport 0) - :height (:height vport 0) - :view-box (utils/format-viewbox vbox)} - - [:g {:pointer-events "none" :opacity 0} - [:& stv/viewport-texts {:key (dm/str "texts-" page-id) - :page-id page-id - :objects objects - :modifiers modifiers - :edition edition}]]] - [:svg.viewport-controls {:xmlns "http://www.w3.org/2000/svg" :xmlnsXlink "http://www.w3.org/1999/xlink" @@ -275,16 +272,20 @@ (when show-text-editor? [:& editor/text-editor-svg {:shape editing-shape}]) + (when show-frame-outline? + [:& outline/shape-outlines + {:objects base-objects + :hover #{(->> @hover-ids + (filter #(cph/frame-shape? base-objects %)) + (remove selected) + (first))} + :zoom zoom}]) + (when show-outlines? [:& outline/shape-outlines {:objects base-objects :selected selected - :hover (cond - (and @hover (or @mod? (not= :frame (:type @hover)))) - #{(:id @hover)} - - @frame-hover - #{@frame-hover}) + :hover #{(:id @hover) @frame-hover} :edition edition :zoom zoom}]) @@ -311,10 +312,9 @@ :zoom zoom}]) [:& widgets/frame-titles - {:objects objects-modified + {:objects base-objects :selected selected :zoom zoom - :modifiers modifiers :show-artboard-names? show-artboard-names? :on-frame-enter on-frame-enter :on-frame-leave on-frame-leave @@ -391,14 +391,6 @@ [:& widgets/viewport-actions] - (when show-prototypes? - [:& interactions/interactions - {:selected selected - :zoom zoom - :objects objects-modified - :current-transform transform - :hover-disabled? hover-disabled?}]) - [:& scroll-bars/viewport-scrollbars {:objects base-objects :zoom zoom @@ -435,6 +427,12 @@ :shapes selected-shapes :zoom zoom :edition edition - :disable-handlers (or drawing-tool edition @space?)}]]) + :disable-handlers (or drawing-tool edition @space?)}] - ]]])) + (when show-prototypes? + [:& interactions/interactions + {:selected selected + :zoom zoom + :objects objects-modified + :current-transform transform + :hover-disabled? hover-disabled?}])])]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 32aaf6e0c7..c7e8b1eb40 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -8,6 +8,7 @@ (:require [app.common.geom.point :as gpt] [app.common.math :as mth] + [app.common.pages.helpers :as cph] [app.common.uuid :as uuid] [app.config :as cfg] [app.main.data.workspace :as dw] @@ -50,10 +51,7 @@ mod? (kbd/mod? event) left-click? (and (not panning) (= 1 (.-which event))) - middle-click? (and (not panning) (= 2 (.-which event))) - - frame? (= :frame type) - selected? (contains? selected id)] + middle-click? (and (not panning) (= 2 (.-which event)))] (cond middle-click? @@ -96,7 +94,7 @@ drawing-tool (st/emit! (dd/start-drawing drawing-tool)) - (or (not id) (and frame? (not selected?)) mod?) + (or (not id) mod?) (st/emit! (dw/handle-area-selection shift? mod?)) (not drawing-tool) @@ -156,13 +154,10 @@ shift? (kbd/shift? event) alt? (kbd/alt? event) meta? (kbd/meta? event) - mod? (kbd/mod? event) - hovering? (some? @hover) - frame? (= :frame (:type @hover))] + hovering? (some? @hover)] (st/emit! (ms/->MouseEvent :click ctrl? shift? alt? meta?)) (when (and hovering? - (or (not frame?) mod?) (not @space?) (not edition) (not drawing-path?) @@ -171,6 +166,7 @@ (defn on-double-click [hover hover-ids drawing-path? objects edition] + (mf/use-callback (mf/deps @hover @hover-ids drawing-path? edition) (fn [event] @@ -180,30 +176,28 @@ alt? (kbd/alt? event) meta? (kbd/meta? event) - {:keys [id type] :as shape} @hover + {:keys [id type] :as shape} (or @hover (get objects (first @hover-ids))) - frame? (= :frame type) - group? (= :group type)] + editable? (contains? #{:text :rect :path :image :circle} type)] (st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt? meta?)) ;; Emit asynchronously so the double click to exit shapes won't break (timers/schedule - #(when (and (not drawing-path?) shape) - (cond - frame? - (st/emit! (dw/select-shape id shift?)) + (fn [] + (when (and (not drawing-path?) shape) + (cond + (and editable? (not= id edition)) + (st/emit! (dw/select-shape id) + (dw/start-editing-selected)) - (and group? (> (count @hover-ids) 1)) - (let [selected (get objects (second @hover-ids))] - (reset! hover selected) - (reset! hover-ids (into [] (rest @hover-ids))) - - (st/emit! (dw/select-shape (:id selected)))) - - (not= id edition) - (st/emit! (dw/select-shape id) - (dw/start-editing-selected))))))))) + :else + (let [;; We only get inside childrens of the hovering shape + hover-ids (->> @hover-ids (filter (partial cph/is-child? objects id))) + selected (get objects (if (> (count hover-ids) 1) (second hover-ids) (first hover-ids)))] + (when (some? selected) + (reset! hover selected) + (st/emit! (dw/select-shape (:id selected))))))))))))) (defn on-context-menu [hover hover-ids] diff --git a/frontend/src/app/main/ui/workspace/viewport/comments.cljs b/frontend/src/app/main/ui/workspace/viewport/comments.cljs index da82aeaf99..34dd92aebb 100644 --- a/frontend/src/app/main/ui/workspace/viewport/comments.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/comments.cljs @@ -12,27 +12,32 @@ [app.main.store :as st] [app.main.ui.comments :as cmt] [cuerdas.core :as str] + [okulary.core :as l] [rumext.alpha :as mf])) (mf/defc comments-layer [{:keys [vbox vport zoom file-id page-id drawing] :as props}] - (let [pos-x (* (- (:x vbox)) zoom) - pos-y (* (- (:y vbox)) zoom) + (let [pos-x (* (- (:x vbox)) zoom) + pos-y (* (- (:y vbox)) zoom) - profile (mf/deref refs/profile) - users (mf/deref refs/users) - local (mf/deref refs/comments-local) - threads-map (mf/deref refs/threads-ref) + profile (mf/deref refs/profile) + users (mf/deref refs/current-file-comments-users) + local (mf/deref refs/comments-local) + threads-position-ref (l/derived (l/in [:workspace-data :pages-index page-id :options :comment-threads-position]) st/state) + threads-position-map (mf/deref threads-position-ref) + threads-map (mf/deref refs/threads-ref) - threads (->> (vals threads-map) - (filter #(= (:page-id %) page-id)) - (dcm/apply-filters local profile)) + update-thread-position (fn update-thread-position [thread] + (if (contains? threads-position-map (:id thread)) + (-> thread + (assoc :position (get-in threads-position-map [(:id thread) :position])) + (assoc :frame-id (get-in threads-position-map [(:id thread) :frame-id]))) + thread)) - on-bubble-click - (fn [{:keys [id] :as thread}] - (if (= (:open local) id) - (st/emit! (dcm/close-thread)) - (st/emit! (dcm/open-thread thread)))) + threads (->> (vals threads-map) + (filter #(= (:page-id %) page-id)) + (mapv update-thread-position) + (dcm/apply-filters local profile)) on-draft-cancel (mf/use-callback @@ -41,7 +46,7 @@ on-draft-submit (mf/use-callback (fn [draft] - (st/emit! (dcm/create-thread draft))))] + (st/emit! (dcm/create-thread-on-workspace draft))))] (mf/use-effect (mf/deps file-id) @@ -58,13 +63,12 @@ (for [item threads] [:& cmt/thread-bubble {:thread item :zoom zoom - :on-click on-bubble-click :open? (= (:id item) (:open local)) :key (:seqn item)}]) (when-let [id (:open local)] (when-let [thread (get threads-map id)] - [:& cmt/thread-comments {:thread thread + [:& cmt/thread-comments {:thread (update-thread-position thread) :users users :zoom zoom}])) @@ -73,5 +77,3 @@ :on-cancel on-draft-cancel :on-submit on-draft-submit :zoom zoom}])]]])) - - 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 013c1cee50..ece6b20b22 100644 --- a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.math :as mth] + [app.common.pages.helpers :as cph] [app.common.uuid :as uuid] [app.main.refs :as refs] [app.util.geom.grid :as gg] @@ -126,13 +127,15 @@ (mf/defc frame-grid {::mf/wrap [mf/memo]} [{:keys [zoom transform selected focus]}] - (let [frames (mf/deref refs/workspace-frames) - moving (when (= :move transform) selected) - is-moving? #(contains? moving (:id %))] + (let [frames (mf/deref refs/workspace-frames) + transforming (when (some? transform) selected) + is-transform? #(contains? transforming (:id %))] [:g.grid-display {:style {:pointer-events "none"}} - (for [frame (remove is-moving? frames)] - (when (or (empty? focus) (contains? focus (:id frame))) + (for [frame frames] + (when (and (not (is-transform? frame)) + (not (cph/rotated-frame? frame)) + (or (empty? focus) (contains? focus (:id frame)))) [:& grid-display-frame {:key (str "grid-" (:id frame)) :zoom zoom :frame (gsh/transform-shape frame)}]))])) diff --git a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs index 08b524f0ba..4648836473 100644 --- a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs @@ -19,7 +19,6 @@ [app.util.dom :as dom] [beicon.core :as rx] [cuerdas.core :as str] - [okulary.core :as l] [rumext.alpha :as mf])) (def gradient-line-stroke-width 2) @@ -32,12 +31,6 @@ (def gradient-square-stroke-color "var(--color-white)") (def gradient-square-stroke-color-selected "var(--color-select)") -(def editing-spot-ref - (l/derived (l/in [:workspace-global :editing-stop]) st/state)) - -(def current-gradient-ref - (l/derived (l/in [:workspace-global :current-gradient]) st/state =)) - (mf/defc shadow [{:keys [id x y width height offset]}] [:filter {:id id :x x @@ -130,27 +123,32 @@ (let [moving-point (mf/use-var nil) angle (+ 90 (gpt/angle from-p to-p)) - on-click (fn [position event] - (dom/stop-propagation event) - (dom/prevent-default event) - (when (#{:from-p :to-p} position) - (st/emit! (dc/select-gradient-stop (case position - :from-p 0 - :to-p 1))))) + on-click + (fn [position event] + (dom/stop-propagation event) + (dom/prevent-default event) + (when (#{:from-p :to-p} position) + (st/emit! (dc/select-colorpicker-gradient-stop + (case position + :from-p 0 + :to-p 1))))) - on-mouse-down (fn [position event] - (dom/stop-propagation event) - (dom/prevent-default event) - (reset! moving-point position) - (when (#{:from-p :to-p} position) - (st/emit! (dc/select-gradient-stop (case position - :from-p 0 - :to-p 1))))) + on-mouse-down + (fn [position event] + (dom/stop-propagation event) + (dom/prevent-default event) + (reset! moving-point position) + (when (#{:from-p :to-p} position) + (st/emit! (dc/select-colorpicker-gradient-stop + (case position + :from-p 0 + :to-p 1))))) - on-mouse-up (fn [_position event] - (dom/stop-propagation event) - (dom/prevent-default event) - (reset! moving-point nil))] + on-mouse-up + (fn [_position event] + (dom/stop-propagation event) + (dom/prevent-default event) + (reset! moving-point nil))] (mf/use-effect (mf/deps @moving-point from-p to-p width-p) @@ -230,37 +228,24 @@ :on-mouse-down (partial on-mouse-down :to-p) :on-mouse-up (partial on-mouse-up :to-p)}]])) - -(mf/defc gradient-handlers - {::mf/wrap [mf/memo]} - [{:keys [id zoom]}] - (let [current-change (mf/use-state {}) - shape-ref (mf/use-memo (mf/deps id) #(refs/object-by-id id)) - shape (mf/deref shape-ref) - - gradient (mf/deref current-gradient-ref) - gradient (merge gradient @current-change) - - editing-spot (mf/deref editing-spot-ref) - - transform (gsh/transform-matrix shape) +(mf/defc gradient-handlers* + [{:keys [zoom stops gradient editing-stop shape]}] + (let [transform (gsh/transform-matrix shape) transform-inverse (gsh/inverse-transform-matrix shape) {:keys [x y width height] :as sr} (:selrect shape) [{start-color :color start-opacity :opacity} - {end-color :color end-opacity :opacity}] (:stops gradient) + {end-color :color end-opacity :opacity}] stops from-p (-> (gpt/point (+ x (* width (:start-x gradient))) (+ y (* height (:start-y gradient)))) - (gpt/transform transform)) - to-p (-> (gpt/point (+ x (* width (:end-x gradient))) (+ y (* height (:end-y gradient)))) (gpt/transform transform)) - gradient-vec (gpt/to-vec from-p to-p) + gradient-vec (gpt/to-vec from-p to-p) gradient-length (gpt/length gradient-vec) width-v (-> gradient-vec @@ -271,13 +256,12 @@ width-p (gpt/add from-p width-v) change! - (mf/use-callback + (mf/use-fn (fn [changes] - (swap! current-change merge changes) - (st/emit! (dc/update-gradient changes)))) + (st/emit! (dc/update-colorpicker-gradient changes)))) on-change-start - (mf/use-callback + (mf/use-fn (mf/deps transform-inverse width height) (fn [point] (let [point (gpt/transform point transform-inverse) @@ -286,7 +270,7 @@ (change! {:start-x start-x :start-y start-y})))) on-change-finish - (mf/use-callback + (mf/use-fn (mf/deps transform-inverse width height) (fn [point] (let [point (gpt/transform point transform-inverse) @@ -295,7 +279,7 @@ (change! {:end-x end-x :end-y end-y})))) on-change-width - (mf/use-callback + (mf/use-fn (mf/deps gradient-length width height) (fn [point] (let [scale-factor-y (/ gradient-length (/ height 2)) @@ -304,17 +288,35 @@ (when (and norm-dist (d/num? norm-dist)) (change! {:width norm-dist})))))] - (when (and gradient + [:& gradient-handler-transformed + {:editing editing-stop + :from-p from-p + :to-p to-p + :width-p (when (= :radial (:type gradient)) width-p) + :from-color {:value start-color :opacity start-opacity} + :to-color {:value end-color :opacity end-opacity} + :zoom zoom + :on-change-start on-change-start + :on-change-finish on-change-finish + :on-change-width on-change-width}])) + +(mf/defc gradient-handlers + {::mf/wrap [mf/memo]} + [{:keys [id zoom]}] + (let [shape-ref (mf/use-memo (mf/deps id) #(refs/object-by-id id)) + shape (mf/deref shape-ref) + + state (mf/deref refs/colorpicker) + gradient (:gradient state) + stops (:stops state) + editing-stop (:editing-stop state)] + + (when (and (some? gradient) (= id (:shape-id gradient)) (not= (:type shape) :text)) - [:& gradient-handler-transformed - {:editing editing-spot - :from-p from-p - :to-p to-p - :width-p (when (= :radial (:type gradient)) width-p) - :from-color {:value start-color :opacity start-opacity} - :to-color {:value end-color :opacity end-opacity} - :zoom zoom - :on-change-start on-change-start - :on-change-finish on-change-finish - :on-change-width on-change-width}]))) + [:& gradient-handlers* + {:zoom zoom + :gradient gradient + :stops stops + :editing-stop editing-stop + :shape shape}]))) diff --git a/frontend/src/app/main/ui/workspace/viewport/guides.cljs b/frontend/src/app/main/ui/workspace/viewport/guides.cljs index 434458d31a..ed338e282e 100644 --- a/frontend/src/app/main/ui/workspace/viewport/guides.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/guides.cljs @@ -10,6 +10,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.math :as mth] + [app.common.pages.helpers :as cph] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] [app.main.refs :as refs] @@ -283,12 +284,16 @@ pos (+ (or (:new-position @state) (:position guide)) (get move-vec axis)) guide-width (/ guide-width zoom) - guide-pill-corner-radius (/ guide-pill-corner-radius zoom)] + guide-pill-corner-radius (/ guide-pill-corner-radius zoom) + + frame-guide-outside? + (and (some? frame) + (not (is-guide-inside-frame? (assoc guide :position pos) frame)))] (when (or (nil? frame) - (is-guide-inside-frame? (assoc guide :position pos) frame) - (:hover @state true)) - [:g.guide-area + (and (cph/root-frame? frame) + (not (cph/rotated-frame? frame)))) + [:g.guide-area {:opacity (when frame-guide-outside? 0)} (when-not disabled-guides? (let [{:keys [x y width height]} (guide-area-axis pos vbox zoom frame axis)] [:rect {:x x @@ -296,7 +301,7 @@ :width width :height height :style {:fill "none" - :pointer-events "fill" + :pointer-events (if frame-guide-outside? "none" "fill") :cursor (if (= axis :x) (cur/resize-ew 0) (cur/resize-ns 0))} :on-pointer-enter on-pointer-enter :on-pointer-leave on-pointer-leave diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 80c600da1c..a6f4881490 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -6,10 +6,10 @@ (ns app.main.ui.workspace.viewport.hooks (:require + [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.common.pages.helpers :as cph] - [app.common.uuid :as uuid] [app.main.data.shortcuts :as dsc] [app.main.data.workspace :as dw] [app.main.data.workspace.path.shortcuts :as psc] @@ -112,6 +112,9 @@ hover-disabled-ref (mf/use-ref hover-disabled?) focus-ref (mf/use-ref focus) + last-point-ref (mf/use-var nil) + mod-str (mf/use-memo #(rx/subject)) + query-point (mf/use-callback (mf/deps page-id) @@ -126,22 +129,22 @@ :page-id page-id :rect rect :include-frames? true - :clip-children? (not mod?) - :reverse? true}))))) ;; we want the topmost shape to be selected first + :clip-children? (not mod?)}))))) over-shapes-stream (mf/use-memo (fn [] (rx/merge - (->> move-stream - ;; When transforming shapes we stop querying the worker - (rx/filter #(not (some? (mf/ref-val transform-ref)))) - (rx/merge-map query-point)) + ;; This stream works to "refresh" the outlines when the control is pressed + ;; but the mouse has not been moved from its position. + (->> mod-str + (rx/observe-on :async) + (rx/map #(deref last-point-ref))) (->> move-stream ;; When transforming shapes we stop querying the worker - (rx/filter #(some? (mf/ref-val transform-ref))) - (rx/map (constantly nil))))))] + (rx/merge-map query-point) + (rx/tap #(reset! last-point-ref %))))))] ;; Refresh the refs on a value change (mf/use-effect @@ -154,7 +157,9 @@ (mf/use-effect (mf/deps @mod?) - #(mf/set-ref-val! mod-ref @mod?)) + (fn [] + (rx/push! mod-str :update) + (mf/set-ref-val! mod-ref @mod?))) (mf/use-effect (mf/deps selected) @@ -172,29 +177,39 @@ over-shapes-stream (mf/deps page-id objects) (fn [ids] - (let [is-group? - (fn [id] - (contains? #{:group :bool} (get-in objects [id :type]))) - - selected (mf/ref-val selected-ref) + (let [selected (mf/ref-val selected-ref) focus (mf/ref-val focus-ref) - mod? (mf/ref-val mod-ref) - remove-xfm (mapcat #(cph/get-parent-ids objects %)) - remove-id? (cond-> (into #{} remove-xfm selected) - (not mod?) - (into (filter #(group-empty-space? % objects ids)) ids) + ids (into + (d/ordered-set) + (cph/sort-z-index objects ids {:bottom-frames? mod?})) - mod? - (into (filter is-group?) ids)) + grouped? (fn [id] (contains? #{:group :bool} (get-in objects [id :type]))) - hover-shape (->> ids - (filter (comp not remove-id?)) - (filter #(or (empty? focus) - (cp/is-in-focus? objects focus %))) - (first) - (get objects))] + + selected-with-parents + (into #{} (mapcat #(cph/get-parent-ids objects %)) selected) + + root-frame-with-data? #(and (cph/root-frame? objects %) (d/not-empty? (get-in objects [% :shapes]))) + + ;; Set with the elements to remove from the hover list + remove-id? + (cond-> selected-with-parents + (not mod?) + (into (filter #(or (root-frame-with-data? %) + (group-empty-space? % objects ids))) + ids) + + mod? + (into (filter grouped?) ids)) + + hover-shape + (->> ids + (remove remove-id?) + (filter #(or (empty? focus) (cp/is-in-focus? objects focus %))) + (first) + (get objects))] (reset! hover hover-shape) (reset! hover-ids ids)))))) @@ -203,13 +218,7 @@ (let [root-frame-ids (mf/use-memo (mf/deps objects) - (fn [] - (let [frame? (into #{} (cph/get-frames-ids objects)) - ;; Removes from zero/shapes attribute all the frames so we can ask only for - ;; the non-frame children - objects (-> objects - (update-in [uuid/zero :shapes] #(filterv (comp not frame?) %)))] - (cph/get-children-ids objects uuid/zero)))) + #(cph/get-root-shapes-ids objects)) modifiers (select-keys modifiers root-frame-ids)] (sfd/use-dynamic-modifiers objects globals/document modifiers))) @@ -220,21 +229,22 @@ (defn setup-active-frames [objects hover-ids selected active-frames zoom transform vbox] - (let [frame? #(= :frame (get-in objects [% :type])) - all-frames (mf/use-memo (mf/deps objects) #(cph/get-frames-ids objects)) + (let [all-frames (mf/use-memo (mf/deps objects) #(cph/get-root-frames-ids objects)) selected-frames (mf/use-memo (mf/deps selected) #(->> all-frames (filter selected))) - xf-selected-frame (comp (remove frame?) (map #(get-in objects [% :frame-id]))) + + xf-selected-frame (comp (remove cph/root-frame?) + (map #(cph/get-shape-id-root-frame objects %))) + selected-shapes-frames (mf/use-memo (mf/deps selected) #(into #{} xf-selected-frame selected)) active-selection (when (and (not= transform :move) (= (count selected-frames) 1)) (first selected-frames)) - hover-frame (last @hover-ids) - last-hover-frame (mf/use-var nil)] + last-hover-ids (mf/use-var nil)] (mf/use-effect - (mf/deps hover-frame) + (mf/deps @hover-ids) (fn [] - (when (some? hover-frame) - (reset! last-hover-frame hover-frame)))) + (when (d/not-empty? @hover-ids) + (reset! last-hover-ids (set @hover-ids))))) (mf/use-effect (mf/deps objects @hover-ids selected zoom transform vbox) @@ -247,7 +257,9 @@ ;; - If no hovering over any frames we keep the previous active one ;; - Check always that the active frames are inside the vbox - (let [is-active-frame? + (let [hover-ids? (set (->> @hover-ids (map #(cph/get-shape-id-root-frame objects %)))) + + is-active-frame? (fn [id] (or ;; Zoom > 130% shows every frame @@ -256,7 +268,7 @@ ;; Zoom >= 25% will show frames hovering (and (>= zoom 0.25) - (or (= id hover-frame) (= id @last-hover-frame))) + (or (contains? hover-ids? id) (contains? @last-hover-ids id))) ;; Otherwise, if it's a selected frame (= id active-selection) diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs index 20a51b826c..1236859e1a 100644 --- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs @@ -11,7 +11,7 @@ [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] - [app.common.spec.interactions :as cti] + [app.common.types.shape.interactions :as ctsi] [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] @@ -275,12 +275,12 @@ [:g.non-selected (for [shape active-shapes] (for [[index interaction] (d/enumerate (:interactions shape))] - (let [dest-shape (when (cti/destination? interaction) + (let [dest-shape (when (ctsi/destination? interaction) (get objects (:destination interaction))) selected? (contains? selected (:id shape)) level (calc-level index (:interactions shape))] (when-not selected? - [:& interaction-path {:key (dm/str (:id shape) "-" index) + [:& interaction-path {:key (dm/str "non-selected-" (:id shape) "-" index) :index index :level level :orig-shape shape @@ -304,38 +304,35 @@ (if (seq (:interactions shape)) (for [[index interaction] (d/enumerate (:interactions shape))] (when-not (= index editing-interaction-index) - (let [dest-shape (when (cti/destination? interaction) + (let [dest-shape (when (ctsi/destination? interaction) (get objects (:destination interaction))) level (calc-level index (:interactions shape))] - [:* - [:& interaction-path {:key (dm/str (:id shape) "-" index) - :index index - :level level - :orig-shape shape - :dest-shape dest-shape - :selected selected - :selected? true - :action-type (:action-type interaction) - :zoom zoom}] - (when (and (or (= (:action-type interaction) :open-overlay) - (= (:action-type interaction) :toggle-overlay)) - (= (:overlay-pos-type interaction) :manual)) - (if (and (some? move-overlay-to) - (= move-overlay-index index)) - [:& overlay-marker {:key (dm/str "pos" (:id shape) "-" index) - :index index - :orig-shape shape - :dest-shape dest-shape - :position move-overlay-to - :objects objects - :hover-disabled? hover-disabled?}] - [:& overlay-marker {:key (dm/str "pos" (:id shape) "-" index) - :index index - :orig-shape shape - :dest-shape dest-shape - :position (:overlay-position interaction) - :objects objects - :hover-disabled? hover-disabled?}]))]))) + [:g {:key (dm/str "interaction-path-" (:id shape) "-" index)} + [:& interaction-path {:index index + :level level + :orig-shape shape + :dest-shape dest-shape + :selected selected + :selected? true + :action-type (:action-type interaction) + :zoom zoom}] + (when (and (or (= (:action-type interaction) :open-overlay) + (= (:action-type interaction) :toggle-overlay)) + (= (:overlay-pos-type interaction) :manual)) + (if (and (some? move-overlay-to) + (= move-overlay-index index)) + [:& overlay-marker {:index index + :orig-shape shape + :dest-shape dest-shape + :position move-overlay-to + :objects objects + :hover-disabled? hover-disabled?}] + [:& overlay-marker {:index index + :orig-shape shape + :dest-shape dest-shape + :position (:overlay-position interaction) + :objects objects + :hover-disabled? hover-disabled?}]))]))) (when (and shape (not (cph/unframed-shape? shape)) (not (#{:move :rotate} current-transform))) diff --git a/frontend/src/app/main/ui/workspace/viewport/outline.cljs b/frontend/src/app/main/ui/workspace/viewport/outline.cljs index 95d58b0167..d1f6eaf46b 100644 --- a/frontend/src/app/main/ui/workspace/viewport/outline.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/outline.cljs @@ -8,8 +8,6 @@ (:require [app.common.exceptions :as ex] [app.common.geom.shapes :as gsh] - [app.common.pages.helpers :as cph] - [app.main.refs :as refs] [app.util.object :as obj] [app.util.path.format :as upf] [clojure.set :as set] @@ -23,7 +21,7 @@ zoom (obj/get props "zoom" 1) color (unchecked-get props "color") - transform (gsh/transform-matrix shape) + transform (gsh/transform-str shape) path? (= :path (:type shape)) path-data (mf/use-memo @@ -41,7 +39,7 @@ common {:fill "none" :stroke color - :strokeWidth (/ 1 zoom) + :strokeWidth (/ 2 zoom) :pointerEvents "none" :transform transform} @@ -82,15 +80,12 @@ [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) - (cph/clean-loops objects)) - + outlines-ids (set/union selected hover) show-outline? (fn [shape] (and (not (:hidden shape)) (not (:blocked shape)))) @@ -100,6 +95,6 @@ (filterv show-outline?) (filter some?))] - [:g.outlines {:display (when (some? transform) "none")} + [:g.outlines [:& 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 dcac1a492f..4a1e4f7de6 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -170,7 +170,7 @@ :height size :fill (if (debug? :handlers) "blue" "none") :stroke-width 0 - :transform (str transform) + :transform (dm/str transform) :on-mouse-down on-rotate}])) (mf/defc resize-point-handler @@ -224,7 +224,7 @@ height (/ resize-side-height zoom) offset-y (if (= align :outside) (- height) (- (/ height 2))) target-y (+ y offset-y) - transform-str (str (gmt/multiply transform (gmt/rotate-matrix angle (gpt/point x y))))] + transform-str (dm/str (gmt/multiply transform (gmt/rotate-matrix angle (gpt/point x y))))] [:g.resize-handler (when show-handler? [:circle {:r (/ resize-point-radius zoom) @@ -271,13 +271,13 @@ current-transform (mf/deref refs/current-transform) selrect (:selrect shape) - transform (gsh/transform-matrix shape {:no-flip true})] + transform (gsh/transform-str shape {:no-flip true})] (when (not (#{:move :rotate} current-transform)) [:g.controls {:pointer-events (if disable-handlers "none" "visible")} ;; Selection rect [:& selection-rect {:rect selrect - :transform (str transform) + :transform transform :zoom zoom :color color :on-move-selected on-move-selected @@ -316,7 +316,7 @@ :color color} props (map->obj (merge common-props props))] (case type - :rotation (when (not= :frame (:type shape)) [:> rotation-handler props]) + :rotation [:> rotation-handler props] :resize-point [:> resize-point-handler props] :resize-side [:> resize-side-handler props])))]))) @@ -327,7 +327,7 @@ (let [{:keys [x y width height]} shape] [:g.controls [:rect.main {:x x :y y - :transform (str (gsh/transform-matrix shape)) + :transform (gsh/transform-str shape) :width width :height height :pointer-events "visible" diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 7f29d47766..e657b72678 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -10,13 +10,14 @@ [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.main.ui.cursors :as cur] + [app.main.ui.formats :refer [format-number]] [app.util.dom :as dom])) (defn format-viewbox [vbox] - (dm/str (:x vbox 0) " " - (:y vbox 0) " " - (:width vbox 0) " " - (:height vbox 0))) + (dm/str (format-number(:x vbox 0)) " " + (format-number (:y vbox 0)) " " + (format-number (:width vbox 0)) " " + (format-number (:height vbox 0)))) (defn translate-point-to-viewport [viewport zoom pt] (let [vbox (.. ^js viewport -viewBox -baseVal) diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 67c58faa94..56725968f8 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -6,10 +6,12 @@ (ns app.main.ui.workspace.viewport.widgets (:require + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] + [app.common.uuid :as uuid] [app.main.data.workspace :as dw] [app.main.data.workspace.interactions :as dwi] [app.main.refs :as refs] @@ -92,10 +94,12 @@ (mf/defc frame-title {::mf/wrap [mf/memo]} - [{:keys [frame modifiers selected? zoom show-artboard-names? on-frame-enter on-frame-leave on-frame-select]}] - (let [{:keys [width x y]} (gsh/transform-shape frame) + [{:keys [frame selected? zoom show-artboard-names? on-frame-enter on-frame-leave on-frame-select]}] + (let [{:keys [width x y]} frame label-pos (gpt/point x (- y (/ 10 zoom))) + frame-transform (gsh/transform-str frame) + on-mouse-down (mf/use-callback (mf/deps (:id frame) on-frame-select) @@ -136,11 +140,9 @@ text-pos-x (if (:use-for-thumbnail? frame) 15 0)] (when (not (:hidden frame)) - [:* + [:g {:id (dm/str "frame-title-" (:id frame))} (when (:use-for-thumbnail? frame) - [:g {:transform (str (when (and selected? modifiers) - (str (:displacement modifiers) " ")) - (text-transform label-pos zoom))} + [:g {:transform (dm/str frame-transform " " (text-transform label-pos zoom))} [:svg {:x 0 :y -9 :width 12 @@ -154,9 +156,7 @@ :width width :height 20 :class "workspace-frame-label" - :transform (str (when (and selected? modifiers) - (str (:displacement modifiers) " ")) - (text-transform label-pos zoom)) + :transform (dm/str frame-transform " " (text-transform label-pos zoom)) :style {:fill (when selected? "var(--color-primary-dark)")} :visibility (if show-artboard-names? "visible" "hidden") :on-mouse-down on-mouse-down @@ -182,15 +182,16 @@ [:g.frame-titles (for [frame frames] - [:& frame-title {:key (dm/str "frame-title-" (:id frame)) - :frame frame - :selected? (contains? selected (:id frame)) - :zoom zoom - :show-artboard-names? show-artboard-names? - :modifiers modifiers - :on-frame-enter on-frame-enter - :on-frame-leave on-frame-leave - :on-frame-select on-frame-select}])])) + (when (= (:frame-id frame) uuid/zero) + [:& frame-title {:key (dm/str "frame-title-" (:id frame)) + :frame frame + :selected? (contains? selected (:id frame)) + :zoom zoom + :show-artboard-names? show-artboard-names? + :modifiers modifiers + :on-frame-enter on-frame-enter + :on-frame-leave on-frame-leave + :on-frame-select on-frame-select}]))])) (mf/defc frame-flow [{:keys [flow frame modifiers selected? zoom on-frame-enter on-frame-leave on-frame-select]}] @@ -252,9 +253,10 @@ on-frame-leave (unchecked-get props "on-frame-leave") on-frame-select (unchecked-get props "on-frame-select")] [:g.frame-flows - (for [flow flows] + (for [[index flow] (d/enumerate flows)] (let [frame (get objects (:starting-frame flow))] - [:& frame-flow {:flow flow + [:& frame-flow {:key (dm/str (:id frame) "-" index) + :flow flow :frame frame :selected? (contains? selected (:id frame)) :zoom zoom diff --git a/frontend/src/app/render.cljs b/frontend/src/app/render.cljs index 5d921f0a72..872e0f2476 100644 --- a/frontend/src/app/render.cljs +++ b/frontend/src/app/render.cljs @@ -98,7 +98,7 @@ state)) (mf/defc object-svg - [{:keys [page-id file-id object-id render-embed? render-texts?]}] + [{:keys [page-id file-id object-id render-embed?]}] (let [fetch-state (mf/use-fn (mf/deps file-id page-id object-id) (fn [] @@ -131,11 +131,10 @@ [:& render/object-svg {:objects objects :object-id object-id - :render-embed? render-embed? - :render-texts? render-texts?}]))) + :render-embed? render-embed?}]))) (mf/defc objects-svg - [{:keys [page-id file-id object-ids render-embed? render-texts?]}] + [{:keys [page-id file-id object-ids render-embed?]}] (let [fetch-state (mf/use-fn (mf/deps file-id page-id) (fn [] @@ -157,27 +156,24 @@ {:objects objects :key (str object-id) :object-id object-id - :render-embed? render-embed? - :render-texts? render-texts?}]))))) + :render-embed? render-embed?}]))))) (s/def ::page-id ::us/uuid) (s/def ::file-id ::us/uuid) (s/def ::object-id (s/or :single ::us/uuid :multiple (s/coll-of ::us/uuid))) -(s/def ::render-text ::us/boolean) (s/def ::embed ::us/boolean) (s/def ::render-objects (s/keys :req-un [::file-id ::page-id ::object-id] - :opt-un [::render-text ::render-embed])) + :opt-un [::render-embed])) (defn- render-objects [params] (let [{:keys [file-id page-id - render-embed - render-texts] + render-embed] :as params} (us/conform ::render-objects params) @@ -190,8 +186,7 @@ {:file-id file-id :page-id page-id :object-id object-id - :render-embed? render-embed - :render-texts? render-texts}]) + :render-embed? render-embed}]) :multiple (mf/html @@ -199,8 +194,7 @@ {:file-id file-id :page-id page-id :object-ids (into #{} object-id) - :render-embed? render-embed - :render-texts? render-texts}])))) + :render-embed? render-embed}])))) ;; ---- COMPONENTS SPRITE diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index dfa9572fd9..f2a5da6ba4 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -37,6 +37,14 @@ (when (some? e) (.-target e))) +(defn event->native-event + [^js e] + (.-nativeEvent e)) + +(defn event->browser-event + [^js e] + (.getBrowserEvent e)) + ;; --- New methods (declare get-elements-by-tag) @@ -91,6 +99,11 @@ (when event (.stopPropagation event))) +(defn stop-immediate-propagation + [^js event] + (when event + (.stopImmediatePropagation event))) + (defn prevent-default [^js event] (when event @@ -102,9 +115,23 @@ (when (some? event) (.-target event))) +(defn select-target + "Extract the target from event instance and select it" + [^js event] + (when (some? event) + (-> event + (.-target) + (.-select)))) + +(defn select-node + "Select element by node" + [^js node] + (when (some? node) + (.-select node))) + (defn get-current-target "Extract the current target from event instance (different from target - when event triggered in a child of the subscribing element)." + when event triggered in a child of the subscribing element)." [^js event] (when (some? event) (.-currentTarget event))) @@ -114,6 +141,14 @@ (when (some? node) (.-parentElement ^js node))) +(defn get-parent-with-selector + [^js node selector] + + (loop [current node] + (if (or (nil? current) (.matches current selector) ) + current + (recur (.-parentElement current))))) + (defn get-value "Extract the value from dom node." [^js node] @@ -373,6 +408,11 @@ (.setProperty (.-style ^js node) property value)) node) +(defn unset-css-property! [^js node property] + (when (some? node) + (.removeProperty (.-style ^js node) property)) + node) + (defn capture-pointer [^js event] (when (some? event) (-> event get-target (.setPointerCapture (.-pointerId event))))) @@ -381,6 +421,9 @@ (when (and (some? event) (.-pointerId event)) (-> event get-target (.releasePointerCapture (.-pointerId event))))) +(defn get-body [] + (.-body globals/document)) + (defn get-root [] (query globals/document "#app")) @@ -566,3 +609,12 @@ (defn load-font [font] (let [fonts (.-fonts globals/document)] (.load fonts font))) + +(defn text-measure [font] + (let [element (.createElement globals/document "canvas") + context (.getContext element "2d") + _ (set! (.-font context) font) + measure ^js (.measureText context "Ag")] + + {:ascent (.-fontBoundingBoxAscent measure) + :descent (.-fontBoundingBoxDescent measure)})) diff --git a/frontend/src/app/util/forms.cljs b/frontend/src/app/util/forms.cljs index 5593f6a2f3..95725d0ee0 100644 --- a/frontend/src/app/util/forms.cljs +++ b/frontend/src/app/util/forms.cljs @@ -162,4 +162,4 @@ (s/def ::email ::us/email) (s/def ::not-empty-string ::us/not-empty-string) -(s/def ::color ::us/color) +(s/def ::color ::us/rgb-color-str) diff --git a/frontend/src/app/util/geom/snap_points.cljs b/frontend/src/app/util/geom/snap_points.cljs index 17df6445a1..1940deaf79 100644 --- a/frontend/src/app/util/geom/snap_points.cljs +++ b/frontend/src/app/util/geom/snap_points.cljs @@ -7,7 +7,8 @@ (ns app.util.geom.snap-points (:require [app.common.geom.point :as gpt] - [app.common.geom.shapes :as gsh])) + [app.common.geom.shapes :as gsh] + [app.common.pages.helpers :as cph])) (defn selrect-snap-points [{:keys [x y width height] :as selrect}] #{(gpt/point x y) @@ -29,11 +30,20 @@ (when (and (not blocked) (not hidden)) (let [shape (gsh/transform-shape shape)] (case (:type shape) - :frame (-> shape :selrect frame-snap-points) + :frame (-> shape :points gsh/points->selrect frame-snap-points) (into #{(gsh/center-shape shape)} (:points shape)))))) (defn guide-snap-points - [guide] - (if (= :x (:axis guide)) + [guide frame] + + (cond + (and (some? frame) + (not (cph/rotated-frame? frame)) + (not (cph/root-frame? frame))) + #{} + + (= :x (:axis guide)) #{(gpt/point (:position guide) 0)} + + :else #{(gpt/point 0 (:position guide))})) diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 3902c18a82..758d99125f 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -7,7 +7,9 @@ (ns app.util.i18n "A i18n foundation." (:require + [app.common.logging :as log] [app.config :as cfg] + [app.util.dom :as dom] [app.util.globals :as globals] [app.util.object :as obj] [app.util.storage :refer [storage]] @@ -16,6 +18,8 @@ [okulary.core :as l] [rumext.alpha :as mf])) +(log/set-level! :info) + (def supported-locales [{:label "English" :value "en"} {:label "Español" :value "es"} @@ -83,11 +87,12 @@ locale (recur (rest locales))) cfg/default-language))] + (swap! storage assoc ::locale lname) (reset! locale lname)) - (do + (let [lname (autodetect)] (swap! storage dissoc ::locale) - (reset! locale (autodetect))))) + (reset! locale lname)))) (defn reset-locale "Set the current locale to the browser detected one if it is @@ -96,6 +101,15 @@ (swap! storage dissoc ::locale) (reset! locale (autodetect))) +(add-watch locale ::browser-font + (fn [_ _ _ locale] + (log/info :hint "locale changed" :locale locale) + (let [node (dom/get-body)] + (if (or (= locale "fa") + (= locale "ar")) + (dom/set-css-property! node "--font-family" "'vazirmatn', 'worksans', sans-serif") + (dom/unset-css-property! node "--font-family"))))) + (deftype C [val] IDeref (-deref [_] val)) diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs index 292431093f..f04ba60c72 100644 --- a/frontend/src/app/util/import/parser.cljs +++ b/frontend/src/app/util/import/parser.cljs @@ -10,7 +10,7 @@ [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] - [app.common.spec.interactions :as cti] + [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] [app.util.color :as uc] [app.util.json :as json] @@ -789,10 +789,14 @@ :content node-content})))) (defn add-frame-data [props node] - (let [grids (parse-grids node)] - (cond-> props - (d/not-empty? grids) - (assoc :grids grids)))) + (let [grids (parse-grids node) + show-content (get-meta node :show-content str->bool) + hide-in-viewer (get-meta node :hide-in-viewer str->bool)] + (-> props + (assoc :show-content show-content) + (assoc :hide-in-viewer hide-in-viewer) + (cond-> (d/not-empty? grids) + (assoc :grids grids))))) (defn has-image? [node] @@ -935,17 +939,17 @@ (let [interaction {:event-type (get-meta node :event-type keyword) :action-type (get-meta node :action-type keyword)}] (cond-> interaction - (cti/has-delay interaction) + (ctsi/has-delay interaction) (assoc :delay (get-meta node :delay d/parse-double)) - (cti/has-destination interaction) + (ctsi/has-destination interaction) (assoc :destination (get-meta node :destination uuid/uuid) :preserve-scroll (get-meta node :preserve-scroll str->bool)) - (cti/has-url interaction) + (ctsi/has-url interaction) (assoc :url (get-meta node :url str)) - (cti/has-overlay-opts interaction) + (ctsi/has-overlay-opts interaction) (assoc :overlay-pos-type (get-meta node :overlay-pos-type keyword) :overlay-position (gpt/point (get-meta node :overlay-position-x d/parse-double) diff --git a/frontend/src/app/util/names.cljs b/frontend/src/app/util/names.cljs new file mode 100644 index 0000000000..6a2288fcdb --- /dev/null +++ b/frontend/src/app/util/names.cljs @@ -0,0 +1,38 @@ +;; 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.names + (:require + [app.common.data :as d] + [app.common.spec :as us] + [cljs.spec.alpha :as s])) + +(s/def ::set-of-string (s/every string? :kind set?)) + +(defn- extract-numeric-suffix + [basename] + (if-let [[_ p1 p2] (re-find #"(.*)-([0-9]+)$" basename)] + [p1 (+ 1 (d/parse-integer p2))] + [basename 1])) + +(defn retrieve-used-names + [objects] + (into #{} (comp (map :name) (remove nil?)) (vals objects))) + +(defn generate-unique-name + "A unique name generator" + [used basename] + (s/assert ::set-of-string used) + (s/assert ::us/string basename) + (if-not (contains? used basename) + basename + (let [[prefix initial] (extract-numeric-suffix basename)] + (loop [counter initial] + (let [candidate (str prefix "-" counter)] + (if (contains? used candidate) + (recur (inc counter)) + candidate)))))) + diff --git a/frontend/src/app/util/object.cljs b/frontend/src/app/util/object.cljs index b5de31cb95..7831647976 100644 --- a/frontend/src/app/util/object.cljs +++ b/frontend/src/app/util/object.cljs @@ -6,12 +6,12 @@ (ns app.util.object "A collection of helpers for work with javascript objects." - (:refer-clojure :exclude [set! get get-in merge clone contains?]) + (:refer-clojure :exclude [set! new get get-in merge clone contains?]) (:require ["lodash/omit" :as omit] [cuerdas.core :as str])) -(defn new [] #js {}) +(defn create [] #js {}) (defn get ([obj k] diff --git a/frontend/src/app/util/path/format.cljs b/frontend/src/app/util/path/format.cljs index 30c2745470..6cd541c53d 100644 --- a/frontend/src/app/util/path/format.cljs +++ b/frontend/src/app/util/path/format.cljs @@ -6,25 +6,58 @@ (ns app.util.path.format (:require + [app.common.math :as mth] [app.common.path.commands :as upc] [app.common.path.subpaths :refer [pt=]] [app.util.array :as arr])) +(def path-precision 3) + (defn- join-params ([a] - (js* "\"\"+~{}" a)) + (js* "\"\"+~{}" + (mth/precision a path-precision))) ([a b] - (js* "\"\"+~{}+\",\"+~{}" a b)) + (js* "\"\"+~{}+\",\"+~{}" + (mth/precision a path-precision) + (mth/precision b path-precision))) ([a b c] - (js* "\"\"+~{}+\",\"+~{}+\",\"+~{}" a b c)) + (js* "\"\"+~{}+\",\"+~{}+\",\"+~{}" + (mth/precision a path-precision) + (mth/precision b path-precision) + (mth/precision c path-precision))) ([a b c d] - (js* "\"\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}" a b c d)) + (js* "\"\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}" + (mth/precision a path-precision) + (mth/precision b path-precision) + (mth/precision c path-precision) + (mth/precision d path-precision) + )) ([a b c d e] - (js* "\"\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}" a b c d e)) + (js* "\"\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}" + (mth/precision a path-precision) + (mth/precision b path-precision) + (mth/precision c path-precision) + (mth/precision d path-precision) + (mth/precision e path-precision))) ([a b c d e f] - (js* "\"\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}" a b c d e f)) + (js* "\"\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}" + (mth/precision a path-precision) + (mth/precision b path-precision) + (mth/precision c path-precision) + (mth/precision d path-precision) + (mth/precision e path-precision) + (mth/precision f path-precision) + )) ([a b c d e f g] - (js* "\"\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}" a b c d e f g))) + (js* "\"\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}+\",\"+~{}" + (mth/precision a path-precision) + (mth/precision b path-precision) + (mth/precision c path-precision) + (mth/precision d path-precision) + (mth/precision e path-precision) + (mth/precision f path-precision) + (mth/precision g path-precision)))) (defn- translate-params [command {:keys [x y] :as params}] diff --git a/frontend/src/app/util/snap_data.cljs b/frontend/src/app/util/snap_data.cljs index 597e6ad4a1..81f60b9520 100644 --- a/frontend/src/app/util/snap_data.cljs +++ b/frontend/src/app/util/snap_data.cljs @@ -55,16 +55,18 @@ (defn get-grids-snap-points [frame coord] - (let [grid->snap (fn [[grid-type position]] - {:type :layout - :id (:id frame) - :grid grid-type - :pt position})] - (->> (:grids frame) - (mapcat (fn [grid] - (->> (gg/grid-snap-points frame grid coord) - (mapv #(vector (:type grid) %))))) - (mapv grid->snap)))) + (if (not (cph/rotated-frame? frame)) + [] + (let [grid->snap (fn [[grid-type position]] + {:type :layout + :id (:id frame) + :grid grid-type + :pt position})] + (->> (:grids frame) + (mapcat (fn [grid] + (->> (gg/grid-snap-points frame grid coord) + (mapv #(vector (:type grid) %))))) + (mapv grid->snap))))) (defn- add-frame [page-data frame] @@ -105,9 +107,10 @@ (defn- add-guide - [page-data guide] + [objects page-data guide] - (let [guide-data (->> (snap/guide-snap-points guide) + (let [frame (get objects (:frame-id guide)) + guide-data (->> (snap/guide-snap-points guide frame) (mapv #(array-map :type :guide :id (:id guide) @@ -178,10 +181,10 @@ (add-shape new-shape))) (defn- update-guide - [page-data [old-guide new-guide]] - (-> page-data - (remove-guide old-guide) - (add-guide new-guide))) + [objects page-data [old-guide new-guide]] + (as-> page-data $ + (remove-guide $ old-guide) + (add-guide objects $ new-guide))) ;; PUBLIC API @@ -203,7 +206,7 @@ (add-root-frame $) (reduce add-frame $ frames) (reduce add-shape $ shapes) - (reduce add-guide $ guides))] + (reduce (partial add-guide objects) $ guides))] (assoc snap-data (:id page) page-data))) (defn update-page @@ -214,7 +217,8 @@ ;; Update page (update snap-data (:id page) (fn [page-data] - (let [{:keys [change-frame-shapes + (let [{:keys [objects]} page + {:keys [change-frame-shapes change-frame-guides removed-frames removed-shapes @@ -235,10 +239,12 @@ (reduce update-shape $ updated-shapes) (reduce add-frame $ new-frames) (reduce add-shape $ new-shapes) - (reduce update-guide $ change-frame-guides) (reduce remove-guide $ removed-guides) - (reduce update-guide $ updated-guides) - (reduce add-guide $ new-guides))))) + + ;; Guides functions. Need objects to get its frame data + (reduce (partial update-guide objects) $ change-frame-guides) + (reduce (partial update-guide objects) $ updated-guides) + (reduce (partial add-guide objects) $ new-guides))))) ;; Page doesn't exist, we create a new entry (add-page snap-data page))) diff --git a/frontend/src/app/util/text_editor.cljs b/frontend/src/app/util/text_editor.cljs index 28da8d72cc..dac7739369 100644 --- a/frontend/src/app/util/text_editor.cljs +++ b/frontend/src/app/util/text_editor.cljs @@ -72,10 +72,10 @@ (defn get-editor-current-inline-styles [state] (if (impl/isCurrentEmpty state) - (let [block (impl/getCurrentBlock state)] - (get-editor-block-data block)) + (get-editor-current-block-data state) (-> (.getCurrentInlineStyle ^js state) - (txt/styles-to-attrs)))) + (txt/styles-to-attrs) + (dissoc :text-align :text-direction)))) (defn update-editor-current-block-data [state attrs] @@ -89,7 +89,8 @@ (impl/updateBlockData state block-key (clj->js attrs)) (let [attrs (-> (impl/getInlineStyle state block-key 0) - (txt/styles-to-attrs))] + (txt/styles-to-attrs) + (dissoc :text-align :text-direction))] (impl/updateBlockData state block-key (clj->js attrs))))) state (impl/applyInlineStyle state (txt/attrs-to-styles attrs)) diff --git a/frontend/src/app/util/text_editor_impl.js b/frontend/src/app/util/text_editor_impl.js index 6a8cbc1787..c032a90fe9 100644 --- a/frontend/src/app/util/text_editor_impl.js +++ b/frontend/src/app/util/text_editor_impl.js @@ -378,10 +378,7 @@ export function insertText(state, text, attrs, inlineStyles) { ); blockArray = blockArray.map((b) => { - if (b.getText() === "") { - return mergeBlockData(b, attrs) - } - return b; + return mergeBlockData(b, attrs); }); const fragment = BlockMapBuilder.createFromArray(blockArray); diff --git a/frontend/src/app/util/text_svg_position.cljs b/frontend/src/app/util/text_svg_position.cljs index 5e00aa7313..f73655a19a 100644 --- a/frontend/src/app/util/text_svg_position.cljs +++ b/frontend/src/app/util/text_svg_position.cljs @@ -8,10 +8,8 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.geom.point :as gpt] [app.common.transit :as transit] [app.main.fonts :as fonts] - [app.main.store :as st] [app.util.dom :as dom] [app.util.text-position-data :as tpd] [promesa.core :as p])) @@ -59,78 +57,54 @@ (p/then #(when (not (dom/check-font? font)) (load-font font)))))) -(defn calc-text-node-positions - [base-node viewport zoom] +(defn- calc-text-node-positions + [shape-id] - (when (and (some? base-node)(some? viewport)) - (let [translate-point - (fn [pt] - (let [vbox (.. ^js viewport -viewBox -baseVal) - brect (dom/get-bounding-rect viewport) - brect (gpt/point (d/parse-integer (:left brect)) - (d/parse-integer (:top brect))) - box (gpt/point (.-x vbox) (.-y vbox)) - zoom (gpt/point zoom)] - - (-> (gpt/subtract pt brect) - (gpt/divide zoom) - (gpt/add box)))) + (when (some? shape-id) + (let [text-nodes (dom/query-all (dm/str "#html-text-node-" shape-id " .text-node")) + load-fonts (->> text-nodes (map resolve-font)) - translate-rect - (fn [{:keys [x y width height] :as rect}] - (let [p1 (-> (gpt/point x y) - (translate-point)) - - p2 (-> (gpt/point (+ x width) (+ y height)) - (translate-point))] - (assoc rect - :x (:x p1) - :y (:y p1) - :width (- (:x p2) (:x p1)) - :height (- (:y p2) (:y p1))))) - - text-nodes (dom/query-all base-node ".text-node, span[data-text]") - load-fonts (->> text-nodes (map resolve-font))] + process-text-node + (fn [parent-node] + (let [root (dom/get-parent-with-selector parent-node ".text-node-html") + shape-x (-> (dom/get-attribute root "data-x") d/parse-double) + shape-y (-> (dom/get-attribute root "data-y") d/parse-double) + direction (.-direction (js/getComputedStyle parent-node))] + (->> (.-childNodes parent-node) + (mapcat #(parse-text-nodes parent-node direction %)) + (mapv #(-> % + (update-in [:position :x] + shape-x) + (update-in [:position :y] + shape-y))))))] (-> (p/all load-fonts) (p/then (fn [] - (->> text-nodes - (mapcat - (fn [parent-node] - (let [direction (.-direction (js/getComputedStyle parent-node))] - (->> (.-childNodes parent-node) - (mapcat #(parse-text-nodes parent-node direction %)))))) - (mapv #(update % :position translate-rect))))))))) + (->> text-nodes (mapcat process-text-node)))))))) (defn calc-position-data - [base-node] - (let [viewport (dom/get-element "render") - zoom (or (get-in @st/state [:workspace-local :zoom]) 1)] - (when (and (some? base-node) (some? viewport)) - (p/let [text-data (calc-text-node-positions base-node viewport zoom)] - (when (d/not-empty? text-data) - (->> text-data - (mapv (fn [{:keys [node position text direction]}] - (let [{:keys [x y width height]} position - styles (js/getComputedStyle ^js node) - get (fn [prop] - (let [value (.getPropertyValue styles prop)] - (when (and value (not= value "")) - value)))] - (d/without-nils - {:x x - :y (+ y height) - :width width - :height height - :direction direction - :font-family (str (get "font-family")) - :font-size (str (get "font-size")) - :font-weight (str (get "font-weight")) - :text-transform (str (get "text-transform")) - :text-decoration (str (get "text-decoration")) - :letter-spacing (str (get "letter-spacing")) - :font-style (str (get "font-style")) - :fills (transit/decode-str (get "--fills")) - :text text})))))))))) - - + [shape-id] + (when (some? shape-id) + (p/let [text-data (calc-text-node-positions shape-id)] + (when (d/not-empty? text-data) + (->> text-data + (mapv (fn [{:keys [node position text direction]}] + (let [{:keys [x y width height]} position + styles (js/getComputedStyle ^js node) + get (fn [prop] + (let [value (.getPropertyValue styles prop)] + (when (and value (not= value "")) + value)))] + (d/without-nils + {:x x + :y (+ y height) + :width width + :height height + :direction direction + :font-family (str (get "font-family")) + :font-size (str (get "font-size")) + :font-weight (str (get "font-weight")) + :text-transform (str (get "text-transform")) + :text-decoration (str (get "text-decoration")) + :letter-spacing (str (get "letter-spacing")) + :font-style (str (get "font-style")) + :fills (transit/decode-str (get "--fills")) + :text text}))))))))) diff --git a/frontend/src/app/util/time.cljs b/frontend/src/app/util/time.cljs index 5fd1ee0c44..a5aa4d66b4 100644 --- a/frontend/src/app/util/time.cljs +++ b/frontend/src/app/util/time.cljs @@ -13,6 +13,7 @@ ["date-fns/locale/el" :default dateFnsLocalesEl] ["date-fns/locale/en-US" :default dateFnsLocalesEnUs] ["date-fns/locale/es" :default dateFnsLocalesEs] + ["date-fns/locale/fa-IR" :default dateFnsLocalesFa] ["date-fns/locale/fr" :default dateFnsLocalesFr] ["date-fns/locale/he" :default dateFnsLocalesHe] ["date-fns/locale/pt-BR" :default dateFnsLocalesPtBr] @@ -218,6 +219,7 @@ :ru dateFnsLocalesRu :ro dateFnsLocalesRo :de dateFnsLocalesDe + :fa dateFnsLocalesFa :pt_br dateFnsLocalesPtBr :zh_cn dateFnsLocalesZhCn}) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 6a568b61be..bd55211100 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -450,13 +450,33 @@ (->> (uz/compress-files data) (rx/map #(vector (get files file-id) %))))))))) -(defmethod impl/handler :export-file +(defmethod impl/handler :export-binary-file + [{:keys [files export-type] :as message}] + (->> (rx/from files) + (rx/mapcat + (fn [file] + (->> (rp/command! :export-binfile {:file-id (:id file) + :include-libraries? (= export-type :all) + :embed-assets? (= export-type :merge)}) + (rx/map #(hash-map :type :finish + :file-id (:id file) + :filename (:name file) + :mtype "application/penpot" + :description "Penpot export (*.penpot)" + :uri (wapi/create-uri (wapi/create-blob %)))) + (rx/catch + (fn [err] + (rx/of {:type :error + :error (str err) + :file-id (:id file)})))))))) + +(defmethod impl/handler :export-standard-file [{:keys [team-id files export-type] :as message}] (->> (rx/from files) (rx/mapcat (fn [file] - (->> (export-file team-id file export-type) + (->> (export-file team-id (:id file) export-type) (rx/map (fn [value] (if (contains? value :type) @@ -465,11 +485,11 @@ {:type :finish :file-id (:id file) :filename (:name file) - :mtype "application/penpot" - :description "Penpot export (*.penpot)" + :mtype "application/zip" + :description "Penpot export (*.zip)" :uri (wapi/create-uri export-blob)})))) (rx/catch (fn [err] (rx/of {:type :error :error (str err) - :file-id file})))))))) + :file-id (:id file)})))))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 9038d81815..aff53ab5b0 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -7,6 +7,7 @@ (ns app.worker.import (:refer-clojure :exclude [resolve]) (:require + ["jszip" :as zip] [app.common.data :as d] [app.common.file-builder :as fb] [app.common.geom.point :as gpt] @@ -20,6 +21,7 @@ [app.util.http :as http] [app.util.import.parser :as cip] [app.util.json :as json] + [app.util.webapi :as wapi] [app.util.zip :as uz] [app.worker.impl :as impl] [beicon.core :as rx] @@ -519,48 +521,95 @@ (rx/flat-map link-file-libraries) (rx/ignore))))) +(defn parse-mtype [ba] + (let [u8 (js/Uint8Array. ba 0 4) + sg (areduce u8 i ret "" (str ret (if (zero? i) "" " ") (.toString (aget u8 i) 8)))] + (case sg + "120 113 3 4" "application/zip" + "application/octet-stream"))) + (defmethod impl/handler :analyze-import [{:keys [files]}] (->> (rx/from files) (rx/flat-map - (fn [uri] - (->> (rx/of uri) - (rx/flat-map uz/load-from-url) - (rx/flat-map #(get-file {:zip %} :manifest)) - (rx/map (comp d/kebab-keys cip/string->uuid)) - (rx/map #(hash-map :uri uri :data %)) - (rx/catch #(rx/of {:uri uri :error (.-message %)}))))))) + (fn [file] + (let [st (->> (http/send! + {:uri (:uri file) + :response-type :blob + :method :get}) + (rx/map :body) + (rx/mapcat wapi/read-file-as-array-buffer) + (rx/map (fn [data] + {:type (parse-mtype data) + :uri (:uri file) + :body data})))] + (->> (rx/merge + (->> st + (rx/filter (fn [data] (= "application/zip" (:type data)))) + (rx/flat-map #(zip/loadAsync (:body %))) + (rx/flat-map #(get-file {:zip %} :manifest)) + (rx/map (comp d/kebab-keys cip/string->uuid)) + (rx/map #(hash-map :uri (:uri file) :data % :type "application/zip"))) + (->> st + (rx/filter (fn [data] (= "application/octet-stream" (:type data)))) + (rx/map (fn [_] + (let [file-id (uuid/next)] + {:uri (:uri file) + :data {:name (:name file) + :file-id file-id + :files {file-id {:name (:name file)}} + :status :ready} + :type "application/octet-stream"}))))) + (rx/catch #(rx/of {:uri (:uri file) :error (.-message %)})))))))) (defmethod impl/handler :import-files [{:keys [project-id files]}] (let [context {:project-id project-id - :resolve (resolve-factory)}] + :resolve (resolve-factory)} + zip-files (filter #(= "application/zip" (:type %)) files) + binary-files (filter #(= "application/octet-stream" (:type %)) files)] - (->> (create-files context files) - (rx/flat-map - (fn [[file data]] - (->> (uz/load-from-url (:uri data)) - (rx/map #(-> context (assoc :zip %) (merge data))) - (rx/merge-map - (fn [context] - ;; process file retrieves a stream that will emit progress notifications - ;; and other that will emit the files once imported - (let [[progress-stream file-stream] (process-file context file)] - (rx/merge progress-stream - (->> file-stream - (rx/map - (fn [file] - {:status :import-finish - :errors (:errors file) - :file-id (:file-id data)}))))))) - (rx/catch (fn [cause] - (log/error :hint (ex-message cause) :file-id (:file-id data) :cause cause) - (rx/of {:status :import-error - :file-id (:file-id data) - :error (ex-message cause) - :error-data (ex-data cause)})))))) + (->> (rx/merge + (->> (create-files context zip-files) + (rx/flat-map + (fn [[file data]] + (->> (uz/load-from-url (:uri data)) + (rx/map #(-> context (assoc :zip %) (merge data))) + (rx/merge-map + (fn [context] + ;; process file retrieves a stream that will emit progress notifications + ;; and other that will emit the files once imported + (let [[progress-stream file-stream] (process-file context file)] + (rx/merge progress-stream + (->> file-stream + (rx/map + (fn [file] + {:status :import-finish + :errors (:errors file) + :file-id (:file-id data)}))))))) + (rx/catch (fn [cause] + (log/error :hint (ex-message cause) :file-id (:file-id data) :cause cause) + (rx/of {:status :import-error + :file-id (:file-id data) + :error (ex-message cause) + :error-data (ex-data cause)}))))))) + + (->> (rx/from binary-files) + (rx/flat-map + (fn [data] + (->> (http/send! + {:uri (:uri data) + :response-type :blob + :method :get}) + (rx/map :body) + (rx/mapcat #(rp/command! :import-binfile {:file % + :project-id project-id})) + (rx/map + (fn [_] + {:status :import-finish + :file-id (:file-id data)}))))))) (rx/catch (fn [cause] (log/error :hint "unexpected error on import process" diff --git a/frontend/src/app/worker/selection.cljs b/frontend/src/app/worker/selection.cljs index 692cf61ce6..b5576d2911 100644 --- a/frontend/src/app/worker/selection.cljs +++ b/frontend/src/app/worker/selection.cljs @@ -84,14 +84,12 @@ index-shape (make-index-shape objects parents-index clip-parents-index) initial-quadtree (qdt/create (clj->js bounds)) - index (reduce index-shape initial-quadtree shapes) + index (reduce index-shape initial-quadtree shapes)] - z-index (cp/calculate-z-index objects)] - - {:index index :z-index z-index :bounds bounds})) + {:index index :bounds bounds})) (defn- update-index - [{index :index z-index :z-index :as data} old-objects new-objects] + [{index :index :as data} old-objects new-objects] (let [changes? (fn [id] (not= (get old-objects id) (get new-objects id))) @@ -110,14 +108,12 @@ new-index (qdt/remove-all index changed-ids) index-shape (make-index-shape new-objects parents-index clip-parents-index) - index (reduce index-shape new-index shapes) + index (reduce index-shape new-index shapes)] - z-index (cp/update-z-index z-index changed-ids old-objects new-objects)] - - (assoc data :index index :z-index z-index))) + (assoc data :index index))) (defn- query-index - [{index :index z-index :z-index} rect frame-id full-frame? include-frames? ignore-groups? clip-children? reverse?] + [{index :index} rect frame-id full-frame? include-frames? ignore-groups? clip-children?] (let [result (-> (qdt/search index (clj->js rect)) (es6-iterator-seq)) @@ -143,32 +139,18 @@ overlaps-parent? (fn [clip-parents] - (->> clip-parents (some (comp not overlaps?)) not)) - - add-z-index - (fn [{:keys [id frame-id] :as shape}] - (assoc shape :z (+ (get z-index id) - (get z-index frame-id 0)))) - - ;; Shapes after filters of overlapping and criteria - matching-shapes - (into [] - (comp (map #(unchecked-get % "data")) - (filter match-criteria?) - (filter overlaps?) - (filter (comp overlaps? :frame)) - (filter (if clip-children? - (comp overlaps-parent? :clip-parents) - (constantly true))) - (map add-z-index)) - result) - - keyfn (if reverse? (comp - :z) :z)] + (->> clip-parents (some (comp not overlaps?)) not))] + ;; Shapes after filters of overlapping and criteria (into (d/ordered-set) - (->> matching-shapes - (sort-by keyfn) - (map :id))))) + (comp (map #(unchecked-get % "data")) + (filter match-criteria?) + (filter overlaps?) + (filter (if clip-children? + (comp overlaps-parent? :clip-parents) + (constantly true))) + (map :id)) + result))) (defmethod impl/handler :selection/initialize-index @@ -203,13 +185,8 @@ nil) (defmethod impl/handler :selection/query - [{:keys [page-id rect frame-id reverse? full-frame? include-frames? ignore-groups? clip-children?] - :or {reverse? false full-frame? false include-frames? false clip-children? true} :as message}] + [{:keys [page-id rect frame-id full-frame? include-frames? ignore-groups? clip-children?] + :or {full-frame? false include-frames? false clip-children? true} :as message}] (when-let [index (get @state page-id)] - (query-index index rect frame-id full-frame? include-frames? ignore-groups? clip-children? reverse?))) + (query-index index rect frame-id full-frame? include-frames? ignore-groups? clip-children?))) -(defmethod impl/handler :selection/query-z-index - [{:keys [page-id objects ids]}] - (when-let [{z-index :z-index} (get @state page-id)] - (->> ids (map #(+ (get z-index %) - (get z-index (get-in objects [% :frame-id]))))))) diff --git a/frontend/src/features.cljs b/frontend/src/features.cljs new file mode 100644 index 0000000000..353504a1e3 --- /dev/null +++ b/frontend/src/features.cljs @@ -0,0 +1,14 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +;; This namespace is only to export the functions for toggle features +(ns features + (:require + [app.main.ui.features :as features])) + +(defn ^:export autolayout [] + (features/toggle-feature! :auto-layout)) + diff --git a/frontend/test/app/components_sync_test.cljs b/frontend/test/app/components_sync_test.cljs index a34af0f167..b3d754a37a 100644 --- a/frontend/test/app/components_sync_test.cljs +++ b/frontend/test/app/components_sync_test.cljs @@ -6,7 +6,7 @@ [app.common.pages.helpers :as cph] [app.main.data.workspace :as dw] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries-helpers :as dwlh] [app.main.data.workspace.state-helpers :as wsh] @@ -192,7 +192,7 @@ (ptk/emit! store - (dwc/delete-shapes #{(:id shape1)}) + (dwsh/delete-shapes #{(:id shape1)}) :the/end))))) (t/deftest test-touched-children-move @@ -767,7 +767,7 @@ (ptk/emit! store - (dwc/delete-shapes #{(:id shape1)}) + (dwsh/delete-shapes #{(:id shape1)}) (dwl/reset-component (:id instance1)) :the/end))))) @@ -1538,7 +1538,7 @@ (ptk/emit! store - (dwc/delete-shapes #{(:id shape1)}) + (dwsh/delete-shapes #{(:id shape1)}) (dwl/update-component-sync (:id instance1) (:id file)) :the/end))))) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 06a8a28687..d46f663085 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -62,23 +62,23 @@ msgstr "سعيد برؤيتك مجددا!" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "تسجيل الدخول عبر Github" +msgstr "Github" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "تسجيل الدخول عبر Gitlab" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "تسجيل الدخول عبر جوجل" +msgstr "جوجل" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "تسجيل الدخول باستخدام LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "تسجيل الدخول باستخدام OpenID (SSO)" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -499,7 +499,7 @@ msgstr "تنسيق الصورة غير مدعوم (يجب أن يكون svg أو #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "الصورة كبيرة جدا بحيث لا يمكن إدراجها (يجب أن تكون أقل من 5mb)." +msgstr "الصورة كبيرة جدا بحيث لا يمكن إدراجها." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -2034,10 +2034,6 @@ msgstr "الصفوف" 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 "ملء المجموعة" @@ -2063,4 +2059,4 @@ msgid "workspace.options.layer-options.blend-mode.difference" msgstr "اختلاف" msgid "workspace.viewport.click-to-close-path" -msgstr "انقر لإغلاق المسار" \ No newline at end of file +msgstr "انقر لإغلاق المسار" diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index 66f55e803c..3f7b30b244 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-03-12 23:56+0000\n" -"Last-Translator: Rubén \n" +"PO-Revision-Date: 2022-07-09 11:14+0000\n" +"Last-Translator: Josep Ponsà \n" "Language-Team: Catalan " "\n" "Language: ca\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.12-dev\n" +"X-Generator: Weblate 4.13.1-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -65,23 +65,23 @@ msgstr "Ens agrada tornar a veure-vos!" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "Entra amb GitHub" +msgstr "GitHub" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Entra amb Gitlab" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "Entra amb Google" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Entra amb LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "Entra amb OpenID (SSO)" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -185,33 +185,15 @@ msgstr "S'ha copiat l'enllaç correctament" msgid "common.share-link.link-deleted-success" msgstr "S'ha eliminat l'enllaç correctament" -msgid "common.share-link.permissions-can-access" -msgstr "Pot accedir" - -msgid "common.share-link.permissions-can-view" -msgstr "Lector" - msgid "common.share-link.permissions-hint" msgstr "Qualsevol persona amb l'enllaç hi tindrà accés" msgid "common.share-link.placeholder" msgstr "L'enllaç per a compartir apareixerà aquí" -msgid "common.share-link.remove-link" -msgstr "Elimina l'enllaç" - msgid "common.share-link.title" msgstr "Compartiu prototips" -msgid "common.share-link.view-all-pages" -msgstr "Totes les pàgines" - -msgid "common.share-link.view-current-page" -msgstr "Només aquesta pàgina" - -msgid "common.share-link.view-selected-pages" -msgstr "Pàgines seleccionades" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Afegeix a la biblioteca compartida" @@ -262,9 +244,39 @@ msgstr "" msgid "dashboard.export-frames" msgstr "Exporta les taules de treball a PDF..." +#: src/app/main/ui/export.cljs +msgid "dashboard.export-frames.title" +msgstr "Exporta a PDF" + msgid "dashboard.export-multi" msgstr "Exporta %s fitxers de Penpot" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "%s de %s elements seleccionats" + +#: src/app/main/ui/workspace/header.cljs +msgid "dashboard.export-shapes" +msgstr "Exporta" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Pots afegir una configuració d'exportació a elements des de les propietats " +"del disseny (a sota de la barra de la dreta)." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "Informació sobre com establir exportacions a Penpot." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "No hi ha elements amb configuració d'exportació." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.title" +msgstr "Selecció d'exportació" + msgid "dashboard.export-single" msgstr "Exporta el fitxer de Penpot" @@ -426,6 +438,14 @@ msgstr "+ Projecte nou" msgid "dashboard.new-project-prefix" msgstr "Projecte nou" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "Envia'm novetats, actualitzacions de producte i recomanacions de Penpot." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Subscripció al butlletí" + #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.no-matches-for" msgstr "No s'ha trobat cap coincidència amb “%s“" @@ -481,6 +501,10 @@ msgstr "Voleu eliminar el vostre compte?" msgid "dashboard.remove-shared" msgstr "Elimina de les biblioteques compartides" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "Desa configuració" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.search-placeholder" msgstr "Cerca…" @@ -623,6 +647,9 @@ msgstr "El correu «%s» té molts informes de retorn permanents." msgid "errors.email-invalid-confirmation" msgstr "El correu de confirmació ha de coincidir" +msgid "errors.email-spam-or-permanent-bounces" +msgstr "El correu «%s» s'ha marcat com a brossa o rebot permanent." + #: 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." @@ -635,6 +662,13 @@ msgstr "L'autenticació amb Google està desactivada en aquest servidor" msgid "errors.invalid-color" msgstr "El color no és vàlid" +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "Invitació no vàlida" + +msgid "errors.invite-invalid.info" +msgstr "Aquesta invitació pot estar cancel·lada o caducada." + #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" msgstr "L'autenticació LDAP està inhabilitada." @@ -644,7 +678,7 @@ msgstr "El format d'imatge no està suportat (ha de ser SVG, JPG o PNG)." #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "La imatge és massa gran (ha de ser inferior a 5 MB)." +msgstr "La imatge és massa gran." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -965,6 +999,10 @@ msgstr "Informació" msgid "history.alert-message" msgstr "Esteu veient la versió %s" +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Dreceres" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.about-penpot" msgstr "Sobre Penpot" @@ -983,6 +1021,12 @@ msgstr "Administració" msgid "labels.all" msgstr "Tot" +msgid "labels.and" +msgstr "i" + +msgid "labels.back" +msgstr "Enrere" + #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.desc-message" msgstr "" @@ -1017,6 +1061,9 @@ msgstr "Contingut" msgid "labels.continue" msgstr "Continua" +msgid "labels.continue-with" +msgstr "Continua amb" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "labels.create" msgstr "Crea" @@ -1051,6 +1098,10 @@ msgstr "Elimina el comentari" msgid "labels.delete-comment-thread" msgstr "Elimina el fil" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.delete-invitation" +msgstr "Esborra invitació" + #: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.delete-multi-files" msgstr "Elimina %s fitxers" @@ -1074,6 +1125,10 @@ msgstr "Editor" msgid "labels.email" msgstr "Correu electrònic" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Ha caducat" + msgid "labels.export" msgstr "Exporta" @@ -1097,6 +1152,10 @@ msgstr "Estils" msgid "labels.fonts" msgstr "Tipus de lletra" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Repositori Github" + #: 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 "Envia opinions" @@ -1131,6 +1190,10 @@ msgstr "" msgid "labels.internal-error.main-message" msgstr "Error intern" +#: 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.invitations" +msgstr "Invitacions" + #: src/app/main/ui/settings/options.cljs msgid "labels.language" msgstr "Llengua" @@ -1149,6 +1212,10 @@ msgstr "Tanca la sessió" msgid "labels.manage-fonts" msgstr "Gestiona els tipus de lletra" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "Membre" + #: 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" @@ -1168,6 +1235,14 @@ msgstr "Següent" msgid "labels.no-comments-available" msgstr "No teniu notificacions de comentaris pendents" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations" +msgstr "No hi ha invitacions." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations-hint" +msgstr "Prem el botó \"Convida a l'equip\" per convidar més membres a aquest equip." + #: src/app/main/ui/static.cljs msgid "labels.not-found.auth-info" msgstr "Sessió iniciada com a" @@ -1218,6 +1293,10 @@ msgstr "Propietari" msgid "labels.password" msgstr "Contrasenya" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "Pendent" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.permissions" msgstr "Permisos" @@ -1241,6 +1320,10 @@ msgstr "Notes de la versió" msgid "labels.remove" msgstr "Elimina" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove-member" +msgstr "Elimina membre" + #: 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 "Canvia el nom" @@ -1249,6 +1332,10 @@ msgstr "Canvia el nom" msgid "labels.rename-team" msgstr "Canvia el nom de l’equip" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.resend-invitation" +msgstr "Reenvia invitació" + #: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "Tornar a intentar-ho" @@ -1308,6 +1395,14 @@ msgstr "Omet" msgid "labels.start" msgstr "Inicia" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "Estat" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Tutorials" + #: src/app/main/ui/settings/profile.cljs msgid "labels.update" msgstr "Actualitza" @@ -1336,6 +1431,10 @@ msgstr "Espai de treball" msgid "labels.write-new-comment" msgstr "Escriu un comentari nou" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(tu)" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.your-account" msgstr "El meu compte" @@ -1385,6 +1484,12 @@ msgstr "Canvia el correu" msgid "modals.change-email.title" msgstr "Canvieu el vostre correu electrònic" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.change-owner-and-leave-confirm.message" +msgstr "" +"Ets qui té la propietat d'aquest equip. Abans de marxar, cal que escullis " +"una persona per substituir-te." + #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" msgstr "Cancel·la i conserva el meu compte" @@ -1505,10 +1610,23 @@ msgstr "Elimina el membre de l'equip" msgid "modals.invite-member-confirm.accept" msgstr "Envia una invitació" +msgid "modals.invite-member.emails" +msgstr "Correus electrònics, separats per una coma" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.invite-member.title" msgstr "Convida a unir-se a l'equip" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "" +"Com que no hi ha ningú més a aquest equip, s'eliminarà l'equip amb els seus " +"arxius i projectes." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.message" +msgstr "Segur que vols deixar l'equip %s?" + msgid "modals.leave-and-reassign.forbiden" msgstr "" "No es pot abandonar l'equip si no hi ha cap altre membre capaç d'ascendir a " @@ -1554,6 +1672,12 @@ msgstr "Quantitat d'empenta" msgid "modals.promote-owner-confirm.accept" msgstr "Ascendeix" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.hint" +msgstr "" +"Si transfereixes la propietat, canviaràs el teu rol a Admin, perdent alguns " +"permisos sobre l'equip. " + #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.message" msgstr "Segur que voleu ascendir aquest usuari a propietari?" @@ -1643,6 +1767,29 @@ msgstr "" "Esteu treballant amb algú? Creeu un equip i convideu persones a treballar " "juntes en projectes i compartiu recursos de disseny." +msgid "onboarding.choice.team-up.create-team" +msgstr "Nom del teu equip" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "Quan hagis posat un nom al teu equip podràs convidar persones a unir-s'hi." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Introdueix el nom de l'equip" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Convida membres" + +msgid "onboarding.choice.team-up.invite-members-desc" +msgstr "" +"També podràs convidar membres o canviar els rols des de la pàgina de " +"l'equip." + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Crea l'equip ara i convida membres en un altre moment" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Crea l'equip i envia les invitacions" + msgid "onboarding.choice.title" msgstr "Et donem la benvinguda a Penpot" @@ -1666,6 +1813,37 @@ msgstr "projecte a github" msgid "onboarding.contrib.title" msgstr "Ets contribuïdor de codi obert?" +msgid "onboarding.newsletter.accept" +msgstr "Sí, subscriu-m'hi" + +msgid "onboarding.newsletter.acceptance-message" +msgstr "" +"S'ha enviat la teva sol·licitud de subscripció. T'enviarem un e-mail per " +"confirmar-ho." + +msgid "onboarding.newsletter.decline" +msgstr "No, gràcies" + +msgid "onboarding.newsletter.desc" +msgstr "" +"Subscriu-te al nostre butlletí per estar al dia de les novetats i del " +"progrés del desenvolupament de producte." + +msgid "onboarding.newsletter.policy" +msgstr "Política de privacitat." + +msgid "onboarding.newsletter.privacy1" +msgstr "Com que ens preocupa la privacitat, aquí hi ha " + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"Només t'enviarem correus rellevants. Et podràs desapuntar sempre que " +"vulguis des del teu perfil d'usuari o des de l'enllaç que trobaràs a " +"qualsevol dels nostres butlletins." + +msgid "onboarding.newsletter.title" +msgstr "Vols rebre novetats de Penpot?" + msgid "onboarding.slide.0.alt" msgstr "Crea dissenys" @@ -1780,6 +1958,420 @@ msgstr "Vés a l'inici de sessió" msgid "settings.multiple" msgstr "Mixt" +# SECTIONS +msgid "shortcut-section.basics" +msgstr "Bàsics" + +msgid "shortcut-section.dashboard" +msgstr "Panell de control" + +msgid "shortcut-section.viewer" +msgstr "Visualitzador" + +msgid "shortcut-section.workspace" +msgstr "Espai de treball" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "Alineació" + +msgid "shortcut-subsection.edit" +msgstr "Edició" + +msgid "shortcut-subsection.general-dashboard" +msgstr "Genèric" + +msgid "shortcut-subsection.general-viewer" +msgstr "Genèric" + +msgid "shortcut-subsection.main-menu" +msgstr "Menú principal" + +msgid "shortcut-subsection.modify-layers" +msgstr "Modifica les capes" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Navegació" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Navegació" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Navegació" + +msgid "shortcut-subsection.panels" +msgstr "Panells" + +msgid "shortcut-subsection.path-editor" +msgstr "Camins" + +msgid "shortcut-subsection.shape" +msgstr "Formes" + +msgid "shortcut-subsection.tools" +msgstr "Eines" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "Zoom" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "Zoom" + +msgid "shortcuts.add-comment" +msgstr "Comentaris" + +msgid "shortcuts.add-node" +msgstr "Afegeix node" + +msgid "shortcuts.align-bottom" +msgstr "Alinea a baix" + +msgid "shortcuts.align-hcenter" +msgstr "Alinea al centre horitzontalment" + +msgid "shortcuts.align-left" +msgstr "Alinea a l'esquerra" + +msgid "shortcuts.align-right" +msgstr "Alinea a la dreta" + +msgid "shortcuts.align-top" +msgstr "Alinea a dalt" + +msgid "shortcuts.align-vcenter" +msgstr "Alinea al centre verticalment" + +msgid "shortcuts.artboard-selection" +msgstr "Crea una taula de treball a partir de selecció" + +msgid "shortcuts.bool-difference" +msgstr "Diferència booleana" + +msgid "shortcuts.bool-exclude" +msgstr "Exclusió booleana" + +msgid "shortcuts.bool-intersection" +msgstr "Intersecció booleana" + +msgid "shortcuts.bool-union" +msgstr "Unió booleana" + +msgid "shortcuts.bring-back" +msgstr "Envia-ho al fons" + +msgid "shortcuts.bring-backward" +msgstr "Envia-ho cap avall" + +msgid "shortcuts.bring-forward" +msgstr "Envia-ho cap amunt" + +msgid "shortcuts.bring-front" +msgstr "Envia-ho a dalt" + +msgid "shortcuts.clear-undo" +msgstr "Esborra desfer" + +msgid "shortcuts.copy" +msgstr "Copia" + +msgid "shortcuts.create-component" +msgstr "Crea component" + +msgid "shortcuts.create-new-project" +msgstr "Crea nou" + +msgid "shortcuts.cut" +msgstr "Retalla" + +msgid "shortcuts.decrease-zoom" +msgstr "Disminueix el zoom" + +msgid "shortcuts.delete" +msgstr "Esborra" + +msgid "shortcuts.delete-node" +msgstr "Esborra el node" + +msgid "shortcuts.detach-component" +msgstr "Separa el component" + +msgid "shortcuts.draw-curve" +msgstr "Corba" + +msgid "shortcuts.draw-ellipse" +msgstr "El·lipse" + +msgid "shortcuts.draw-frame" +msgstr "Taula de treball" + +msgid "shortcuts.draw-nodes" +msgstr "Dibuixa el camí" + +msgid "shortcuts.draw-path" +msgstr "Camí" + +msgid "shortcuts.draw-rect" +msgstr "Rectangle" + +msgid "shortcuts.draw-text" +msgstr "Text" + +msgid "shortcuts.duplicate" +msgstr "Duplica" + +msgid "shortcuts.escape" +msgstr "Cancel·la" + +msgid "shortcuts.export-shapes" +msgstr "Exporta formes" + +msgid "shortcuts.fit-all" +msgstr "Amplia per encabir-ho tot" + +msgid "shortcuts.flip-horizontal" +msgstr "Gira horitzontalment" + +msgid "shortcuts.flip-vertical" +msgstr "Gira verticalment" + +msgid "shortcuts.go-to-drafts" +msgstr "Ves als esborranys" + +msgid "shortcuts.go-to-libs" +msgstr "Ves a les llibreries compartides" + +msgid "shortcuts.go-to-search" +msgstr "Cerca" + +msgid "shortcuts.group" +msgstr "Grup" + +msgid "shortcuts.h-distribute" +msgstr "Distribueix horitzontalment" + +msgid "shortcuts.hide-ui" +msgstr "Mostra/Amaga UI" + +msgid "shortcuts.increase-zoom" +msgstr "Amplia" + +msgid "shortcuts.insert-image" +msgstr "Insereix imatge" + +msgid "shortcuts.join-nodes" +msgstr "Uneix nodes" + +msgid "shortcuts.make-corner" +msgstr "Fes cantonada" + +msgid "shortcuts.make-curve" +msgstr "Fes corba" + +msgid "shortcuts.mask" +msgstr "Màscara" + +msgid "shortcuts.merge-nodes" +msgstr "Fusiona nodes" + +msgid "shortcuts.move" +msgstr "Mou" + +msgid "shortcuts.move-fast-down" +msgstr "Mou avall ràpidament" + +msgid "shortcuts.move-fast-left" +msgstr "Mou a l'esquerra ràpidament" + +msgid "shortcuts.move-fast-right" +msgstr "Mou a la dreta ràpidament" + +msgid "shortcuts.move-fast-up" +msgstr "Mou amunt ràpidament" + +msgid "shortcuts.move-nodes" +msgstr "Mou node" + +msgid "shortcuts.move-unit-down" +msgstr "Mou avall" + +msgid "shortcuts.move-unit-left" +msgstr "Mou a l'esquerra" + +msgid "shortcuts.move-unit-right" +msgstr "Mou a la dreta" + +msgid "shortcuts.move-unit-up" +msgstr "Mou amunt" + +msgid "shortcuts.next-frame" +msgstr "Següent taula de treball" + +msgid "shortcuts.opacity-0" +msgstr "Fixa l'opacitat al 100%" + +msgid "shortcuts.opacity-1" +msgstr "Fixa l'opacitat al 10%" + +msgid "shortcuts.opacity-2" +msgstr "Fixa l'opacitat al 20%" + +msgid "shortcuts.opacity-3" +msgstr "Fixa l'opacitat al 30%" + +msgid "shortcuts.opacity-4" +msgstr "Fixa l'opacitat al 40%" + +msgid "shortcuts.opacity-5" +msgstr "Fixa l'opacitat al 50%" + +msgid "shortcuts.opacity-6" +msgstr "Fixa l'opacitat al 60%" + +msgid "shortcuts.opacity-7" +msgstr "Fixa l'opacitat al 70%" + +msgid "shortcuts.opacity-8" +msgstr "Fixa l'opacitat al 80%" + +msgid "shortcuts.opacity-9" +msgstr "Fixa l'opacitat al 90%" + +msgid "shortcuts.open-color-picker" +msgstr "Selector de color" + +msgid "shortcuts.open-comments" +msgstr "Ves a la visualització de comentaris" + +msgid "shortcuts.open-dashboard" +msgstr "Ves al panell de control" + +msgid "shortcuts.open-handoff" +msgstr "Ves a la secció de transferència d'observadors" + +msgid "shortcuts.open-interactions" +msgstr "Ves a la secció d'interaccions de visitant" + +msgid "shortcuts.open-viewer" +msgstr "Ves a la secció d'interaccions de visitant" + +msgid "shortcuts.open-workspace" +msgstr "Ves a l'espai de treball" + +msgid "shortcuts.paste" +msgstr "Enganxa" + +msgid "shortcuts.prev-frame" +msgstr "Taula de treball anterior" + +msgid "shortcuts.redo" +msgstr "Refés" + +msgid "shortcuts.reset-zoom" +msgstr "Desfés zoom" + +msgid "shortcuts.search-placeholder" +msgstr "Cerca dreceres" + +msgid "shortcuts.select-all" +msgstr "Selecciona-ho tot" + +msgid "shortcuts.separate-nodes" +msgstr "Separa nodes" + +msgid "shortcuts.show-pixel-grid" +msgstr "Mostra/Amaga graella de píxels" + +msgid "shortcuts.show-shortcuts" +msgstr "Mostra/Amaga dreceres" + +msgid "shortcuts.snap-nodes" +msgstr "Ajusta als nodes" + +msgid "shortcuts.snap-pixel-grid" +msgstr "Ajusta a la graella de píxels" + +msgid "shortcuts.start-editing" +msgstr "Comença a editar" + +msgid "shortcuts.start-measure" +msgstr "Comença a mesurar" + +msgid "shortcuts.stop-measure" +msgstr "Deixa de mesurar" + +msgid "shortcuts.thumbnail-set" +msgstr "Estableix miniatures" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "Dreceres de teclat" + +msgid "shortcuts.toggle-alignment" +msgstr "Commuta l'alineació dinàmica" + +msgid "shortcuts.toggle-assets" +msgstr "Commuta actius" + +msgid "shortcuts.toggle-colorpalette" +msgstr "Commuta paleta de colors" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Commuta el mode d'enfocament" + +msgid "shortcuts.toggle-grid" +msgstr "Mostra/Amaga graella" + +msgid "shortcuts.toggle-history" +msgstr "Commuta l'historial" + +msgid "shortcuts.toggle-layers" +msgstr "Commuta capes" + +msgid "shortcuts.toggle-lock" +msgstr "Bloqueja la selecció" + +msgid "shortcuts.toggle-lock-size" +msgstr "Bloqueja proporcions" + +msgid "shortcuts.toggle-rules" +msgstr "Mostra/Amaga regles" + +msgid "shortcuts.toggle-scale-text" +msgstr "Commuta l'escala del text" + +msgid "shortcuts.toggle-snap-grid" +msgstr "Ajusta a la graella" + +msgid "shortcuts.toggle-snap-guide" +msgstr "Ajusta a les guies" + +msgid "shortcuts.toggle-textpalette" +msgstr "Commuta paleta de text" + +msgid "shortcuts.toggle-visibility" +msgstr "Commuta visibilitat" + +msgid "shortcuts.toggle-zoom-style" +msgstr "Commuta estil de zoom" + +msgid "shortcuts.toogle-fullscreen" +msgstr "Commuta pantalla completa" + +msgid "shortcuts.undo" +msgstr "Desfés" + +msgid "shortcuts.ungroup" +msgstr "Desagrupa" + +msgid "shortcuts.unmask" +msgstr "Desemmascara" + +msgid "shortcuts.v-distribute" +msgstr "Distribueix verticalment" + +msgid "shortcuts.zoom-selected" +msgstr "Amplia a la selecció" + #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" msgstr "%s - Penpot" @@ -1824,6 +2416,10 @@ msgstr "Contrasenya - Penpot" msgid "title.settings.profile" msgstr "Perfil - Penpot" +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-invitations" +msgstr "Invitacions - %s - Penpot" + #: src/app/main/ui/dashboard/team.cljs msgid "title.team-members" msgstr "Membres - %s - Penpot" @@ -2076,6 +2672,18 @@ msgstr "Transforma el text" msgid "workspace.assets.ungroup" msgstr "Desagrupa" +msgid "workspace.focus.focus-mode" +msgstr "Mode d'enfocament" + +msgid "workspace.focus.focus-off" +msgstr "Enfocament apagat" + +msgid "workspace.focus.focus-on" +msgstr "Enfocament actiu" + +msgid "workspace.focus.selection" +msgstr "Selecció" + #: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs msgid "workspace.gradients.linear" msgstr "Degradat lineal" @@ -2100,6 +2708,9 @@ msgstr "Desactiva l'ajust a la quadrícula" msgid "workspace.header.menu.disable-snap-guides" msgstr "No ajustes a les guies" +msgid "workspace.header.menu.disable-snap-pixel-grid" +msgstr "Desactiva l'ajustament al píxel" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.enable-dynamic-alignment" msgstr "Activa l'alineació dinàmica" @@ -2116,6 +2727,9 @@ msgstr "Ajusta a la quadrícula" msgid "workspace.header.menu.enable-snap-guides" msgstr "Ajusta a les guies" +msgid "workspace.header.menu.enable-snap-pixel-grid" +msgstr "Activa l'ajustament al píxel" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-artboard-names" msgstr "Amaga els noms de les taules" @@ -2128,6 +2742,9 @@ msgstr "Amaga la quadrícula" msgid "workspace.header.menu.hide-palette" msgstr "Amaga la paleta de colors" +msgid "workspace.header.menu.hide-pixel-grid" +msgstr "Amaga la graella de píxels" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-rules" msgstr "Amaga les regles" @@ -2145,6 +2762,10 @@ msgstr "Edita" msgid "workspace.header.menu.option.file" msgstr "Fixer" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "Ajuda & Informació" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.option.preferences" msgstr "Preferències" @@ -2169,6 +2790,9 @@ msgstr "Mostra la quadrícula" msgid "workspace.header.menu.show-palette" msgstr "Mostra la paleta de colors" +msgid "workspace.header.menu.show-pixel-grid" +msgstr "Mostra graella de píxels" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-rules" msgstr "Mostra les regles" @@ -2416,6 +3040,10 @@ msgstr "Disseny" msgid "workspace.options.export" msgstr "Exporta" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-multiple" +msgstr "Exporta la selecció" + #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" msgstr "Exporta" @@ -2424,10 +3052,22 @@ msgstr "Exporta" msgid "workspace.options.export.suffix" msgstr "Sufix" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "Exportació completa" + #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.exporting-object" msgstr "S'està exportant…" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-error" +msgstr "Exportació fallida" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-slow" +msgstr "Exportació inesperadament lenta" + #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.fill" msgstr "Emplenat" @@ -2527,10 +3167,6 @@ msgstr "Files" msgid "workspace.options.grid.square" msgstr "Quadrat" -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.title" -msgstr "Quadrícula i disposicions" - #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.group-fill" msgstr "Emplenament del grup" @@ -2829,6 +3465,14 @@ msgstr "Agrupa les capes" msgid "workspace.options.layer-options.title.multiple" msgstr "Capes seleccionades" +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-colors" +msgstr "Més colors" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-lib-colors" +msgstr "Més llibreries de colors" + msgid "workspace.options.opacity" msgstr "Opacitat" @@ -2854,6 +3498,10 @@ msgstr "Cantons individuals" msgid "workspace.options.recent-fonts" msgstr "Recent" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.retry" +msgstr "Torna-ho a provar" + #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.rotation" msgstr "Rotació" @@ -2871,6 +3519,10 @@ msgstr "" msgid "workspace.options.select-artboard" msgstr "Selecciona la taula de treball" +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.selection-color" +msgstr "Colors seleccionats" + #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.selection-fill" msgstr "Emplenament de selecció" @@ -3280,6 +3932,12 @@ msgstr "Mostra" msgid "workspace.shape.menu.show-main" msgstr "Vés al component principal" +msgid "workspace.shape.menu.thumbnail-remove" +msgstr "Esborra la miniatura" + +msgid "workspace.shape.menu.thumbnail-set" +msgstr "Estableix com a miniatura" + msgid "workspace.shape.menu.transform-to-path" msgstr "Transforma en camí" @@ -3314,6 +3972,30 @@ msgstr "Historial (%s)" msgid "workspace.sidebar.layers" msgstr "Capes" +msgid "workspace.sidebar.layers.components" +msgstr "Components" + +msgid "workspace.sidebar.layers.frames" +msgstr "Taules de treball" + +msgid "workspace.sidebar.layers.groups" +msgstr "Grups" + +msgid "workspace.sidebar.layers.images" +msgstr "Imatges" + +msgid "workspace.sidebar.layers.masks" +msgstr "Màscares" + +msgid "workspace.sidebar.layers.search" +msgstr "Cerca capes" + +msgid "workspace.sidebar.layers.shapes" +msgstr "Formes" + +msgid "workspace.sidebar.layers.texts" +msgstr "Textos" + #: 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 "Atributs SVG importats" @@ -3366,6 +4048,10 @@ msgstr "Camí (%s)" msgid "workspace.toolbar.rect" msgstr "Rectangle (%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "Dreceres (%s)" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.text" msgstr "Text (%s)" @@ -3502,4 +4188,4 @@ msgid "workspace.updates.update" msgstr "Actualitza" msgid "workspace.viewport.click-to-close-path" -msgstr "Feu clic per a tancar el camí" \ No newline at end of file +msgstr "Feu clic per a tancar el camí" diff --git a/frontend/translations/da.po b/frontend/translations/da.po index 9f09c8dbd5..03988030a4 100644 --- a/frontend/translations/da.po +++ b/frontend/translations/da.po @@ -65,23 +65,23 @@ msgstr "Fedt at se dig igen!" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "Log på med Github" +msgstr "Github" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Log på med Gitlab" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "Log på med Google" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Log på med LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "Log på med OpenID (SSO)" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -476,4 +476,4 @@ msgstr "Skrifttype Udbydere - %s - Penpot" #: src/app/main/ui/dashboard/fonts.cljs msgid "title.dashboard.fonts" -msgstr "Skrifttyper - %s - Penpot" \ No newline at end of file +msgstr "Skrifttyper - %s - Penpot" diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 3f7645b39b..a58c546323 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -66,23 +66,23 @@ msgstr "Schön, Sie wiederzusehen!" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "Anmelden mit GitHub" +msgstr "GitHub" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Einloggen mit Gitlab" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "Anmelden mit Google" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Anmelden mit LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "Anmelden mit OpenID (SSO)" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -188,33 +188,15 @@ msgstr "Link wurde erfolgreich kopiert" msgid "common.share-link.link-deleted-success" msgstr "Link wurde erfolgreich gelöscht" -msgid "common.share-link.permissions-can-access" -msgstr "Freigabe für" - -msgid "common.share-link.permissions-can-view" -msgstr "Sichtbar" - msgid "common.share-link.permissions-hint" msgstr "Jeder mit dem Link kann auf die Datei zugreifen" msgid "common.share-link.placeholder" msgstr "Link zum Teilen wird hier erscheinen" -msgid "common.share-link.remove-link" -msgstr "Link entfernen" - msgid "common.share-link.title" msgstr "Prototypen teilen" -msgid "common.share-link.view-all-pages" -msgstr "Alle Seiten" - -msgid "common.share-link.view-current-page" -msgstr "Nur diese Seite" - -msgid "common.share-link.view-selected-pages" -msgstr "Ausgewählte Seiten" - #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" @@ -711,7 +693,7 @@ msgstr "Das Bildformat wird nicht unterstützt (es muss ein SVG, JPG oder PNG se #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "Das Bild ist zu groß, um eingefügt zu werden (es muss unter 5MB sein)." +msgstr "Das Bild ist zu groß, um eingefügt zu werden." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -2813,10 +2795,6 @@ msgstr "Zeile" msgid "workspace.options.grid.square" msgstr "Quadrat" -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.title" -msgstr "Raster & Layouts" - #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.group-fill" msgstr "Gruppe füllen" @@ -3807,4 +3785,4 @@ msgid "workspace.updates.update" msgstr "Aktualisieren" msgid "workspace.viewport.click-to-close-path" -msgstr "Klicken Sie, um den Pfad zu schließen" \ No newline at end of file +msgstr "Klicken Sie, um den Pfad zu schließen" diff --git a/frontend/translations/el.po b/frontend/translations/el.po index 7db6d07fd2..1598117ea6 100644 --- a/frontend/translations/el.po +++ b/frontend/translations/el.po @@ -60,15 +60,15 @@ msgstr "Χαίρομαι που σας ξαναδώ" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "Συνδεθείτε με το Github" +msgstr "Github" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Συνδεθείτε με το Gitlab" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Συνδεθείτε με το LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -414,7 +414,7 @@ msgstr "Η μορφή εικόνας δεν αναγνωρίζεται (πρέπ #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "Η εικόνα είναι πολύ μεγάλη (πρέπει να είναι μικρότερη από 5mb)." +msgstr "Η εικόνα είναι πολύ μεγάλη." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -1706,10 +1706,6 @@ msgstr "Σειρές" 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 "Συμπλήρωση ομάδας" @@ -2311,4 +2307,4 @@ msgid "workspace.updates.update" msgstr "Ενημέρωση" msgid "workspace.viewport.click-to-close-path" -msgstr "Κάντε κλικ για να κλείσετε τη διαδρομή" \ No newline at end of file +msgstr "Κάντε κλικ για να κλείσετε τη διαδρομή" diff --git a/frontend/translations/en.po b/frontend/translations/en.po index de84307295..465199ae49 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-05-20 14:12+0000\n" -"Last-Translator: Jan C. Borchardt \n" +"PO-Revision-Date: 2022-05-28 21:16+0000\n" +"Last-Translator: Andrés Moya \n" "Language-Team: English " "\n" "Language: en\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.7-dev\n" +"X-Generator: Weblate 4.13-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -75,11 +75,11 @@ msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Login with LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "OpenID Connect" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -169,11 +169,20 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "We've sent a verification email to" +msgid "common.share-link.all-users" +msgstr "All Penpot users" + msgid "common.share-link.confirm-deletion-link-description" msgstr "" "Are you sure you want to remove this link? If you do it, it's no longer be " "available for anyone" +msgid "common.share-link.current-tag" +msgstr "(current)" + +msgid "common.share-link.destroy-link" +msgstr "Destroy link" + msgid "common.share-link.get-link" msgstr "Get link" @@ -183,32 +192,37 @@ msgstr "Link copied successfully" msgid "common.share-link.link-deleted-success" msgstr "Link deleted successfully" -msgid "common.share-link.permissions-can-access" -msgstr "Can access" +msgid "common.share-link.manage-ops" +msgstr "Manage permissions" -msgid "common.share-link.permissions-can-view" -msgstr "Can view" +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 page shared" +msgstr[1] "%s pages shared" + +msgid "common.share-link.permissions-can-comment" +msgstr "Can comment" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Can inspect code" msgid "common.share-link.permissions-hint" msgstr "Anyone with link will have access" +msgid "common.share-link.permissions-pages" +msgstr "Pages shared" + msgid "common.share-link.placeholder" msgstr "Shareable link will appear here" -msgid "common.share-link.remove-link" -msgstr "Remove link" +msgid "common.share-link.team-members" +msgstr "Only team members" msgid "common.share-link.title" msgstr "Share prototypes" -msgid "common.share-link.view-all-pages" -msgstr "All pages" - -msgid "common.share-link.view-current-page" -msgstr "Only this page" - -msgid "common.share-link.view-selected-pages" -msgstr "Selected pages" +msgid "common.share-link.view-all" +msgstr "Select All" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" @@ -234,6 +248,12 @@ msgstr "Your Penpot" msgid "dashboard.delete-team" msgstr "Delete team" +msgid "dashboard.download-binary-file" +msgstr "Download Penpot file (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "Download standard file (.svg + .json)" + msgid "dashboard.draft-title" msgstr "Draft" @@ -256,8 +276,11 @@ msgstr "" "Oh no! You have no files yet! If you want to try with some templates go to " "[Libraries & templates](https://penpot.app/libraries-templates.html)" +msgid "dashboard.export-binary-multi" +msgstr "Download %s Penpot files (.penpot)" + msgid "dashboard.export-frames" -msgstr "Export artboards to PDF..." +msgstr "Export artboards to PDF…" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" @@ -295,6 +318,9 @@ msgstr "Export selection" msgid "dashboard.export-single" msgstr "Export Penpot file" +msgid "dashboard.export-standard-multi" +msgstr "Download %s standard files (.svg + .json)" + msgid "dashboard.export.detail" msgstr "* Might include components, graphics, colors and/or typographies." @@ -633,6 +659,10 @@ msgstr "Are you sure?" msgid "ds.updated-at" msgstr "Updated: %s" +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "Authentication provider not configured." + msgid "errors.auth.unable-to-login" msgstr "Looks like you are not authenticated or session expired." @@ -690,7 +720,7 @@ 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)." +msgstr "The image is too large to be inserted." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -763,6 +793,20 @@ msgstr "Feeling like talking? Chat with us at Gitter" msgid "feedback.description" msgstr "Description" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Go to Penpot forum" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "" +"We're happy to have you here. If you need help, please search before you " +"post." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Penpot community" + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subject" msgstr "Subject" @@ -777,18 +821,6 @@ msgstr "" msgid "feedback.title" msgstr "Email" -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-go-to" -msgstr "Go to Penpot forum" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-subtitle1" -msgstr "We're happy to have you here. If you need help, please search before you post." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-title" -msgstr "Penpot community" - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.twitter-go-to" msgstr "Go to Twitter" @@ -988,7 +1020,7 @@ msgid "handoff.tabs.code.selected.curve" msgstr "Curve" msgid "handoff.tabs.code.selected.frame" -msgstr "Artboard" +msgstr "Board" msgid "handoff.tabs.code.selected.group" msgstr "Group" @@ -1074,6 +1106,10 @@ msgstr "Close" msgid "labels.comments" msgstr "Comments" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Community" + #: src/app/main/ui/settings/password.cljs msgid "labels.confirm-password" msgstr "Confirm password" @@ -1087,6 +1123,9 @@ msgstr "Continue" msgid "labels.continue-with" msgstr "Continue with" +msgid "labels.continue-with-penpot" +msgstr "You can continue with a Penpot account" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "labels.create" msgstr "Create" @@ -1228,6 +1267,9 @@ msgstr "Libraries & Templates" msgid "labels.link" msgstr "Link" +msgid "labels.log-or-sign" +msgstr "Log in or sign up" + #: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.logout" msgstr "Logout" @@ -1286,8 +1328,8 @@ msgstr[1] "%s files" msgid "labels.num-of-frames" msgid_plural "labels.num-of-frames" -msgstr[0] "1 artboard" -msgstr[1] "%s artboards" +msgstr[0] "1 board" +msgstr[1] "%s boards" #: src/app/main/ui/dashboard/team.cljs msgid "labels.num-of-projects" @@ -1402,6 +1444,9 @@ msgstr "Shared Libraries" msgid "labels.show-all-comments" msgstr "Show all comments" +msgid "labels.show-comments-list" +msgstr "Show comments list" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.show-your-comments" msgstr "Show only your comments" @@ -1420,10 +1465,6 @@ msgstr "Start" msgid "labels.status" msgstr "Status" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.community" -msgstr "Community" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.tutorials" msgstr "Tutorials" @@ -2034,7 +2075,7 @@ msgid "shortcuts.align-vcenter" msgstr "Align center vertically" msgid "shortcuts.artboard-selection" -msgstr "Create artboard from selection" +msgstr "Create board from selection" msgid "shortcuts.bool-difference" msgstr "Boolean difference" @@ -2094,7 +2135,7 @@ msgid "shortcuts.draw-ellipse" msgstr "Ellipse" msgid "shortcuts.draw-frame" -msgstr "Artboard" +msgstr "Board" msgid "shortcuts.draw-nodes" msgstr "Draw path" @@ -2196,7 +2237,10 @@ msgid "shortcuts.move-unit-up" msgstr "Move up" msgid "shortcuts.next-frame" -msgstr "Next artboard" +msgstr "Next board" + +msgid "shortcuts.not-found" +msgstr "No shortcuts found" msgid "shortcuts.opacity-0" msgstr "Set opacity to 100%" @@ -2249,11 +2293,14 @@ msgstr "Go to viewer interactions section" msgid "shortcuts.open-workspace" msgstr "Go to workspace" +msgid "shortcuts.or" +msgstr " or " + msgid "shortcuts.paste" msgstr "Paste" msgid "shortcuts.prev-frame" -msgstr "Previous artboard" +msgstr "Previous board" msgid "shortcuts.redo" msgstr "Redo" @@ -2438,11 +2485,11 @@ msgstr "Sorry!" #: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs msgid "viewer.empty-state" -msgstr "No artboards found on the page." +msgstr "No boards found on the page." #: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs msgid "viewer.frame-not-found" -msgstr "Artboard not found." +msgstr "Board not found." msgid "viewer.header.comments-section" msgstr "Comments (%s)" @@ -2714,7 +2761,7 @@ msgstr "Enable snap to pixel" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-artboard-names" -msgstr "Hide artboard names" +msgstr "Hide board names" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-grid" @@ -2761,7 +2808,7 @@ msgstr "Select all" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-artboard-names" -msgstr "Show artboards names" +msgstr "Show boards names" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-grid" @@ -2968,6 +3015,9 @@ msgstr "Selection blur" msgid "workspace.options.canvas-background" msgstr "Canvas background" +msgid "workspace.options.clip-content" +msgstr "Clip content" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs msgid "workspace.options.component" msgstr "Component" @@ -3074,6 +3124,10 @@ msgstr "Auto" msgid "workspace.options.grid.column" msgstr "Columns" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.grid-title" +msgstr "Grid" + msgid "workspace.options.grid.params.color" msgstr "Color" @@ -3149,10 +3203,6 @@ msgstr "Rows" 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" @@ -3448,6 +3498,154 @@ msgstr "Group layers" msgid "workspace.options.layer-options.title.multiple" msgstr "Selected layers" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.advanced-ops" +msgstr "Advanced options" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.max-h" +msgstr "Max.Height" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.max-w" +msgstr "Max.Width" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.min-h" +msgstr "Min.Height" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.min-w" +msgstr "Min.Width" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Element resizing" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.max-h" +msgstr "Maximum height" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.max-w" +msgstr "Maximum width" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.min-h" +msgstr "Minimum height" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.min-w" +msgstr "Minimum width" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "Bottom" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "Column" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.left" +msgstr "Row" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.right" +msgstr "Reverse row" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.top" +msgstr "Reverse column" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.gap" +msgstr "Gap" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "center" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.left" +msgstr "left" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.right" +msgstr "right" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "Left" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "Margin" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-all" +msgstr "All sides" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "Simple margin" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.no-wrap" +msgstr "no wrap" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.packed" +msgstr "packed" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding" +msgstr "Padding" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-all" +msgstr "All sides" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-simple" +msgstr "Simple padding" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "Right" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-around" +msgstr "space around" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-between" +msgstr "space between" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.title" +msgstr "Layout" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Top" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "bottom" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "center" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "top" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.wrap" +msgstr "wrap" + #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "More colors" @@ -3494,11 +3692,11 @@ msgstr "Search font" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.select-a-shape" -msgstr "Select a shape, artboard or group to drag a connection to other artboard." +msgstr "Select a shape, board or group to drag a connection to other board." #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.select-artboard" -msgstr "Select artboard" +msgstr "Select board" #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.selection-color" @@ -3555,6 +3753,9 @@ msgstr "Selection shadows" msgid "workspace.options.show-fill-on-export" msgstr "Show in exports" +msgid "workspace.options.show-in-viewer" +msgstr "Show in view mode" + #: 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" @@ -3799,7 +4000,7 @@ msgstr "Copy" #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.create-artboard-from-selection" -msgstr "Selection to artboard" +msgstr "Selection to board" #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.create-component" @@ -3955,7 +4156,7 @@ msgid "workspace.sidebar.layers.components" msgstr "Components" msgid "workspace.sidebar.layers.frames" -msgstr "Artboards" +msgstr "Boards" msgid "workspace.sidebar.layers.groups" msgstr "Groups" @@ -4009,7 +4210,7 @@ msgstr "Ellipse (%s)" #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.frame" -msgstr "Artboard (%s)" +msgstr "Board (%s)" #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.image" @@ -4068,7 +4269,7 @@ msgid "workspace.undo.entry.multiple.curve" msgstr "curves" msgid "workspace.undo.entry.multiple.frame" -msgstr "artboard" +msgstr "board" msgid "workspace.undo.entry.multiple.group" msgstr "groups" @@ -4114,7 +4315,7 @@ msgid "workspace.undo.entry.single.curve" msgstr "curve" msgid "workspace.undo.entry.single.frame" -msgstr "artboard" +msgstr "board" msgid "workspace.undo.entry.single.group" msgstr "group" @@ -4168,12 +4369,3 @@ msgstr "Update" msgid "workspace.viewport.click-to-close-path" msgstr "Click to close the path" - -msgid "shortcut-subsection.zoom-viewer" -msgstr "Zoom" - -msgid "shortcuts.or" -msgstr " or " - -msgid "shortcuts.not-found" -msgstr "No shortcuts found" \ No newline at end of file diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 206baeef21..653b5eecd7 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-05-25 15:16+0000\n" -"Last-Translator: Andrés Moya \n" +"PO-Revision-Date: 2022-06-23 12:19+0000\n" +"Last-Translator: andy \n" "Language-Team: Spanish " "\n" "Language: es\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.13-dev\n" +"X-Generator: Weblate 4.13.1-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -78,11 +78,11 @@ msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Entrar con LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "OpenID Connect" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -174,11 +174,20 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "Hemos enviado un email de verificación a" +msgid "common.share-link.all-users" +msgstr "Todo usario de Penpot" + msgid "common.share-link.confirm-deletion-link-description" msgstr "" "¿Estas seguro que quieres eliminar el enlace? Si lo haces, el enlace dejara " "de funcionar para cualquiera" +msgid "common.share-link.current-tag" +msgstr "(actual)" + +msgid "common.share-link.destroy-link" +msgstr "Eliminar enlace" + msgid "common.share-link.get-link" msgstr "Obtener enlace" @@ -188,32 +197,37 @@ msgstr "Enlace copiado satisfactoriamente" msgid "common.share-link.link-deleted-success" msgstr "Enlace eliminado correctamente" -msgid "common.share-link.permissions-can-access" -msgstr "Puede acceder a" +msgid "common.share-link.manage-ops" +msgstr "Gestionar permisos" -msgid "common.share-link.permissions-can-view" -msgstr "Puede ver" +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 página compartida" +msgstr[1] "%s páginas compartidas" + +msgid "common.share-link.permissions-can-comment" +msgstr "Pueden comentar" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Pueden ver código" msgid "common.share-link.permissions-hint" msgstr "Cualquiera con el enlace puede acceder" +msgid "common.share-link.permissions-pages" +msgstr "Páginas compartidas" + msgid "common.share-link.placeholder" msgstr "El enlace para compartir aparecerá aquí" -msgid "common.share-link.remove-link" -msgstr "Eliminar enlace" +msgid "common.share-link.team-members" +msgstr "Sólo integrantes del equipo" msgid "common.share-link.title" msgstr "Compartir prototipos" -msgid "common.share-link.view-all-pages" -msgstr "Todas las paginas" - -msgid "common.share-link.view-current-page" -msgstr "Solo esta pagina" - -msgid "common.share-link.view-selected-pages" -msgstr "Paginas seleccionadas" +msgid "common.share-link.view-all" +msgstr "Selecctionar todas" #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs @@ -240,6 +254,12 @@ msgstr "Tu Penpot" msgid "dashboard.delete-team" msgstr "Eliminar equipo" +msgid "dashboard.download-binary-file" +msgstr "Descargar archivo Penpot (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "Descargar archivo estándar (.svg + .json)" + msgid "dashboard.draft-title" msgstr "Borrador" @@ -262,6 +282,9 @@ msgstr "" "¡Oh, no! ¡Aún no tienes archivos! Si quieres probar con alguna plantilla ve " "a [Bibliotecas y plantillas](https://penpot.app/libraries-templates.html)" +msgid "dashboard.export-binary-multi" +msgstr "Descargar %s archivos Penpot (.penpot)" + msgid "dashboard.export-frames" msgstr "Exportar tableros a PDF..." @@ -301,6 +324,9 @@ msgstr "Exportar selección" msgid "dashboard.export-single" msgstr "Exportar archivo Penpot" +msgid "dashboard.export-standard-multi" +msgstr "Descargar %s archivos estándar (.svg + .json)" + msgid "dashboard.export.detail" msgstr "* Pueden incluir components, gráficos, colores y/o tipografias." @@ -712,7 +738,7 @@ 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)." +msgstr "La imagen es demasiado grande." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -803,6 +829,20 @@ msgstr "¿Deseas conversar? Entra al nuestro chat de la comunidad en Gitter" msgid "feedback.description" msgstr "Descripción" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Ir al foro de Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "" +"Estamos encantados de tenerte por aquí. Si necesitas ayuda, busca, escribe " +"o pregunta lo que necesites." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Comunidad de Penpot" + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-go-to" msgstr "Ir a las discusiones" @@ -836,18 +876,6 @@ msgstr "" msgid "feedback.title" msgstr "Correo electrónico" -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-go-to" -msgstr "Ir al foro de Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-subtitle1" -msgstr "Estamos encantados de tenerte por aquí. Si necesitas ayuda, busca, escribe o pregunta lo que necesites." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-title" -msgstr "Comunidad de Penpot" - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.twitter-go-to" msgstr "Ir a Twitter" @@ -1131,6 +1159,10 @@ msgstr "Cerrar" msgid "labels.comments" msgstr "Comentarios" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Comunidad" + #: src/app/main/ui/settings/password.cljs msgid "labels.confirm-password" msgstr "Confirmar contraseña" @@ -1144,6 +1176,9 @@ msgstr "Continuar" msgid "labels.continue-with" msgstr "Continúa con" +msgid "labels.continue-with-penpot" +msgstr "Puedes continuar con una cuenta de Penpot" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "labels.create" msgstr "Crear" @@ -1293,6 +1328,9 @@ msgstr "Bibliotecas y Plantillas" msgid "labels.link" msgstr "Enlace" +msgid "labels.log-or-sign" +msgstr "Entra o regístrate" + #: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.logout" msgstr "Salir" @@ -1342,7 +1380,7 @@ 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!" +msgstr "¡Uy!" #: src/app/main/ui/dashboard/team.cljs msgid "labels.num-of-files" @@ -1474,6 +1512,9 @@ msgstr "Bibliotecas Compartidas" msgid "labels.show-all-comments" msgstr "Mostrar todos los comentarios" +msgid "labels.show-comments-list" +msgstr "Mostrar lista de 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" @@ -1492,10 +1533,6 @@ msgstr "Comenzar" msgid "labels.status" msgstr "Status" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.community" -msgstr "Comunidad" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.tutorials" msgstr "Tutoriales" @@ -2322,6 +2359,9 @@ msgstr "Mover hacia arriba" msgid "shortcuts.next-frame" msgstr "Siguiente tablero" +msgid "shortcuts.not-found" +msgstr "No hay resultados" + msgid "shortcuts.opacity-0" msgstr "Opacidad 100%" @@ -2373,6 +2413,9 @@ msgstr "Ir al modo de visualización" msgid "shortcuts.open-workspace" msgstr "Ir al área de trabajo" +msgid "shortcuts.or" +msgstr " o " + msgid "shortcuts.paste" msgstr "Pegar" @@ -3126,6 +3169,9 @@ msgstr "Desenfoque de la selección" msgid "workspace.options.canvas-background" msgstr "Color de fondo" +msgid "workspace.options.clip-content" +msgstr "Truncar contenido" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs msgid "workspace.options.component" msgstr "Componente" @@ -3236,6 +3282,10 @@ msgstr "Automático" msgid "workspace.options.grid.column" msgstr "Columnas" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.grid-title" +msgstr "Rejilla" + msgid "workspace.options.grid.params.color" msgstr "Color" @@ -3311,10 +3361,6 @@ msgstr "Filas" 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" @@ -3610,6 +3656,154 @@ msgstr "Capas de grupo" msgid "workspace.options.layer-options.title.multiple" msgstr "Capas seleccionadas" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.advanced-ops" +msgstr "Opciones avanzadas" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.max-h" +msgstr "AlturaMax." + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.max-w" +msgstr "AnchoMax." + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.min-h" +msgstr "AlturaMin." + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.min-w" +msgstr "AnchoMin." + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Redimensionado de elemento" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.max-h" +msgstr "Altura máxima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.max-w" +msgstr "Ancho máximo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.min-h" +msgstr "Altura mínima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.min-w" +msgstr "Ancho mínimo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "Abajo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "Columna" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.left" +msgstr "Fila" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.right" +msgstr "Fila invertida" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.top" +msgstr "Columna invertida" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.gap" +msgstr "Espacio" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.left" +msgstr "izquierda" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.right" +msgstr "derecha" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "Izquierda" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "Margen" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-all" +msgstr "Todos" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "Margen sencillo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.no-wrap" +msgstr "no agrupar" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.packed" +msgstr "juntar" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding" +msgstr "Padding" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-all" +msgstr "Todos" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-simple" +msgstr "Padding sencillo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "Derecha" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-around" +msgstr "separar" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-between" +msgstr "espaciar" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.title" +msgstr "Layout" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Arriba" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "abajo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "arriba" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.wrap" +msgstr "agrupar" + #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Más colores" @@ -3721,6 +3915,9 @@ msgstr "Sombras de la seleccíón" msgid "workspace.options.show-fill-on-export" msgstr "Mostrar al exportar" +msgid "workspace.options.show-in-viewer" +msgstr "Mostrar en modo visualizació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" @@ -4351,12 +4548,3 @@ msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" msgstr "Pulsar para cerrar la ruta" - -msgid "shortcut-subsection.zoom-viewer" -msgstr "Zoom" - -msgid "shortcuts.or" -msgstr " o " - -msgid "shortcuts.not-found" -msgstr "No hay resultados" diff --git a/frontend/translations/fa.po b/frontend/translations/fa.po index 597fada276..eaa690f767 100644 --- a/frontend/translations/fa.po +++ b/frontend/translations/fa.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-05-04 10:14+0000\n" +"PO-Revision-Date: 2022-06-28 13:16+0000\n" "Last-Translator: Ahmad HosseinBor <123hozeifeh@gmail.com>\n" "Language-Team: Persian " "\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.12.1\n" +"X-Generator: Weblate 4.13.1-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -69,23 +69,23 @@ msgstr "خوشحالم که دوباره شما را می‌بینم!" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "ورود با گیتهاب" +msgstr "گیتهاب" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "ورود با گیتلب" +msgstr "گیتلب" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "ورود با گوگل" +msgstr "گوگل" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "ورود با LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "ورود با OpenID" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -189,30 +189,15 @@ msgstr "لینک با موفقیت کپی شد" msgid "common.share-link.link-deleted-success" msgstr "لینک با موفقیت حذف شد" -msgid "common.share-link.permissions-can-access" -msgstr "می‌تواند دسترسی داشته باشد" - -msgid "common.share-link.permissions-can-view" -msgstr "می‌تواند مشاهده کند" - msgid "common.share-link.permissions-hint" msgstr "هر کسی که لینک داشته باشد دسترسی خواهد داشت" -msgid "common.share-link.remove-link" -msgstr "حذف لینک" +msgid "common.share-link.placeholder" +msgstr "پیوند قابل اشتراک‌گذاری در اینجا ظاهر می‌شود" msgid "common.share-link.title" msgstr "اشتراک‌گذاری پروتوتایپ‌ها" -msgid "common.share-link.view-all-pages" -msgstr "تمام صفحات" - -msgid "common.share-link.view-current-page" -msgstr "فقط این صفحه" - -msgid "common.share-link.view-selected-pages" -msgstr "صفحات انتخاب‌شده" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "افزودن به‌عنوان کتابخانه مشترک" @@ -244,24 +229,255 @@ msgstr "پیش‌نویس" msgid "dashboard.duplicate" msgstr "تکثیر" +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "فایل‌های %s را کپی کنید" + #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.empty-files" msgstr "شما هنوز هیچ فایلی در اینجا ندارید" +#: src/app/main/ui/dashboard/grid.cljs +#, markdown +msgid "dashboard.empty-placeholder-drafts" +msgstr "" +"وای نه! شما هنوز هیچ فایلی ندارید! اگر می‌خواهید چند الگو را امتحان کنید، " +"به [کتابخانه‌ها و الگوها] بروید " +"(https://penpot.app/libraries-templates.html)" + msgid "dashboard.export-frames" -msgstr "خروجی آرت‌بوردها به پی‌دی‌اف..." +msgstr "خروجی آرت‌بوردها به پی‌دی‌اف…" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" msgstr "اکسپورت به پی‌دی‌اف" +msgid "dashboard.export-multi" +msgstr "خروجی فایل‌های %s پن‌پات" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "%s از %s عناصر انتخاب‌شده" + #: src/app/main/ui/workspace/header.cljs msgid "dashboard.export-shapes" msgstr "اکسپورت" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"می‌توانید تنظیمات اکسپورت را از ویژگی‌های طراحی (در پایین نوار کناری سمت " +"راست) به عناصر اضافه کنید." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "اطلاعات نحوه تنظیم اکسپورت در پنپات." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "هیچ عنصری با تنظیمات اکسپورت وجود ندارد." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.title" +msgstr "انتخاب اکسپورت" + +msgid "dashboard.export-single" +msgstr "اکسپورت فایل پنپات" + +msgid "dashboard.export.detail" +msgstr "* ممکن است شامل کامپوننت‌ها، گرافیک، رنگ‌ها و/یا تایپوگرافی باشد." + +msgid "dashboard.export.explain" +msgstr "" +"یک یا چند فایلی که می‌خواهید اکسپورت کنید از کتابخانه‌های مشترک استفاده " +"می‌کنند. با دارایی‌های آن‌ها چه می‌خواهید بکنید*؟" + +msgid "dashboard.export.options.all.message" +msgstr "" +"فایل‌های دارای کتابخانه‌های مشترک در اکسپورت گنجانده می‌شوند و پیوند خود را " +"حفظ می‌کنند." + +msgid "dashboard.export.options.all.title" +msgstr "اکسپورت کتابخانه‌های مشترک" + +msgid "dashboard.export.options.detach.message" +msgstr "" +"کتابخانه‌های مشترک در صادرات گنجانده نخواهند شد و هیچ دارایی به کتابخانه " +"اضافه نخواهد شد. " + +msgid "dashboard.export.options.detach.title" +msgstr "دارایی‌های کتابخانه مشترک را به عنوان اشیاء اساسی در نظر بگیرید" + +msgid "dashboard.export.options.merge.message" +msgstr "" +"فایل شما با تمام دارایی‌های خارجی که در کتابخانه فایل ادغام شده‌اند اکسپورت " +"می‌شود." + +msgid "dashboard.export.options.merge.title" +msgstr "دارایی‌های کتابخانه مشترک را در کتابخانه‌های فایل قرار دهید" + +msgid "dashboard.export.title" +msgstr "خروجی از فایل‌ها" + +msgid "dashboard.fonts.deleted-placeholder" +msgstr "فونت حذف شد" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.dismiss-all" +msgstr "ردکردن همه" + +msgid "dashboard.fonts.empty-placeholder" +msgstr "شما هنوز هیچ فونت سفارشی‌ای نصب نکرده‌اید." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "۱ فونت اضافه شد" +msgstr[1] "%s فونت اضافه شد" + +#, markdown +msgid "dashboard.fonts.hero-text1" +msgstr "" +"هر وب فونتی که در اینجا آپلود کنید به لیست خانواده فونت‌های موجود در " +"ویژگی‌های متن فایل‌های این تیم اضافه می‌شود. فونت‌هایی با نام یکسان به " +"عنوان یک **خانواده فونت تک** گروه‌بندی می‌شوند. می‌توانید فونت‌هایی را با " +"فرمت‌های زیر بارگذاری کنید: **TTF، OTF و WOFF** (فقط یکی مورد نیاز خواهد " +"بود)." + +#, markdown +msgid "dashboard.fonts.hero-text2" +msgstr "" +"شما فقط باید فونت‌هایی را که مالک آنها هستید یا مجوز استفاده از آنها را در " +"Penpot دارید آپلود کنید. در بخش حقوق محتوا [شرایط خدمات Penpot] " +"(https://penpot.app/terms.html) اطلاعات بیشتری کسب کنید. همچنین ممکن است " +"بخواهید درباره [مجوز فونت] (https://www.typography.com/faq) مطالعه کنید." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "آپلود همه" + +msgid "dashboard.import" +msgstr "ایمپورت کردن فایل‌های پن‌پات" + +msgid "dashboard.import.analyze-error" +msgstr "اوه! ما نتوانستیم این فایل را ایمپورت کنیم" + +msgid "dashboard.import.import-error" +msgstr "مشکلی در ایمپورت کردن فایل وجود داشت. فایل ایمپورت نشد." + +msgid "dashboard.import.import-message" +msgstr "فایل‌های %s با موفقیت ایمپورت شد." + +msgid "dashboard.import.progress.process-colors" +msgstr "در حال پردازش رنگ‌ها" + +msgid "dashboard.import.progress.process-components" +msgstr "در حال پردازش کامپوننت‌ها" + +#, fuzzy +msgid "dashboard.import.progress.process-media" +msgstr "در حال پردازش رسانه‌ها" + +msgid "dashboard.import.progress.process-page" +msgstr "در حال پردازش صفحه: %s" + +msgid "dashboard.import.progress.process-typographies" +msgstr "در حال پردازش تایپوگرافی‌ها" + +msgid "dashboard.import.progress.upload-data" +msgstr "در حال آپلود اطلاعات روی سرور (%s/%s)" + +msgid "dashboard.import.progress.upload-media" +msgstr "در حال آپلود فایل: %s" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "دعوت به تیم" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "خروج از تیم" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "کتابخانه‌های مشترک" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "در حال بارگذاری فایل‌های شما …" + +msgid "dashboard.loading-fonts" +msgstr "در حال بارگیری فونت‌های شما …" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "انتقال به" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "انتقال فایل‌های %s به" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "انتقال به تیم دیگر" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ فایل جدید" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "فایل جدید" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ پروژه جدید" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "پروژه جدید" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "" +"اخبار، به‌روزرسانی‌های محصول و توصیه‌های مربوط به Penpot را برای من ارسال " +"کنید." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "اشتراک خبرنامه" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "پروژه‌های پین شده در اینجا ظاهر می‌شوند" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "آدرس ایمیل شما با موفقیت به‌روز شد" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "آدرس ایمیل شما با موفقیت تایید شد" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "رمزعبور با موفقیت ذخیره شد!" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "اعضای %s" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "باز کردن فایل در تب جدید" + msgid "dashboard.options" msgstr "گزینه‌ها" +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "تغییر رمزعبور" + #: src/app/main/ui/dashboard/project_menu.cljs msgid "dashboard.pin-unpin" msgstr "پین/برداشتن پین" @@ -270,34 +486,275 @@ msgstr "پین/برداشتن پین" msgid "dashboard.projects-title" msgstr "پروژه‌ها" +#: src/app/main/ui/dashboard/team.cljs +#, fuzzy +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 +#, fuzzy +msgid "dashboard.remove-shared" +msgstr "حذف به عنوان کتابخانه مشترک" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +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 +#, fuzzy +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 +#, fuzzy +msgid "dashboard.success-duplicate-file" +msgstr "فایل شما با موفقیت duplicate شد" + +#: src/app/main/ui/dashboard/project_menu.cljs +#, fuzzy +msgid "dashboard.success-duplicate-project" +msgstr "پروژه شما با موفقیت duplicate شد" + +#: src/app/main/ui/dashboard/grid.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-file" +msgstr "فایل شما با موفقیت منتقل شد" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +msgstr "فایل‌های شما با موفقیت منتقل شدند" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "پروژه شما با موفقیت منتقل شد" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "تغییر تیم" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-info" +msgstr "اطلاعات تیم" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-members" +msgstr "اعضای تیم" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-projects" +msgstr "پروژه‌های تیم" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.theme-change" +msgstr "تم رابط کاربری" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.title-search" +msgstr "نتایج جستجو" + +#: src/app/main/ui/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 "پن‌پات شما" + +#: src/app/main/ui/confirm.cljs +#, fuzzy +msgid "ds.component-subtitle" +msgstr "کامپوننت‌ها برای به‌روزرسانی:" + #: src/app/main/ui/confirm.cljs msgid "ds.confirm-cancel" msgstr "لغو" #: src/app/main/ui/confirm.cljs msgid "ds.confirm-ok" -msgstr "باشه" +msgstr "بله" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "مطمئنی؟" + +#: src/app/main/ui/dashboard/grid.cljs +#, fuzzy +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 +#, fuzzy +msgid "errors.email-already-exists" +msgstr "ایمیل قبلا استفاده شده است" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "ایمیل قبلاً تأیید شده است." + +msgid "errors.email-as-password" +msgstr "شما نمی‌توانید از ایمیل خود به عنوان رمزعبور استفاده کنید" + +#: 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 +#, fuzzy +msgid "errors.generic" +msgstr "اشتباهی رخ داده است." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.google-auth-not-enabled" +msgstr "احراز هویت با گوگل در بک‌اند غیرفعال است" + +#: src/app/main/ui/components/color_input.cljs +msgid "errors.invalid-color" +msgstr "رنگ نامعتبر" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "دعوت نامعتبر" + +msgid "errors.invite-invalid.info" +msgstr "این دعوت ممکن است لغو یا منقضی شده باشد." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.ldap-disabled" +msgstr "احراز هویت LDAP غیرفعال است." + +msgid "errors.media-format-unsupported" +msgstr "فرمت تصویر پشتیبانی نمی‌شود (باید svg، jpg یا png باشد)." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "تصویر برای درج خیلی بزرگ است." + +#: 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 +#, fuzzy +msgid "errors.password-too-short" +msgstr "رمزعبور باید حداقل 8 کاراکتر باشد" + +#: src/app/main/ui/auth/register.cljs +msgid "errors.registration-disabled" +msgstr "ثبت‌نام در حال حاضر غیرفعال است." + +msgid "errors.team-leave.insufficient-members" +msgstr "اعضای کافی برای ترک تیم وجود ندارد، احتمالاً می‌خواهید آن را حذف کنید." + +msgid "errors.team-leave.owner-cant-leave" +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.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-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 "محو" @@ -322,6 +779,10 @@ msgstr "RGBA" 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 "ارتفاع" @@ -378,6 +839,11 @@ msgstr "Y" msgid "handoff.attributes.shadow.shorthand.spread" msgstr "S" +#: src/app/main/ui/handoff/attributes/stroke.cljs +#, fuzzy +msgid "handoff.attributes.stroke" +msgstr "استروک" + #, permanent msgid "handoff.attributes.stroke.alignment.center" msgstr "مرکز" @@ -399,6 +865,10 @@ msgstr "‏مخلوط" msgid "handoff.attributes.stroke.style.none" msgstr "هیچ‌یک" +#, fuzzy +msgid "handoff.attributes.stroke.style.solid" +msgstr "جامد" + #: src/app/main/ui/handoff/attributes/stroke.cljs msgid "handoff.attributes.stroke.width" msgstr "عرض" @@ -407,12 +877,45 @@ msgstr "عرض" 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 "ارتفاع خط" + msgid "handoff.attributes.typography.text-decoration.none" 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.uppercase" +msgstr "حروف بزرگ" + #: src/app/main/ui/handoff/right_sidebar.cljs msgid "handoff.tabs.code" msgstr "کد" @@ -454,6 +957,10 @@ msgstr "متن" msgid "handoff.tabs.info" msgstr "اطلاعات" +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "میانبرها" + msgid "labels.accept" msgstr "تایید" @@ -465,6 +972,12 @@ msgstr "مدیر" msgid "labels.all" msgstr "همه" +msgid "labels.and" +msgstr "و" + +msgid "labels.back" +msgstr "بازگشت" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.cancel" msgstr "لغو" @@ -542,6 +1055,10 @@ msgstr "لینک" 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.member" +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 "اعضا" @@ -568,6 +1085,10 @@ msgstr "مالک" msgid "labels.password" msgstr "کلمه‌عبور" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "در انتظار" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.permissions" msgstr "مجوزها" @@ -583,6 +1104,10 @@ 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 "حذف" @@ -614,12 +1139,24 @@ msgstr "درحال ارسال…" msgid "labels.settings" msgstr "تنظیمات" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.shared-libraries" +msgstr "کتابخانه‌های مشترک" + msgid "labels.skip" msgstr "رد" msgid "labels.start" msgstr "شروع" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "وضعیت" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "آموزش‌ها" + #: src/app/main/ui/settings/profile.cljs msgid "labels.update" msgstr "به‌روزرسانی" @@ -637,6 +1174,24 @@ msgstr "بیننده" msgid "labels.workspace" msgstr "فضای‌کاری" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(شما)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.your-account" +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 "آیا مطمئن هستید که می‌خواهید حساب خود را حذف کنید؟" + msgid "modals.delete-font-variant.message" msgstr "" "آیا مطمئن هستید که می‌خواهید این سبک فونت را حذف کنید؟ اگر در یک فایل " @@ -647,6 +1202,14 @@ 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/sidebar.cljs msgid "modals.delete-team-confirm.message" msgstr "" @@ -661,17 +1224,269 @@ msgstr "به‌روزرسانی" msgid "modals.update-remote-component.cancel" msgstr "لغو" +#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs +msgid "notifications.profile-saved" +msgstr "پروفایل با موفقیت ذخیره شد!" + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"ما فقط ایمیل‌های مربوطه را برای شما ارسال می‌کنیم. شما می‌توانید در هر زمان " +"در پروفایل کاربری خود یا از طریق پیوند لغو اشتراک در هر یک از خبرنامه‌های " +"ما، اشتراک خود را لغو کنید." + msgid "onboarding.welcome.alt" msgstr "Penpot" +#, fuzzy +msgid "onboarding.welcome.desc2" +msgstr "" +"Penpot به لطف ترکیبی از ویژگی‌های اصلی، بلوغ، پایداری و اعتبارسنجی " +"شگفت‌انگیز جامعه به‌عنوان یک کل، در اولین نسخه بتا خود قرار دارد، که از آن " +"استقبال می‌کنید." + #: 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 "مخلوط" +msgid "shortcut-section.dashboard" +msgstr "داشبورد" + +msgid "shortcut-section.viewer" +msgstr "بیننده" + +msgid "shortcuts.add-comment" +msgstr "نظرات" + +msgid "shortcuts.copy" +msgstr "کپی" + +msgid "shortcuts.delete" +msgstr "حذف" + +msgid "shortcuts.draw-curve" +msgstr "منحنی" + +msgid "shortcuts.draw-ellipse" +msgstr "بیضی" + +msgid "shortcuts.draw-frame" +msgstr "آرت‌بورد" + +msgid "shortcuts.draw-path" +msgstr "مسیر" + +msgid "shortcuts.draw-rect" +msgstr "مستطیل" + +msgid "shortcuts.draw-text" +msgstr "متن" + +msgid "shortcuts.escape" +msgstr "لغو" + +msgid "shortcuts.go-to-search" +msgstr "جستجو" + +msgid "shortcuts.group" +msgstr "گروه" + +msgid "shortcuts.mask" +msgstr "ماسک" + +msgid "shortcuts.move" +msgstr "انتقال" + +msgid "shortcuts.paste" +msgstr "چسباندن" + +#, fuzzy +msgid "shortcuts.toggle-grid" +msgstr "نمایش/پنهان کردن شبکه‌بندی" + +msgid "shortcuts.toggle-rules" +msgstr "نمایش/پنهان کردن خط‌کش‌ها" + +msgid "shortcuts.undo" +msgstr "واگرد" + +#, fuzzy +msgid "shortcuts.ungroup" +msgstr "حذف گروه" + +msgid "shortcuts.unmask" +msgstr "برداشتن ماسک" + +msgid "shortcuts.v-distribute" +msgstr "توزیع به صورت عمودی" + +#: src/app/main/ui/dashboard/files.cljs +#, fuzzy +msgid "title.dashboard.files" +msgstr "%s - Penpot" + +#: src/app/main/ui/dashboard/fonts.cljs +#, fuzzy +msgid "title.dashboard.fonts" +msgstr "فونت‌ها - %s - Penpot" + +#: src/app/main/ui/dashboard/projects.cljs +#, fuzzy +msgid "title.dashboard.projects" +msgstr "پروژه‌ها - %s - Penpot" + +#: src/app/main/ui/dashboard/search.cljs +#, fuzzy +msgid "title.dashboard.search" +msgstr "جستجو - %s - Penpot" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "کتابخانه‌های مشترک - %s - پن‌پات" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot - طراحی آزاد برای تیم‌ها" + +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "بازخورد بدهید - Penpot" + +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "تنظیمات - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "رمزعبور - Penpot" + +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "پروفایل - پن‌پات" + +#: src/app/main/ui/dashboard/team.cljs +#, fuzzy +msgid "title.team-invitations" +msgstr "دعوت‌نامه‌ها - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-members" +msgstr "اعضا - %s - پن‌پات" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "تنظیمات - %s - پن‌پات" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - حالت مشاهده - پن‌پات" + +#: src/app/main/ui/workspace.cljs +#, fuzzy +msgid "title.workspace" +msgstr "%s - Penpot" + +msgid "viewer.breaking-change.description" +msgstr "" +"این لینک قابل اشتراک‌گذاری دیگر معتبر نیست. یک مورد جدید ایجاد کنید یا از " +"مالک یک مورد جدید بخواهید." + +msgid "viewer.breaking-change.message" +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 "آرت‌بورد یافت نشد." + +msgid "viewer.header.comments-section" +msgstr "نظرات (%s)" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "تعاملات را نشان ندهید" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +msgstr "تمام صفحه" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "تعاملات" + +msgid "viewer.header.interactions-section" +msgstr "تعاملات (%s)" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.copy-link" +msgstr "کپی کردن لینک" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.create-link" +msgstr "ایجاد لینک" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.placeholder" +msgstr "لینک اشتراک‌گذاری در اینجا ظاهر می‌شود" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.remove-link" +msgstr "حذف لینک" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.subtitle" +msgstr "هر کسی که لینک را داشته باشد دسترسی خواهد داشت" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions" +msgstr "نشان دادن تعاملات" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions-on-click" +msgstr "نمایش تعاملات در کلیک" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.sitemap" msgstr "نقشه سایت" +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hcenter" +msgstr "تراز کردن مرکز افقی (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hdistribute" +msgstr "توزیع فاصله افقی (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hleft" +msgstr "تراز به چپ (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hright" +msgstr "تراز کردن به راست (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vbottom" +msgstr "تراز کردن پایین (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vcenter" +msgstr "تراز کردن مرکز عمودی (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vdistribute" +msgstr "توزیع فاصله عمودی (%s)" + +#: 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 "گرافیک" @@ -683,6 +1498,14 @@ msgstr "رنگ‌ها" msgid "workspace.assets.components" msgstr "کامپوننت‌ها" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group" +msgstr "ایجاد یک گروه" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group-hint" +msgstr "آیتم‌های شما به طور خودکار به عنوان \"نام گروه / نام آیتم\" نامگذاری می‌شوند" + #: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.delete" msgstr "حذف" @@ -723,10 +1546,25 @@ msgstr "فونت" 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.text-transform" +msgstr "تبدیل متن" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.ungroup" msgstr "حذف گروه" +msgid "workspace.focus.selection" +msgstr "انتخاب" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-textpalette" +msgstr "پنهان کردن پالت فونت‌ها" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.option.edit" msgstr "ویرایش" @@ -735,14 +1573,36 @@ msgstr "ویرایش" msgid "workspace.header.menu.option.file" msgstr "فایل" +#: src/app/main/ui/workspace/header.cljs +#, fuzzy +msgid "workspace.header.menu.option.view" +msgstr "بازدید" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-textpalette" +msgstr "نمایش پالت فونت‌ها" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.reset-zoom" 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 +#, fuzzy +msgid "workspace.header.saving" +msgstr "ذخیره‌کردن" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "تغییرات ذخیره نشده" + #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.add" msgstr "افزودن" @@ -755,6 +1615,10 @@ msgstr "HSV" msgid "workspace.libraries.colors.rgba" msgstr "RGBA" +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "ذخیره استایل رنگ" + #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.libraries" msgstr "کتابخانه‌ها" @@ -763,6 +1627,22 @@ msgstr "کتابخانه‌ها" 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-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 "به‌روزرسانی" @@ -814,7 +1694,7 @@ msgstr "بالا" #: src/app/main/ui/workspace/sidebar/options.cljs msgid "workspace.options.design" -msgstr "طرح" +msgstr "طراحی" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export" @@ -859,10 +1739,19 @@ msgstr "لبه" msgid "workspace.options.grid.params.rows" msgstr "ردیف‌ها" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +#, fuzzy +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 "پایین" @@ -910,6 +1799,10 @@ msgstr "انیمیشن" msgid "workspace.options.interaction-animation-none" msgstr "هیچ‌یک" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-push" +msgstr "هل" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-animation-slide" msgstr "اسلاید" @@ -918,6 +1811,14 @@ msgstr "اسلاید" msgid "workspace.options.interaction-delay" msgstr "تاخیر" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-destination" +msgstr "مقصد" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-duration" +msgstr "مدت‌زمان" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-easing" msgstr "تسهیل" @@ -938,6 +1839,10 @@ msgstr "در" msgid "workspace.options.interaction-ms" msgstr "م‌ث" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-none" +msgstr "(تنظیم نشده)" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-out" msgstr "خارج" @@ -966,6 +1871,11 @@ msgstr "URL" msgid "workspace.options.layer-options.blend-mode.color" msgstr "رنگ" +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +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 "تفاوت" @@ -994,9 +1904,17 @@ msgstr "موقعیت" msgid "workspace.options.prototype" msgstr "پروتوتایپ" +#, fuzzy +msgid "workspace.options.radius" +msgstr "گردی" + msgid "workspace.options.recent-fonts" msgstr "اخیر" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.retry" +msgstr "تلاش دوباره" + #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.rotation" msgstr "چرخش" @@ -1021,6 +1939,11 @@ msgstr "سایه" msgid "workspace.options.size" msgstr "اندازه" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +#, fuzzy +msgid "workspace.options.stroke" +msgstr "استروک" + #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke-cap.none" msgstr "هیچ‌یک" @@ -1057,6 +1980,31 @@ msgstr "مخلوط" msgid "workspace.options.stroke.outer" 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 +#, fuzzy +msgid "workspace.options.text-options.align-center" +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 "تراز بالا" + #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.direction-ltr" msgstr "LTR" @@ -1069,10 +2017,26 @@ msgstr "RTL" 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 "حروف کوچک" @@ -1081,14 +2045,38 @@ msgstr "حروف کوچک" msgid "workspace.options.text-options.none" msgstr "هیچ‌یک" +#, fuzzy +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 +#, fuzzy +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/text.cljs +msgid "workspace.options.text-options.underline" +msgstr "خط‌زیر" + #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.options.text-options.uppercase" msgstr "حروف بزرگ" +msgid "workspace.options.text-options.vertical-align" +msgstr "تراز عمودی" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.use-play-button" +msgstr "از دکمه پخش در هدر برای اجرای نمای پروتوتایپ استفاده کنید." + msgid "workspace.options.width" msgstr "پهنا" @@ -1098,10 +2086,35 @@ msgstr "X" msgid "workspace.options.y" msgstr "Y" +msgid "workspace.path.actions.add-node" +msgstr "افزودن گره (%s)" + +msgid "workspace.path.actions.delete-node" +msgstr "حذف گره (%s)" + +msgid "workspace.path.actions.merge-nodes" +msgstr "ادغام گره‌ها (%s)" + +#, fuzzy +msgid "workspace.path.actions.move-nodes" +msgstr "انتقال گره‌ها (%s)" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.back" +msgstr "فرستادن به پشت" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.backward" +msgstr "فرستادن به عقب" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.copy" msgstr "کپی" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "ایجاد کامپوننت" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.cut" msgstr "برش" @@ -1124,6 +2137,27 @@ msgstr "ویزایش" msgid "workspace.shape.menu.flatten" msgstr "صاف" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-vertical" +msgstr "چرخش عمودی" + +#: src/app/main/ui/workspace/context_menu.cljs +#, fuzzy +msgid "workspace.shape.menu.flow-start" +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 "گروه" @@ -1132,6 +2166,9 @@ msgstr "گروه" msgid "workspace.shape.menu.hide" msgstr "مخفی" +msgid "workspace.shape.menu.hide-ui" +msgstr "نمایش/پنهان کردن رابط کاربری" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.lock" msgstr "قفل" @@ -1147,26 +2184,233 @@ msgstr "چسباندن" msgid "workspace.shape.menu.path" msgstr "مسیر" +#: src/app/main/ui/workspace/context_menu.cljs +#, fuzzy +msgid "workspace.shape.menu.select-layer" +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 "نمایش کامپوننت اصلی" + +msgid "workspace.shape.menu.transform-to-path" +msgstr "تبدیل به مسیر" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.ungroup" msgstr "حذف گروه" +#, fuzzy +msgid "workspace.shape.menu.union" +msgstr "متحد" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.unlock" 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-components-in-bulk" +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 "لایه‌ها" +msgid "workspace.sidebar.layers.components" +msgstr "کامپوننت‌ها" + +msgid "workspace.sidebar.layers.frames" +msgstr "آرت‌بوردها" + +msgid "workspace.sidebar.layers.groups" +msgstr "گروه‌ها" + +msgid "workspace.sidebar.layers.images" +msgstr "تصاویر" + +msgid "workspace.sidebar.layers.masks" +msgstr "ماسک‌ها" + +msgid "workspace.sidebar.layers.search" +msgstr "جستجوی لایه‌ها" + +msgid "workspace.sidebar.layers.shapes" +msgstr "شکل‌ها" + +msgid "workspace.sidebar.layers.texts" +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 "نقشه سایت" \ No newline at end of file +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.comments" +msgstr "نظرات (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.curve" +msgstr "منحنی (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.ellipse" +msgstr "بیضی (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "آرت‌بورد (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "تصویر (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.move" +msgstr "انتقال (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.path" +msgstr "مسیر (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "مستطیل (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "میانبرها (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "متن (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text-palette" +msgstr "تایپوگرافی‌ها (%s)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.delete" +msgstr "%s حذف شد" + +msgid "workspace.undo.entry.multiple.circle" +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.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 "متن‌ها" + +#: 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.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.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 "برای بستن مسیر کلیک کنید" diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index 75778b523a..9a3b1f975f 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-01-20 20:55+0000\n" -"Last-Translator: Voxybuns \n" +"PO-Revision-Date: 2022-07-02 09:14+0000\n" +"Last-Translator: Lucie Lesage \n" "Language-Team: French " "\n" "Language: fr\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" -"X-Generator: Weblate 4.11-dev\n" +"X-Generator: Weblate 4.13.1-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -66,23 +66,23 @@ 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" +msgstr "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" +msgstr "GitLab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "Se connecter via Google" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Se connecter via LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "Se connecter via OpenID (SSO)" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -186,33 +186,15 @@ msgstr "Lien copié avec succès" msgid "common.share-link.link-deleted-success" msgstr "Lien supprimé avec succès" -msgid "common.share-link.permissions-can-access" -msgstr "Peut y accéder" - -msgid "common.share-link.permissions-can-view" -msgstr "Peut le visionner" - msgid "common.share-link.permissions-hint" msgstr "N'importe qui possédant ce lien peut y accéder" msgid "common.share-link.placeholder" msgstr "Le lien à partager apparaîtra ici" -msgid "common.share-link.remove-link" -msgstr "Supprimer le lien" - msgid "common.share-link.title" msgstr "Partager les prototypes" -msgid "common.share-link.view-all-pages" -msgstr "Toutes les pages" - -msgid "common.share-link.view-current-page" -msgstr "Seulement cette page" - -msgid "common.share-link.view-selected-pages" -msgstr "Pages sélectionnées" - #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" @@ -254,8 +236,16 @@ msgstr "Dupliquer %s fichiers" msgid "dashboard.empty-files" msgstr "Vous n’avez encore aucun fichier ici" +#: src/app/main/ui/dashboard/grid.cljs +#, markdown +msgid "dashboard.empty-placeholder-drafts" +msgstr "" +"Oh non ! Vous n'avez pas encore de fichiers ! Si vous voulez essayer avec " +"des modèles, allez sur [Bibliothèques et modèles] " +"(https://penpot.app/libraries-templates.html)" + msgid "dashboard.export-frames" -msgstr "Exporter les plans de travail comme PDF..." +msgstr "Exporter les plans de travail au format PDF…" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" @@ -264,10 +254,32 @@ msgstr "Exporter en PDF" msgid "dashboard.export-multi" msgstr "Exporter %s fichiers" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "%s de %s éléments sélectionnés" + #: src/app/main/ui/workspace/header.cljs msgid "dashboard.export-shapes" msgstr "Exporter" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Vous pouvez ajouter des paramètres d'exportation aux éléments depuis les " +"propriétés de design (en bas de la barre latérale de droite)." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "Information sur comment configurer l'export dans Penpot." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "Il n'y a pas d'éléments avec des paramètres d'exportation." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.title" +msgstr "Exporter la sélection" + msgid "dashboard.export-single" msgstr "Exporter le fichier" @@ -281,12 +293,53 @@ msgstr "" "Un ou plusieurs fichiers que vous souhaitez exporter utilisent des " "bibliothèques partagées. Que voulez-vous faire avec leurs ressources ?" +msgid "dashboard.export.options.all.message" +msgstr "" +"Les fichiers avec des bibliothèques partagées seront inclus dans " +"l'exportation, en maintenant leur liaison." + +msgid "dashboard.export.options.all.title" +msgstr "Exporter les bibliothèques partagées" + +msgid "dashboard.export.options.detach.message" +msgstr "" +"Les bibliothèques partagées ne seront pas incluses dans l'exportation et " +"aucune ressource ne sera ajoutée à la librairie. " + +msgid "dashboard.export.options.detach.title" +msgstr "" +"Considérer les ressources des bibliothèques partagées comme des objets " +"basiques" + +msgid "dashboard.export.options.merge.message" +msgstr "" +"Votre fichier sera exporté avec toutes les ressources externes fusionnées " +"dans la bibliothèque de fichiers." + +msgid "dashboard.export.options.merge.title" +msgstr "" +"Inclure les ressources des bibliothèques partagées dans les bibliothèques " +"de fichiers" + +msgid "dashboard.export.title" +msgstr "Exporter les fichiers" + msgid "dashboard.fonts.deleted-placeholder" msgstr "Police supprimée" +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.dismiss-all" +msgstr "Rejeter tout" + msgid "dashboard.fonts.empty-placeholder" msgstr "Vous n'avez installé aucune police personnalisée." +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "1 police ajoutée" +msgstr[1] "%s polices ajoutées" + msgid "dashboard.fonts.hero-text1" msgstr "" "Toute police Web que vous téléchargez sera ajoutée à la liste de polices de " @@ -303,9 +356,48 @@ msgstr "" "de Penpot](https://penpot.app/terms.html). Vous pouvez également vous " "renseigner sur les [licenses de polices](https://www.typography.com/faq)." +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "Uploader tout" + msgid "dashboard.import" msgstr "Importer fichiers" +msgid "dashboard.import.analyze-error" +msgstr "Oups ! Nous n'avons pas pu importer ce fichier" + +msgid "dashboard.import.import-error" +msgstr "" +"Un problème est survenu lors de l'importation du fichier. Le fichier n'a " +"pas été importé." + +msgid "dashboard.import.import-message" +msgstr "%s fichiers ont été importés avec succès." + +msgid "dashboard.import.import-warning" +msgstr "Certains fichiers contenaient des objets invalides qui ont été supprimés." + +msgid "dashboard.import.progress.process-colors" +msgstr "Traitement des couleurs" + +msgid "dashboard.import.progress.process-components" +msgstr "Traitement des composants" + +msgid "dashboard.import.progress.process-media" +msgstr "Médias en cours de traitement" + +msgid "dashboard.import.progress.process-page" +msgstr "Traitement de la page : %s" + +msgid "dashboard.import.progress.process-typographies" +msgstr "Traitement des typographies" + +msgid "dashboard.import.progress.upload-data" +msgstr "Envoi des données au serveur (%s/%s)" + +msgid "dashboard.import.progress.upload-media" +msgstr "Envoi du fichier : %s" + #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" msgstr "Inviter dans l’équipe" @@ -356,6 +448,16 @@ msgstr "+ Nouveau projet" msgid "dashboard.new-project-prefix" msgstr "Nouveau projet" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "" +"Envoyez-moi des actualités, des mises à jour du produit, et des " +"recommandations à propos de Penpot." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Abonnement à la newsletter" + #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.no-matches-for" msgstr "Aucune correspondance pour « %s »" @@ -412,6 +514,10 @@ msgstr "Vous souhaitez supprimer votre compte ?" msgid "dashboard.remove-shared" msgstr "Retirer en tant que Bibliothèque Partagée" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "Enregistrer les paramètres" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.search-placeholder" msgstr "Rechercher…" @@ -515,6 +621,10 @@ msgstr "Votre nom complet" msgid "dashboard.your-penpot" msgstr "Votre Penpot" +#: src/app/main/ui/confirm.cljs +msgid "ds.component-subtitle" +msgstr "Composants à mettre à jour :" + #: src/app/main/ui/confirm.cljs msgid "ds.confirm-cancel" msgstr "Annuler" @@ -531,6 +641,9 @@ msgstr "Êtes‑vous sûr ?" msgid "ds.updated-at" msgstr "Mise à jour : %s" +msgid "errors.auth.unable-to-login" +msgstr "Il semblerait que vous n'êtes pas authentifié ou que votre session a expiré." + #: src/app/main/data/workspace.cljs msgid "errors.clipboard-not-implemented" msgstr "Votre navigateur ne peut pas effectuer cette opération" @@ -544,6 +657,9 @@ msgstr "Adresse e‑mail déjà utilisée" msgid "errors.email-already-validated" msgstr "Adresse e‑mail déjà validée." +msgid "errors.email-as-password" +msgstr "Vous ne pouvez pas utiliser votre adresse e-mail comme mot de passe" + #: src/app/main/ui/auth/register.cljs, #: src/app/main/ui/auth/recovery_request.cljs, #: src/app/main/ui/settings/change_email.cljs, @@ -555,6 +671,9 @@ msgstr "L'adresse e-mail « %s » a un taux de rebond trop élevé." msgid "errors.email-invalid-confirmation" msgstr "L’adresse e‑mail de confirmation doit correspondre" +msgid "errors.email-spam-or-permanent-bounces" +msgstr "L'e-mail \"%s\" a été signalé comme spam ou a été rejeté." + #: 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" @@ -568,6 +687,13 @@ msgstr "Authentification via Google désactivée dans le backend" msgid "errors.invalid-color" msgstr "Couleur invalide" +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "Invitation invalide" + +msgid "errors.invite-invalid.info" +msgstr "Cette invitation est peut-être été annulée ou a expiré." + #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" msgstr "Authentification LDAP désactivée." @@ -577,7 +703,7 @@ 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)." +msgstr "L’image est trop grande." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -618,6 +744,19 @@ msgstr "" msgid "errors.registration-disabled" msgstr "L’enregistrement est actuellement désactivé." +msgid "errors.team-leave.insufficient-members" +msgstr "" +"Il n'y a pas assez de membres pour quitter l'équipe, vous voulez " +"probablement la supprimer." + +msgid "errors.team-leave.member-does-not-exists" +msgstr "Le membre que vous essayez d'assigner n'existe pas." + +msgid "errors.team-leave.owner-cant-leave" +msgstr "" +"Le propriétaire ne peut pas quitter l'équipe, vous devez réassigner le rôle " +"de propriétaire." + msgid "errors.terms-privacy-agreement-invalid" msgstr "" "Vous devez accepter nos conditions générales d'utilisation et notre " @@ -850,6 +989,9 @@ msgstr "Code" msgid "handoff.tabs.code.selected.circle" msgstr "Cercle" +msgid "handoff.tabs.code.selected.component" +msgstr "Composant" + msgid "handoff.tabs.code.selected.curve" msgstr "Courbe" @@ -862,6 +1004,9 @@ msgstr "Groupe" msgid "handoff.tabs.code.selected.image" msgstr "Image" +msgid "handoff.tabs.code.selected.mask" +msgstr "Masque" + #: src/app/main/ui/handoff/right_sidebar.cljs msgid "handoff.tabs.code.selected.multiple" msgstr "%s Sélectionné" @@ -885,6 +1030,14 @@ msgstr "Information" msgid "history.alert-message" msgstr "Vous voyez la version %s" +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Raccourcis" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.about-penpot" +msgstr "À propos de Penpot" + msgid "labels.accept" msgstr "Accepter" @@ -899,6 +1052,12 @@ msgstr "Administration" msgid "labels.all" msgstr "Tous" +msgid "labels.and" +msgstr "et" + +msgid "labels.back" +msgstr "Retour" + #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.desc-message" msgstr "" @@ -916,6 +1075,9 @@ msgstr "Annuler" msgid "labels.centered" msgstr "Centré" +msgid "labels.close" +msgstr "Fermer" + #: src/app/main/ui/dashboard/comments.cljs msgid "labels.comments" msgstr "Commentaires" @@ -927,6 +1089,12 @@ msgstr "Confirmer le mot de passe" msgid "labels.content" msgstr "Contenu" +msgid "labels.continue" +msgstr "Continuer" + +msgid "labels.continue-with" +msgstr "Continuer avec" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "labels.create" msgstr "Créer" @@ -947,6 +1115,9 @@ msgstr "Polices personnalisées" msgid "labels.dashboard" msgstr "Tableau de bord" +msgid "labels.default" +msgstr "par défaut" + #: src/app/main/ui/dashboard/project_menu.cljs, #: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.delete" @@ -960,6 +1131,10 @@ msgstr "Supprimer le commentaire" msgid "labels.delete-comment-thread" msgstr "Supprimer le fil" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.delete-invitation" +msgstr "Supprimer l'invitation" + #: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.delete-multi-files" msgstr "Supprimer %s fichiers" @@ -975,6 +1150,9 @@ msgstr "Brouillons" msgid "labels.edit" msgstr "Modifier" +msgid "labels.edit-file" +msgstr "Modifier le fichier" + #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.editor" msgstr "Éditeur" @@ -983,6 +1161,13 @@ msgstr "Éditeur" msgid "labels.email" msgstr "Adresse e‑mail" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Expirée" + +msgid "labels.export" +msgstr "Exporter" + #: src/app/main/ui/settings/feedback.cljs msgid "labels.feedback-disabled" msgstr "Avis désactivés" @@ -1003,6 +1188,10 @@ msgstr "Styles" msgid "labels.fonts" msgstr "Polices" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Dépôt GitHub" + #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/settings/sidebar.cljs, #: src/app/main/ui/dashboard/sidebar.cljs @@ -1012,6 +1201,10 @@ msgstr "Donnez votre avis" msgid "labels.go-back" msgstr "Retour" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.help-center" +msgstr "Centre d'aide" + #: 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" @@ -1035,10 +1228,21 @@ msgstr "" msgid "labels.internal-error.main-message" msgstr "Erreur interne" +#: 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.invitations" +msgstr "Invitations" + #: src/app/main/ui/settings/options.cljs msgid "labels.language" msgstr "Langue" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.libraries-and-templates" +msgstr "Bibliothèques et modèles" + +msgid "labels.link" +msgstr "Lien" + #: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.logout" msgstr "Se déconnecter" @@ -1046,6 +1250,10 @@ msgstr "Se déconnecter" msgid "labels.manage-fonts" msgstr "Gérer les polices" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "Membre" + #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.members" msgstr "Membres" @@ -1058,11 +1266,24 @@ msgstr "Nom" msgid "labels.new-password" msgstr "Nouveau mot de passe" +msgid "labels.next" +msgstr "Suivant" + #: 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/dashboard/team.cljs +msgid "labels.no-invitations" +msgstr "Il n'y a pas d'invitations." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations-hint" +msgstr "" +"Appuyez sur le bouton \"Inviter à l'équipe\" pour inviter d'autres membres " +"à cette équipe." + #: src/app/main/ui/static.cljs msgid "labels.not-found.auth-info" msgstr "Vous êtes connecté en tant que" @@ -1114,6 +1335,10 @@ msgstr "Propriétaire" msgid "labels.password" msgstr "Mot de passe" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "En attente" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.permissions" msgstr "Permissions" @@ -1139,6 +1364,10 @@ msgstr "Notes de version" msgid "labels.remove" msgstr "Retirer" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove-member" +msgstr "Retirer le membre" + #: src/app/main/ui/dashboard/sidebar.cljs, #: src/app/main/ui/dashboard/project_menu.cljs, #: src/app/main/ui/dashboard/file_menu.cljs @@ -1149,6 +1378,10 @@ msgstr "Renommer" msgid "labels.rename-team" msgstr "Renommer l'équipe" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.resend-invitation" +msgstr "Renvoyer l'invitation" + #: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "Réessayer" @@ -1204,6 +1437,20 @@ msgstr "Afficher uniquement vos commentaires" msgid "labels.sign-out" msgstr "Se déconnecter" +msgid "labels.skip" +msgstr "Passer" + +msgid "labels.start" +msgstr "Démarrer" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "Statut" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Tutoriels" + #: src/app/main/ui/settings/profile.cljs msgid "labels.update" msgstr "Actualiser" @@ -1225,10 +1472,21 @@ msgstr "Téléchargement…" msgid "labels.viewer" msgstr "Spectateur" +msgid "labels.workspace" +msgstr "Espace de travail" + #: src/app/main/ui/comments.cljs msgid "labels.write-new-comment" msgstr "Écrire un nouveau commentaire" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(vous)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.your-account" +msgstr "Votre compte" + #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "media.loading" msgstr "Chargement de l’image…" @@ -1251,6 +1509,10 @@ msgstr "" msgid "modals.add-shared-confirm.message" msgstr "Ajouter « %s » comme Bibliothèque Partagée" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.big-nudge" +msgstr "Grand nudge" + #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.confirm-email" msgstr "Vérifier la nouvelle adresse e‑mail" @@ -1273,6 +1535,12 @@ msgstr "Changer adresse e‑mail" msgid "modals.change-email.title" msgstr "Changez votre adresse e‑mail" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.change-owner-and-leave-confirm.message" +msgstr "" +"Vous êtes le propriétaire de cette équipe. Avant de la quitter, veuillez " +"sélectionner un autre membre pour le promouvoir en propriétaire." + #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" msgstr "Annuler et conserver mon compte" @@ -1395,10 +1663,23 @@ msgstr "Supprimer un membre d’équipe" msgid "modals.invite-member-confirm.accept" msgstr "Envoyer l'invitation" +msgid "modals.invite-member.emails" +msgstr "Adresse e-mail, séparées par des virgules" + #: 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-close-confirm.hint" +msgstr "" +"Comme vous êtes le seul membre de l'équipe, celle-ci sera supprimée avec " +"ses projets et fichiers." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.message" +msgstr "Êtes-vous sûr de vouloir quitter l'équipe %s ?" + msgid "modals.leave-and-reassign.forbiden" msgstr "" "Vous ne pouvez pas quitter l'équipe si il n'y a aucun membre à promouvoir " @@ -1436,10 +1717,21 @@ msgstr "Êtes‑vous sûr de vouloir quitter cette équipe ?" msgid "modals.leave-confirm.title" msgstr "Quitter l’équipe" +#: src/app/main/ui/workspace/nudge.cljs +#, fuzzy +msgid "modals.nudge-title" +msgstr "Taille du nudge" + #: 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.hint" +msgstr "" +"Si vous transférez la propriété, vous changerez votre rôle en Admin, " +"perdant ainsi certaines permissions sur cette équipe. " + #: 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 ?" @@ -1465,6 +1757,20 @@ msgstr "" msgid "modals.remove-shared-confirm.message" msgstr "Retirer « %s » en tant que Bibliothèque Partagée" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.small-nudge" +msgstr "Petit nudge" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.hint" +msgstr "" +"Vous êtes sur le point de mettre à jour les composants 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-in-bulk.message" +msgstr "Mise à jour des composants dans une 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" @@ -1505,6 +1811,96 @@ msgstr "Profil enregistré avec succès !" msgid "notifications.validation-email-sent" msgstr "E‑mail de vérification envoyé à %s. Vérifiez votre e‑mail !" +msgid "onboarding.choice.desc" +msgstr "Comment voulez-vous commencer ?" + +#, fuzzy +msgid "onboarding.choice.fly-solo" +msgstr "C'est parti !" + +#, fuzzy +msgid "onboarding.choice.fly-solo-desc" +msgstr "Essayez Penpot et commencez à concevoir par vous-même." + +#, fuzzy +msgid "onboarding.choice.team-up" +msgstr "Faites équipe" + +#, fuzzy +msgid "onboarding.choice.team-up-desc" +msgstr "" +"Vous travaillez avec quelqu'un ? Créez une équipe et invitez des personnes " +"à travailler ensemble sur des projets et à partager des composants de " +"design." + +msgid "onboarding.choice.team-up.create-team" +msgstr "Le nom de votre équipe" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "" +"Après avoir nommé votre équipe, vous pourrez inviter des personnes à la " +"rejoindre." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Entrez le nom de l'équipe" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Inviter des membres" + +msgid "onboarding.choice.team-up.invite-members-desc" +msgstr "" +"Vous pourrez également inviter des membres et changer les rôles plus tard " +"dans la section équipe." + +#, fuzzy +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Créer une équipe et inviter plus tard" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Créer une équipe et envoyer des invitations" + +msgid "onboarding.choice.title" +msgstr "Bienvenue dans Penpot" + +#, fuzzy +msgid "onboarding.contrib.alt" +msgstr "Source ouverte" + +#, fuzzy +msgid "onboarding.contrib.desc1" +msgstr "" +"Penpot est à Source Ouverte, fabriqué par et pour la communauté. Si vous " +"vous voulez collaborer, vous êtes les bienvenues !" + +msgid "onboarding.contrib.desc2.1" +msgstr "Vous pouvez accéder au" + +msgid "onboarding.contrib.desc2.2" +msgstr "et suivre les instructions pour contribuer :)" + +msgid "onboarding.contrib.link" +msgstr "projet sur github" + +#, fuzzy +msgid "onboarding.contrib.title" +msgstr "Contributeur Open Source ?" + +#, fuzzy +msgid "onboarding.newsletter.accept" +msgstr "Oui, je veux être abonné" + +msgid "onboarding.newsletter.acceptance-message" +msgstr "Demande d'abonnement envoyé, vous allez recevoir un e-mail de confirmation." + +msgid "onboarding.newsletter.decline" +msgstr "Non, merci" + +#, fuzzy +msgid "onboarding.newsletter.desc" +msgstr "" +"Abonnez-vous à la newsletter pour être tenu informé des développements, du " +"progrès et des nouveautés du produit." + #: src/app/main/ui/auth/recovery.cljs msgid "profile.recovery.go-to-login" msgstr "Aller à la page de connexion" @@ -2186,10 +2582,6 @@ msgstr "Lignes" 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" @@ -2846,4 +3238,4 @@ 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 +msgstr "Cliquez pour fermer le chemin" diff --git a/frontend/translations/gl.po b/frontend/translations/gl.po new file mode 100644 index 0000000000..e9c505bc8f --- /dev/null +++ b/frontend/translations/gl.po @@ -0,0 +1,568 @@ +msgid "" +msgstr "" +"PO-Revision-Date: 2022-06-11 09:14+0000\n" +"Last-Translator: ascarida \n" +"Language-Team: Galician " +"\n" +"Language: gl\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" +"X-Generator: Weblate 4.13-dev\n" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Xa tes unha conta?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "" +"Consulta o teu correo electrónico e preme na ligazón de verificación para " +"comezar a usar Penpot." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "Confirmar o contrasinal" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Crear unha conta de proba" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "Queres probar?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"Este é un servizo de DEMOSTRACIÓN. NON USAR para traballo real, os " +"proxectos borraranse." + +#: 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 "Esqueciches o teu contrasinal?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "Nome completo" + +#: 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-with-github-submit" +msgstr "GitHub" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "Gitlab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Google" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "LDAP" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "OpenID" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Escribe un contrasinal novo" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.newsletter-subscription" +msgstr "Acepto subscribirme á lista de correo de Penpot." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "O código de recuperación non é correcto." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-successfully" +msgstr "O contrasinal cambiouse correctamente" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "Perfil sen verificar, valida o perfil antes de continuar." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Enviouse ó teu correo electrónico un enlace co que recuperar o contrasinal." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Unícheste ao equipo" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Contrasinal" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "Un mínimo de 8 caracteres" + +msgid "auth.privacy-policy" +msgstr "Política de privacidade" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Recuperar contrasinal" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "Enviarémosche un correo electrónico con instrucións" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Esqueceches o teu contrasinal?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Cambiar o teu contrasinal" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Ainda non tes unha conta?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Crea unha conta" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "É libre, é Open Source" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Crea unha conta" + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "A solución de código aberto para deseñar e crear prototipos." + +msgid "auth.terms-of-service" +msgstr "Condicións de servizo" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Ao crear unha nova conta, aceptas as nosas condicións de servizo e a " +"política de privacidade." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.verification-email-sent" +msgstr "Enviamos un correo electrónico de verificación a" + +msgid "common.share-link.confirm-deletion-link-description" +msgstr "" +"Seguro que queres eliminar esta ligazón? Se o fas, non estará dispoñible " +"para ninguén" + +msgid "common.share-link.get-link" +msgstr "Obter ligazón" + +msgid "common.share-link.link-copied-success" +msgstr "A ligazón copiouse correctamente" + +msgid "common.share-link.link-deleted-success" +msgstr "A ligazón eliminouse correctamente" + +msgid "common.share-link.permissions-can-access" +msgstr "Pode acceder a" + +msgid "common.share-link.permissions-can-view" +msgstr "Pode ver" + +msgid "common.share-link.permissions-hint" +msgstr "Calquera persoa ca ligazón terá acceso" + +msgid "common.share-link.placeholder" +msgstr "A ligazón para compartir aparecerá aquí" + +msgid "common.share-link.remove-link" +msgstr "Eliminar ligazón" + +msgid "common.share-link.title" +msgstr "Compartir prototipos" + +msgid "common.share-link.view-all-pages" +msgstr "Todas as páxinas" + +msgid "common.share-link.view-current-page" +msgstr "Só esta páxina" + +msgid "common.share-link.view-selected-pages" +msgstr "Páxinas seleccionadas" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Engadir como Biblioteca Compartida" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Cambiar correo electrónico" + +#: 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 novo equipo" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "O teu 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 % ficheiros" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Aínda non tes ficheiros" + +#: src/app/main/ui/dashboard/grid.cljs +#, markdown +msgid "dashboard.empty-placeholder-drafts" +msgstr "" +"Ai non! Ainda non tes ficheiros! Se queres facer a proba con algún modelo " +"vai a [Bibliotecas e modelos] (https://penpot.app/libraries-templates.html)" + +msgid "dashboard.export-frames" +msgstr "Exportar marcos a PDF…" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-frames.title" +msgstr "Exportar a PDF" + +msgid "dashboard.export-multi" +msgstr "Exportar % ficheiros Penpot" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "% de % elementos seleccionados" + +#: src/app/main/ui/workspace/header.cljs +msgid "dashboard.export-shapes" +msgstr "Exportar" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Podes engadir axustes na exportación aos elementos dende as propiedades do " +"deseño (parte inferior da barra lateral dereita)." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "Información sobre como configurar as exportacións en Penpot." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.title" +msgstr "Exportar selección" + +msgid "dashboard.export-single" +msgstr "Exportar arquivo Penpot" + +msgid "dashboard.export.detail" +msgstr "* Pode incluir compoñentes, gráficos, cores e/ou fontes." + +msgid "dashboard.export.explain" +msgstr "" +"Un ou máis ficheiros dos que queres exportar usan bibliotecas compartidas. " +"Que queres facer cos recursos?" + +msgid "dashboard.export.options.all.message" +msgstr "" +"os ficheiros con bibliotecas compartidas incluiranse na exportación " +"mantendo os vínculos." + +msgid "dashboard.export.options.all.title" +msgstr "Exportar bibliotecas compartidas" + +msgid "dashboard.export.options.detach.message" +msgstr "" +"As bibliotecas compartidas non se incluirán na exportación e non se " +"engadirán recursos á biblioteca. " + +msgid "dashboard.export.options.detach.title" +msgstr "Tratar os recursos da biblioteca compartida coma obxetos básicos" + +msgid "dashboard.export.options.merge.message" +msgstr "" +"O teu ficheiro exportarase con todos os recursos externos metidos na " +"biblioteca do ficheiro." + +msgid "dashboard.export.options.merge.title" +msgstr "Incluir os recursos de bibliotecas compartidas na biblioteca do ficheiro" + +msgid "dashboard.export.title" +msgstr "Exportar ficheiros" + +msgid "dashboard.fonts.deleted-placeholder" +msgstr "Fonte eliminada" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.dismiss-all" +msgstr "Desbotar todas" + +msgid "dashboard.fonts.empty-placeholder" +msgstr "Aínda non instalaches as túas propas fontes." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "Engadiuse 1 fonte" +msgstr[1] "Engadíronse % fontes" + +#, markdown +msgid "dashboard.fonts.hero-text1" +msgstr "" +"Calquera fonte que cargues aquí engadirase na listaxe de familias de fontes " +"situada nas propiedades de texto dos ficheiros deste equipo. As fontes co " +"mesmo nome de familia agruparanse coma **unha única familia de fontes**. " +"Podes cargar fontes cos seguintes formatos: **TTF, OFT e WOFF** (só se " +"precisa un)." + +#, markdown +msgid "dashboard.fonts.hero-text2" +msgstr "" +"Só debes cargar fontes da túa propiedade ou das que teñas licenza para usar " +"en Penpot. Atopa máis información na sección de dereitos de contido nas " +"[Condicións do servizo de Penpot](https://penpot.app/terms.html). Podes ler " +"máis sobre [licenzas de fontes](https://www.typography.com/faq)." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "Cargar todas" + +msgid "dashboard.import" +msgstr "Importar archivos de Penpot" + +msgid "dashboard.import.analyze-error" +msgstr "Vaia! Non se puido importar o ficheiro" + +msgid "dashboard.import.import-error" +msgstr "Houbo un problema ao importar o ficheiro. Non se puido importar o ficheiro." + +msgid "dashboard.import.import-message" +msgstr "% ficheiros importáronse correctamente." + +msgid "dashboard.import.import-warning" +msgstr "Algúns ficheiros contiñan obxectos non válidos que foron eliminados." + +msgid "dashboard.import.progress.process-colors" +msgstr "Procesando cores" + +msgid "dashboard.import.progress.process-components" +msgstr "Procesando compoñentes" + +msgid "dashboard.import.progress.process-media" +msgstr "Procesando medios" + +msgid "dashboard.import.progress.process-page" +msgstr "Procesando páxina: %s" + +msgid "dashboard.import.progress.process-typographies" +msgstr "Procesando fontes" + +msgid "dashboard.import.progress.upload-data" +msgstr "Enviando datos ao servidor (%s/%s)" + +msgid "dashboard.import.progress.upload-media" +msgstr "Enviando ficheiro: %s" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "Invitar ao equipo" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Abandonar o 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 os teus ficheiros …" + +msgid "dashboard.loading-fonts" +msgstr "cargando as túas fontes …" + +#: 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 % ficheiros a" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Mover a outro equipo" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ Novo ficheiro" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "Novo ficheiro" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ Novo proxecto" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "Novo proxecto" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "Envíame noticias, actualizacións do produto e recomendacións sobre Penpot." + +msgid "dashboard.options" +msgstr "Opcións" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "Proxectos" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +msgstr "Buscar…" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "Correo electrónico" + +#: 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/settings/feedback.cljs +msgid "feedback.description" +msgstr "Descrición" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subject" +msgstr "Asunto" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "Correo electrónico" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur" +msgstr "Desenfoque" + +#: 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 "Recheo" + +#: 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 "Disposición" + +#: 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 "Esquerda" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Radio" + +#: 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 "Enriba" + +#: 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" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Centro" diff --git a/frontend/translations/he.po b/frontend/translations/he.po index ca7d6b7a39..8445462b5f 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-03-27 23:09+0000\n" +"PO-Revision-Date: 2022-05-28 21:16+0000\n" "Last-Translator: Yaron Shahrabani \n" "Language-Team: Hebrew " "\n" @@ -10,7 +10,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && " "n % 10 == 0) ? 2 : 3));\n" -"X-Generator: Weblate 4.12-dev\n" +"X-Generator: Weblate 4.13-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -75,11 +75,11 @@ msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "כניסה עם LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "OpenID Connect" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -179,33 +179,15 @@ msgstr "הקישור הועתק בהצלחה" msgid "common.share-link.link-deleted-success" msgstr "הקישור נמחק בהצלחה" -msgid "common.share-link.permissions-can-access" -msgstr "יש גישה" - -msgid "common.share-link.permissions-can-view" -msgstr "אפשר לצפות" - msgid "common.share-link.permissions-hint" msgstr "כל מי שיש לו את הקישור יכול לגשת" msgid "common.share-link.placeholder" msgstr "הקישור לשיתוף יופיע כאן" -msgid "common.share-link.remove-link" -msgstr "הסרת קישור" - msgid "common.share-link.title" msgstr "שיתוף אבות טיפוס" -msgid "common.share-link.view-all-pages" -msgstr "כל העמודים" - -msgid "common.share-link.view-current-page" -msgstr "רק העמוד הזה" - -msgid "common.share-link.view-selected-pages" -msgstr "עמודים נבחרים" - #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" @@ -677,6 +659,13 @@ msgstr "אימות מול Google הושבת במנגנון" msgid "errors.invalid-color" msgstr "צבע שגוי" +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "ההזמנה שגויה" + +msgid "errors.invite-invalid.info" +msgstr "ההזמנה כנראה בוטלה או שתוקפה פג." + #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" msgstr "אימות מול LDAP הושבת." @@ -686,7 +675,7 @@ msgstr "סוג התמונה אינו נתמך (חייב להיות svg,‏ jpg #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "התמונה גדולה מכדי להוסיף אותה (חייבת להיות פחות מ־5 מ״ב)." +msgstr "התמונה גדולה מכדי להוסיף אותה." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -1000,6 +989,10 @@ msgstr "מידע" msgid "history.alert-message" msgstr "הגרסה שמוצגת היא %s" +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "קיצורי דרך" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.about-penpot" msgstr "על Penpot" @@ -1021,6 +1014,9 @@ msgstr "הכול" msgid "labels.and" msgstr "וגם" +msgid "labels.back" +msgstr "חזרה" + #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.desc-message" msgstr "" @@ -1151,6 +1147,10 @@ msgstr "סגנונות" msgid "labels.fonts" msgstr "גופנים" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "מאגר GitHub" + #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/settings/sidebar.cljs, #: src/app/main/ui/dashboard/sidebar.cljs @@ -1405,6 +1405,10 @@ msgstr "התחלה" msgid "labels.status" msgstr "מצב" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "מדריכים" + #: src/app/main/ui/settings/profile.cljs msgid "labels.update" msgstr "עדכון" @@ -1768,6 +1772,27 @@ msgstr "" "עובדים בשותף? אפשר ליצור צוות ולהזמין אנשים לעבוד יחד על מיזמים ולשתף משאבי " "עיצוב." +msgid "onboarding.choice.team-up.create-team" +msgstr "שם הצוות שלך" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "לאחר מתן שם לצוות שלך, יתאפשר לך להזמין אנשים להצטרף." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "נא למלא את שם הצוות" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "הזמנת חברים" + +msgid "onboarding.choice.team-up.invite-members-desc" +msgstr "תהיה לך אפשרות להזמין חברים ולהחליף את תפקידיהם בהמשך בסעיף הצוות." + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "יצירת צוות והזמנה בהמשך" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "יצירת צוות ושליחת הזמנות" + msgid "onboarding.choice.title" msgstr "ברוך בואך אל Penpot" @@ -1939,6 +1964,420 @@ msgstr "מעבר למסך הכניסה" msgid "settings.multiple" msgstr "מעורב" +# SECTIONS +msgid "shortcut-section.basics" +msgstr "יסודות" + +msgid "shortcut-section.dashboard" +msgstr "לוח בקרה" + +msgid "shortcut-section.viewer" +msgstr "מציג" + +msgid "shortcut-section.workspace" +msgstr "סביבת עבודה" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "יישור" + +msgid "shortcut-subsection.edit" +msgstr "עריכה" + +msgid "shortcut-subsection.general-dashboard" +msgstr "כללי" + +msgid "shortcut-subsection.general-viewer" +msgstr "כללי" + +msgid "shortcut-subsection.main-menu" +msgstr "תפריט ראשי" + +msgid "shortcut-subsection.modify-layers" +msgstr "שינוי שכבות" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "ניווט" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "ניווט" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "ניווט" + +msgid "shortcut-subsection.panels" +msgstr "לוחות" + +msgid "shortcut-subsection.path-editor" +msgstr "נתיבים" + +msgid "shortcut-subsection.shape" +msgstr "צורות" + +msgid "shortcut-subsection.tools" +msgstr "כלים" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "תקריב" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "תקריב" + +msgid "shortcuts.add-comment" +msgstr "הערות" + +msgid "shortcuts.add-node" +msgstr "הוספת מפרק" + +msgid "shortcuts.align-bottom" +msgstr "יישור לתחתית" + +msgid "shortcuts.align-hcenter" +msgstr "יישור למרכז אופקית" + +msgid "shortcuts.align-left" +msgstr "יישור שמאלה" + +msgid "shortcuts.align-right" +msgstr "יישור ימינה" + +msgid "shortcuts.align-top" +msgstr "יישור לראש" + +msgid "shortcuts.align-vcenter" +msgstr "יישור למרכז אנכית" + +msgid "shortcuts.artboard-selection" +msgstr "יצירת לוח אומנות מהבחירה" + +msgid "shortcuts.bool-difference" +msgstr "הבדל בוליאני" + +msgid "shortcuts.bool-exclude" +msgstr "החרגה בוליאנית" + +msgid "shortcuts.bool-intersection" +msgstr "הצלבה בוליאנית" + +msgid "shortcuts.bool-union" +msgstr "איחוד בוליאני" + +msgid "shortcuts.bring-back" +msgstr "שליחה אחורה" + +msgid "shortcuts.bring-backward" +msgstr "הרחקה לאחור" + +msgid "shortcuts.bring-forward" +msgstr "קידום" + +msgid "shortcuts.bring-front" +msgstr "הבאה לחזית" + +msgid "shortcuts.clear-undo" +msgstr "פינוי ביטול" + +msgid "shortcuts.copy" +msgstr "העתקה" + +msgid "shortcuts.create-component" +msgstr "יצירת רכיב" + +msgid "shortcuts.create-new-project" +msgstr "יצירת חדש" + +msgid "shortcuts.cut" +msgstr "גזירה" + +msgid "shortcuts.decrease-zoom" +msgstr "התרחקות" + +msgid "shortcuts.delete" +msgstr "מחיקה" + +msgid "shortcuts.delete-node" +msgstr "מחיקת מפרק" + +msgid "shortcuts.detach-component" +msgstr "ניתוק רכיב" + +msgid "shortcuts.draw-curve" +msgstr "עיקול" + +msgid "shortcuts.draw-ellipse" +msgstr "אליפסה" + +msgid "shortcuts.draw-frame" +msgstr "לוח אומנות" + +msgid "shortcuts.draw-nodes" +msgstr "ציור נתיב" + +msgid "shortcuts.draw-path" +msgstr "נתיב" + +msgid "shortcuts.draw-rect" +msgstr "ריבוע" + +msgid "shortcuts.draw-text" +msgstr "טקסט" + +msgid "shortcuts.duplicate" +msgstr "שכפול" + +msgid "shortcuts.escape" +msgstr "ביטול" + +msgid "shortcuts.export-shapes" +msgstr "ייצוא צורות" + +msgid "shortcuts.fit-all" +msgstr "כיוון תקריב כדי להציג הכול" + +msgid "shortcuts.flip-horizontal" +msgstr "היפוך אופקי" + +msgid "shortcuts.flip-vertical" +msgstr "היפוך אנכי" + +msgid "shortcuts.go-to-drafts" +msgstr "מעבר לטיוטות" + +msgid "shortcuts.go-to-libs" +msgstr "מעבר לספריות המשותפות" + +msgid "shortcuts.go-to-search" +msgstr "חיפוש" + +msgid "shortcuts.group" +msgstr "קבוצה" + +msgid "shortcuts.h-distribute" +msgstr "פיזור אופקית" + +msgid "shortcuts.hide-ui" +msgstr "הצגת/הסתרת מנשק משתמש" + +msgid "shortcuts.increase-zoom" +msgstr "התקרבות" + +msgid "shortcuts.insert-image" +msgstr "הוספת תמונה" + +msgid "shortcuts.join-nodes" +msgstr "צירוף מפרקים" + +msgid "shortcuts.make-corner" +msgstr "הפיכה לפינה" + +msgid "shortcuts.make-curve" +msgstr "הפיכה לעיקול" + +msgid "shortcuts.mask" +msgstr "מסכה" + +msgid "shortcuts.merge-nodes" +msgstr "מיזוג מפרקים" + +msgid "shortcuts.move" +msgstr "העברה" + +msgid "shortcuts.move-fast-down" +msgstr "העברה למטה מהר" + +msgid "shortcuts.move-fast-left" +msgstr "העברה שמאלה מהר" + +msgid "shortcuts.move-fast-right" +msgstr "העברה ימינה מהר" + +msgid "shortcuts.move-fast-up" +msgstr "העברה למעלה מהר" + +msgid "shortcuts.move-nodes" +msgstr "העברת מפרק" + +msgid "shortcuts.move-unit-down" +msgstr "העברה למטה" + +msgid "shortcuts.move-unit-left" +msgstr "העברה שמאלה" + +msgid "shortcuts.move-unit-right" +msgstr "העברה ימינה" + +msgid "shortcuts.move-unit-up" +msgstr "העברה למעלה" + +msgid "shortcuts.next-frame" +msgstr "לוח האומנות הבא" + +msgid "shortcuts.opacity-0" +msgstr "הגדרת אטימות ל־100%" + +msgid "shortcuts.opacity-1" +msgstr "הגדרת אטימות ל־10%" + +msgid "shortcuts.opacity-2" +msgstr "הגדרת אטימות ל־20%" + +msgid "shortcuts.opacity-3" +msgstr "הגדרת אטימות ל־30%" + +msgid "shortcuts.opacity-4" +msgstr "הגדרת אטימות ל־40%" + +msgid "shortcuts.opacity-5" +msgstr "הגדרת אטימות ל־50%" + +msgid "shortcuts.opacity-6" +msgstr "הגדרת אטימות ל־60%" + +msgid "shortcuts.opacity-7" +msgstr "הגדרת אטימות ל־70%" + +msgid "shortcuts.opacity-8" +msgstr "הגדרת אטימות ל־80%" + +msgid "shortcuts.opacity-9" +msgstr "הגדרת אטימות ל־90%" + +msgid "shortcuts.open-color-picker" +msgstr "בוחר צבעים" + +msgid "shortcuts.open-comments" +msgstr "מעבר לסעיף הערות צופים" + +msgid "shortcuts.open-dashboard" +msgstr "מעבר ללוח בקרה" + +msgid "shortcuts.open-handoff" +msgstr "מעבר לסעיף הנחיות צופים" + +msgid "shortcuts.open-interactions" +msgstr "מעבר לסעיף תפעול משתמש" + +msgid "shortcuts.open-viewer" +msgstr "מעבר לסעיף תפעול משתמש" + +msgid "shortcuts.open-workspace" +msgstr "מעבר לסביבת עבודה" + +msgid "shortcuts.paste" +msgstr "הדבקה" + +msgid "shortcuts.prev-frame" +msgstr "לוח אומנות קודם" + +msgid "shortcuts.redo" +msgstr "ביצוע חוזר" + +msgid "shortcuts.reset-zoom" +msgstr "איפוס תקריב" + +msgid "shortcuts.search-placeholder" +msgstr "חיפוש בקיצורי הדרך" + +msgid "shortcuts.select-all" +msgstr "בחירה בהכול" + +msgid "shortcuts.separate-nodes" +msgstr "הפרדת מפרקים" + +msgid "shortcuts.show-pixel-grid" +msgstr "הצגת/הסתרת רשת פיקסלים" + +msgid "shortcuts.show-shortcuts" +msgstr "הצגת/הסתרת קיצורי דרך" + +msgid "shortcuts.snap-nodes" +msgstr "הצמדה למפרקים" + +msgid "shortcuts.snap-pixel-grid" +msgstr "הצמדה לרשת פיקסלים" + +msgid "shortcuts.start-editing" +msgstr "התחלת עריכה" + +msgid "shortcuts.start-measure" +msgstr "התחלת מדידה" + +msgid "shortcuts.stop-measure" +msgstr "עצירת מדידה" + +msgid "shortcuts.thumbnail-set" +msgstr "הגדרת תמונות ממוזערות" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "קיצורי מקלדת" + +msgid "shortcuts.toggle-alignment" +msgstr "החלפת מצב יישור דינמי" + +msgid "shortcuts.toggle-assets" +msgstr "החלפת מצב נכסים" + +msgid "shortcuts.toggle-colorpalette" +msgstr "החלפת מצב לוח צבעים" + +msgid "shortcuts.toggle-focus-mode" +msgstr "החלפת מצב מיקוד" + +msgid "shortcuts.toggle-grid" +msgstr "הצגת/הסתרת רשת" + +msgid "shortcuts.toggle-history" +msgstr "החלפת הצגת היסטוריה" + +msgid "shortcuts.toggle-layers" +msgstr "החלפת הצגת שכבות" + +msgid "shortcuts.toggle-lock" +msgstr "נעילת הנבחרים" + +msgid "shortcuts.toggle-lock-size" +msgstr "נעילת יחס" + +msgid "shortcuts.toggle-rules" +msgstr "הצגת/הסתרת סרגלים" + +msgid "shortcuts.toggle-scale-text" +msgstr "החלפת מצב שינוי קנה מידה של כתב" + +msgid "shortcuts.toggle-snap-grid" +msgstr "הצמדה לרשת" + +msgid "shortcuts.toggle-snap-guide" +msgstr "הצמדה לקווים מנחים" + +msgid "shortcuts.toggle-textpalette" +msgstr "החלפת לוח טקסט" + +msgid "shortcuts.toggle-visibility" +msgstr "החלפת מצב הצגה" + +msgid "shortcuts.toggle-zoom-style" +msgstr "החלפת סגנון תקריב" + +msgid "shortcuts.toogle-fullscreen" +msgstr "החלפת מילוי מסך" + +msgid "shortcuts.undo" +msgstr "ביטול" + +msgid "shortcuts.ungroup" +msgstr "פירוק קבוצה" + +msgid "shortcuts.unmask" +msgstr "ביטול מסכה" + +msgid "shortcuts.v-distribute" +msgstr "פיזור אנכי" + +msgid "shortcuts.zoom-selected" +msgstr "התמקדות על הנבחר" + #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" msgstr "%s‏ - Penpot" @@ -2341,6 +2780,10 @@ msgstr "עריכה" msgid "workspace.header.menu.option.file" msgstr "קובץ" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "עזרה ומידע" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.option.preferences" msgstr "העדפות" @@ -2629,8 +3072,8 @@ msgstr "ייצוא" msgid "workspace.options.export-multiple" msgstr "ייצוא הבחירה" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -#, fuzzy +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" msgstr "ייצוא רכיב" @@ -2754,10 +3197,6 @@ msgstr "שורות" 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 "מילוי קבוצה" @@ -3657,6 +4096,10 @@ msgstr "נתיב (%s)" msgid "workspace.toolbar.rect" msgstr "ריבוע (%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "קיצורי דרך (%s)" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.text" msgstr "טקסט (%s)" diff --git a/frontend/translations/id.po b/frontend/translations/id.po index adc798da59..b70e2ab453 100644 --- a/frontend/translations/id.po +++ b/frontend/translations/id.po @@ -69,23 +69,23 @@ msgstr "Senang bertemu denganmu lagi!" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "Masuk dengan Github" +msgstr "Github" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Masuk dengan Gitlab" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "Masuk dengan Google" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Masuk dengan LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "Masuk dengan OpenID (SSO)" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -189,21 +189,9 @@ msgstr "Tautan berhasil disalin" msgid "common.share-link.link-deleted-success" msgstr "Tautan berhasil dihapus" -msgid "common.share-link.permissions-can-access" -msgstr "Dapat mengakses" - -msgid "common.share-link.permissions-can-view" -msgstr "Dapat melihat" - msgid "common.share-link.permissions-hint" msgstr "Siapapun yang memiliki tautan dapat mengakses" -msgid "common.share-link.view-current-page" -msgstr "Hanya halaman ini" - -msgid "common.share-link.view-selected-pages" -msgstr "Halaman yang dipilih" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Tambahkan sebagai pustaka bersama" @@ -266,4 +254,4 @@ msgstr "* Dapat mencakup komponen, grafik, warna dan/atau tipografi." msgid "dashboard.export.explain" msgstr "" "Satu atau lebih file yang ingin anda ekspor, menggunakan pustaka bersama. " -"Apa yang akan anda lakukan dengan asetnya*?" \ No newline at end of file +"Apa yang akan anda lakukan dengan asetnya*?" diff --git a/frontend/translations/lt.po b/frontend/translations/lt.po index 016e16ab9b..a3acf4cc38 100644 --- a/frontend/translations/lt.po +++ b/frontend/translations/lt.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-05-25 09:24+0000\n" +"PO-Revision-Date: 2022-05-28 21:16+0000\n" "Last-Translator: Vincas Dundzys \n" "Language-Team: Lithuanian " "\n" @@ -79,11 +79,11 @@ msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Prisijungti su LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "OpenID prisijungimas" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -188,33 +188,15 @@ msgstr "Nuoroda sėkmingai nukopijuota" msgid "common.share-link.link-deleted-success" msgstr "Nuoroda sėkmingai ištrinta" -msgid "common.share-link.permissions-can-access" -msgstr "Gali pasiekti" - -msgid "common.share-link.permissions-can-view" -msgstr "Galima peržiūrėti" - msgid "common.share-link.permissions-hint" msgstr "Kiekvienas, turintis nuorodą, turės prieigą" msgid "common.share-link.placeholder" msgstr "Bendrinama nuoroda bus rodoma čia" -msgid "common.share-link.remove-link" -msgstr "Pašalinti nuorodą" - msgid "common.share-link.title" msgstr "Dalinkitės prototipais" -msgid "common.share-link.view-all-pages" -msgstr "Visi puslapiai" - -msgid "common.share-link.view-current-page" -msgstr "Tik šis puslapis" - -msgid "common.share-link.view-selected-pages" -msgstr "Parinkti puslapiai" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Pridėti kaip bendrinamą biblioteką" @@ -345,4 +327,69 @@ msgid "dashboard.fonts.dismiss-all" msgstr "Atmesti visus" msgid "dashboard.fonts.empty-placeholder" -msgstr "Vis dar neįdiegėte tinkintų šriftų." \ No newline at end of file +msgstr "Vis dar neįdiegėte tinkintų šriftų." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "Pridėtas 1 šriftas" +msgstr[1] "Pridėti %s šriftai" +msgstr[2] "Šriftas(-ai) pridėti" + +#, markdown +msgid "dashboard.fonts.hero-text1" +msgstr "" +"Bet kuris čia įkeltas žiniatinklio šriftas bus įtrauktas į šriftų šeimų " +"sąrašą. Jis bus prieinamas šios komandos failų teksto savybėse. Šriftai, " +"turintys tą patį šrifto šeimos pavadinimą, bus sugrupuoti kaip **viena " +"šriftų šeima**. Galite įkelti šių formatų šriftus: **TTF, OTF ir WOFF** " +"(reikės tik vieno)." + +#, markdown +msgid "dashboard.fonts.hero-text2" +msgstr "" +"Turėtumėte įkelti tik tuos šriftus, kurie jums priklauso arba kuriuos " +"turite licenciją naudoti \"Penpot\". Daugiau informacijos rasite " +"[Penpot'paslaugų teikimo sąlygų](https://penpot.app/terms.html) skyriuje " +"\"Turinio teisės\". Taip pat galite paskaityti apie [šriftų " +"licencijavimą](https://www.typography.com/faq)." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "Įkelti viską" + +msgid "dashboard.import" +msgstr "Importuokite Penpot failus" + +msgid "dashboard.import.analyze-error" +msgstr "Oi! Nepavyko importuoti šio failo" + +msgid "dashboard.import.import-error" +msgstr "Iškilo problema importuojant failą. Failas nebuvo importuotas." + +msgid "dashboard.import.import-message" +msgstr "%s failai sėkmingai importuoti." + +msgid "dashboard.import.import-warning" +msgstr "Kai kuriuose failuose buvo netinkamų objektų, kurie buvo pašalinti." + +msgid "dashboard.import.progress.process-colors" +msgstr "Apdorojimo spalvos" + +msgid "dashboard.import.progress.process-components" +msgstr "Komponentų apdorojimas" + +msgid "dashboard.import.progress.process-media" +msgstr "Apdorojamos laikmenos" + +msgid "dashboard.import.progress.process-page" +msgstr "Apdorojamas puslapis: %s" + +msgid "dashboard.import.progress.process-typographies" +msgstr "Tipografijų apdorojimas" + +msgid "dashboard.import.progress.upload-data" +msgstr "Įkeliami duomenys į serverį (%s/%s)" + +msgid "dashboard.import.progress.upload-media" +msgstr "Įkeliamas failas: %s" diff --git a/frontend/translations/ml.po b/frontend/translations/ml.po index 80e7216828..c123de101b 100644 --- a/frontend/translations/ml.po +++ b/frontend/translations/ml.po @@ -69,23 +69,23 @@ msgstr "നിങ്ങളെ വീണ്ടും കാണാൻ കഴിഞ #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "ഗിറ്റ്ഹബ്ബ് ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക" +msgstr "ഗിറ്റ്ഹബ്ബ്" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "ഗിറ്റ്ലാബ് ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക" +msgstr "ഗിറ്റ്ലാബ്" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "ഗൂഗിൾ ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക" +msgstr "ഗൂഗിൾ" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "LDAP ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "ഓപ്പൺഐഡി (SSO) ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക" +msgstr "ഓപ്പൺഐഡി" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -189,33 +189,15 @@ msgstr "കണ്ണി വിജയകരമായി പകർത്തി" msgid "common.share-link.link-deleted-success" msgstr "കണ്ണി വിജയകരമായി മായിച്ചു" -msgid "common.share-link.permissions-can-access" -msgstr "പ്രാപ്യമാണ്" - -msgid "common.share-link.permissions-can-view" -msgstr "കാണാവുന്നതാണ്" - msgid "common.share-link.permissions-hint" msgstr "കണ്ണിയുള്ള ആർക്കും പ്രാപ്യമാകും" msgid "common.share-link.placeholder" msgstr "പങ്കുവെക്കാവുന്ന കണ്ണി ഇവിടെ ലഭ്യമാകും" -msgid "common.share-link.remove-link" -msgstr "കണ്ണി നീക്കുക" - msgid "common.share-link.title" msgstr "പ്രോട്ടോടൈപ്പുകൾ പങ്കുവെയ്ക്കുക" -msgid "common.share-link.view-all-pages" -msgstr "എല്ലാ താളുകളും" - -msgid "common.share-link.view-current-page" -msgstr "ഈ താൾ മാത്രം" - -msgid "common.share-link.view-selected-pages" -msgstr "തിരഞ്ഞെടുത്ത താളുകൾ" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "പങ്കിട്ട ലൈബ്രറിയായി ചേർക്കുക" @@ -281,4 +263,4 @@ msgid "dashboard.export-single" msgstr "പെൻപോട്ട് ഫയൽ എക്സ്പോർട്ട് ചെയ്യുക" msgid "dashboard.export.detail" -msgstr "* ഘടകങ്ങൾ, ഗ്രാഫിക്സ്, നിറങ്ങൾ അല്ലെങ്കിൽ മുദ്രണകലകൾ എന്നിവ ഉൾപ്പെടാം." \ No newline at end of file +msgstr "* ഘടകങ്ങൾ, ഗ്രാഫിക്സ്, നിറങ്ങൾ അല്ലെങ്കിൽ മുദ്രണകലകൾ എന്നിവ ഉൾപ്പെടാം." diff --git a/frontend/translations/pl.po b/frontend/translations/pl.po index d3f5e10a8d..00d313b19a 100644 --- a/frontend/translations/pl.po +++ b/frontend/translations/pl.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-05-10 12:15+0000\n" +"PO-Revision-Date: 2022-05-28 21:16+0000\n" "Last-Translator: Radek Sawicki \n" "Language-Team: Polish " "\n" @@ -10,7 +10,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 4.12.1\n" +"X-Generator: Weblate 4.13-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -78,11 +78,11 @@ msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Zaloguj się przez LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "OpenID Connect" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -186,33 +186,15 @@ msgstr "Link skopiowano pomyślnie" msgid "common.share-link.link-deleted-success" msgstr "Link usunięto pomyślnie" -msgid "common.share-link.permissions-can-access" -msgstr "Można uzyskać dostęp" - -msgid "common.share-link.permissions-can-view" -msgstr "Można zobaczyć" - msgid "common.share-link.permissions-hint" msgstr "Każdy, kto ma link, będzie miał dostęp" msgid "common.share-link.placeholder" msgstr "Tutaj pojawi się link do udostępniania" -msgid "common.share-link.remove-link" -msgstr "Usuń link" - msgid "common.share-link.title" msgstr "Udostępnij prototypy" -msgid "common.share-link.view-all-pages" -msgstr "Wszystkie strony" - -msgid "common.share-link.view-current-page" -msgstr "Tylko ta strona" - -msgid "common.share-link.view-selected-pages" -msgstr "Wybrane strony" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Dodaj jako Udostępnioną Bibliotekę" @@ -261,7 +243,7 @@ msgstr "" "szablony](https://penpot.app/libraries-templates.html)" msgid "dashboard.export-frames" -msgstr "Eksportuj obszary kompozycji do PDF..." +msgstr "Eksportuj obszary kompozycji do PDF…" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" @@ -682,6 +664,13 @@ msgstr "Uwierzytelnianie z Google wyłączone po stronie zaplecza" msgid "errors.invalid-color" msgstr "Nieprawidłowy kolor" +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "Nieprawidłowe zaproszenie" + +msgid "errors.invite-invalid.info" +msgstr "To zaproszenie może być anulowane lub wygasło." + #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" msgstr "Uwierzytelnianie LDAP jest wyłączone." @@ -691,7 +680,7 @@ msgstr "Format obrazu nie jest obsługiwany (musi to być svg, jpg lub png)." #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "Obraz jest zbyt duży (musi mieć mniej niż 5 MB)." +msgstr "Obraz jest zbyt duży." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -1031,6 +1020,10 @@ msgstr "Informacje" msgid "history.alert-message" msgstr "Widzisz wersję %s" +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Skróty" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.about-penpot" msgstr "O Penpot" @@ -1052,6 +1045,9 @@ msgstr "Wszystko" msgid "labels.and" msgstr "i" +msgid "labels.back" +msgstr "Cofnij" + #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.desc-message" msgstr "" @@ -1177,6 +1173,10 @@ msgstr "Style" msgid "labels.fonts" msgstr "Fonty" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Repozytorium Github" + #: 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 "Dodaj opinię" @@ -1423,6 +1423,10 @@ msgstr "Start" msgid "labels.status" msgstr "Status" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Samouczki" + #: src/app/main/ui/settings/profile.cljs msgid "labels.update" msgstr "Aktualizuj" @@ -1786,6 +1790,29 @@ msgstr "" "Pracujesz z kimś? Utwórz zespół i zaproś ludzi do wspólnej pracy nad " "projektami i udostępniania zasobów projektowych." +msgid "onboarding.choice.team-up.create-team" +msgstr "Nazwa twojego zespołu" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "Po nazwaniu swojego zespołu będziesz mógł zapraszać osoby do dołączenia." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Wprowadź nazwę zespołu" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Zaproś członków" + +msgid "onboarding.choice.team-up.invite-members-desc" +msgstr "" +"Będziesz także mógł zapraszać członków i zmieniać później role w sekcji " +"zespołu." + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Utwórz zespół i zaproś później" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Utwórz zespół i wyślij zaproszenia" + msgid "onboarding.choice.title" msgstr "Witamy w Penpot" @@ -1939,6 +1966,420 @@ msgstr "Przejdź do logowania" msgid "settings.multiple" msgstr "Mieszane" +# SECTIONS +msgid "shortcut-section.basics" +msgstr "Podstawy" + +msgid "shortcut-section.dashboard" +msgstr "Kokpit" + +msgid "shortcut-section.viewer" +msgstr "Widz" + +msgid "shortcut-section.workspace" +msgstr "Obszar roboczy" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "Wyrównanie" + +msgid "shortcut-subsection.edit" +msgstr "Edytuj" + +msgid "shortcut-subsection.general-dashboard" +msgstr "Ogólny" + +msgid "shortcut-subsection.general-viewer" +msgstr "Ogólny" + +msgid "shortcut-subsection.main-menu" +msgstr "Menu główne" + +msgid "shortcut-subsection.modify-layers" +msgstr "Modyfikuj warstwy" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Nawigacja" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Nawigacja" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Nawigacja" + +msgid "shortcut-subsection.panels" +msgstr "Panele" + +msgid "shortcut-subsection.path-editor" +msgstr "Ścieżki" + +msgid "shortcut-subsection.shape" +msgstr "Kształty" + +msgid "shortcut-subsection.tools" +msgstr "Narzędzia" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "Przybliżenie" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "Przybliżenie" + +msgid "shortcuts.add-comment" +msgstr "Komentarze" + +msgid "shortcuts.add-node" +msgstr "Dodaj węzeł" + +msgid "shortcuts.align-bottom" +msgstr "Wyrównaj do dołu" + +msgid "shortcuts.align-hcenter" +msgstr "Wyrównaj do środka w poziomie" + +msgid "shortcuts.align-left" +msgstr "Wyrównaj do lewej" + +msgid "shortcuts.align-right" +msgstr "Wyrównaj do prawej" + +msgid "shortcuts.align-top" +msgstr "Wyrównaj do góry" + +msgid "shortcuts.align-vcenter" +msgstr "Wyrównaj do środka w pionie" + +msgid "shortcuts.artboard-selection" +msgstr "Utwórz obszar kompozycji z wybranych" + +msgid "shortcuts.bool-difference" +msgstr "Różnica logiczna" + +msgid "shortcuts.bool-exclude" +msgstr "Wykluczenie logiczne" + +msgid "shortcuts.bool-intersection" +msgstr "Logiczny punkt przecięcia" + +msgid "shortcuts.bool-union" +msgstr "Związek logiczny" + +msgid "shortcuts.bring-back" +msgstr "Przesuń na spód" + +msgid "shortcuts.bring-backward" +msgstr "Przesuń niżej" + +msgid "shortcuts.bring-forward" +msgstr "Przesuń wyżej" + +msgid "shortcuts.bring-front" +msgstr "Przesuń na wierzch" + +msgid "shortcuts.clear-undo" +msgstr "Wyczyść cofnięcia" + +msgid "shortcuts.copy" +msgstr "Kopiuj" + +msgid "shortcuts.create-component" +msgstr "Utwórz komponent" + +msgid "shortcuts.create-new-project" +msgstr "Stwórz nowy" + +msgid "shortcuts.cut" +msgstr "WYtnij" + +msgid "shortcuts.decrease-zoom" +msgstr "Oddal" + +msgid "shortcuts.delete" +msgstr "Usuń" + +msgid "shortcuts.delete-node" +msgstr "Usuń węzeł" + +msgid "shortcuts.detach-component" +msgstr "Odłącz komponent" + +msgid "shortcuts.draw-curve" +msgstr "Krzywa" + +msgid "shortcuts.draw-ellipse" +msgstr "Elipsa" + +msgid "shortcuts.draw-frame" +msgstr "Obszar kompozycji" + +msgid "shortcuts.draw-nodes" +msgstr "Rysuj ścieżkę" + +msgid "shortcuts.draw-path" +msgstr "Ścieżka" + +msgid "shortcuts.draw-rect" +msgstr "Prostokąt" + +msgid "shortcuts.draw-text" +msgstr "Tekst" + +msgid "shortcuts.duplicate" +msgstr "Duplikuj" + +msgid "shortcuts.escape" +msgstr "Anuluj" + +msgid "shortcuts.export-shapes" +msgstr "Eksportuj kształty" + +msgid "shortcuts.fit-all" +msgstr "Powiększ, aby dopasować wszystko" + +msgid "shortcuts.flip-horizontal" +msgstr "Obróć poziomo" + +msgid "shortcuts.flip-vertical" +msgstr "Obróć pionowo" + +msgid "shortcuts.go-to-drafts" +msgstr "Idź do szkiców" + +msgid "shortcuts.go-to-libs" +msgstr "Idź do bibliotek współdzielonych" + +msgid "shortcuts.go-to-search" +msgstr "Szukaj" + +msgid "shortcuts.group" +msgstr "Grupa" + +msgid "shortcuts.h-distribute" +msgstr "Rozłóż w poziomie" + +msgid "shortcuts.hide-ui" +msgstr "Pokaż/ukryj UI" + +msgid "shortcuts.increase-zoom" +msgstr "Przybliż" + +msgid "shortcuts.insert-image" +msgstr "Wstaw obraz" + +msgid "shortcuts.join-nodes" +msgstr "Połącz węzły" + +msgid "shortcuts.make-corner" +msgstr "Zrób róg" + +msgid "shortcuts.make-curve" +msgstr "Zrób krzywą" + +msgid "shortcuts.mask" +msgstr "Maska" + +msgid "shortcuts.merge-nodes" +msgstr "Scal węzły" + +msgid "shortcuts.move" +msgstr "Przesuń" + +msgid "shortcuts.move-fast-down" +msgstr "Szybko w dół" + +msgid "shortcuts.move-fast-left" +msgstr "Szybko w lewo" + +msgid "shortcuts.move-fast-right" +msgstr "Szybko w prawo" + +msgid "shortcuts.move-fast-up" +msgstr "Szybko w górę" + +msgid "shortcuts.move-nodes" +msgstr "Przesuń węzeł" + +msgid "shortcuts.move-unit-down" +msgstr "W dół" + +msgid "shortcuts.move-unit-left" +msgstr "W lewo" + +msgid "shortcuts.move-unit-right" +msgstr "W prawo" + +msgid "shortcuts.move-unit-up" +msgstr "W górę" + +msgid "shortcuts.next-frame" +msgstr "Następny obszar kompozycji" + +msgid "shortcuts.opacity-0" +msgstr "Ustaw krycie na 100%" + +msgid "shortcuts.opacity-1" +msgstr "Ustaw krycie na 10%" + +msgid "shortcuts.opacity-2" +msgstr "Ustaw krycie na 20%" + +msgid "shortcuts.opacity-3" +msgstr "Ustaw krycie na 30%" + +msgid "shortcuts.opacity-4" +msgstr "Ustaw krycie na 40%" + +msgid "shortcuts.opacity-5" +msgstr "Ustaw krycie na 50%" + +msgid "shortcuts.opacity-6" +msgstr "Ustaw krycie na 60%" + +msgid "shortcuts.opacity-7" +msgstr "Ustaw krycie na 70%" + +msgid "shortcuts.opacity-8" +msgstr "Ustaw krycie na 80%" + +msgid "shortcuts.opacity-9" +msgstr "Ustaw krycie na 90%" + +msgid "shortcuts.open-color-picker" +msgstr "Próbnik kolorów" + +msgid "shortcuts.open-comments" +msgstr "Przejdź do sekcji komentarzy widzów" + +msgid "shortcuts.open-dashboard" +msgstr "Idź do kokpitu" + +msgid "shortcuts.open-handoff" +msgstr "Przejdź do sekcji przekazania widza" + +msgid "shortcuts.open-interactions" +msgstr "Idź do sekcji interakcji widza" + +msgid "shortcuts.open-viewer" +msgstr "Idż do sekcji interakcji widza" + +msgid "shortcuts.open-workspace" +msgstr "Idż do obszaru roboczego" + +msgid "shortcuts.paste" +msgstr "Wklej" + +msgid "shortcuts.prev-frame" +msgstr "Poprzedni artboard" + +msgid "shortcuts.redo" +msgstr "Ponów" + +msgid "shortcuts.reset-zoom" +msgstr "Resetuj powiększenie" + +msgid "shortcuts.search-placeholder" +msgstr "Szukaj skrótów" + +msgid "shortcuts.select-all" +msgstr "Wybierz wszystko" + +msgid "shortcuts.separate-nodes" +msgstr "Oddziel węzły" + +msgid "shortcuts.show-pixel-grid" +msgstr "Pokaż/ukryj siatkę pikseli" + +msgid "shortcuts.show-shortcuts" +msgstr "Pokaż/ukryj skróty" + +msgid "shortcuts.snap-nodes" +msgstr "Przyciągaj do węzłów" + +msgid "shortcuts.snap-pixel-grid" +msgstr "Przyciągaj do siatki pikseli" + +msgid "shortcuts.start-editing" +msgstr "Rozpocznij edycję" + +msgid "shortcuts.start-measure" +msgstr "Rozpocznij pomiary" + +msgid "shortcuts.stop-measure" +msgstr "Zakończ pomiary" + +msgid "shortcuts.thumbnail-set" +msgstr "Ustaw miniaturki" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "Skróty klawiaturowe" + +msgid "shortcuts.toggle-alignment" +msgstr "Przełącz dynamiczne wyrównanie" + +msgid "shortcuts.toggle-assets" +msgstr "Przełącz zasoby" + +msgid "shortcuts.toggle-colorpalette" +msgstr "Przełącz paletę kolorów" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Przełącz tryb skupienia" + +msgid "shortcuts.toggle-grid" +msgstr "Pokaż/ukryj siatkę" + +msgid "shortcuts.toggle-history" +msgstr "Przełącz historię" + +msgid "shortcuts.toggle-layers" +msgstr "Przełącz warstwy" + +msgid "shortcuts.toggle-lock" +msgstr "Zablokuj wybrane" + +msgid "shortcuts.toggle-lock-size" +msgstr "Zablokuj proporcje" + +msgid "shortcuts.toggle-rules" +msgstr "Pokaż/ukryj linijki" + +msgid "shortcuts.toggle-scale-text" +msgstr "Przełącz skalowanie tekstu" + +msgid "shortcuts.toggle-snap-grid" +msgstr "Przyciągaj do siatki" + +msgid "shortcuts.toggle-snap-guide" +msgstr "Przyciągaj do prowadnic" + +msgid "shortcuts.toggle-textpalette" +msgstr "Przełącz paletę tekstu" + +msgid "shortcuts.toggle-visibility" +msgstr "Przełącz widoczność" + +msgid "shortcuts.toggle-zoom-style" +msgstr "Przełącz sposób powiększania" + +msgid "shortcuts.toogle-fullscreen" +msgstr "Przełącz tryb pełnoekranowy" + +msgid "shortcuts.undo" +msgstr "Cofnij" + +msgid "shortcuts.ungroup" +msgstr "Rozgrupuj" + +msgid "shortcuts.unmask" +msgstr "Usuń maskę" + +msgid "shortcuts.v-distribute" +msgstr "Rozłóż w pionie" + +msgid "shortcuts.zoom-selected" +msgstr "Przybliż wybrane" + #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" msgstr "%s - Penpot" @@ -2329,6 +2770,10 @@ msgstr "Edytuj" msgid "workspace.header.menu.option.file" msgstr "Plik" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "Pomoc i info" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.option.preferences" msgstr "Ustawienia" @@ -2740,10 +3185,6 @@ msgstr "Rzędy" msgid "workspace.options.grid.square" msgstr "Kwadrat" -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.title" -msgstr "Siatka i układy" - #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.group-fill" msgstr "Wypełnienie grupy" @@ -2953,7 +3394,7 @@ msgstr "URL" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-while-hovering" -msgstr "Przy hoverze" +msgstr "Przy najechaniu" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-while-pressing" @@ -3468,7 +3909,7 @@ msgid "workspace.shape.menu.hide" msgstr "Ukryj" msgid "workspace.shape.menu.hide-ui" -msgstr "Pokaż/Ukrzj UI" +msgstr "Pokaż/Ukryj UI" msgid "workspace.shape.menu.intersection" msgstr "Przecięcie" @@ -3620,13 +4061,17 @@ msgstr "Ścieżka (%s)" msgid "workspace.toolbar.rect" msgstr "Prostokąt (%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "Skróty (%s)" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.text" msgstr "Tekst (%s)" #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.text-palette" -msgstr "Typografie *%s)" +msgstr "Typografie (%s)" #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.empty" @@ -3756,4 +4201,4 @@ msgid "workspace.updates.update" msgstr "Aktualizuj" msgid "workspace.viewport.click-to-close-path" -msgstr "Kliknij, aby zamknąć ścieżkę" \ No newline at end of file +msgstr "Kliknij, aby zamknąć ścieżkę" diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index ce1c152f25..350c29fcbf 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-02-22 11:57+0000\n" -"Last-Translator: John Terroa \n" +"PO-Revision-Date: 2022-07-01 01:20+0000\n" +"Last-Translator: Eranot \n" "Language-Team: Portuguese (Brazil) " "\n" "Language: pt_BR\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.11-dev\n" +"X-Generator: Weblate 4.13.1-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -65,23 +65,23 @@ msgstr "Bom te ver de novo!" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "Entrar com o Github" +msgstr "Github" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Entrar com o Gitlab" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "Entrar com o Google" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Entrar com LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "Entrar com OpenID (SSO)" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -158,6 +158,9 @@ msgstr "Criar uma conta" msgid "auth.sidebar-tagline" msgstr "A solução de código aberto para design e prototipagem." +msgid "auth.terms-of-service" +msgstr "Termos de serviço" + #: src/app/main/ui/auth/register.cljs msgid "auth.terms-privacy-agreement" msgstr "" @@ -168,6 +171,47 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "Enviamos um e-mail de verificação para" +msgid "common.share-link.confirm-deletion-link-description" +msgstr "" +"Tem certeza de que deseja remover este link? Se você fizer isso, ele não " +"estará mais disponível para ninguém" + +msgid "common.share-link.get-link" +msgstr "Obter link" + +msgid "common.share-link.link-copied-success" +msgstr "Link copiado com sucesso" + +msgid "common.share-link.link-deleted-success" +msgstr "Link excluído com sucesso" + +msgid "common.share-link.permissions-can-access" +msgstr "Pode acessar" + +msgid "common.share-link.permissions-can-view" +msgstr "Pode visualizar" + +msgid "common.share-link.permissions-hint" +msgstr "Qualquer pessoa com o link terá acesso" + +msgid "common.share-link.placeholder" +msgstr "O link compartilhável aparecerá aqui" + +msgid "common.share-link.remove-link" +msgstr "Remover link" + +msgid "common.share-link.title" +msgstr "Compartilhar protótipos" + +msgid "common.share-link.view-all-pages" +msgstr "Todas as páginas" + +msgid "common.share-link.view-current-page" +msgstr "Somente esta página" + +msgid "common.share-link.view-selected-pages" +msgstr "Páginas selecionadas" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Adicionar como Biblioteca Compartilhada" @@ -207,12 +251,69 @@ msgstr "Duplicar %s arquivos" msgid "dashboard.empty-files" msgstr "Você ainda não tem arquivos aqui" +#: src/app/main/ui/dashboard/grid.cljs +#, markdown +msgid "dashboard.empty-placeholder-drafts" +msgstr "" +"Ah não! Você ainda não tem arquivos! Se você quiser experimentar alguns " +"modelos, vá para [Bibliotecas & " +"modelos](https://penpot.app/libraries-templates.html)" + +msgid "dashboard.export-frames" +msgstr "Exportar pranchetas para PDF…" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-frames.title" +msgstr "Exportar para PDF" + msgid "dashboard.export-multi" msgstr "Exportar %s arquivos" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "%s de %s elementos selecionados" + +#: src/app/main/ui/workspace/header.cljs +msgid "dashboard.export-shapes" +msgstr "Exportar" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Você pode adicionar configurações de exportação em elementos nas " +"propriedades de design (na parte inferior da barra lateral direita)." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "Informações sobre como configurar exportações na Penpot." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "Não há elementos com configurações de exportação." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.title" +msgstr "Exportar seleção" + msgid "dashboard.export-single" msgstr "Exportar arquivo" +msgid "dashboard.export.detail" +msgstr "* Pode incluir componentes, gráficos, cores e/ou tipografias." + +msgid "dashboard.export.explain" +msgstr "" +"Um ou mais arquivos que você deseja exportar estão usando bibliotecas " +"compartilhadas. O que você quer fazer com seus recursos*?" + +msgid "dashboard.export.options.all.message" +msgstr "" +"arquivos com bibliotecas compartilhadas serão incluídos na exportação, " +"mantendo sua vinculação." + +msgid "dashboard.export.options.all.title" +msgstr "Exportar bibliotecas compartilhadas" + msgid "dashboard.fonts.deleted-placeholder" msgstr "Fonte deletada" @@ -490,7 +591,7 @@ msgstr "O formato da imagem não é compatível (deve ser svg, jpg ou png)." #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "A imagem é muito grande para ser inserida (deve ter menos de 5mb)." +msgstr "A imagem é muito grande para ser inserida." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -1627,10 +1728,6 @@ msgstr "Linhas" msgid "workspace.options.grid.square" msgstr "Quadrado" -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.title" -msgstr "Grades & Layouts" - #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.blend-mode.color" msgstr "Cor" @@ -1996,4 +2093,4 @@ msgid "workspace.updates.update" msgstr "Atualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Clique para fechar o caminho" \ No newline at end of file +msgstr "Clique para fechar o caminho" diff --git a/frontend/translations/ro.po b/frontend/translations/ro.po index f0447a8d45..ddae146e7b 100644 --- a/frontend/translations/ro.po +++ b/frontend/translations/ro.po @@ -66,23 +66,23 @@ msgstr "Mă bucur să te văd din nou!" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "Conectează-te cu Github" +msgstr "Github" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Conectează-te cu Gitlab" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "Conectează-te cu Google" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Conectează-te cu LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "Conectează-te cu OpenID (SSO)" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -469,7 +469,7 @@ msgstr "Formatul imaginii nu este acceptat (poate fi svg, jpg sau png)." #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "Imaginea este prea mare pentru a fi inserată (trebuie să fie sub 5mb)." +msgstr "Imaginea este prea mare pentru a fi inserată." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -1919,10 +1919,6 @@ msgstr "Rânduri" msgid "workspace.options.grid.square" msgstr "Pătrat" -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.title" -msgstr "Grilă & Layout" - #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.group-fill" msgstr "Group fill" @@ -2561,4 +2557,4 @@ msgid "workspace.updates.update" msgstr "Actualizează" msgid "workspace.viewport.click-to-close-path" -msgstr "Click pentru a închide calea" \ No newline at end of file +msgstr "Click pentru a închide calea" diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index 605a3c69a1..a94c4c0076 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -63,23 +63,23 @@ msgstr "Рады видеть Вас снова!" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "Вход через Gitnub" +msgstr "Gitnub" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Вход через Gitlab" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "Войти с Google" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Вход через LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "Войти с OpenID (SSO)" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -186,35 +186,16 @@ msgstr "Ссылка скопирована" msgid "common.share-link.link-deleted-success" msgstr "Ссылка удалена" -#, fuzzy -msgid "common.share-link.permissions-can-access" -msgstr "Могут зайти" - -msgid "common.share-link.permissions-can-view" -msgstr "Могут видеть" - msgid "common.share-link.permissions-hint" msgstr "Доступ открыт для получателей ссылки" msgid "common.share-link.placeholder" msgstr "Ссылка появится здесь" -msgid "common.share-link.remove-link" -msgstr "Удалить ссылку" - #, fuzzy msgid "common.share-link.title" msgstr "Поделиться прототипами" -msgid "common.share-link.view-all-pages" -msgstr "Все страницы" - -msgid "common.share-link.view-current-page" -msgstr "Только эту страницу" - -msgid "common.share-link.view-selected-pages" -msgstr "Выбранные страницы" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Добавить как общую библиотеку" @@ -651,7 +632,7 @@ msgstr "Формат изображения не поддерживается ( #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "Изображение слишком большое для вставки (должно быть меньше 5mb)." +msgstr "Изображение слишком большое для вставки." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -2034,10 +2015,6 @@ msgstr "Строки" 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 "Заливка для группы" @@ -2357,4 +2334,4 @@ msgid "workspace.updates.update" msgstr "" msgid "workspace.viewport.click-to-close-path" -msgstr "Нажмите для замыкания контура" \ No newline at end of file +msgstr "Нажмите для замыкания контура" diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index e777f23dd8..45dec99631 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-04-04 11:11+0000\n" +"PO-Revision-Date: 2022-06-05 13:15+0000\n" "Last-Translator: Oğuz Ersen \n" "Language-Team: Turkish " "\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.12-dev\n" +"X-Generator: Weblate 4.13-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -78,11 +78,11 @@ msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "LDAP ile oturum aç" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "OpenID ile Bağlan" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -186,33 +186,15 @@ msgstr "Bağlantı başarıyla kopyalandı" msgid "common.share-link.link-deleted-success" msgstr "Bağlantı başarıyla silindi" -msgid "common.share-link.permissions-can-access" -msgstr "Erişebilir" - -msgid "common.share-link.permissions-can-view" -msgstr "Görüntüleyebilir" - msgid "common.share-link.permissions-hint" msgstr "Bağlantıya sahip olan herkes erişebilir" msgid "common.share-link.placeholder" msgstr "Paylaşılabilir bağlantı burada görünecek" -msgid "common.share-link.remove-link" -msgstr "Bağlantıyı kaldır" - msgid "common.share-link.title" msgstr "Prototipleri paylaş" -msgid "common.share-link.view-all-pages" -msgstr "Tüm sayfalar" - -msgid "common.share-link.view-current-page" -msgstr "Yalnızca bu sayfa" - -msgid "common.share-link.view-selected-pages" -msgstr "Seçili sayfalar" - #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" @@ -262,7 +244,7 @@ msgstr "" "sayfasına gidin" msgid "dashboard.export-frames" -msgstr "Çalışma yüzeylerini PDF olarak dışarı aktar..." +msgstr "Çalışma yüzeylerini PDF'ye aktar…" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" @@ -694,6 +676,13 @@ msgstr "Google ile oturum açma devre dışı bırakıldı" msgid "errors.invalid-color" msgstr "Geçersiz renk" +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "Geçersiz davet" + +msgid "errors.invite-invalid.info" +msgstr "Bu davet iptal edilmiş veya süresi dolmuş olabilir." + #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" msgstr "LDAP ile oturum açma devre dışı bırakıldı." @@ -703,7 +692,7 @@ msgstr "Görsel biçimi 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ı)." +msgstr "Bu görsel eklemek için çok büyük." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -1021,6 +1010,10 @@ msgstr "Bilgi" msgid "history.alert-message" msgstr "%s sürümünü görüyorsun" +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Kısayollar" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.about-penpot" msgstr "Penpot Hakkında" @@ -1042,6 +1035,9 @@ msgstr "Hepsi" msgid "labels.and" msgstr "ve" +msgid "labels.back" +msgstr "Geri" + #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.desc-message" msgstr "" @@ -1175,6 +1171,10 @@ msgstr "Biçimler" msgid "labels.fonts" msgstr "Yazı tipleri" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Github deposu" + #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/settings/sidebar.cljs, #: src/app/main/ui/dashboard/sidebar.cljs @@ -1426,6 +1426,10 @@ msgstr "Başla" msgid "labels.status" msgstr "Durum" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Öğreticiler" + #: src/app/main/ui/settings/profile.cljs msgid "labels.update" msgstr "Güncelle" @@ -1803,6 +1807,29 @@ msgstr "" "Biriyle mi çalışıyorsunuz? Bir takım oluşturun ve insanları projeler " "üzerinde birlikte çalışmaya ve tasarım varlıklarını paylaşmaya davet edin." +msgid "onboarding.choice.team-up.create-team" +msgstr "Takımınızın adı" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "Takımınızı adlandırdıktan sonra, insanları katılmaya davet edebileceksiniz." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Takımın adını girin" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Üyeleri davet edin" + +msgid "onboarding.choice.team-up.invite-members-desc" +msgstr "" +"Ayrıca daha sonra takım bölümünden üyeleri davet edebilecek ve rolleri " +"değiştirebileceksiniz." + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Takım oluştur ve daha sonra davet et" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Takım oluştur ve davet gönder" + msgid "onboarding.choice.title" msgstr "Penpot'a Hoş Geldiniz" @@ -1980,6 +2007,420 @@ msgstr "Oturum açmaya git" msgid "settings.multiple" msgstr "Karışık" +# SECTIONS +msgid "shortcut-section.basics" +msgstr "Temel bilgiler" + +msgid "shortcut-section.dashboard" +msgstr "Denetim paneli" + +msgid "shortcut-section.viewer" +msgstr "Görüntüleyici" + +msgid "shortcut-section.workspace" +msgstr "Çalışma alanı" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "Hizalama" + +msgid "shortcut-subsection.edit" +msgstr "Düzenle" + +msgid "shortcut-subsection.general-dashboard" +msgstr "Genel" + +msgid "shortcut-subsection.general-viewer" +msgstr "Genel" + +msgid "shortcut-subsection.main-menu" +msgstr "Ana menü" + +msgid "shortcut-subsection.modify-layers" +msgstr "Katmanları değiştir" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Gezinme" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Gezinme" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Gezinme" + +msgid "shortcut-subsection.panels" +msgstr "Paneller" + +msgid "shortcut-subsection.path-editor" +msgstr "Yollar" + +msgid "shortcut-subsection.shape" +msgstr "Şekiller" + +msgid "shortcut-subsection.tools" +msgstr "Araçlar" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "Yakınlaştır" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "Yakınlaştır" + +msgid "shortcuts.add-comment" +msgstr "Yorumlar" + +msgid "shortcuts.add-node" +msgstr "Düğüm ekle" + +msgid "shortcuts.align-bottom" +msgstr "Alta hizala" + +msgid "shortcuts.align-hcenter" +msgstr "Ortayı yatay olarak hizala" + +msgid "shortcuts.align-left" +msgstr "Sola hizala" + +msgid "shortcuts.align-right" +msgstr "Sağa hizala" + +msgid "shortcuts.align-top" +msgstr "Üste hizala" + +msgid "shortcuts.align-vcenter" +msgstr "Ortayı dikey olarak hizala" + +msgid "shortcuts.artboard-selection" +msgstr "Seçimden çalışma yüzeyi oluştur" + +msgid "shortcuts.bool-difference" +msgstr "Boole fark" + +msgid "shortcuts.bool-exclude" +msgstr "Boole hariç tut" + +msgid "shortcuts.bool-intersection" +msgstr "Boole kesişim" + +msgid "shortcuts.bool-union" +msgstr "Boole birleşim" + +msgid "shortcuts.bring-back" +msgstr "En arkaya gönder" + +msgid "shortcuts.bring-backward" +msgstr "Arkaya gönder" + +msgid "shortcuts.bring-forward" +msgstr "Öne getir" + +msgid "shortcuts.bring-front" +msgstr "En öne getir" + +msgid "shortcuts.clear-undo" +msgstr "Geri almayı temizle" + +msgid "shortcuts.copy" +msgstr "Kopyala" + +msgid "shortcuts.create-component" +msgstr "Bileşen oluştur" + +msgid "shortcuts.create-new-project" +msgstr "Yeni oluştur" + +msgid "shortcuts.cut" +msgstr "Kes" + +msgid "shortcuts.decrease-zoom" +msgstr "Uzaklaştır" + +msgid "shortcuts.delete" +msgstr "Sil" + +msgid "shortcuts.delete-node" +msgstr "Düğümü sil" + +msgid "shortcuts.detach-component" +msgstr "Bileşeni ayır" + +msgid "shortcuts.draw-curve" +msgstr "Eğri" + +msgid "shortcuts.draw-ellipse" +msgstr "Elips" + +msgid "shortcuts.draw-frame" +msgstr "Çalışma yüzeyi" + +msgid "shortcuts.draw-nodes" +msgstr "Yol çiz" + +msgid "shortcuts.draw-path" +msgstr "Yol" + +msgid "shortcuts.draw-rect" +msgstr "Dikdörtgen" + +msgid "shortcuts.draw-text" +msgstr "Metin" + +msgid "shortcuts.duplicate" +msgstr "Çoğalt" + +msgid "shortcuts.escape" +msgstr "İptal" + +msgid "shortcuts.export-shapes" +msgstr "Şekilleri dışa aktar" + +msgid "shortcuts.fit-all" +msgstr "Tümüne uydurmak için yakınlaştır" + +msgid "shortcuts.flip-horizontal" +msgstr "Yatay olarak çevir" + +msgid "shortcuts.flip-vertical" +msgstr "Dikey olarak çevir" + +msgid "shortcuts.go-to-drafts" +msgstr "Taslaklara git" + +msgid "shortcuts.go-to-libs" +msgstr "Paylaşılan kütüphanelere git" + +msgid "shortcuts.go-to-search" +msgstr "Ara" + +msgid "shortcuts.group" +msgstr "Grup" + +msgid "shortcuts.h-distribute" +msgstr "Yatay olarak dağıt" + +msgid "shortcuts.hide-ui" +msgstr "Kullanıcı arayüzünü göster/gizle" + +msgid "shortcuts.increase-zoom" +msgstr "Yakınlaştır" + +msgid "shortcuts.insert-image" +msgstr "Görsel ekle" + +msgid "shortcuts.join-nodes" +msgstr "Düğümlere katıl" + +msgid "shortcuts.make-corner" +msgstr "Köşe yap" + +msgid "shortcuts.make-curve" +msgstr "Eğri yap" + +msgid "shortcuts.mask" +msgstr "Maskele" + +msgid "shortcuts.merge-nodes" +msgstr "Düğümleri birleştir" + +msgid "shortcuts.move" +msgstr "Taşı" + +msgid "shortcuts.move-fast-down" +msgstr "Hızlı aşağı taşı" + +msgid "shortcuts.move-fast-left" +msgstr "Hızlı sola taşı" + +msgid "shortcuts.move-fast-right" +msgstr "Hızlı sağa taşı" + +msgid "shortcuts.move-fast-up" +msgstr "Hızlı yukarı taşı" + +msgid "shortcuts.move-nodes" +msgstr "Düğümü taşı" + +msgid "shortcuts.move-unit-down" +msgstr "Aşağı taşı" + +msgid "shortcuts.move-unit-left" +msgstr "Sola taşı" + +msgid "shortcuts.move-unit-right" +msgstr "Sağa taşı" + +msgid "shortcuts.move-unit-up" +msgstr "Yukarı taşı" + +msgid "shortcuts.next-frame" +msgstr "Sonraki çalışma yüzeyi" + +msgid "shortcuts.opacity-0" +msgstr "Opaklığı %100 olarak ayarla" + +msgid "shortcuts.opacity-1" +msgstr "Opaklığı %10 olarak ayarla" + +msgid "shortcuts.opacity-2" +msgstr "Opaklığı %20 olarak ayarla" + +msgid "shortcuts.opacity-3" +msgstr "Opaklığı %30 olarak ayarla" + +msgid "shortcuts.opacity-4" +msgstr "Opaklığı %40 olarak ayarla" + +msgid "shortcuts.opacity-5" +msgstr "Opaklığı %50 olarak ayarla" + +msgid "shortcuts.opacity-6" +msgstr "Opaklığı %60 olarak ayarla" + +msgid "shortcuts.opacity-7" +msgstr "Opaklığı %70 olarak ayarla" + +msgid "shortcuts.opacity-8" +msgstr "Opaklığı %80 olarak ayarla" + +msgid "shortcuts.opacity-9" +msgstr "Opaklığı %90 olarak ayarla" + +msgid "shortcuts.open-color-picker" +msgstr "Renk seçici" + +msgid "shortcuts.open-comments" +msgstr "Görüntüleyici yorum bölümüne git" + +msgid "shortcuts.open-dashboard" +msgstr "Denetim paneline git" + +msgid "shortcuts.open-handoff" +msgstr "Görüntüleyici teslim bölümüne git" + +msgid "shortcuts.open-interactions" +msgstr "Görüntüleyici etkileşimleri bölümüne git" + +msgid "shortcuts.open-viewer" +msgstr "Görüntüleyici etkileşimleri bölümüne git" + +msgid "shortcuts.open-workspace" +msgstr "Çalışma alanına git" + +msgid "shortcuts.paste" +msgstr "Yapıştır" + +msgid "shortcuts.prev-frame" +msgstr "Önceki çalışma yüzeyi" + +msgid "shortcuts.redo" +msgstr "Yeniden yap" + +msgid "shortcuts.reset-zoom" +msgstr "Yakınlaştırmayı sıfırla" + +msgid "shortcuts.search-placeholder" +msgstr "Kısayolları ara" + +msgid "shortcuts.select-all" +msgstr "Tümünü seç" + +msgid "shortcuts.separate-nodes" +msgstr "Düğümleri ayır" + +msgid "shortcuts.show-pixel-grid" +msgstr "Piksel ızgarasını göster/gizle" + +msgid "shortcuts.show-shortcuts" +msgstr "Kısayolları göster/gizle" + +msgid "shortcuts.snap-nodes" +msgstr "Düğümlere tuttur" + +msgid "shortcuts.snap-pixel-grid" +msgstr "Piksel ızgarasına tuttur" + +msgid "shortcuts.start-editing" +msgstr "Düzenlemeye başla" + +msgid "shortcuts.start-measure" +msgstr "Ölçüme başla" + +msgid "shortcuts.stop-measure" +msgstr "Ölçümü durdur" + +msgid "shortcuts.thumbnail-set" +msgstr "Küçük resimleri ayarla" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "Klavye kısayolları" + +msgid "shortcuts.toggle-alignment" +msgstr "Dinamik hizalamayı aç/kapat" + +msgid "shortcuts.toggle-assets" +msgstr "Varlıkları değiştir" + +msgid "shortcuts.toggle-colorpalette" +msgstr "Renk paletini değiştir" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Odak modunu değiştir" + +msgid "shortcuts.toggle-grid" +msgstr "Izgarayı göster/gizle" + +msgid "shortcuts.toggle-history" +msgstr "Geçmişi değiştir" + +msgid "shortcuts.toggle-layers" +msgstr "Katmanları değiştir" + +msgid "shortcuts.toggle-lock" +msgstr "Seçileni kilitle" + +msgid "shortcuts.toggle-lock-size" +msgstr "Oranları kilitle" + +msgid "shortcuts.toggle-rules" +msgstr "Kuralları göster/gizle" + +msgid "shortcuts.toggle-scale-text" +msgstr "Ölçek metnini değiştir" + +msgid "shortcuts.toggle-snap-grid" +msgstr "Izgaraya tuttur" + +msgid "shortcuts.toggle-snap-guide" +msgstr "Kılavuzlara tuttur" + +msgid "shortcuts.toggle-textpalette" +msgstr "Metin paletini değiştir" + +msgid "shortcuts.toggle-visibility" +msgstr "Görünürlüğü değiştir" + +msgid "shortcuts.toggle-zoom-style" +msgstr "Yakınlaştırma şeklini değiştir" + +msgid "shortcuts.toogle-fullscreen" +msgstr "Tam ekranı değiştir" + +msgid "shortcuts.undo" +msgstr "Geri al" + +msgid "shortcuts.ungroup" +msgstr "Grubu dağıt" + +msgid "shortcuts.unmask" +msgstr "Maskelemeyi kaldır" + +msgid "shortcuts.v-distribute" +msgstr "Dikey olarak dağıt" + +msgid "shortcuts.zoom-selected" +msgstr "Seçilene yakınlaştır" + #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" msgstr "%s - Penpot" @@ -2388,6 +2829,10 @@ msgstr "Düzenle" msgid "workspace.header.menu.option.file" msgstr "Dosya" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "Yardım ve bilgi" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.option.preferences" msgstr "Tercihler" @@ -2672,15 +3117,6 @@ msgstr "Tasarım" msgid "workspace.options.export" msgstr "Dışa aktar" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -msgid "workspace.options.export-multiple" -msgstr "Seçimi dışa aktar" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -#, fuzzy -msgid "workspace.options.export-object" -msgstr "Dışa aktar" - #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-multiple" @@ -2688,7 +3124,6 @@ msgstr "Seçimi dışa aktar" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs -#, fuzzy msgid "workspace.options.export-object" msgstr "1 ögeyi dışa aktar" @@ -2813,10 +3248,6 @@ msgstr "Satırlar" msgid "workspace.options.grid.square" msgstr "Kare" -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.title" -msgstr "Izgara ve Yerleşim Düzenleri" - #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.group-fill" msgstr "Grubu doldur" @@ -3718,6 +4149,10 @@ msgstr "Yol (%s)" msgid "workspace.toolbar.rect" msgstr "Dikdörtgen (%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "Kısayollar (%s)" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.text" msgstr "Metin (%s)" diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index 219906d33c..8bde2b82e9 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-04-01 07:10+0000\n" -"Last-Translator: bingling_sama \n" +"PO-Revision-Date: 2022-06-19 04:19+0000\n" +"Last-Translator: Wang Jiaxiang \n" "Language-Team: Chinese (Simplified) " "\n" "Language: zh_CN\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Weblate 4.12-dev\n" +"X-Generator: Weblate 4.13.1-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -61,23 +61,23 @@ msgstr "很高兴又见到你!" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "使用GitHub登录" +msgstr "GitHub" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "使用Gitlab登录" +msgstr "Gitlab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "使用Google登录" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "使用LDAP登录" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "使用OpenID登录" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -177,35 +177,21 @@ msgstr "链接已复制" msgid "common.share-link.link-deleted-success" msgstr "链接已移除" -#, fuzzy msgid "common.share-link.permissions-can-access" msgstr "可访问" msgid "common.share-link.permissions-can-view" msgstr "可浏览" -#, fuzzy msgid "common.share-link.permissions-hint" msgstr "任何人通过此链接都可访问" msgid "common.share-link.placeholder" msgstr "可分享的链接会在此处显示" -msgid "common.share-link.remove-link" -msgstr "移除链接" - msgid "common.share-link.title" msgstr "分享原型" -msgid "common.share-link.view-all-pages" -msgstr "全部页面" - -msgid "common.share-link.view-current-page" -msgstr "仅此页面" - -msgid "common.share-link.view-selected-pages" -msgstr "选中的页面" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "添加为共享库" @@ -251,7 +237,7 @@ msgid "dashboard.empty-placeholder-drafts" msgstr "现在尚无文件。如果你想尝试一些模板,请点击[库和模板](https://penpot.app/libraries-templates.html)" msgid "dashboard.export-frames" -msgstr "导出画板到PDF..." +msgstr "导出画板到PDF…" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" @@ -260,6 +246,14 @@ msgstr "导出为PDF" msgid "dashboard.export-multi" msgstr "导出 %s 个文件" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "已选择 %s / %s 元素" + +#: src/app/main/ui/workspace/header.cljs +msgid "dashboard.export-shapes" +msgstr "导出" + #: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.how-to" msgstr "你可以在设计选项中为元素添加导出设置(位于右侧边栏底部)" @@ -345,7 +339,7 @@ msgid "dashboard.import.analyze-error" msgstr "文件无法导入" msgid "dashboard.import.import-error" -msgstr "文件导入过程中出现未知问题,导入失败" +msgstr "文件导入过程中出现未知问题,导入失败。" msgid "dashboard.import.import-message" msgstr "文件导入成功" @@ -421,6 +415,14 @@ msgstr "+ 新项目" msgid "dashboard.new-project-prefix" msgstr "新建项目" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "向我发送有关 Penpot 的新闻、更新和推广。" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "订阅通知" + #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.no-matches-for" msgstr "没有找到“%s”的匹配项" @@ -476,6 +478,10 @@ msgstr "希望注销您的账号?" msgid "dashboard.remove-shared" msgstr "不再作为共享库" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "保存设置" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.search-placeholder" msgstr "搜索…" @@ -633,6 +639,13 @@ msgstr "后端禁用了Google授权" msgid "errors.invalid-color" msgstr "无效的颜色" +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "邀请无效" + +msgid "errors.invite-invalid.info" +msgstr "此邀请可能已取消或已过期。" + #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" msgstr "仅用了LDAP授权。" @@ -642,7 +655,7 @@ msgstr "不支持该图片格式(只能是svg、jpg或png)。" #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "图片尺寸过大,故无法插入(不能超过5MB)。" +msgstr "图片尺寸过大,故无法插入。" #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" @@ -948,6 +961,10 @@ msgstr "信息" msgid "history.alert-message" msgstr "你正在查看%s版本" +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "快捷方式" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.about-penpot" msgstr "关于 Penpot" @@ -966,6 +983,12 @@ msgstr "管理员" msgid "labels.all" msgstr "全部" +msgid "labels.and" +msgstr "和" + +msgid "labels.back" +msgstr "后退" + #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.desc-message" msgstr "请过会儿再来试试,我们正在对服务器进行一些简单维护。" @@ -1089,6 +1112,10 @@ msgstr "样式" msgid "labels.fonts" msgstr "字体" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Github仓库" + #: 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 "提交反馈" @@ -1328,6 +1355,10 @@ msgstr "开始" msgid "labels.status" msgstr "状态" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "教程" + #: src/app/main/ui/settings/profile.cljs msgid "labels.update" msgstr "更新" @@ -1380,6 +1411,10 @@ msgstr "一旦添加为共享库,此文档库中的素材就可被用于你的 msgid "modals.add-shared-confirm.message" msgstr "将“%s”添加为共享库" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.big-nudge" +msgstr "小幅微调" + #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.confirm-email" msgstr "验证新的邮件" @@ -1566,6 +1601,10 @@ msgstr "你确定要离开本团队吗?" msgid "modals.leave-confirm.title" msgstr "正在退出团队" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.nudge-title" +msgstr "微调量" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.accept" msgstr "转让所有权" @@ -1594,6 +1633,10 @@ msgstr "一旦不再作为共享库,该文档库就不能继续用于你的其 msgid "modals.remove-shared-confirm.message" msgstr "不再将“%s”作为共享库" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.small-nudge" +msgstr "小幅微调" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" msgstr "你即将更新共享库中的组件,这可能会影响使用这些组件的其他文档" @@ -1649,6 +1692,27 @@ msgstr "组建团队" msgid "onboarding.choice.team-up-desc" msgstr "你在和别人合作吗?创建一个团队,邀请成员参与合作,共享设计素材。" +msgid "onboarding.choice.team-up.create-team" +msgstr "团队名称" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "命名团队后,您将能够邀请他人加入。" + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "输入团队名称" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "邀请成员" + +msgid "onboarding.choice.team-up.invite-members-desc" +msgstr "您还可以稍后在团队页面邀请成员和更改角色。" + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "创建团队并稍后邀请" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "创建团队并发送邀请" + msgid "onboarding.choice.title" msgstr "欢迎来到Penpot" @@ -1664,6 +1728,36 @@ msgstr "您可以访问" msgid "onboarding.contrib.desc2.2" msgstr "并遵循贡献说明:)" +msgid "onboarding.contrib.link" +msgstr "Github的项目" + +msgid "onboarding.contrib.title" +msgstr "开源贡献者?" + +msgid "onboarding.newsletter.accept" +msgstr "是的,订阅" + +msgid "onboarding.newsletter.acceptance-message" +msgstr "您的订阅请求已发送,我们将向您发送一封电子邮件进行确认。" + +msgid "onboarding.newsletter.decline" +msgstr "不,谢谢" + +msgid "onboarding.newsletter.desc" +msgstr "订阅我们的时事通讯,随时了解产品开发进度和新闻。" + +msgid "onboarding.newsletter.policy" +msgstr "隐私策略。" + +msgid "onboarding.newsletter.privacy1" +msgstr "我们关注个人隐私,这里是我们的 " + +msgid "onboarding.newsletter.privacy2" +msgstr "我们只会向您发送相关电子邮件。您可以随时在您的用户个人资料中取消订阅,也可以通过我们任何新闻通讯中的取消订阅链接取消订阅。" + +msgid "onboarding.newsletter.title" +msgstr "想要接收 Penpot 新闻?" + msgid "onboarding.slide.0.alt" msgstr "创作设计" @@ -1691,6 +1785,9 @@ msgstr "通过交互使您的设计栩栩如生" msgid "onboarding.slide.2.alt" msgstr "获取反馈" +msgid "onboarding.slide.2.desc1" +msgstr "所有团队成员使用实时设计多人协同以及设计上的集中评论,想法和反馈。" + msgid "onboarding.slide.2.title" msgstr "获取反馈、展示和分享您的工作" @@ -1718,7 +1815,6 @@ msgstr "Penpot" msgid "onboarding.welcome.desc1" msgstr "Hooray!你已经是Penpot的用户了 :)" -#, fuzzy msgid "onboarding.welcome.desc2" msgstr "Penpot处于第一个测试版,这要归功于核心功能,成熟度,稳定性和整个社区的惊人验证的组合,我们非常欢迎您。" @@ -1736,6 +1832,98 @@ msgstr "去登录" msgid "settings.multiple" msgstr "混合" +# SECTIONS +msgid "shortcut-section.basics" +msgstr "基础" + +msgid "shortcut-section.dashboard" +msgstr "仪表盘" + +msgid "shortcut-section.viewer" +msgstr "观察者" + +msgid "shortcut-section.workspace" +msgstr "工作区" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "对准" + +msgid "shortcut-subsection.edit" +msgstr "编辑" + +msgid "shortcut-subsection.general-dashboard" +msgstr "通用" + +msgid "shortcut-subsection.general-viewer" +msgstr "通用" + +msgid "shortcut-subsection.main-menu" +msgstr "主菜单" + +msgid "shortcut-subsection.modify-layers" +msgstr "修改图层" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "导航" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "导航" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "导航" + +msgid "shortcut-subsection.panels" +msgstr "面板" + +msgid "shortcut-subsection.path-editor" +msgstr "路径" + +msgid "shortcuts.hide-ui" +msgstr "显示/隐藏UI" + +msgid "shortcuts.increase-zoom" +msgstr "放大" + +msgid "shortcuts.insert-image" +msgstr "插入图片" + +msgid "shortcuts.join-nodes" +msgstr "链接节点" + +msgid "shortcuts.make-corner" +msgstr "制作圆角" + +msgid "shortcuts.make-curve" +msgstr "制作曲线" + +msgid "shortcuts.mask" +msgstr "遮罩" + +msgid "shortcuts.merge-nodes" +msgstr "合并节点" + +msgid "shortcuts.move" +msgstr "移动" + +msgid "shortcuts.move-fast-down" +msgstr "快速下移" + +msgid "shortcuts.move-fast-left" +msgstr "快速向左移动" + +msgid "shortcuts.move-fast-right" +msgstr "快速向右移动" + +msgid "shortcuts.move-fast-up" +msgstr "快速上移" + +msgid "shortcuts.move-nodes" +msgstr "移动节点" + +msgid "shortcuts.move-unit-down" +msgstr "向下移动" + #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" msgstr "%s - Penpot" @@ -2392,7 +2580,7 @@ msgstr "导出已选择" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgstr "导出形状" +msgstr "导出 %s 个元素" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -2497,10 +2685,6 @@ msgstr "行" 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 "编组填充" @@ -3053,7 +3237,6 @@ msgstr "删除节点(%s)" msgid "workspace.path.actions.draw-nodes" msgstr "绘制节点(%s)" -#, fuzzy msgid "workspace.path.actions.join-nodes" msgstr "连接节点(%s)" @@ -3234,6 +3417,10 @@ msgstr "矩形(%s)" msgid "workspace.toolbar.text" msgstr "文本(%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text-palette" +msgstr "排字式样 (%s)" + #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.empty" msgstr "目前没有历史修改" @@ -3362,4 +3549,4 @@ msgid "workspace.updates.update" msgstr "更新" msgid "workspace.viewport.click-to-close-path" -msgstr "单击以闭合路径" \ No newline at end of file +msgstr "单击以闭合路径" diff --git a/frontend/translations/zh_Hant.po b/frontend/translations/zh_Hant.po index eade57f69f..57311fc5a9 100644 --- a/frontend/translations/zh_Hant.po +++ b/frontend/translations/zh_Hant.po @@ -61,23 +61,23 @@ msgstr "很高興再次見到你!" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "透過 GitHub 登入" +msgstr "GitHub" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "透過 GitLab 登入" +msgstr "GitLab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "透過 Google 登入" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "透過 LDAP 登入" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "使用 OpenID (SSO) 登入" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -181,33 +181,15 @@ msgstr "成功複製連結" msgid "common.share-link.link-deleted-success" msgstr "成功刪除連結" -msgid "common.share-link.permissions-can-access" -msgstr "能夠存取" - -msgid "common.share-link.permissions-can-view" -msgstr "能夠檢視" - msgid "common.share-link.permissions-hint" msgstr "任何有連結的人皆能存取" msgid "common.share-link.placeholder" msgstr "可分享的連結將會在此顯示" -msgid "common.share-link.remove-link" -msgstr "移除連結" - msgid "common.share-link.title" msgstr "分享原型" -msgid "common.share-link.view-all-pages" -msgstr "所有頁面" - -msgid "common.share-link.view-current-page" -msgstr "僅此頁面" - -msgid "common.share-link.view-selected-pages" -msgstr "選擇的頁面" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs #, fuzzy msgid "dashboard.add-shared" @@ -559,7 +541,7 @@ msgstr "不支援此影像格式(必須是 svg,jpg 或者 png)" #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "影像檔案過大,無法插入(須小於 5MB)" +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" @@ -1108,4 +1090,4 @@ msgstr "歷史" #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.update" -msgstr "更新" \ No newline at end of file +msgstr "更新" diff --git a/frontend/yarn.lock b/frontend/yarn.lock index fee26840af..767f5e1068 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4922,10 +4922,10 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass@^1.49.9: - version "1.49.9" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.9.tgz#b15a189ecb0ca9e24634bae5d1ebc191809712f9" - integrity sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A== +sass@^1.53.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.0.tgz#24873673265e2a4fe3d3a997f714971db2fba1f4" + integrity sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -5013,10 +5013,10 @@ shadow-cljs-jar@1.3.2: resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b" integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg== -shadow-cljs@2.17.8: - version "2.17.8" - resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.17.8.tgz#7ee27ccf7585991f6c042f66f07f17582c0b70af" - integrity sha512-O39cLA7ukEh+OeH1yZlaWjGFinPOsDD87TetAWPe1QBD9TZQ0Ail+2ovaXeAyZpJ+6Z37joFfival+LNuCgsmQ== +shadow-cljs@2.19.8: + version "2.19.8" + resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.19.8.tgz#1ce96cab3e4903bed8d401ffbe88b8939f5454d3" + integrity sha512-6qek3mcAP0hrnC5FxrTebBrgLGpOuhlnp06vdxp6g0M5Gl6w2Y0hzSwa1s2K8fMOkzE4/ciQor75b2y64INgaw== dependencies: node-libs-browser "^2.2.1" readline-sync "^1.4.7" diff --git a/manage.sh b/manage.sh index c86ef23a7c..293a074b24 100755 --- a/manage.sh +++ b/manage.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -e +set -ex export ORGANIZATION="penpotapp"; export DEVENV_IMGNAME="$ORGANIZATION/devenv"; diff --git a/version.txt b/version.txt index a7525c96c4..f1eb667f4f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.14.2-beta +1.15.0-beta