diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9265e4c38f..55acb5a3cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,14 @@ # Contributing Guide # -Thank you for your interest in contributing to UXBox. This is a -generic guide that details how to contribute to UXBox in a way that is +Thank you for your interest in contributing to Penpot. This is a +generic guide that details how to contribute to Penpot in a way that is efficient for everyone. If you want a specific documentation for different parts of the platform, please refer to `docs/` directory. ## Reporting Bugs ## -We are using [GitHub Issues](https://github.com/uxbox/uxbox/issues) +We are using [GitHub Issues](https://github.com/penpot/penpot/issues) for our public bugs. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn't already exist. diff --git a/README.md b/README.md index 9a050d668b..dff4de3838 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ [![Managed with Taiga.io](https://img.shields.io/badge/managed%20with-TAIGA.io-709f14.svg)](https://tree.taiga.io/project/uxbox/ "Managed with Taiga.io") -# UXBOX # +# PENPOT # -![UXBOX](https://piweek.com/images/projects/uxbox.jpg) +![PENPOT](https://raw.githubusercontent.com/penpot/penpot/develop/docs/screenshot.png) ## Introduction ## -The open-source solution for design and prototyping. UXBOX is +The open-source solution for design and prototyping. PENPOT is currently at an early development stage but we are working hard to bring you the beta version as soon as possible. Follow the project progress in Twitter or Github and stay tuned! @@ -23,8 +23,8 @@ progress in Twitter or Github and stay tuned! ## SVG based ## -UXBOX works with SVG, a standard format, for all your designs and -prototypes . This means that all your stuff in UXBOX is portable and +Penpot works with SVG, a standard format, for all your designs and +prototypes . This means that all your stuff in Penpot is portable and editable in many other vector tools and easy to use on the web. @@ -34,7 +34,7 @@ editable in many other vector tools and easy to use on the web. We love the open source software community. Contributing is our passion and because of this, we'll be glad if you want to participate -and improve UXBOX. All your awesome ideas and code are welcome! +and improve Penpot. All your awesome ideas and code are welcome! Please refer to the [Contributing Guide](./CONTRIBUTING.md) diff --git a/backend/resources/emails-mjml/invite-to-team/en.mjml b/backend/resources/emails-mjml/invite-to-team/en.mjml new file mode 100644 index 0000000000..dbef7afe5f --- /dev/null +++ b/backend/resources/emails-mjml/invite-to-team/en.mjml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + Hello! + + {{invited-by}} has invited you to join the team “{{ team }}”. + + + Accept invite + + Enjoy! + The UXBOX team. + + + + + + + UXBOX is the first Open Source prototyping platform that will be embraced by multidisciplinary teams. + + + + + + + + + + + + + + + + + + + + UXBOX © 2020 | Made with <3 and Open Source + + + + + + diff --git a/backend/resources/emails/invite-to-team/en.html b/backend/resources/emails/invite-to-team/en.html new file mode 100644 index 0000000000..3a77a7a6ec --- /dev/null +++ b/backend/resources/emails/invite-to-team/en.html @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + +
+ + + + + + +
+ +
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + +
+
Hello!
+
+
{{invited-by}} has invited you to join the team “{{ team }}”.
+
+ + + + +
+ Accept invite +
+
+
Enjoy!
+
+
The UXBOX team.
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + +
+
UXBOX is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + +
+ + + + + +
+ + + + +
+ + + +
+
+ + + + + +
+ + + + +
+ + + +
+
+ + + + + +
+ + + + +
+ + + +
+
+ + + + + +
+ + + + +
+ + + +
+
+ + + + + +
+ + + + +
+ + + +
+
+ +
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + +
+
UXBOX © 2020 | Made with <3 and Open Source
+
+
+ +
+
+ +
+ + + \ No newline at end of file diff --git a/backend/resources/emails/invite-to-team/en.subj b/backend/resources/emails/invite-to-team/en.subj new file mode 100644 index 0000000000..1ff46884ac --- /dev/null +++ b/backend/resources/emails/invite-to-team/en.subj @@ -0,0 +1 @@ +Inviation to join {{team}} \ No newline at end of file diff --git a/backend/resources/emails/invite-to-team/en.txt b/backend/resources/emails/invite-to-team/en.txt new file mode 100644 index 0000000000..c999019cc3 --- /dev/null +++ b/backend/resources/emails/invite-to-team/en.txt @@ -0,0 +1,10 @@ +Hello! + +{{invited-by}} has invited you to join the team “{{ team }}”. + +Accept invitation using this link: + +{{ public-uri }}/#/auth/verify-token?token={{token}} + +Enjoy! +The UXBOX team. diff --git a/backend/scripts/build.sh b/backend/scripts/build.sh index 23f262aca7..8da54b34d4 100755 --- a/backend/scripts/build.sh +++ b/backend/scripts/build.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash CLASSPATH=`(clojure -Spath)` -NEWCP="./resources:./app.jar" +NEWCP="./resources:./main:./common" rm -rf ./target/dist mkdir -p ./target/dist/deps @@ -15,10 +15,9 @@ for item in $(echo $CLASSPATH | tr ":" "\n"); do done cp ./resources/log4j2-bundle.xml ./target/dist/log4j2.xml +cp -r ./src ./target/dist/main +cp -r ../common ./target/dist/common -clojure -Ajar - -cp ./target/app.jar ./target/dist/app.jar echo $NEWCP > ./target/dist/classpath; tee -a ./target/dist/run.sh >> /dev/null < +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL (ns app.db (:require - [clojure.spec.alpha :as s] + [app.common.exceptions :as ex] + [app.common.geom.point :as gpt] + [app.config :as cfg] + [app.metrics :as mtx] + [app.util.data :as data] + [app.util.time :as dt] + [app.util.transit :as t] [clojure.data.json :as json] + [clojure.spec.alpha :as s] [clojure.string :as str] [clojure.tools.logging :as log] [lambdaisland.uri :refer [uri]] @@ -17,19 +27,17 @@ [next.jdbc.optional :as jdbc-opt] [next.jdbc.result-set :as jdbc-rs] [next.jdbc.sql :as jdbc-sql] - [next.jdbc.sql.builder :as jdbc-bld] - [app.common.exceptions :as ex] - [app.config :as cfg] - [app.metrics :as mtx] - [app.util.time :as dt] - [app.util.transit :as t] - [app.util.data :as data]) + [next.jdbc.sql.builder :as jdbc-bld]) (:import - org.postgresql.util.PGobject - org.postgresql.util.PGInterval - com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory com.zaxxer.hikari.HikariConfig - com.zaxxer.hikari.HikariDataSource)) + com.zaxxer.hikari.HikariDataSource + com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory + java.sql.Connection + java.sql.Savepoint + org.postgresql.jdbc.PgArray + org.postgresql.geometric.PGpoint + org.postgresql.util.PGInterval + org.postgresql.util.PGobject)) (def initsql (str "SET statement_timeout = 10000;\n" @@ -158,10 +166,42 @@ [v] (instance? PGInterval v)) +(defn pgpoint? + [v] + (instance? PGpoint v)) + +(defn pgarray? + [v] + (instance? PgArray v)) + +(defn pgarray-of-uuid? + [v] + (and (pgarray? v) (= "uuid" (.getBaseTypeName ^PgArray v)))) + +(defn pgpoint + [p] + (PGpoint. (:x p) (:y p))) + +(defn decode-pgpoint + [^PGpoint v] + (gpt/point (.-x v) (.-y v))) + (defn pginterval [data] (org.postgresql.util.PGInterval. ^String data)) +(defn savepoint + ([^Connection conn] + (.setSavepoint conn)) + ([^Connection conn label] + (.setSavepoint conn (name label)))) + +(defn rollback! + ([^Connection conn] + (.rollback conn)) + ([^Connection conn ^Savepoint sp] + (.rollback conn sp))) + (defn interval [data] (cond @@ -222,6 +262,14 @@ (.setType "jsonb") (.setValue (json/write-str data)))) +(defn pgarray->set + [v] + (set (.getArray ^PgArray v))) + +(defn pgarray->vector + [v] + (vec (.getArray ^PgArray v))) + ;; Instrumentation (mtx/instrument-with-counter! diff --git a/backend/src/app/emails.clj b/backend/src/app/emails.clj index 95a96d13b6..9bef19b2ee 100644 --- a/backend/src/app/emails.clj +++ b/backend/src/app/emails.clj @@ -74,3 +74,16 @@ (def change-email "Password change confirmation email" (emails/build ::change-email default-context)) + +(s/def :internal.emails.invite-to-team/invited-by ::us/string) +(s/def :internal.emails.invite-to-team/team ::us/string) +(s/def :internal.emails.invite-to-team/token ::us/string) + +(s/def ::invite-to-team + (s/keys :keys [:internal.emails.invite-to-team/invited-by + :internal.emails.invite-to-team/token + :internal.emails.invite-to-team/team])) + +(def invite-to-team + "Teams member invitation email." + (emails/build ::invite-to-team default-context)) diff --git a/backend/src/app/http/handlers.clj b/backend/src/app/http/handlers.clj index f49e6da458..f859b7a362 100644 --- a/backend/src/app/http/handlers.clj +++ b/backend/src/app/http/handlers.clj @@ -20,7 +20,7 @@ #{:create-demo-profile :logout :profile - :verify-profile-token + :verify-token :recover-profile :register-profile :request-profile-recovery @@ -34,8 +34,7 @@ {::sq/type type}) data (cond-> data (:profile-id req) (assoc :profile-id (:profile-id req)))] - (if (or (:profile-id req) - (contains? unauthorized-services type)) + (if (or (:profile-id req) (contains? unauthorized-services type)) {:status 200 :body (sq/handle (with-meta data {:req req}))} {:status 403 @@ -51,18 +50,14 @@ {::sm/type type}) data (cond-> data (:profile-id req) (assoc :profile-id (:profile-id req)))] - (if (or (:profile-id req) - (contains? unauthorized-services type)) - (let [body (sm/handle (with-meta data {:req req}))] - (if (= type :delete-profile) - (do - (some-> (session/extract-auth-token req) - (session/delete)) - {:status 204 - :cookies (session/cookies "" {:max-age -1}) - :body ""}) - {:status 200 - :body body})) + (if (or (:profile-id req) (contains? unauthorized-services type)) + (let [result (sm/handle (with-meta data {:req req})) + mdata (meta result) + resp {:status (if (nil? (seq result)) 204 200) + :body result}] + (cond->> resp + (:transform-response mdata) ((:transform-response mdata) req))) + {:status 403 :body {:type :authentication :code :unauthorized}}))) diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index be3384364a..75fe605b50 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -7,6 +7,8 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL +;; TODO: move to services. + (ns app.http.session (:require [app.db :as db] diff --git a/backend/src/app/media.clj b/backend/src/app/media.clj index 6e0a77c368..91a59d611b 100644 --- a/backend/src/app/media.clj +++ b/backend/src/app/media.clj @@ -10,19 +10,19 @@ (ns app.media "Media postprocessing." (:require + [app.common.data :as d] + [app.common.exceptions :as ex] + [app.common.media :as cm] + [app.common.spec :as us] + [app.config :as cfg] + [app.media-storage :as mst] + [app.util.http :as http] + [app.util.storage :as ust] [clojure.core.async :as a] [clojure.java.io :as io] [clojure.spec.alpha :as s] [datoteka.core :as fs] - [mount.core :refer [defstate]] - [app.config :as cfg] - [app.common.data :as d] - [app.common.media :as cm] - [app.common.exceptions :as ex] - [app.common.spec :as us] - [app.media-storage :as mst] - [app.util.storage :as ust] - [app.util.http :as http]) + [mount.core :refer [defstate]]) (:import java.io.ByteArrayInputStream java.io.InputStream @@ -34,6 +34,21 @@ (defstate semaphore :start (Semaphore. (:image-process-max-threads cfg/config 1))) + +;; --- Generic specs + +(s/def :internal.http.upload/filename ::us/string) +(s/def :internal.http.upload/size ::us/integer) +(s/def :internal.http.upload/content-type cm/valid-media-types) +(s/def :internal.http.upload/tempfile any?) + +(s/def ::upload + (s/keys :req-un [:internal.http.upload/filename + :internal.http.upload/size + :internal.http.upload/tempfile + :internal.http.upload/content-type])) + + ;; --- Thumbnails Generation (s/def ::cmd keyword?) diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index 920ad7d277..54fa2a50da 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -98,6 +98,18 @@ {:name "0027-mod-file-table-ignore-sync" :fn (mg/resource "app/migrations/sql/0027-mod-file-table-ignore-sync.sql")} + + {:name "0028-add-team-project-profile-rel-table" + :fn (mg/resource "app/migrations/sql/0028-add-team-project-profile-rel-table.sql")} + + {:name "0029-del-project-profile-rel-indexes" + :fn (mg/resource "app/migrations/sql/0029-del-project-profile-rel-indexes.sql")} + + {:name "0030-mod-file-table-add-missing-index" + :fn (mg/resource "app/migrations/sql/0030-mod-file-table-add-missing-index.sql")} + + {:name "0031-add-conversation-related-tables" + :fn (mg/resource "app/migrations/sql/0031-add-conversation-related-tables.sql")} ]}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/backend/src/app/migrations/sql/0002-add-profile-tables.sql b/backend/src/app/migrations/sql/0002-add-profile-tables.sql index 230a35a5b7..1e8e4d7491 100644 --- a/backend/src/app/migrations/sql/0002-add-profile-tables.sql +++ b/backend/src/app/migrations/sql/0002-add-profile-tables.sql @@ -78,7 +78,6 @@ VALUES ('00000000-0000-0000-0000-000000000000'::uuid, true); - CREATE TABLE team_profile_rel ( team_id uuid NOT NULL REFERENCES team(id) ON DELETE CASCADE, profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE RESTRICT, diff --git a/backend/src/app/migrations/sql/0003-add-project-tables.sql b/backend/src/app/migrations/sql/0003-add-project-tables.sql index 9ba826010a..925f7c8b00 100644 --- a/backend/src/app/migrations/sql/0003-add-project-tables.sql +++ b/backend/src/app/migrations/sql/0003-add-project-tables.sql @@ -28,9 +28,6 @@ CREATE TABLE project_profile_rel ( PRIMARY KEY (profile_id, project_id) ); -COMMENT ON TABLE project_profile_rel - IS 'Relation between projects and profiles (NM)'; - CREATE INDEX project_profile_rel__profile_id__idx ON project_profile_rel(profile_id); diff --git a/backend/src/app/migrations/sql/0028-add-team-project-profile-rel-table.sql b/backend/src/app/migrations/sql/0028-add-team-project-profile-rel-table.sql new file mode 100644 index 0000000000..a2490b1cc4 --- /dev/null +++ b/backend/src/app/migrations/sql/0028-add-team-project-profile-rel-table.sql @@ -0,0 +1,12 @@ +CREATE TABLE team_project_profile_rel ( + team_id uuid NOT NULL REFERENCES team(id) ON DELETE CASCADE, + profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE, + project_id uuid NOT NULL REFERENCES project(id) ON DELETE CASCADE, + + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + modified_at timestamptz NOT NULL DEFAULT clock_timestamp(), + + is_pinned boolean NOT NULL DEFAULT false, + + PRIMARY KEY (team_id, profile_id, project_id) +); diff --git a/backend/src/app/migrations/sql/0029-del-project-profile-rel-indexes.sql b/backend/src/app/migrations/sql/0029-del-project-profile-rel-indexes.sql new file mode 100644 index 0000000000..7a35640aca --- /dev/null +++ b/backend/src/app/migrations/sql/0029-del-project-profile-rel-indexes.sql @@ -0,0 +1,4 @@ +--- Drop duplicate indexes + +DROP INDEX IF EXISTS project_profile_rel__project_id__idx; +DROP INDEX IF EXISTS project_profile_rel__profile_id__idx; diff --git a/backend/src/app/migrations/sql/0030-mod-file-table-add-missing-index.sql b/backend/src/app/migrations/sql/0030-mod-file-table-add-missing-index.sql new file mode 100644 index 0000000000..5c940bee7a --- /dev/null +++ b/backend/src/app/migrations/sql/0030-mod-file-table-add-missing-index.sql @@ -0,0 +1 @@ +CREATE INDEX IF NOT EXISTS file__project_id__idx ON file (project_id); diff --git a/backend/src/app/migrations/sql/0031-add-conversation-related-tables.sql b/backend/src/app/migrations/sql/0031-add-conversation-related-tables.sql new file mode 100644 index 0000000000..0049f7fef1 --- /dev/null +++ b/backend/src/app/migrations/sql/0031-add-conversation-related-tables.sql @@ -0,0 +1,48 @@ +CREATE TABLE comment_thread ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE, + owner_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE, + + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + modified_at timestamptz NOT NULL DEFAULT clock_timestamp(), + + page_id uuid NOT NULL, + + participants jsonb NOT NULL, + seqn integer NOT NULL DEFAULT 0, + + position point NOT NULL, + + is_resolved boolean NOT NULL DEFAULT false +); + +CREATE INDEX comment_thread__owner_id__idx ON comment_thread(owner_id); +CREATE UNIQUE INDEX comment_thread__file_id__seqn__idx ON comment_thread(file_id, seqn); + +CREATE TABLE comment_thread_status ( + thread_id uuid NOT NULL REFERENCES comment_thread(id) ON DELETE CASCADE, + profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE, + + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + modified_at timestamptz NOT NULL DEFAULT clock_timestamp(), + + PRIMARY KEY (thread_id, profile_id) +); + +CREATE TABLE comment ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + + thread_id uuid NOT NULL REFERENCES comment_thread(id) ON DELETE CASCADE, + owner_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE, + + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + modified_at timestamptz NOT NULL DEFAULT clock_timestamp(), + + content text NOT NULL +); + +CREATE INDEX comment__thread_id__idx ON comment(thread_id); +CREATE INDEX comment__owner_id__idx ON comment(owner_id); + +ALTER TABLE file ADD COLUMN comment_thread_seqn integer DEFAULT 0; + diff --git a/backend/src/app/services/init.clj b/backend/src/app/services/init.clj index 37edf7d4e8..4df4707edc 100644 --- a/backend/src/app/services/init.clj +++ b/backend/src/app/services/init.clj @@ -15,9 +15,9 @@ (defn- load-query-services [] (require 'app.services.queries.media) - (require 'app.services.queries.colors) (require 'app.services.queries.projects) (require 'app.services.queries.files) + (require 'app.services.queries.comments) (require 'app.services.queries.profile) (require 'app.services.queries.recent-files) (require 'app.services.queries.viewer)) @@ -26,11 +26,12 @@ [] (require 'app.services.mutations.demo) (require 'app.services.mutations.media) - (require 'app.services.mutations.colors) (require 'app.services.mutations.projects) (require 'app.services.mutations.files) + (require 'app.services.mutations.comments) (require 'app.services.mutations.profile) - (require 'app.services.mutations.viewer)) + (require 'app.services.mutations.viewer) + (require 'app.services.mutations.verify-token)) (defstate query-services :start (load-query-services)) diff --git a/backend/src/app/services/mutations/colors.clj b/backend/src/app/services/mutations/colors.clj deleted file mode 100644 index 1fbb2dd4fa..0000000000 --- a/backend/src/app/services/mutations/colors.clj +++ /dev/null @@ -1,150 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL - -(ns app.services.mutations.colors - (:require - [clojure.spec.alpha :as s] - [app.common.exceptions :as ex] - [app.common.spec :as us] - [app.common.uuid :as uuid] - [app.config :as cfg] - [app.db :as db] - [app.services.mutations :as sm] - [app.services.queries.teams :as teams] - [app.tasks :as tasks] - [app.util.time :as dt])) - -;; --- Helpers & Specs - -(s/def ::id ::us/uuid) -(s/def ::name ::us/string) -(s/def ::profile-id ::us/uuid) -(s/def ::team-id ::us/uuid) -(s/def ::library-id ::us/uuid) -(s/def ::content ::us/string) - -;; --- Mutation: Create Color - -(declare select-file-for-update) -(declare create-color) - -(s/def ::create-color - (s/keys :req-un [::profile-id ::name ::content ::file-id] - :opt-un [::id])) - -(sm/defmutation ::create-color - [{:keys [profile-id file-id] :as params}] - (db/with-atomic [conn db/pool] - (let [file (select-file-for-update conn file-id)] - (teams/check-edition-permissions! conn profile-id (:team-id file)) - (create-color conn params)))) - -(def ^:private sql:create-color - "insert into color (id, name, file_id, content) - values ($1, $2, $3, $4) returning *") - -(defn create-color - [conn {:keys [id name file-id content]}] - (let [id (or id (uuid/next))] - (db/insert! conn :color {:id id - :name name - :file-id file-id - :content content}))) - -(def ^:private sql:select-file-for-update - "select file.*, - project.team_id as team_id - from file - inner join project on (project.id = file.project_id) - where file.id = ? - for update of file") - -(defn- select-file-for-update - [conn id] - (let [row (db/exec-one! conn [sql:select-file-for-update id])] - (when-not row - (ex/raise :type :not-found)) - row)) - - -;; --- Mutation: Rename Color - -(declare select-color-for-update) - -(s/def ::rename-color - (s/keys :req-un [::id ::profile-id ::name])) - -(sm/defmutation ::rename-color - [{:keys [id profile-id name] :as params}] - (db/with-atomic [conn db/pool] - (let [clr (select-color-for-update conn id)] - (teams/check-edition-permissions! conn profile-id (:team-id clr)) - (db/update! conn :color - {:name name} - {:id id})))) - -(def ^:private sql:select-color-for-update - "select c.*, - p.team_id as team_id - from color as c - inner join file as f on f.id = c.file_id - inner join project as p on p.id = f.project_id - where c.id = ? - for update of c") - -(defn- select-color-for-update - [conn id] - (let [row (db/exec-one! conn [sql:select-color-for-update id])] - (when-not row - (ex/raise :type :not-found)) - row)) - - -;; --- Mutation: Update Color - -(s/def ::update-color - (s/keys :req-un [::profile-id ::id ::content])) - -(sm/defmutation ::update-color - [{:keys [profile-id id content] :as params}] - (db/with-atomic [conn db/pool] - (let [clr (select-color-for-update conn id) - ;; IMPORTANT: if the previous name was equal to the hex content, - ;; we must rename it in addition to changing the value. - new-name (if (= (:name clr) (:content clr)) - content - (:name clr))] - (teams/check-edition-permissions! conn profile-id (:team-id clr)) - (db/update! conn :color - {:name new-name - :content content} - {:id id})))) - -;; --- Delete Color - -(declare delete-color) - -(s/def ::delete-color - (s/keys :req-un [::id ::profile-id])) - -(sm/defmutation ::delete-color - [{:keys [profile-id id] :as params}] - (db/with-atomic [conn db/pool] - (let [clr (select-color-for-update conn id)] - (teams/check-edition-permissions! conn profile-id (:team-id clr)) - - ;; Schedule object deletion - (tasks/submit! conn {:name "delete-object" - :delay cfg/default-deletion-delay - :props {:id id :type :color}}) - - (db/update! conn :color - {:deleted-at (dt/now)} - {:id id}) - nil))) diff --git a/backend/src/app/services/mutations/comments.clj b/backend/src/app/services/mutations/comments.clj new file mode 100644 index 0000000000..af798bb073 --- /dev/null +++ b/backend/src/app/services/mutations/comments.clj @@ -0,0 +1,260 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.services.mutations.comments + (:require + [clojure.spec.alpha :as s] + [app.common.exceptions :as ex] + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.config :as cfg] + [app.db :as db] + [app.services.mutations :as sm] + [app.services.queries.projects :as proj] + [app.services.queries.files :as files] + [app.services.queries.comments :as comments] + [app.tasks :as tasks] + [app.util.blob :as blob] + [app.util.storage :as ust] + [app.util.transit :as t] + [app.util.time :as dt])) + +;; --- Mutation: Create Comment Thread + +(declare upsert-comment-thread-status!) +(declare create-comment-thread) + +(s/def ::file-id ::us/uuid) +(s/def ::profile-id ::us/uuid) +(s/def ::position ::us/point) +(s/def ::content ::us/string) +(s/def ::page-id ::us/uuid) + +(s/def ::create-comment-thread + (s/keys :req-un [::profile-id ::file-id ::position ::content ::page-id])) + +(sm/defmutation ::create-comment-thread + [{:keys [profile-id file-id] :as params}] + (db/with-atomic [conn db/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) + + thread (db/insert! conn :comment-thread + {:file-id file-id + :owner-id profile-id + :participants (db/tjson #{profile-id}) + :page-id page-id + :created-at now + :modified-at now + :seqn seqn + :position (db/pgpoint position)}) + ;; Create a comment entry + comment (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}) + + (-> (assoc thread + :content content + :comment comment) + (comments/decode-row)))) + +(defn- create-comment-thread + [conn params] + (loop [sp (db/savepoint conn) + rc 0] + (let [res (ex/try (create-comment-thread* conn params))] + (cond + (and (instance? Throwable res) + (< rc 3)) + (do + (db/rollback! conn sp) + (recur (db/savepoint conn) + (inc rc))) + + (instance? Throwable res) + (throw res) + + :else res)))) + + +;; --- Mutation: Update Comment Thread Status + +(s/def ::id ::us/uuid) + +(s/def ::update-comment-thread-status + (s/keys :req-un [::profile-id ::id])) + +(sm/defmutation ::update-comment-thread-status + [{:keys [profile-id id] :as params}] + (db/with-atomic [conn db/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])) + + +;; --- Mutation: Update Comment Thread + +(s/def ::is-resolved ::us/boolean) +(s/def ::update-comment-thread + (s/keys :req-un [::profile-id ::id ::is-resolved])) + +(sm/defmutation ::update-comment-thread + [{:keys [profile-id id is-resolved] :as params}] + (db/with-atomic [conn db/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)) + + (db/update! conn :comment-thread + {:is-resolved is-resolved} + {:id id}) + nil)))) + + +;; --- Mutation: Add Comment + +(s/def ::add-comment + (s/keys :req-un [::profile-id ::thread-id ::content])) + +(sm/defmutation ::add-comment + [{:keys [profile-id thread-id content] :as params}] + (db/with-atomic [conn db/pool] + (let [thread (-> (db/get-by-id conn :comment-thread thread-id {:for-update true}) + (comments/decode-row))] + + ;; Standard Checks + (when-not thread + (ex/raise :type :not-found)) + + (files/check-read-permissions! conn profile-id (:file-id thread)) + + ;; 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 bacause 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)))) + + +;; --- Mutation: Update Comment + +(s/def ::update-comment + (s/keys :req-un [::profile-id ::id ::content])) + +(sm/defmutation ::update-comment + [{:keys [profile-id id content] :as params}] + (db/with-atomic [conn db/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))] + + (files/check-read-permissions! conn profile-id (:file-id thread)) + (db/update! conn :comment + {:content content + :modified-at (dt/now)} + {:id (:id comment)}) + (db/update! conn :comment-thread + {:modified-at (dt/now)} + {:id (:id thread)}) + nil))) + + +;; --- Mutation: Delete Comment Thread + +(s/def ::delete-comment-thread + (s/keys :req-un [::profile-id ::id])) + +(sm/defmutation ::delete-comment-thread + [{:keys [profile-id id] :as params}] + (db/with-atomic [conn db/pool] + (let [cthr (db/get-by-id conn :comment-thread id {:for-update true})] + (when-not (= (:owner-id cthr) profile-id) + (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])) + +(sm/defmutation ::delete-comment + [{:keys [profile-id id] :as params}] + (db/with-atomic [conn db/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})))) diff --git a/backend/src/app/services/mutations/files.clj b/backend/src/app/services/mutations/files.clj index d3db468845..0246d58f4e 100644 --- a/backend/src/app/services/mutations/files.clj +++ b/backend/src/app/services/mutations/files.clj @@ -21,7 +21,7 @@ [app.db :as db] [app.redis :as redis] [app.services.mutations :as sm] - [app.services.mutations.projects :as proj] + [app.services.queries.projects :as proj] [app.services.queries.files :as files] [app.tasks :as tasks] [app.util.blob :as blob] @@ -49,6 +49,7 @@ (sm/defmutation ::create-file [{:keys [profile-id project-id] :as params}] (db/with-atomic [conn db/pool] + (proj/check-edition-permissions! conn profile-id project-id) (create-file conn params))) (defn- create-file-profile @@ -241,10 +242,14 @@ ;; File changes that affect to the library, and must be notified ;; to all clients using it. -(def library-changes - #{:add-color :mod-color :del-color - :add-media :mod-media :del-media - :add-component :mod-component :del-component}) +(defn library-change? + [change] + (or (#{:add-color :mod-color :del-color + :add-media :mod-media :del-media + :add-component :mod-component :del-component + :add-typography :mod-typography :del-typography} (:type change)) + (and (= (:type change) :mod-obj) + (some? (:component-id change))))) (declare update-file) (declare retrieve-lagged-changes) @@ -285,12 +290,12 @@ :revn (:revn file) :changes changes} - library-changes (filter #(library-changes (:type %)) changes)] + library-changes (filter library-change? changes)] @(redis/run! :publish {:channel (str (:id file)) :message (t/encode-str msg)}) - (if (and (:is-shared file) (seq library-changes)) + (when (and (:is-shared file) (seq library-changes)) (let [{:keys [team-id] :as project} (db/get-by-id conn :project (:project-id file)) @@ -299,7 +304,7 @@ :file-id (:id file) :session-id sid :revn (:revn file) - :modified-at (:modified-at file) + :modified-at (dt/now) :changes library-changes}] @(redis/run! :publish {:channel (str team-id) diff --git a/backend/src/app/services/mutations/media.clj b/backend/src/app/services/mutations/media.clj index f26bc7eded..1495656d3a 100644 --- a/backend/src/app/services/mutations/media.clj +++ b/backend/src/app/services/mutations/media.clj @@ -46,19 +46,7 @@ (declare persist-media-object-on-fs) (declare persist-media-thumbnail-on-fs) -(s/def :app$upload/filename ::us/string) -(s/def :app$upload/size ::us/integer) -(s/def :app$upload/content-type cm/valid-media-types) -(s/def :app$upload/tempfile any?) - -(s/def ::upload - (s/keys :req-un [:app$upload/filename - :app$upload/size - :app$upload/tempfile - :app$upload/content-type])) - -(s/def ::content ::upload) - +(s/def ::content ::media/upload) (s/def ::is-local ::us/boolean) (s/def ::add-media-object-from-url diff --git a/backend/src/app/services/mutations/profile.clj b/backend/src/app/services/mutations/profile.clj index ee8217979e..a2f587cac3 100644 --- a/backend/src/app/services/mutations/profile.clj +++ b/backend/src/app/services/mutations/profile.clj @@ -18,32 +18,30 @@ [app.emails :as emails] [app.media :as media] [app.media-storage :as mst] + [app.http.session :as session] [app.services.mutations :as sm] - [app.services.mutations.media :as media-mutations] [app.services.mutations.projects :as projects] [app.services.mutations.teams :as teams] [app.services.queries.profile :as profile] [app.services.tokens :as tokens] + [app.services.mutations.verify-token :refer [process-token]] [app.tasks :as tasks] [app.util.blob :as blob] [app.util.storage :as ust] [app.util.time :as dt] - [buddy.core.codecs :as bc] - [buddy.core.nonce :as bn] [buddy.hashers :as hashers] [clojure.spec.alpha :as s] - [cuerdas.core :as str] - [datoteka.core :as fs])) + [cuerdas.core :as str])) ;; --- Helpers & Specs (s/def ::email ::us/email) -(s/def ::fullname ::us/string) -(s/def ::lang ::us/string) +(s/def ::fullname ::us/not-empty-string) +(s/def ::lang ::us/not-empty-string) (s/def ::path ::us/string) (s/def ::profile-id ::us/uuid) -(s/def ::password ::us/string) -(s/def ::old-password ::us/string) +(s/def ::password ::us/not-empty-string) +(s/def ::old-password ::us/not-empty-string) (s/def ::theme ::us/string) ;; --- Mutation: Register Profile @@ -51,22 +49,15 @@ (declare check-profile-existence!) (declare create-profile) (declare create-profile-relations) +(declare email-domain-in-whitelist?) +(s/def ::token ::us/not-empty-string) (s/def ::register-profile - (s/keys :req-un [::email ::password ::fullname])) - -(defn email-domain-in-whitelist? - "Returns true if email's domain is in the given whitelist or if given - whitelist is an empty string." - [whitelist email] - (if (str/blank? whitelist) - true - (let [domains (str/split whitelist #",\s*") - email-domain (second (str/split email #"@"))] - (contains? (set domains) email-domain)))) + (s/keys :req-un [::email ::password ::fullname] + :opt-un [::token])) (sm/defmutation ::register-profile - [params] + [{:keys [token] :as params}] (when-not (:registration-enabled cfg/config) (ex/raise :type :restriction :code :registration-disabled)) @@ -79,25 +70,68 @@ (db/with-atomic [conn db/pool] (check-profile-existence! conn params) (let [profile (->> (create-profile conn params) - (create-profile-relations conn)) - token (tokens/generate - {:iss :verify-email - :exp (dt/in-future "48h") - :profile-id (:id profile) - :email (:email profile)})] + (create-profile-relations conn))] - (emails/send! conn emails/register - {:to (:email profile) - :name (:fullname profile) - :token token}) - profile))) + (if token + ;; If token comes in params, this is because the user comes + ;; from team-invitation process; in this case we revalidate + ;; the token and process the token claims again with the new + ;; profile data. + (let [claims (tokens/verify token {:iss :team-invitation}) + claims (assoc claims :member-id (:id profile)) + params (assoc params :profile-id (:id profile))] + (process-token conn params claims) + + ;; Automatically mark the created profile as active because + ;; we already have the verification of email with the + ;; team-invitation token. + (db/update! conn :profile + {:is-active true} + {:id (:id profile)}) + + ;; Return profile data and create http session for + ;; automatically login the profile. + (with-meta (assoc profile + :is-active true + :claims claims) + {:transform-response + (fn [request response] + (let [uagent (get-in request [:headers "user-agent"]) + id (session/create (:id profile) uagent)] + (assoc response + :cookies (session/cookies id))))})) + + ;; If no token is provided, send a verification email + (let [token (tokens/generate + {:iss :verify-email + :exp (dt/in-future "48h") + :profile-id (:id profile) + :email (:email profile)})] + + (emails/send! conn emails/register + {:to (:email profile) + :name (:fullname profile) + :token token}) + + 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." + [whitelist email] + (if (str/blank? whitelist) + true + (let [domains (str/split whitelist #",\s*") + email-domain (second (str/split email #"@"))] + (contains? (set domains) email-domain)))) (def ^:private sql:profile-existence "select exists (select * from profile where email = ? and deleted_at is null) as val") -(defn- check-profile-existence! +(defn check-profile-existence! [conn {:keys [email] :as params}] (let [email (str/lower email) result (db/exec-one! conn [sql:profile-existence email])] @@ -151,8 +185,6 @@ ;; --- Mutation: Login -(declare retrieve-profile-by-email) - (s/def ::email ::us/email) (s/def ::scope ::us/string) @@ -181,22 +213,12 @@ profile)] (db/with-atomic [conn db/pool] - (let [prof (-> (retrieve-profile-by-email conn email) + (let [prof (-> (profile/retrieve-profile-data-by-email conn email) (validate-profile) (profile/strip-private-attrs)) addt (profile/retrieve-additional-data conn (:id prof))] (merge prof addt))))) -(def sql:profile-by-email - "select * from profile - where email=? - and deleted_at is null") - -(defn- retrieve-profile-by-email - [conn email] - (let [email (str/lower email)] - (db/exec-one! conn [sql:profile-by-email email]))) - ;; --- Mutation: Register if not exists @@ -221,7 +243,7 @@ (create-profile-relations conn)))] (db/with-atomic [conn db/pool] - (let [profile (retrieve-profile-by-email conn email) + (let [profile (profile/retrieve-profile-data-by-email conn email) profile (if profile (populate-additional-data conn profile) (register-profile conn params))] @@ -272,10 +294,9 @@ ;; --- Mutation: Update Photo -(declare upload-photo) (declare update-profile-photo) -(s/def ::file ::media-mutations/upload) +(s/def ::file ::media/upload) (s/def ::update-profile-photo (s/keys :req-un [::profile-id ::file])) @@ -286,7 +307,7 @@ (let [profile (profile/retrieve-profile conn profile-id) _ (media/run {:cmd :info :input {:path (:tempfile file) :mtype (:content-type file)}}) - photo (upload-photo conn params)] + photo (teams/upload-photo conn params)] ;; Schedule deletion of old photo (when (and (string? (:photo profile)) @@ -296,22 +317,6 @@ ;; Save new photo (update-profile-photo conn profile-id photo)))) -(defn- upload-photo - [conn {:keys [file profile-id]}] - (let [prefix (-> (bn/random-bytes 8) - (bc/bytes->b64u) - (bc/bytes->str)) - thumb (media/run - {:cmd :profile-thumbnail - :format :jpeg - :quality 85 - :width 256 - :height 256 - :input {:path (fs/path (:tempfile file)) - :mtype (:content-type file)}}) - name (str prefix (cm/format->extension (:format thumb)))] - (ust/save! mst/media-storage name (:data thumb)))) - (defn- update-profile-photo [conn profile-id path] (db/update! conn :profile @@ -345,63 +350,10 @@ :token token}) nil))) -(defn- select-profile-for-update +(defn select-profile-for-update [conn id] (db/get-by-id conn :profile id {:for-update true})) - -;; --- Mutation: Verify Profile Token - -;; Generic mutation for perform token based verification for auth -;; domain. - -(defmulti process-token (fn [conn claims] (:iss claims))) - -(s/def ::verify-profile-token - (s/keys :req-un [::token])) - -(sm/defmutation ::verify-profile-token - [{:keys [token] :as params}] - (db/with-atomic [conn db/pool] - (let [claims (tokens/verify token)] - (process-token conn claims)))) - -(defmethod process-token :change-email - [conn {:keys [profile-id email] :as claims}] - (let [profile (select-profile-for-update conn profile-id)] - (check-profile-existence! conn {:email email}) - (db/update! conn :profile - {:email email} - {:id profile-id}) - claims)) - -(defmethod process-token :verify-email - [conn {:keys [profile-id] :as claims}] - (let [profile (select-profile-for-update conn profile-id)] - (when (:is-active profile) - (ex/raise :type :validation - :code :email-already-validated)) - (when (not= (:email profile) - (:email claims)) - (ex/raise :type :validation - :code :invalid-token)) - - (db/update! conn :profile - {:is-active true} - {:id (:id profile)}) - claims)) - -(defmethod process-token :auth - [conn {:keys [profile-id] :as claims}] - (let [profile (profile/retrieve-profile conn profile-id)] - (assoc claims :profile profile))) - -(defmethod process-token :default - [conn claims] - (ex/raise :type :validation - :code :invalid-token)) - - ;; --- Mutation: Request Profile Recovery (s/def ::request-profile-recovery @@ -424,7 +376,7 @@ (db/with-atomic [conn db/pool] (some->> email - (retrieve-profile-by-email conn) + (profile/retrieve-profile-data-by-email conn) (create-recovery-token conn) (send-email-notification conn)) nil))) @@ -473,7 +425,14 @@ (db/update! conn :profile {:deleted-at (dt/now)} {:id profile-id}) - nil)) + + (with-meta {} + {:transform-response + (fn [request response] + (some-> (session/extract-auth-token request) + (session/delete)) + (assoc response + :cookies (session/cookies "" {:max-age -1})))}))) (def ^:private sql:teams-ownership-check "with teams as ( diff --git a/backend/src/app/services/mutations/projects.clj b/backend/src/app/services/mutations/projects.clj index 1b7d42b384..bb36b89776 100644 --- a/backend/src/app/services/mutations/projects.clj +++ b/backend/src/app/services/mutations/projects.clj @@ -16,6 +16,7 @@ [app.config :as cfg] [app.db :as db] [app.services.mutations :as sm] + [app.services.queries.projects :as proj] [app.tasks :as tasks] [app.util.blob :as blob])) @@ -25,42 +26,12 @@ (s/def ::name ::us/string) (s/def ::profile-id ::us/uuid) -;; --- Permissions Checks - -(def ^:private sql:project-permissions - "select tpr.is_owner, - tpr.is_admin, - tpr.can_edit - from team_profile_rel as tpr - inner join project as p on (p.team_id = tpr.team_id) - where p.id = ? - and tpr.profile_id = ? - union all - select ppr.is_owner, - ppr.is_admin, - ppr.can_edit - from project_profile_rel as ppr - where ppr.project_id = ? - and ppr.profile_id = ?") - -(defn check-edition-permissions! - [conn profile-id project-id] - (let [rows (db/exec! conn [sql:project-permissions - project-id profile-id - project-id profile-id])] - (when (empty? rows) - (ex/raise :type :not-found)) - (when-not (or (some :can-edit rows) - (some :is-admin rows) - (some :is-owner rows)) - (ex/raise :type :validation - :code :not-authorized)))) - ;; --- Mutation: Create Project (declare create-project) (declare create-project-profile) +(declare create-team-project-profile) (s/def ::team-id ::us/uuid) (s/def ::create-project @@ -70,9 +41,11 @@ (sm/defmutation ::create-project [params] (db/with-atomic [conn db/pool] - (let [proj (create-project conn params)] - (create-project-profile conn (assoc params :project-id (:id proj))) - proj))) + (let [proj (create-project conn params) + params (assoc params :project-id (:id proj))] + (create-project-profile conn params) + (create-team-project-profile conn params) + (assoc proj :is-pinned true)))) (defn create-project [conn {:keys [id profile-id team-id name default?] :as params}] @@ -93,6 +66,35 @@ :is-admin true :can-edit true})) +(defn create-team-project-profile + [conn {:keys [team-id project-id profile-id] :as params}] + (db/insert! conn :team-project-profile-rel + {:project-id project-id + :profile-id profile-id + :team-id team-id + :is-pinned true})) + + +;; --- Mutation: Toggle Project Pin + +(def ^:private + sql:update-project-pin + "insert into team_project_profile_rel (team_id, project_id, profile_id, is_pinned) + values (?, ?, ?, ?) + on conflict (team_id, project_id, profile_id) + do update set is_pinned=?") + +(s/def ::is-pinned ::us/boolean) +(s/def ::project-id ::us/uuid) + +(s/def ::update-project-pin + (s/keys :req-un [::profile-id ::id ::team-id ::is-pinned])) + +(sm/defmutation ::update-project-pin + [{:keys [id profile-id team-id is-pinned] :as params}] + (db/with-atomic [conn db/pool] + (db/exec-one! conn [sql:update-project-pin team-id id profile-id is-pinned is-pinned]) + nil)) ;; --- Mutation: Rename Project @@ -106,7 +108,7 @@ [{:keys [id profile-id name] :as params}] (db/with-atomic [conn db/pool] (let [project (db/get-by-id conn :project id {:for-update true})] - (check-edition-permissions! conn profile-id id) + (proj/check-edition-permissions! conn profile-id id) (db/update! conn :project {:name name} {:id id})))) @@ -121,7 +123,7 @@ (sm/defmutation ::delete-project [{:keys [id profile-id] :as params}] (db/with-atomic [conn db/pool] - (check-edition-permissions! conn profile-id id) + (proj/check-edition-permissions! conn profile-id id) ;; Schedule object deletion (tasks/submit! conn {:name "delete-object" diff --git a/backend/src/app/services/mutations/teams.clj b/backend/src/app/services/mutations/teams.clj index 614d2496d3..305b702753 100644 --- a/backend/src/app/services/mutations/teams.clj +++ b/backend/src/app/services/mutations/teams.clj @@ -9,13 +9,28 @@ (ns app.services.mutations.teams (:require - [clojure.spec.alpha :as s] + [app.common.data :as d] [app.common.exceptions :as ex] + [app.common.media :as cm] [app.common.spec :as us] [app.common.uuid :as uuid] [app.db :as db] + [app.emails :as emails] + [app.media :as media] + [app.media-storage :as mst] [app.services.mutations :as sm] - [app.util.blob :as blob])) + [app.services.mutations.projects :as projects] + [app.services.queries.teams :as teams] + [app.services.tokens :as tokens] + [app.services.queries.profile :as profile] + [app.tasks :as tasks] + [app.util.storage :as ust] + [app.util.time :as dt] + [buddy.core.codecs :as bc] + [buddy.core.nonce :as bn] + [clojure.spec.alpha :as s] + [cuerdas.core :as str] + [datoteka.core :as fs])) ;; --- Helpers & Specs @@ -27,6 +42,7 @@ (declare create-team) (declare create-team-profile) +(declare create-team-default-project) (s/def ::create-team (s/keys :req-un [::profile-id ::name] @@ -35,8 +51,10 @@ (sm/defmutation ::create-team [params] (db/with-atomic [conn db/pool] - (let [team (create-team conn params)] - (create-team-profile conn (assoc params :team-id (:id team))) + (let [team (create-team conn params) + params (assoc params :team-id (:id team))] + (create-team-profile conn params) + (create-team-default-project conn params) team))) (defn create-team @@ -57,3 +75,244 @@ :is-owner true :is-admin true :can-edit true})) + +(defn create-team-default-project + [conn {:keys [team-id profile-id] :as params}] + (let [proj (projects/create-project conn {:team-id team-id + :name "Drafts" + :default? true})] + (projects/create-project-profile conn {:project-id (:id proj) + :profile-id profile-id}))) + + +;; --- Mutation: Update Team + +(s/def ::update-team + (s/keys :req-un [::profile-id ::name ::id])) + +(sm/defmutation ::update-team + [{:keys [id name profile-id] :as params}] + (db/with-atomic [conn db/pool] + (teams/check-edition-permissions! conn profile-id id) + (db/update! conn :team + {:name name} + {:id id}) + nil)) + + +;; --- Mutation: Leave Team + +(s/def ::leave-team + (s/keys :req-un [::profile-id ::id])) + +(sm/defmutation ::leave-team + [{:keys [id profile-id] :as params}] + (db/with-atomic [conn db/pool] + (let [perms (teams/check-read-permissions! conn profile-id id) + members (teams/retrieve-team-members conn id)] + + (when (:is-owner perms) + (ex/raise :type :validation + :code :owner-cant-leave-team + :hint "reasing owner before leave")) + + (when-not (> (count members) 1) + (ex/raise :type :validation + :code :cant-leave-team + :context {:members (count members)})) + + (db/delete! conn :team-profile-rel + {:profile-id profile-id + :team-id id}) + + nil))) + + +;; --- Mutation: Delete Team + +(s/def ::delete-team + (s/keys :req-un [::profile-id ::id])) + +(sm/defmutation ::delete-team + [{:keys [id profile-id] :as params}] + (db/with-atomic [conn db/pool] + (let [perms (teams/check-edition-permissions! conn profile-id id)] + (when-not (:is-owner perms) + (ex/raise :type :validation + :code :only-owner-can-delete-team)) + + (db/delete! conn :team {:id id}) + nil))) + +;; --- Mutation: Tean Update Role + +(declare retrieve-team-member) +(declare role->params) + +(s/def ::team-id ::us/uuid) +(s/def ::member-id ::us/uuid) +(s/def ::role #{:owner :admin :editor :viewer}) + +(s/def ::update-team-member-role + (s/keys :req-un [::profile-id ::team-id ::member-id ::role])) + +(sm/defmutation ::update-team-member-role + [{:keys [team-id profile-id member-id role] :as params}] + (db/with-atomic [conn db/pool] + (let [perms (teams/check-read-permissions! conn profile-id team-id) + + ;; We retrieve all team members instead of query the + ;; database for a single member. This is just for + ;; convenience, if this bocomes a bottleneck or problematic, + ;; we will change it to more efficient fetch mechanims. + members (teams/retrieve-team-members conn team-id) + member (d/seek #(= member-id (:id %)) members)] + + ;; If no member is found, just 404 + (when-not member + (ex/raise :type :not-found + :code :member-does-not-exist)) + + ;; First check if we have permissions to change roles + (when-not (or (:is-owner perms) + (:is-admin perms)) + (ex/raise :type :validation + :code :insufficient-permissions)) + + ;; Don't allow change role of owner member + (when (:is-owner member) + (ex/raise :type :validation + :code :cant-change-role-to-owner)) + + ;; Don't allow promote to owner to admin users. + (when (and (= role :owner) + (not (:is-owner perms))) + (ex/raise :type :validation + :code :cant-promote-to-owner)) + + (let [params (role->params role)] + ;; Only allow single owner on team + (when (and (= role :owner) + (:is-owner perms)) + (db/update! conn :team-profile-rel + {:is-owner false} + {:team-id team-id + :profile-id profile-id})) + + (db/update! conn :team-profile-rel params + {:team-id team-id + :profile-id member-id}) + nil)))) + +(defn role->params + [role] + (case role + :admin {:is-owner false :is-admin true :can-edit true} + :editor {:is-owner false :is-admin false :can-edit true} + :owner {:is-owner true :is-admin true :can-edit true} + :viewer {:is-owner false :is-admin false :can-edit false})) + + +;; --- Mutation: Team Update Role + +(s/def ::delete-team-member + (s/keys :req-un [::profile-id ::team-id ::member-id])) + +(sm/defmutation ::delete-team-member + [{:keys [team-id profile-id member-id] :as params}] + (db/with-atomic [conn db/pool] + (let [perms (teams/check-read-permissions! conn profile-id team-id)] + (when-not (or (:is-owner perms) + (:is-admin perms)) + (ex/raise :type :validation + :code :insufficient-permissions)) + + (when (= member-id profile-id) + (ex/raise :type :validation + :code :cant-remove-yourself)) + + (db/delete! conn :team-profile-rel {:profile-id member-id + :team-id team-id}) + + nil))) + + +;; --- Mutation: Update Team Photo + +(declare upload-photo) + +(s/def ::file ::media/upload) +(s/def ::update-team-photo + (s/keys :req-un [::profile-id ::team-id ::file])) + +(sm/defmutation ::update-team-photo + [{:keys [profile-id file team-id] :as params}] + (media/validate-media-type (:content-type file)) + (db/with-atomic [conn db/pool] + (teams/check-edition-permissions! conn profile-id team-id) + (let [team (teams/retrieve-team conn profile-id team-id) + _ (media/run {:cmd :info :input {:path (:tempfile file) + :mtype (:content-type file)}}) + photo (upload-photo conn params)] + + ;; Schedule deletion of old photo + (when (and (string? (:photo team)) + (not (str/blank? (:photo team)))) + (tasks/submit! conn {:name "remove-media" + :props {:path (:photo team)}})) + ;; Save new photo + (db/update! conn :team + {:photo (str photo)} + {:id team-id}) + + (assoc team :photo (str photo))))) + +(defn upload-photo + [conn {:keys [file profile-id]}] + (let [prefix (-> (bn/random-bytes 8) + (bc/bytes->b64u) + (bc/bytes->str)) + thumb (media/run + {:cmd :profile-thumbnail + :format :jpeg + :quality 85 + :width 256 + :height 256 + :input {:path (fs/path (:tempfile file)) + :mtype (:content-type file)}}) + name (str prefix (cm/format->extension (:format thumb)))] + (ust/save! mst/media-storage name (:data thumb)))) + + +;; --- Mutation: Invite Member + +(s/def ::email ::us/email) +(s/def ::invite-team-member + (s/keys :req-un [::profile-id ::team-id ::email ::role])) + +(sm/defmutation ::invite-team-member + [{:keys [profile-id team-id email role] :as params}] + (db/with-atomic [conn db/pool] + (let [perms (teams/check-edition-permissions! conn profile-id team-id) + profile (db/get-by-id conn :profile profile-id) + member (profile/retrieve-profile-data-by-email conn email) + team (db/get-by-id conn :team team-id) + token (tokens/generate + {:iss :team-invitation + :exp (dt/in-future "24h") + :profile-id (:id profile) + :role role + :team-id team-id + :member-email (:email member email) + :member-id (:id member)})] + + (when-not (:is-admin perms) + (ex/raise :type :validation + :code :insufficient-permissions)) + + (emails/send! conn emails/invite-to-team + {:to email + :invited-by (:fullname profile) + :team (:name team) + :token token}) + nil))) diff --git a/backend/src/app/services/mutations/verify_token.clj b/backend/src/app/services/mutations/verify_token.clj new file mode 100644 index 0000000000..638a048794 --- /dev/null +++ b/backend/src/app/services/mutations/verify_token.clj @@ -0,0 +1,143 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.services.mutations.verify-token + (:require + [app.common.exceptions :as ex] + [app.common.media :as cm] + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.config :as cfg] + [app.db :as db] + [app.emails :as emails] + [app.http.session :as session] + [app.media :as media] + [app.media-storage :as mst] + [app.services.mutations :as sm] + [app.services.mutations.teams :as teams] + [app.services.queries.profile :as profile] + [app.services.tokens :as tokens] + [app.tasks :as tasks] + [app.util.blob :as blob] + [app.util.storage :as ust] + [app.util.time :as dt] + [buddy.hashers :as hashers] + [clojure.spec.alpha :as s] + [cuerdas.core :as str])) + +(defmulti process-token (fn [conn params claims] (:iss claims))) + +(s/def ::verify-token + (s/keys :req-un [::token] + :opt-un [::profile-id])) + +(sm/defmutation ::verify-token + [{:keys [token] :as params}] + (db/with-atomic [conn db/pool] + (let [claims (tokens/verify token)] + (process-token conn params claims)))) + +(defmethod process-token :change-email + [conn params {:keys [profile-id email] :as claims}] + (let [profile (db/get-by-id conn :profile profile-id {:for-update true})] + (when (profile/retrieve-profile-data-by-email conn email) + (ex/raise :type :validation + :code :email-already-exists)) + (db/update! conn :profile + {:email email} + {:id profile-id}) + claims)) + +(defmethod process-token :verify-email + [conn params {:keys [profile-id] :as claims}] + (let [profile (db/get-by-id conn :profile profile-id {:for-update true})] + (when (:is-active profile) + (ex/raise :type :validation + :code :email-already-validated)) + (when (not= (:email profile) + (:email claims)) + (ex/raise :type :validation + :code :invalid-token)) + + (db/update! conn :profile + {:is-active true} + {:id (:id profile)}) + claims)) + +(defmethod process-token :auth + [conn params {:keys [profile-id] :as claims}] + (let [profile (profile/retrieve-profile conn profile-id)] + (assoc claims :profile profile))) + + +;; --- Team Invitation + +(s/def ::iss keyword?) +(s/def ::exp ::us/inst) + +(s/def :internal.tokens.team-invitation/profile-id ::us/uuid) +(s/def :internal.tokens.team-invitation/role ::us/keyword) +(s/def :internal.tokens.team-invitation/team-id ::us/uuid) +(s/def :internal.tokens.team-invitation/member-email ::us/email) +(s/def :internal.tokens.team-invitation/member-id (s/nilable ::us/uuid)) + +(s/def ::team-invitation-claims + (s/keys :req-un [::iss ::exp + :internal.tokens.team-invitation/profile-id + :internal.tokens.team-invitation/role + :internal.tokens.team-invitation/team-id + :internal.tokens.team-invitation/member-email] + :opt-un [:internal.tokens.team-invitation/member-id])) + +(defmethod process-token :team-invitation + [conn {:keys [profile-id token]} {:keys [member-id team-id role] :as claims}] + (us/assert ::team-invitation-claims claims) + (if (uuid? member-id) + (let [params (merge {:team-id team-id + :profile-id member-id} + (teams/role->params role)) + claims (assoc claims :state :created)] + (db/insert! conn :team-profile-rel params) + (if (and (uuid? profile-id) + (= member-id profile-id)) + ;; If the current session is already matches the invited + ;; member, then just return the token and leave the frontend + ;; app redirect to correct team. + claims + + ;; If the session does not matches the invited member id, + ;; replace the session with a new one matching the invited + ;; member. This techinique should be considered secure because + ;; the user clicking the link he already has access to the + ;; email account. + (with-meta claims + {:transform-response + (fn [request response] + (let [uagent (get-in request [:headers "user-agent"]) + id (session/create member-id uagent)] + (assoc response + :cookies (session/cookies id))))}))) + + ;; In this case, we waint until frontend app redirect user to + ;; registeration page, the user is correctly registered and the + ;; register mutation call us again with the same token to finally + ;; create the corresponding team-profile relation from the first + ;; condition of this if. + (assoc claims + :token token + :state :pending))) + + +;; --- Default + +(defmethod process-token :default + [conn params claims] + (ex/raise :type :validation + :code :invalid-token)) + diff --git a/backend/src/app/services/queries/colors.clj b/backend/src/app/services/queries/colors.clj deleted file mode 100644 index 881cff34b6..0000000000 --- a/backend/src/app/services/queries/colors.clj +++ /dev/null @@ -1,104 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns app.services.queries.colors - (:require - [clojure.spec.alpha :as s] - [promesa.core :as p] - [promesa.exec :as px] - [app.common.exceptions :as ex] - [app.common.spec :as us] - [app.common.uuid :as uuid] - [app.db :as db] - [app.services.queries :as sq] - [app.services.queries.teams :as teams] - [app.util.blob :as blob] - [app.util.data :as data])) - -;; --- Helpers & Specs - -(s/def ::id ::us/uuid) -(s/def ::profile-id ::us/uuid) -(s/def ::team-id ::us/uuid) -(s/def ::file-id ::us/uuid) - - -;; --- Query: Colors (by file) - -(declare retrieve-colors) -(declare retrieve-file) - -(s/def ::colors - (s/keys :req-un [::profile-id ::file-id])) - -(sq/defquery ::colors - [{:keys [profile-id file-id] :as params}] - (db/with-atomic [conn db/pool] - (let [file (retrieve-file conn file-id)] - (teams/check-read-permissions! conn profile-id (:team-id file)) - (retrieve-colors conn file-id)))) - -(def ^:private sql:colors - "select * - from color - where color.deleted_at is null - and color.file_id = ? - order by created_at desc") - -(defn- retrieve-colors - [conn file-id] - (db/exec! conn [sql:colors file-id])) - -(def ^:private sql:retrieve-file - "select file.*, - project.team_id as team_id - from file - inner join project on (project.id = file.project_id) - where file.id = ?") - -(defn- retrieve-file - [conn id] - (let [row (db/exec-one! conn [sql:retrieve-file id])] - (when-not row - (ex/raise :type :not-found)) - row)) - - -;; --- Query: Color (by ID) - -(declare retrieve-color) - -(s/def ::id ::us/uuid) -(s/def ::color - (s/keys :req-un [::profile-id ::id])) - -(sq/defquery ::color - [{:keys [profile-id id] :as params}] - (db/with-atomic [conn db/pool] - (let [color (retrieve-color conn id)] - (teams/check-read-permissions! conn profile-id (:team-id color)) - color))) - -(def ^:private sql:single-color - "select color.*, - p.team_id as team_id - from color as color - inner join file as f on (color.file_id = f.id) - inner join project as p on (p.id = f.project_id) - where color.deleted_at is null - and color.id = ? - order by created_at desc") - -(defn retrieve-color - [conn id] - (let [row (db/exec-one! conn [sql:single-color id])] - (when-not row - (ex/raise :type :not-found)) - row)) - diff --git a/backend/src/app/services/queries/comments.clj b/backend/src/app/services/queries/comments.clj new file mode 100644 index 0000000000..5b000b2123 --- /dev/null +++ b/backend/src/app/services/queries/comments.clj @@ -0,0 +1,109 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.services.queries.comments + (:require + [app.common.exceptions :as ex] + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.config :as cfg] + [app.db :as db] + [app.services.queries :as sq] + [app.services.queries.files :as files] + [app.util.time :as dt] + [app.util.transit :as t] + [clojure.spec.alpha :as s] + [datoteka.core :as fs] + [promesa.core :as p])) + +(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)))) + +;; --- Query: Comment Threads + +(declare retrieve-comment-threads) + +(s/def ::file-id ::us/uuid) +(s/def ::comment-threads + (s/keys :req-un [::profile-id ::file-id])) + +(sq/defquery ::comment-threads + [{:keys [profile-id file-id] :as params}] + (with-open [conn (db/open)] + (files/check-read-permissions! conn profile-id file-id) + (retrieve-comment-threads conn params))) + +(def sql:comment-threads + "select distinct on (ct.id) + ct.*, + 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) + 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]}] + (->> (db/exec! conn [sql:comment-threads profile-id file-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])) + +(sq/defquery ::comment-thread + [{:keys [profile-id file-id id] :as params}] + (with-open [conn (db/open)] + (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))))) + + +;; --- 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])) + +(sq/defquery ::comments + [{:keys [profile-id thread-id] :as params}] + (with-open [conn (db/open)] + (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)))) + +(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)))) diff --git a/backend/src/app/services/queries/files.clj b/backend/src/app/services/queries/files.clj index 61665dff72..0aa6b134d2 100644 --- a/backend/src/app/services/queries/files.clj +++ b/backend/src/app/services/queries/files.clj @@ -33,6 +33,61 @@ (s/def ::team-id ::us/uuid) (s/def ::search-term ::us/string) + +;; --- Query: File Permissions + +(def ^:private sql:file-permissions + "select fpr.is_owner, + fpr.is_admin, + fpr.can_edit + from file_profile_rel as fpr + where fpr.file_id = ? + and fpr.profile_id = ? + union all + select tpr.is_owner, + tpr.is_admin, + tpr.can_edit + from team_profile_rel as tpr + inner join project as p on (p.team_id = tpr.team_id) + inner join file as f on (p.id = f.project_id) + where f.id = ? + and tpr.profile_id = ? + union all + select ppr.is_owner, + ppr.is_admin, + ppr.can_edit + from project_profile_rel as ppr + inner join file as f on (f.project_id = ppr.project_id) + where f.id = ? + and ppr.profile_id = ?") + +(defn check-edition-permissions! + [conn profile-id file-id] + (let [rows (db/exec! conn [sql:file-permissions + file-id profile-id + file-id profile-id + file-id profile-id])] + (when (empty? rows) + (ex/raise :type :not-found)) + + (when-not (or (some :can-edit rows) + (some :is-admin rows) + (some :is-owner rows)) + (ex/raise :type :validation + :code :not-authorized)))) + + +(defn check-read-permissions! + [conn profile-id file-id] + (let [rows (db/exec! conn [sql:file-permissions + file-id profile-id + file-id profile-id + file-id profile-id])] + (when-not (seq rows) + (ex/raise :type :validation + :code :not-authorized)))) + + ;; --- Query: Files search ;; TODO: this query need to a good refactor @@ -99,52 +154,8 @@ (sq/defquery ::files [{:keys [profile-id project-id] :as params}] (with-open [conn (db/open)] - (let [project (db/get-by-id conn :project project-id)] - (projects/check-edition-permissions! conn profile-id project) - (into [] decode-row-xf (db/exec! conn [sql:files project-id]))))) - - -;; --- Query: File Permissions - -(def ^:private sql:file-permissions - "select fpr.is_owner, - fpr.is_admin, - fpr.can_edit - from file_profile_rel as fpr - where fpr.file_id = ? - and fpr.profile_id = ? - union all - select tpr.is_owner, - tpr.is_admin, - tpr.can_edit - from team_profile_rel as tpr - inner join project as p on (p.team_id = tpr.team_id) - inner join file as f on (p.id = f.project_id) - where f.id = ? - and tpr.profile_id = ? - union all - select ppr.is_owner, - ppr.is_admin, - ppr.can_edit - from project_profile_rel as ppr - inner join file as f on (f.project_id = ppr.project_id) - where f.id = ? - and ppr.profile_id = ?") - -(defn check-edition-permissions! - [conn profile-id file-id] - (let [rows (db/exec! conn [sql:file-permissions - file-id profile-id - file-id profile-id - file-id profile-id])] - (when (empty? rows) - (ex/raise :type :not-found)) - - (when-not (or (some :can-edit rows) - (some :is-admin rows) - (some :is-owner rows)) - (ex/raise :type :validation - :code :not-authorized)))) + (projects/check-read-permissions! conn profile-id project-id) + (into [] decode-row-xf (db/exec! conn [sql:files project-id])))) ;; --- Query: File (By ID) diff --git a/backend/src/app/services/queries/profile.clj b/backend/src/app/services/queries/profile.clj index 5ffd10cef6..29b64583e1 100644 --- a/backend/src/app/services/queries/profile.clj +++ b/backend/src/app/services/queries/profile.clj @@ -2,11 +2,15 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) 2016 Andrey Antukh +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL (ns app.services.queries.profile (:require [clojure.spec.alpha :as s] + [cuerdas.core :as str] [app.common.exceptions :as ex] [app.common.spec :as us] [app.db :as db] @@ -53,16 +57,16 @@ (def ^:private sql:default-team-and-project "select t.id from team as t - inner join team_profile_rel as tpr on (tpr.team_id = t.id) - where tpr.profile_id = ? - and tpr.is_owner is true + inner join team_profile_rel as tp on (tp.team_id = t.id) + where tp.profile_id = ? + and tp.is_owner is true and t.is_default is true union all select p.id from project as p - inner join project_profile_rel as tpr on (tpr.project_id = p.id) - where tpr.profile_id = ? - and tpr.is_owner is true + inner join project_profile_rel as tp on (tp.project_id = p.id) + where tp.profile_id = ? + and tp.is_owner is true and p.is_default is true") (defn retrieve-additional-data @@ -87,6 +91,18 @@ profile)) + +(def sql:profile-by-email + "select * from profile + where email=? + and deleted_at is null") + +(defn retrieve-profile-data-by-email + [conn email] + (let [email (str/lower email)] + (db/exec-one! conn [sql:profile-by-email email]))) + + ;; --- Attrs Helpers (defn strip-private-attrs diff --git a/backend/src/app/services/queries/projects.clj b/backend/src/app/services/queries/projects.clj index 688549d38a..892a122b53 100644 --- a/backend/src/app/services/queries/projects.clj +++ b/backend/src/app/services/queries/projects.clj @@ -18,41 +18,46 @@ ;; --- Check Project Permissions -;; This SQL checks if the: (1) project is part of the team where the -;; profile has edition permissions or (2) the profile has direct -;; edition access granted to this project. - -(def sql:project-permissions - "select tp.can_edit, - tp.is_admin, - tp.is_owner - from team_profile_rel as tp - where tp.profile_id = ? - and tp.team_id = ? - union - select pp.can_edit, - pp.is_admin, - pp.is_owner - from project_profile_rel as pp - where pp.profile_id = ? - and pp.project_id = ?;") +(def ^:private sql:project-permissions + "select tpr.is_owner, + tpr.is_admin, + tpr.can_edit + from team_profile_rel as tpr + inner join project as p on (p.team_id = tpr.team_id) + where p.id = ? + and tpr.profile_id = ? + union all + select ppr.is_owner, + ppr.is_admin, + ppr.can_edit + from project_profile_rel as ppr + where ppr.project_id = ? + and ppr.profile_id = ?") (defn check-edition-permissions! - [conn profile-id project] + [conn profile-id project-id] (let [rows (db/exec! conn [sql:project-permissions - profile-id - (:team-id project) - profile-id - (:id project)])] + project-id profile-id + project-id profile-id])] (when (empty? rows) (ex/raise :type :not-found)) - (when-not (or (some :can-edit rows) (some :is-admin rows) (some :is-owner rows)) (ex/raise :type :validation :code :not-authorized)))) +(defn check-read-permissions! + [conn profile-id project-id] + (let [rows (db/exec! conn [sql:project-permissions + project-id profile-id + project-id profile-id])] + + (when-not (seq rows) + (ex/raise :type :validation + :code :not-authorized)))) + + ;; --- Query: Projects @@ -60,7 +65,6 @@ (s/def ::team-id ::us/uuid) (s/def ::profile-id ::us/uuid) - (s/def ::projects (s/keys :req-un [::profile-id ::team-id])) @@ -68,31 +72,37 @@ [{:keys [profile-id team-id]}] (with-open [conn (db/open)] (teams/check-read-permissions! conn profile-id team-id) - (retrieve-projects conn team-id))) + (retrieve-projects conn profile-id team-id))) (def sql:projects "select p.*, + coalesce(tpp.is_pinned, false) as is_pinned, (select count(*) from file as f - where f.project_id = p.id - and deleted_at is null) + where f.project_id = p.id + and deleted_at is null) as count from project as p + left join team_project_profile_rel as tpp + on (tpp.project_id = p.id and + tpp.team_id = p.team_id and + tpp.profile_id = ?) where p.team_id = ? and p.deleted_at is null order by p.modified_at desc") (defn retrieve-projects - [conn team-id] - (db/exec! conn [sql:projects team-id])) + [conn profile-id team-id] + (db/exec! conn [sql:projects profile-id team-id])) -;; --- Query: Projec by ID -(s/def ::project-id ::us/uuid) -(s/def ::project-by-id - (s/keys :req-un [::profile-id ::project-id])) +;; --- Query: Project -(sq/defquery ::project-by-id - [{:keys [profile-id project-id]}] +(s/def ::id ::us/uuid) +(s/def ::project + (s/keys :req-un [::profile-id ::id])) + +(sq/defquery ::project + [{:keys [profile-id id]}] (with-open [conn (db/open)] - (let [project (db/get-by-id conn :project project-id)] - (check-edition-permissions! conn profile-id project) + (let [project (db/get-by-id conn :project id)] + (check-read-permissions! conn profile-id id) project))) diff --git a/backend/src/app/services/queries/recent_files.clj b/backend/src/app/services/queries/recent_files.clj index 6e16dacc89..159bd3025c 100644 --- a/backend/src/app/services/queries/recent_files.clj +++ b/backend/src/app/services/queries/recent_files.clj @@ -18,19 +18,18 @@ [app.services.queries.projects :as projects :refer [retrieve-projects]] [app.services.queries.files :refer [decode-row-xf]])) -(def sql:project-recent-files - "select f.* - from file as f - where f.project_id = ? - and f.deleted_at is null - order by f.modified_at desc - limit 5") - -(defn recent-by-project - [conn profile-id project] - (let [project-id (:id project)] - (projects/check-edition-permissions! conn profile-id project) - (into [] decode-row-xf (db/exec! conn [sql:project-recent-files project-id])))) +(def sql:recent-files + "with recent_files as ( + select f.*, row_number() over w as row_num + from file as f + join project as p on (p.id = f.project_id) + where p.team_id = ? + and p.deleted_at is null + and f.deleted_at is null + window w as (partition by f.project_id order by f.modified_at desc) + order by f.modified_at desc + ) + select * from recent_files where row_num <= 6;") (s/def ::team-id ::us/uuid) (s/def ::profile-id ::us/uuid) @@ -42,9 +41,7 @@ [{:keys [profile-id team-id]}] (with-open [conn (db/open)] (teams/check-read-permissions! conn profile-id team-id) - (->> (retrieve-projects conn team-id) - ;; Retrieve for each proyect the 5 more recent files - (map (partial recent-by-project conn profile-id)) - ;; Change the structure so it's a map with project-id as keys - (flatten) - (group-by :project-id)))) + (let [files (db/exec! conn [sql:recent-files team-id])] + (into [] decode-row-xf files)))) + + diff --git a/backend/src/app/services/queries/teams.clj b/backend/src/app/services/queries/teams.clj index 4d86477e11..cddd8b5ab8 100644 --- a/backend/src/app/services/queries/teams.clj +++ b/backend/src/app/services/queries/teams.clj @@ -15,6 +15,7 @@ [app.common.uuid :as uuid] [app.db :as db] [app.services.queries :as sq] + [app.services.queries.profile :as profile] [app.util.blob :as blob])) ;; --- Team Edition Permissions @@ -34,7 +35,8 @@ (:is-admin row) (:is-owner row)) (ex/raise :type :validation - :code :not-authorized)))) + :code :not-authorized)) + row)) (defn check-read-permissions! [conn profile-id team-id] @@ -42,4 +44,89 @@ ;; when row is found this means that read permission is granted. (when-not row (ex/raise :type :validation - :code :not-authorized)))) + :code :not-authorized)) + row)) + + +;; --- Query: Teams + +(declare retrieve-teams) + +(s/def ::profile-id ::us/uuid) +(s/def ::teams + (s/keys :req-un [::profile-id])) + +(sq/defquery ::teams + [{:keys [profile-id]}] + (with-open [conn (db/open)] + (retrieve-teams conn profile-id))) + +(def sql:teams + "select t.*, + tp.is_owner, + tp.is_admin, + tp.can_edit, + (t.id = ?) as is_default + from team_profile_rel as tp + join team as t on (t.id = tp.team_id) + where t.deleted_at is null + and tp.profile_id = ? + order by t.created_at asc") + +(defn retrieve-teams + [conn profile-id] + (let [defaults (profile/retrieve-additional-data conn profile-id)] + (db/exec! conn [sql:teams (:default-team-id defaults) profile-id]))) + +;; --- Query: Team (by ID) + +(declare retrieve-team) + +(s/def ::id ::us/uuid) +(s/def ::team + (s/keys :req-un [::profile-id ::id])) + +(sq/defquery ::team + [{:keys [profile-id id]}] + (with-open [conn (db/open)] + (retrieve-team conn profile-id id))) + +(defn retrieve-team + [conn profile-id team-id] + (let [defaults (profile/retrieve-additional-data conn profile-id) + sql (str "WITH teams AS (" sql:teams ") SELECT * FROM teams WHERE id=?") + result (db/exec-one! conn [sql (:default-team-id defaults) profile-id team-id])] + (when-not result + (ex/raise :type :not-found + :code :object-does-not-exists)) + result)) + + +;; --- Query: Team Members + +(declare retrieve-team-members) + +(s/def ::team-id ::us/uuid) +(s/def ::team-members + (s/keys :req-un [::profile-id ::team-id])) + +(sq/defquery ::team-members + [{:keys [profile-id team-id]}] + (with-open [conn (db/open)] + (check-edition-permissions! conn profile-id team-id) + (retrieve-team-members conn team-id))) + +(def sql:team-members + "select tp.*, + p.id, + p.email, + p.fullname as name, + p.photo, + p.is_active + from team_profile_rel as tp + join profile as p on (p.id = tp.profile_id) + where tp.team_id = ?") + +(defn retrieve-team-members + [conn team-id] + (db/exec! conn [sql:team-members team-id])) diff --git a/backend/tests/app/tests/helpers.clj b/backend/tests/app/tests/helpers.clj index a1c22e73ee..c99294344f 100644 --- a/backend/tests/app/tests/helpers.clj +++ b/backend/tests/app/tests/helpers.clj @@ -22,7 +22,6 @@ [app.services.mutations.projects :as projects] [app.services.mutations.teams :as teams] [app.services.mutations.files :as files] - [app.services.mutations.colors :as colors] [app.migrations] [app.media] [app.media-storage] diff --git a/common/app/common/data.cljc b/common/app/common/data.cljc index fe84e3f832..c938e0b757 100644 --- a/common/app/common/data.cljc +++ b/common/app/common/data.cljc @@ -6,7 +6,7 @@ (ns app.common.data "Data manipulation and query helper functions." - (:refer-clojure :exclude [concat read-string]) + (:refer-clojure :exclude [concat read-string hash-map]) (:require [clojure.set :as set] [linked.set :as lks] #?(:cljs [cljs.reader :as r] @@ -109,7 +109,7 @@ ([coll value] (sequence (replace-by-id value) coll))) -(defn remove-nil-vals +(defn without-nils "Given a map, return a map removing key-value pairs when value is `nil`." [data] diff --git a/common/app/common/exceptions.cljc b/common/app/common/exceptions.cljc index ee1370279f..2abd014260 100644 --- a/common/app/common/exceptions.cljc +++ b/common/app/common/exceptions.cljc @@ -25,9 +25,24 @@ [& {:keys [type code message hint cause] :as params}] (s/assert ::error-params params) (let [message (or message hint "") - payload (dissoc params :cause :message)] + payload (dissoc params :cause)] (ex-info message payload cause))) (defmacro raise [& args] `(throw (error ~@args))) + +(defn try* + [f on-error] + (try (f) (catch #?(:clj Exception :cljs :default) e (on-error e)))) + +;; http://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/ +;; Explains the use of ^:once metadata + +(defmacro ignoring + [& exprs] + `(try* (^:once fn* [] ~@exprs) (constantly nil))) + +(defmacro try + [& exprs] + `(try* (^:once fn* [] ~@exprs) identity)) diff --git a/common/app/common/geom/point.cljc b/common/app/common/geom/point.cljc index b523dae2c5..65b453b561 100644 --- a/common/app/common/geom/point.cljc +++ b/common/app/common/geom/point.cljc @@ -199,3 +199,27 @@ (defn center-points [points] (let [k (point (count points))] (reduce #(add %1 (divide %2 k)) (point) points))) + +(defn normal-left + "Returns the normal unit vector on the left side" + [{:keys [x y]}] + (unit (point (- y) x))) + +(defn normal-right + "Returns the normal unit vector on the right side" + [{:keys [x y]}] + (unit (point y (- x)))) + +(defn point-line-distance + "Returns the distance from a point to a line defined by two points" + [point line-point1 line-point2] + (let [{x0 :x y0 :y} point + {x1 :x y1 :y} line-point1 + {x2 :x y2 :y} line-point2 + num (mth/abs + (+ (* x0 (- y2 y1)) + (- (* y0 (- x2 x1))) + (* x2 y1) + (- (* y2 x1)))) + dist (distance line-point2 line-point1)] + (/ num dist))) diff --git a/common/app/common/geom/shapes.cljc b/common/app/common/geom/shapes.cljc index 83fbd97911..ed053344f3 100644 --- a/common/app/common/geom/shapes.cljc +++ b/common/app/common/geom/shapes.cljc @@ -11,7 +11,6 @@ (:require [clojure.spec.alpha :as s] [app.common.spec :as us] - [app.common.pages-helpers :as cph] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.math :as mth] @@ -58,10 +57,17 @@ (update :points #(mapv inc-point %)) (update :segments #(mapv inc-point %))))) +;; Duplicated from pages-helpers to remove cyclic dependencies +(defn get-children [id objects] + (let [shapes (vec (get-in objects [id :shapes]))] + (if shapes + (d/concat shapes (mapcat #(get-children % objects) shapes)) + []))) + (defn recursive-move "Move the shape and all its recursive children." [shape dpoint objects] - (let [children-ids (cph/get-children (:id shape) objects) + (let [children-ids (get-children (:id shape) objects) children (map #(get objects %) children-ids)] (map #(move % dpoint) (cons shape children)))) @@ -406,6 +412,10 @@ :y miny :width (- maxx minx) :height (- maxy miny) + :points [(gpt/point minx miny) + (gpt/point maxx miny) + (gpt/point maxx maxy) + (gpt/point minx maxy)] :type :rect})) (defn translate-to-frame @@ -674,7 +684,7 @@ resize-transform (:resize-transform modifiers (gmt/matrix)) resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix)) - rt-modif (:rotation modifiers 0) + rt-modif (or (:rotation modifiers) 0) shape (-> shape (transform ds-modifier)) @@ -792,8 +802,8 @@ (assoc $ :points (shape->points $)) (assoc $ :selrect (points->selrect (:points $))) (update $ :selrect fix-invalid-rect-values) - (update $ :rotation #(mod (+ (or % 0) (get-in $ [:modifiers :rotation] 0)) 360)) - )] + (update $ :rotation #(mod (+ (or % 0) + (or (get-in $ [:modifiers :rotation]) 0)) 360)))] new-shape)) (declare update-path-selrect) diff --git a/common/app/common/math.cljc b/common/app/common/math.cljc index d49bcf42cf..9125c7c351 100644 --- a/common/app/common/math.cljc +++ b/common/app/common/math.cljc @@ -12,6 +12,10 @@ #?(:cljs (:require [goog.math :as math]))) +(def PI + #?(:cljs (.-PI js/Math) + :clj Math/PI)) + (defn nan? [v] #?(:cljs (js/isNaN v) diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 6aa325c367..348d98b47b 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -46,6 +46,7 @@ (<= % max-safe-int))) (s/def ::component-id uuid?) (s/def ::component-file uuid?) +(s/def ::component-root? boolean?) (s/def ::shape-ref uuid?) (s/def ::safe-number @@ -54,6 +55,93 @@ (>= % min-safe-int) (<= % max-safe-int))) +;; GRADIENTS + +(s/def :internal.gradient.stop/color ::string) +(s/def :internal.gradient.stop/opacity ::safe-number) +(s/def :internal.gradient.stop/offset ::safe-number) + +(s/def :internal.gradient/type #{:linear :radial}) +(s/def :internal.gradient/start-x ::safe-number) +(s/def :internal.gradient/start-y ::safe-number) +(s/def :internal.gradient/end-x ::safe-number) +(s/def :internal.gradient/end-y ::safe-number) +(s/def :internal.gradient/width ::safe-number) + +(s/def :internal.gradient/stop + (s/keys :req-un [:internal.gradient.stop/color + :internal.gradient.stop/opacity + :internal.gradient.stop/offset])) + +(s/def :internal.gradient/stops + (s/coll-of :internal.gradient/stop :kind vector?)) + +(s/def ::gradient + (s/keys :req-un [:internal.gradient/type + :internal.gradient/start-x + :internal.gradient/start-y + :internal.gradient/end-x + :internal.gradient/end-y + :internal.gradient/width + :internal.gradient/stops])) + + +;;; COLORS + +(s/def :internal.color/name ::string) +(s/def :internal.color/value (s/nilable ::string)) +(s/def :internal.color/color (s/nilable ::string)) +(s/def :internal.color/opacity (s/nilable ::safe-number)) +(s/def :internal.color/gradient (s/nilable ::gradient)) + +(s/def ::color + (s/keys :opt-un [::id + :internal.color/name + :internal.color/value + :internal.color/color + :internal.color/opacity + :internal.color/gradient])) + + + +;;; SHADOW EFFECT + +(s/def :internal.shadow/id uuid?) +(s/def :internal.shadow/style #{:drop-shadow :inner-shadow}) +(s/def :internal.shadow/color ::color) +(s/def :internal.shadow/offset-x ::safe-number) +(s/def :internal.shadow/offset-y ::safe-number) +(s/def :internal.shadow/blur ::safe-number) +(s/def :internal.shadow/spread ::safe-number) +(s/def :internal.shadow/hidden boolean?) + +(s/def :internal.shadow/shadow + (s/keys :req-un [:internal.shadow/id + :internal.shadow/style + :internal.shadow/color + :internal.shadow/offset-x + :internal.shadow/offset-y + :internal.shadow/blur + :internal.shadow/spread + :internal.shadow/hidden])) + +(s/def ::shadow + (s/coll-of :internal.shadow/shadow :kind vector?)) + + +;;; BLUR EFFECT + +(s/def :internal.blur/id uuid?) +(s/def :internal.blur/type #{:layer-blur}) +(s/def :internal.blur/value ::safe-number) +(s/def :internal.blur/hidden boolean?) + +(s/def ::blur + (s/keys :req-un [:internal.blur/id + :internal.blur/type + :internal.blur/value + :internal.blur/hidden])) + ;; Page Options (s/def :internal.page.grid.color/value string?) (s/def :internal.page.grid.color/opacity ::safe-number) @@ -109,10 +197,13 @@ (s/def :internal.shape/blocked boolean?) (s/def :internal.shape/collapsed boolean?) (s/def :internal.shape/content any?) + (s/def :internal.shape/fill-color string?) +(s/def :internal.shape/fill-opacity ::safe-number) +(s/def :internal.shape/fill-gradient (s/nilable ::gradient)) (s/def :internal.shape/fill-color-ref-file (s/nilable uuid?)) (s/def :internal.shape/fill-color-ref-id (s/nilable uuid?)) -(s/def :internal.shape/fill-opacity ::safe-number) + (s/def :internal.shape/font-family string?) (s/def :internal.shape/font-size ::safe-integer) (s/def :internal.shape/font-style string?) @@ -141,6 +232,8 @@ (s/def :internal.shape/width ::safe-number) (s/def :internal.shape/height ::safe-number) (s/def :internal.shape/index integer?) +(s/def :internal.shape/shadow ::shadow) +(s/def :internal.shape/blur ::blur) (s/def :internal.shape/x1 ::safe-number) (s/def :internal.shape/y1 ::safe-number) @@ -211,7 +304,36 @@ :internal.shape/height :internal.shape/interactions :internal.shape/selrect - :internal.shape/points])) + :internal.shape/points + :internal.shape/masked-group? + :internal.shape/shadow + :internal.shape/blur])) + +(def component-sync-attrs {:fill-color :fill-group + :fill-color-ref-file :fill-group + :fill-color-ref-id :fill-group + :fill-opacity :fill-group + :content :text-content-group + :font-family :text-font-group + :font-size :text-font-group + :font-style :text-font-group + :font-weight :text-font-group + :letter-spacing :text-display-group + :line-height :text-display-group + :text-align :text-display-group + :stroke-color :stroke-group + :stroke-color-ref-file :stroke-group + :stroke-color-ref-id :stroke-group + :stroke-opacity :stroke-group + :stroke-style :stroke-group + :stroke-width :stroke-group + :stroke-alignment :stroke-group + :width :size-group + :height :size-group + :proportion :size-group + :rx :radius-group + :ry :radius-group + :masked-group? :mask-group}) (s/def ::minimal-shape (s/keys :req-un [::type ::name] @@ -222,6 +344,7 @@ (s/keys :opt-un [::id ::component-id ::component-file + ::component-root? ::shape-ref]))) (s/def :internal.page/objects (s/map-of uuid? ::shape)) @@ -232,13 +355,12 @@ :internal.page/options :internal.page/objects])) -(s/def :internal.color/name ::string) -(s/def :internal.color/value ::string) -(s/def ::color - (s/keys :req-un [::id - :internal.color/name - :internal.color/value])) +(s/def ::recent-color + (s/keys :opt-un [:internal.color/value + :internal.color/color + :internal.color/opacity + :internal.color/gradient])) (s/def :internal.media-object/name ::string) (s/def :internal.media-object/path ::string) @@ -264,7 +386,32 @@ (s/map-of ::uuid ::color)) (s/def :internal.file/recent-colors - (s/coll-of ::string :kind vector?)) + (s/coll-of ::recent-color :kind vector?)) + +(s/def :internal.typography/id ::id) +(s/def :internal.typography/name ::string) +(s/def :internal.typography/font-id ::string) +(s/def :internal.typography/font-family ::string) +(s/def :internal.typography/font-variant-id ::string) +(s/def :internal.typography/font-size ::string) +(s/def :internal.typography/font-weight ::string) +(s/def :internal.typography/font-style ::string) +(s/def :internal.typography/line-height ::string) +(s/def :internal.typography/letter-spacing ::string) +(s/def :internal.typography/text-transform ::string) + +(s/def ::typography + (s/keys :req-un [:internal.typography/id + :internal.typography/name + :internal.typography/font-id + :internal.typography/font-family + :internal.typography/font-variant-id + :internal.typography/font-size + :internal.typography/font-weight + :internal.typography/font-style + :internal.typography/line-height + :internal.typography/letter-spacing + :internal.typography/text-transform])) (s/def :internal.file/pages (s/coll-of ::uuid :kind vector?)) @@ -286,11 +433,16 @@ (s/def :internal.operations.set/attr keyword?) (s/def :internal.operations.set/val any?) +(s/def :internal.operations.set/touched + (s/nilable (s/every keyword? :kind set?))) (defmethod operation-spec :set [_] (s/keys :req-un [:internal.operations.set/attr :internal.operations.set/val])) +(defmethod operation-spec :set-touched [_] + (s/keys :req-un [:internal.operations.set/touched])) + (defmulti change-spec :type) (s/def :internal.changes.set-option/option any?) @@ -311,7 +463,7 @@ (s/def ::operations (s/coll-of ::operation)) (defmethod change-spec :mod-obj [_] - (s/keys :req-un [::id ::page-id ::operations])) + (s/keys :req-un [::id (or ::page-id ::component-id) ::operations])) (defmethod change-spec :del-obj [_] (s/keys :req-un [::id ::page-id])) @@ -348,8 +500,10 @@ (defmethod change-spec :del-color [_] (s/keys :req-un [::id])) +(s/def :internal.changes.add-recent-color/color ::recent-color) + (defmethod change-spec :add-recent-color [_] - (s/keys :req-un [:recent-color/color])) + (s/keys :req-un [:internal.changes.add-recent-color/color])) (s/def :internal.changes.media/object ::media-object) @@ -374,6 +528,17 @@ (defmethod change-spec :del-component [_] (s/keys :req-un [::id])) +(s/def :internal.changes.typography/typography ::typography) + +(defmethod change-spec :add-typography [_] + (s/keys :req-un [:internal.changes.typography/typography])) + +(defmethod change-spec :mod-typography [_] + (s/keys :req-un [:internal.changes.typography/typography])) + +(defmethod change-spec :del-typography [_] + (s/keys :req-un [:internal.typography/id])) + (s/def ::change (s/multi-spec change-spec :type)) (s/def ::changes (s/coll-of ::change)) @@ -559,12 +724,14 @@ :else (cph/insert-at-index shapes index [id])))))))))))) (defmethod process-change :mod-obj - [data {:keys [id page-id operations] :as change}] - (d/update-in-when data [:pages-index page-id :objects] - (fn [objects] - (if-let [obj (get objects id)] - (assoc objects id (reduce process-operation obj operations)) - objects)))) + [data {:keys [id page-id component-id operations] :as change}] + (let [update-fn (fn [objects] + (if-let [obj (get objects id)] + (assoc objects id (reduce process-operation obj operations)) + objects))] + (if page-id + (d/update-in-when data [:pages-index page-id :objects] update-fn) + (d/update-in-when data [:components component-id :objects] update-fn)))) (defmethod process-change :del-obj [data {:keys [page-id id] :as change}] @@ -616,7 +783,10 @@ (assoc :modifiers (rotation-modifiers gcenter % (- (:rotation group 0)))) (geom/transform-shape)))) - selrect (-> (into [] gxfm (:shapes group)) + inner-shapes (if (:masked-group? group) + [(first (:shapes group))] + (:shapes group)) + selrect (-> (into [] gxfm inner-shapes) (geom/selection-rect))] ;; Rotate the group shape change the data and rotate back again @@ -629,7 +799,6 @@ (d/update-in-when data [:pages-index page-id :objects] reg-objects))) - (defmethod process-change :mov-objects [data {:keys [parent-id shapes index page-id] :as change}] (letfn [(is-valid-move? [objects shape-id] @@ -641,34 +810,40 @@ (let [prev-shapes (or prev-shapes [])] (if index (cph/insert-at-index prev-shapes index shapes) - (reduce (fn [acc id] - (if (some #{id} acc) - acc - (conj acc id))) - prev-shapes - shapes)))) + (cph/append-at-the-end prev-shapes shapes)))) + + (check-insert-items [prev-shapes parent index shapes] + (if-not (:masked-group? parent) + (insert-items prev-shapes index shapes) + ;; For masked groups, the first shape is the mask + ;; and it cannot be moved. + (let [mask-id (first prev-shapes) + other-ids (rest prev-shapes) + not-mask-shapes (strip-id shapes mask-id) + new-index (if (nil? index) nil (max (dec index) 0)) + new-shapes (insert-items other-ids new-index not-mask-shapes)] + (d/concat [mask-id] new-shapes)))) (strip-id [coll id] (filterv #(not= % id) coll)) - (remove-from-old-parent [cpindex objects shape-id] - (let [prev-parent-id (get cpindex shape-id)] - ;; Do nothing if the parent id of the shape is the same as - ;; the new destination target parent id. - (if (= prev-parent-id parent-id) - objects - (loop [sid shape-id - pid prev-parent-id - objects objects] - (let [obj (get objects pid)] - (if (and (= 1 (count (:shapes obj))) - (= sid (first (:shapes obj))) - (= :group (:type obj))) - (recur pid - (:parent-id obj) - (dissoc objects pid)) - (update-in objects [pid :shapes] strip-id sid))))))) - + (remove-from-old-parent [cpindex objects shape-id] + (let [prev-parent-id (get cpindex shape-id)] + ;; Do nothing if the parent id of the shape is the same as + ;; the new destination target parent id. + (if (= prev-parent-id parent-id) + objects + (loop [sid shape-id + pid prev-parent-id + objects objects] + (let [obj (get objects pid)] + (if (and (= 1 (count (:shapes obj))) + (= sid (first (:shapes obj))) + (= :group (:type obj))) + (recur pid + (:parent-id obj) + (dissoc objects pid)) + (update-in objects [pid :shapes] strip-id sid))))))) (update-parent-id [objects id] (update objects id assoc :parent-id parent-id)) @@ -700,7 +875,7 @@ (if valid? (as-> objects $ - (update-in $ [parent-id :shapes] insert-items index shapes) + (update-in $ [parent-id :shapes] check-insert-items parent index shapes) (reduce update-parent-id $ shapes) (reduce (partial remove-from-old-parent cpindex) $ shapes) (reduce (partial update-frame-ids frm-id) $ (get-in $ [parent-id :shapes]))) @@ -748,7 +923,7 @@ (defmethod process-change :mod-color [data {:keys [color]}] - (d/update-in-when data [:colors (:id color)] merge color)) + (d/assoc-in-when data [:colors (:id color)] color)) (defmethod process-change :del-color [data {:keys [id]}] @@ -763,6 +938,8 @@ (subvec rc 1) rc))))) +;; -- Media + (defmethod process-change :add-media [data {:keys [object]}] (update data :media assoc (:id object) object)) @@ -775,6 +952,8 @@ [data {:keys [id]}] (update data :media dissoc id)) +;; -- Components + (defmethod process-change :add-component [data {:keys [id name shapes]}] (assoc-in data [:components id] @@ -793,16 +972,51 @@ [data {:keys [id]}] (d/dissoc-in data [:components id])) +;; -- Typography + +(defmethod process-change :add-typography + [data {:keys [typography]}] + (update data :typographies assoc (:id typography) typography)) + +(defmethod process-change :mod-typography + [data {:keys [typography]}] + (d/update-in-when data [:typographies (:id typography)] merge typography)) + +(defmethod process-change :del-typography + [data {:keys [id]}] + (update data :typographies dissoc id)) + +;; -- Operations + (defmethod process-operation :set [shape op] - (let [attr (:attr op) - val (:val op)] - (if (nil? val) - (dissoc shape attr) - (assoc shape attr val)))) + (let [attr (:attr op) + val (:val op) + ignore (:ignore-touched op) + shape-ref (:shape-ref shape) + group (get component-sync-attrs attr)] + + (cond-> shape + (and shape-ref group (not ignore) (not= val (get shape attr))) + (update :touched #(conj (or % #{}) group)) + + (nil? val) + (dissoc attr) + + (some? val) + (assoc attr val)))) + +(defmethod process-operation :set-touched + [shape op] + (let [touched (:touched op) + shape-ref (:shape-ref shape)] + (if (or (nil? shape-ref) (nil? touched) (empty? touched)) + (dissoc shape :touched) + (assoc shape :touched touched)))) (defmethod process-operation :default [shape op] (ex/raise :type :not-implemented :code :operation-not-implemented :context {:type (:type op)})) + diff --git a/common/app/common/pages_helpers.cljc b/common/app/common/pages_helpers.cljc index 53be7402a0..28f5a3b40a 100644 --- a/common/app/common/pages_helpers.cljc +++ b/common/app/common/pages_helpers.cljc @@ -10,7 +10,8 @@ (ns app.common.pages-helpers (:require [app.common.data :as d] - [app.common.uuid :as uuid])) + [app.common.uuid :as uuid] + [app.common.geom.shapes :as gsh])) (defn walk-pages "Go through all pages of a file and apply a function to each one" @@ -20,9 +21,10 @@ (update data :pages-index #(d/mapm f %))) (defn select-objects - "Get a list of all objects in a page that satisfy a condition" - [f page] - (filter f (vals (get page :objects)))) + "Get a list of all objects in a container (a page or a component) that + satisfy a condition" + [f container] + (filter f (vals (get container :objects)))) (defn update-object-list "Update multiple objects in a page at once" @@ -30,15 +32,15 @@ (update page :objects #(into % (d/index-by :id objects-list)))) -(defn get-root-component - "Get the root shape linked to the component for this shape, if any" - [id objects] - (let [obj (get objects id)] - (if-let [component-id (:component-id obj)] - id - (if-let [parent-id (:parent-id obj)] - (get-root-component parent-id obj) - nil)))) +(defn get-root-shape + "Get the root shape linked to a component for this shape, if any" + [shape objects] + (if (:component-root? shape) + shape + (if-let [parent-id (:parent-id shape)] + (get-root-shape (get objects (:parent-id shape)) + objects) + nil))) (defn get-children "Retrieve all children ids recursively for a given object" @@ -57,7 +59,7 @@ (defn get-object-with-children "Retrieve a list with an object and all of its children" [id objects] - (map #(get objects %) (concat [id] (get-children id objects)))) + (map #(get objects %) (cons id (get-children id objects)))) (defn is-shape-grouped "Checks if a shape is inside a group" @@ -115,6 +117,15 @@ ids (remove p? after)))) +(defn append-at-the-end + [prev-ids ids] + (reduce (fn [acc id] + (if (some #{id} acc) + acc + (conj acc id))) + prev-ids + ids)) + (defn select-toplevel-shapes ([objects] (select-toplevel-shapes objects nil)) ([objects {:keys [include-frames?] :or {include-frames? false}}] @@ -186,7 +197,7 @@ updated-object (update-original-object object new-object) - updated-objects (if (= object updated-object) + updated-objects (if (identical? object updated-object) updated-children (concat [updated-object] updated-children))] @@ -204,3 +215,44 @@ (concat new-children new-child-objects) (concat updated-children updated-child-objects)))))))) + +(defn indexed-shapes + "Retrieves a list with the indexes for each element in the layer tree. + This will be used for shift+selection." + [objects] + (let [rec-index + (fn rec-index [cur-idx id] + (let [object (get objects id) + red-fn + (fn [cur-idx id] + (let [[prev-idx _] (first cur-idx) + prev-idx (or prev-idx 0) + cur-idx (conj cur-idx [(inc prev-idx) id])] + (rec-index cur-idx id)))] + (reduce red-fn cur-idx (reverse (:shapes object)))))] + (into {} (rec-index '() uuid/zero)))) + + +(defn expand-region-selection + "Given a selection selects all the shapes between the first and last in + an indexed manner (shift selection)" + [objects selection] + (let [indexed-shapes (indexed-shapes objects) + filter-indexes (->> indexed-shapes + (filter (comp selection second)) + (map first)) + + from (apply min filter-indexes) + to (apply max filter-indexes)] + (->> indexed-shapes + (filter (fn [[idx _]] (and (>= idx from) (<= idx to)))) + (map second) + (into #{})))) + +(defn frame-id-by-position [objects position] + (let [frames (select-frames objects)] + (or + (->> frames + (d/seek #(gsh/has-point? % position)) + :id) + uuid/zero))) diff --git a/common/app/common/spec.cljc b/common/app/common/spec.cljc index 4b58b561a5..b9ab80199b 100644 --- a/common/app/common/spec.cljc +++ b/common/app/common/spec.cljc @@ -12,11 +12,16 @@ (:refer-clojure :exclude [assert]) #?(:cljs (:require-macros [app.common.spec :refer [assert]])) (:require - #?(:clj [clojure.spec.alpha :as s] + #?(:clj [clojure.spec.alpha :as s] :cljs [cljs.spec.alpha :as s]) + + #?(:clj [clojure.spec.test.alpha :as stest] + :cljs [cljs.spec.test.alpha :as stest]) + [expound.alpha :as expound] [app.common.uuid :as uuid] [app.common.exceptions :as ex] + [app.common.geom.point :as gpt] [cuerdas.core :as str])) (s/check-asserts true) @@ -38,7 +43,7 @@ (if (string? v) (if (re-matches uuid-rx v) (uuid/uuid v) - (if (str/empty? v) nil ::s/invalid)) + ::s/invalid) ::s/invalid))) (defn boolean-conformer @@ -87,9 +92,21 @@ v ::s/invalid)) +(defn keyword-conformer + [v] + (cond + (keyword? v) + v + + (string? v) + (keyword v) + + :else + ::s/invalid)) + ;; --- Default Specs -(s/def ::keyword keyword?) +(s/def ::keyword (s/conformer keyword-conformer name)) (s/def ::inst inst?) (s/def ::string string?) (s/def ::email (s/conformer email-conformer str)) @@ -101,27 +118,54 @@ (s/def ::not-empty-string (s/and string? #(not (str/empty? %)))) (s/def ::url string?) (s/def ::fn fn?) +(s/def ::point gpt/point?) ;; --- Macros (defn spec-assert - [spec x] + [spec x message] (if (s/valid? spec x) x (ex/raise :type :assertion :data (s/explain-data spec x) - #?@(:cljs [:stack (.-stack (ex-info "assertion" {}))])))) + :message message + #?@(:cljs [:stack (.-stack (ex-info message {}))])))) + +(defn spec-assert* + [spec x message context] + (if (s/valid? spec x) + x + (ex/raise :type :assertion + :data (s/explain-data spec x) + :context context + :message message + #?@(:cljs [:stack (.-stack (ex-info message {}))])))) + (defmacro assert "Development only assertion macro." [spec x] (when *assert* - `(spec-assert ~spec ~x))) + (let [nsdata (:ns &env) + context (when nsdata + {:ns (str (:name nsdata)) + :name (pr-str spec) + :line (:line &env) + :file (:file (:meta nsdata))}) + message (str "Spec Assertion: '" (pr-str spec) "'")] + `(spec-assert* ~spec ~x ~message ~context)))) (defmacro verify "Always active assertion macro (does not obey to :elide-asserts)" [spec x] - `(spec-assert ~spec ~x)) + (let [nsdata (:ns &env) + context (when nsdata + {:ns (str (:name nsdata)) + :name (pr-str spec) + :line (:line &env) + :file (:file (:meta nsdata))}) + message (str "Spec Assertion: '" (pr-str spec) "'")] + `(spec-assert* ~spec ~x ~message ~context))) ;; --- Public Api @@ -136,3 +180,14 @@ (expound/printer edata)) :data (::s/problems edata))))) result)) + +(defmacro instrument! + [& {:keys [sym spec]}] + (when *assert* + (let [message (str "Spec failed on: " sym)] + `(let [origf# ~sym + mdata# (meta (var ~sym))] + (set! ~sym (fn [& params#] + (spec-assert* ~spec params# ~message mdata#) + (apply origf# params#))))))) + diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index 72bf68956d..6493e6f112 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -4,18 +4,16 @@ LABEL maintainer="Andrey Antukh " ARG DEBIAN_FRONTEND=noninteractive ARG EXTERNAL_UID=1000 -ENV NODE_VERSION=v12.18.4 \ - CLOJURE_VERSION=1.10.1.681 \ +ENV NODE_VERSION=v12.19.0 \ + CLOJURE_VERSION=1.10.1.727 \ LANG=en_US.UTF-8 \ - LC_ALL=C.UTF-8 + LC_ALL=en_US.UTF-8 RUN set -ex; \ mkdir -p /etc/resolvconf/resolv.conf.d; \ - echo "nameserver 8.8.8.8" > /etc/resolvconf/resolv.conf.d/tail; - -RUN set -ex; \ - apt-get update && \ - apt-get install -yq \ + echo "nameserver 8.8.8.8" > /etc/resolvconf/resolv.conf.d/tail; \ + apt-get -qq update; \ + apt-get -qqy install --no-install-recommends \ locales \ gnupg2 \ ca-certificates \ @@ -27,6 +25,19 @@ RUN set -ex; \ bash \ git \ rlwrap \ + ; \ + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \ + locale-gen; \ + rm -rf /var/lib/apt/lists/*; + +RUN set -ex; \ + useradd -m -g users -s /bin/bash -u $EXTERNAL_UID uxbox; \ + passwd uxbox -d; \ + echo "uxbox ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +RUN set -ex; \ + apt-get -qq update; \ + apt-get -qqy install --no-install-recommends \ python \ build-essential \ imagemagick \ @@ -40,8 +51,8 @@ RUN set -ex; \ rm -rf /var/lib/apt/lists/*; RUN set -ex; \ - apt-get update && \ - apt-get install -yq \ + apt-get -qq update; \ + apt-get -qqy install \ gconf-service \ libasound2 \ libatk1.0-0 \ @@ -80,30 +91,25 @@ RUN set -ex; \ rm -rf /var/lib/apt/lists/*; RUN set -ex; \ - apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 0xB1998361219BD9C9; \ - echo "deb http://repos.azulsystems.com/debian stable main" >> /etc/apt/sources.list.d/zulu.list; \ + mkdir -p /usr/share/man/man1; \ + mkdir -p /usr/share/man/man7; \ + wget -qO - https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public | apt-key add -; \ + echo "deb https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/ buster main" >> /etc/apt/sources.list.d/adoptopenjdk.list; \ apt-get -qq update; \ - apt-get -qqy install zulu-14; \ + apt-get -qqy install adoptopenjdk-15-hotspot; \ rm -rf /var/lib/apt/lists/*; \ wget "https://download.clojure.org/install/linux-install-$CLOJURE_VERSION.sh"; \ chmod +x "linux-install-$CLOJURE_VERSION.sh"; \ "./linux-install-$CLOJURE_VERSION.sh"; \ rm -rf "linux-install-$CLOJURE_VERSION.sh" -ENV JAVA_HOME=/usr/lib/jvm/zulu-14-amd64 - 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 buster-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; \ apt-get -qq update; \ - apt-get -qqy install postgresql-client-12; \ + apt-get -qqy install postgresql-client-13; \ rm -rf /var/lib/apt/lists/*; -RUN set -ex; \ - useradd -m -g users -s /bin/bash -u $EXTERNAL_UID uxbox; \ - passwd uxbox -d; \ - echo "uxbox ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers - RUN set -ex; \ wget https://github.com/RazrFalcon/svgcleaner/releases/download/v0.9.5/svgcleaner_linux_x86_64_0.9.5.tar.gz; \ tar xvf svgcleaner_linux_x86_64_0.9.5.tar.gz; \ @@ -111,12 +117,12 @@ RUN set -ex; \ rm -rf svgcleaner_linux_x86_64_0.9.5.tar.gz; COPY files/phantomjs-mock /usr/bin/phantomjs -COPY files/bashrc /root/.bashrc -COPY files/vimrc /root/.vimrc -COPY files/tmux.conf /root/.tmux.conf -COPY files/start-tmux.sh /home/start-tmux.sh -COPY files/entrypoint.sh /home/entrypoint.sh -COPY files/init.sh /home/init.sh +COPY files/bashrc /root/.bashrc +COPY files/vimrc /root/.vimrc +COPY files/tmux.conf /root/.tmux.conf +COPY files/start-tmux.sh /home/start-tmux.sh +COPY files/entrypoint.sh /home/entrypoint.sh +COPY files/init.sh /home/init.sh USER uxbox WORKDIR /home/uxbox @@ -134,5 +140,5 @@ EXPOSE 3449 EXPOSE 6060 EXPOSE 9090 -ENTRYPOINT ["bash", "/home/entrypoint.sh"] +ENTRYPOINT ["/home/entrypoint.sh"] CMD ["/home/init.sh"] diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml index d93f69e586..bbbbfa40d0 100644 --- a/docker/devenv/docker-compose.yaml +++ b/docker/devenv/docker-compose.yaml @@ -55,13 +55,11 @@ services: - OPENDKIM_DOMAINS=smtp.uxbox.io postgres: - image: postgres:12 + image: postgres:13 command: postgres -c config_file=/etc/postgresql.conf container_name: "uxbox-devenv-postgres" restart: always stop_signal: SIGINT - ports: - - 5432:5432 environment: - POSTGRES_INITDB_ARGS=--data-checksums - POSTGRES_DB=uxbox @@ -77,6 +75,3 @@ services: hostname: "uxbox-devenv-redis" container_name: "uxbox-devenv-redis" restart: always - - ports: - - 6379:6379 diff --git a/docker/devenv/files/entrypoint.sh b/docker/devenv/files/entrypoint.sh index 53a27a077c..bbf4504367 100755 --- a/docker/devenv/files/entrypoint.sh +++ b/docker/devenv/files/entrypoint.sh @@ -1,13 +1,12 @@ #!/usr/bin/env bash -source /home/uxbox/.bashrc - -set -ex +set -e sudo cp /root/.bashrc /home/uxbox/.bashrc sudo cp /root/.vimrc /home/uxbox/.vimrc sudo cp /root/.tmux.conf /home/uxbox/.tmux.conf +source /home/uxbox/.bashrc sudo chown uxbox:users /home/uxbox exec "$@" diff --git a/docker/devenv/files/postgresql.conf b/docker/devenv/files/postgresql.conf index 859062c7b6..da63d26021 100644 --- a/docker/devenv/files/postgresql.conf +++ b/docker/devenv/files/postgresql.conf @@ -1,753 +1,22 @@ - -# ----------------------------- -# PostgreSQL configuration file -# ----------------------------- -# -# This file consists of lines of the form: -# -# name = value -# -# (The "=" is optional.) Whitespace may be used. Comments are introduced with -# "#" anywhere on a line. The complete list of parameter names and allowed -# values can be found in the PostgreSQL documentation. -# -# The commented-out settings shown in this file represent the default values. -# Re-commenting a setting is NOT sufficient to revert it to the default value; -# you need to reload the server. -# -# This file is read on server startup and when the server receives a SIGHUP -# signal. If you edit the file on a running system, you have to SIGHUP the -# server for the changes to take effect, run "pg_ctl reload", or execute -# "SELECT pg_reload_conf()". Some parameters, which are marked below, -# require a server shutdown and restart to take effect. -# -# Any parameter can also be given as a command-line option to the server, e.g., -# "postgres -c log_connections=on". Some parameters can be changed at run time -# with the "SET" SQL command. -# -# Memory units: kB = kilobytes Time units: ms = milliseconds -# MB = megabytes s = seconds -# GB = gigabytes min = minutes -# TB = terabytes h = hours -# d = days - - -#------------------------------------------------------------------------------ -# FILE LOCATIONS -#------------------------------------------------------------------------------ - -# The default values of these variables are driven from the -D command-line -# option or PGDATA environment variable, represented here as ConfigDir. - -#data_directory = 'ConfigDir' # use data in another directory - # (change requires restart) -#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file - # (change requires restart) -#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file - # (change requires restart) - -# If external_pid_file is not explicitly set, no extra PID file is written. -#external_pid_file = '' # write an extra PID file - # (change requires restart) - - -#------------------------------------------------------------------------------ -# CONNECTIONS AND AUTHENTICATION -#------------------------------------------------------------------------------ - -# - Connection Settings - - listen_addresses = '*' - # comma-separated list of addresses; - # defaults to 'localhost'; use '*' for all - # (change requires restart) -#port = 5432 # (change requires restart) -max_connections = 100 # (change requires restart) -#superuser_reserved_connections = 3 # (change requires restart) -#unix_socket_directories = '/var/run/postgresql' # comma-separated list of directories - # (change requires restart) -#unix_socket_group = '' # (change requires restart) -#unix_socket_permissions = 0777 # begin with 0 to use octal notation - # (change requires restart) -#bonjour = off # advertise server via Bonjour - # (change requires restart) -#bonjour_name = '' # defaults to the computer name - # (change requires restart) +max_connections = 100 +shared_buffers = 128MB +temp_buffers = 8MB +work_mem = 8MB -# - TCP settings - -# see "man 7 tcp" for details - -#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds; - # 0 selects the system default -#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds; - # 0 selects the system default -#tcp_keepalives_count = 0 # TCP_KEEPCNT; - # 0 selects the system default -#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds; - # 0 selects the system default - -# - Authentication - - -#authentication_timeout = 1min # 1s-600s -#password_encryption = md5 # md5 or scram-sha-256 -#db_user_namespace = off - -# GSSAPI using Kerberos -#krb_server_keyfile = '' -#krb_caseins_users = off - -# - SSL - - -#ssl = off -#ssl_ca_file = '' -#ssl_cert_file = 'server.crt' -#ssl_crl_file = '' -#ssl_key_file = 'server.key' -#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers -#ssl_prefer_server_ciphers = on -#ssl_ecdh_curve = 'prime256v1' -#ssl_min_protocol_version = 'TLSv1' -#ssl_max_protocol_version = '' -#ssl_dh_params_file = '' -#ssl_passphrase_command = '' -#ssl_passphrase_command_supports_reload = off - - -#------------------------------------------------------------------------------ -# RESOURCE USAGE (except WAL) -#------------------------------------------------------------------------------ - -# - Memory - - -shared_buffers = 256MB # min 128kB - # (change requires restart) -#huge_pages = try # on, off, or try - # (change requires restart) -temp_buffers = 8MB # min 800kB -#max_prepared_transactions = 0 # zero disables the feature - # (change requires restart) -# Caution: it is not advisable to set max_prepared_transactions nonzero unless -# you actively intend to use prepared transactions. -work_mem = 8MB # min 64kB -#maintenance_work_mem = 64MB # min 1MB -#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem -#max_stack_depth = 2MB # min 100kB -#shared_memory_type = mmap # the default is the first option - # supported by the operating system: - # mmap - # sysv - # windows - # (change requires restart) -dynamic_shared_memory_type = posix # the default is the first option - # supported by the operating system: - # posix - # sysv - # windows - # mmap - # (change requires restart) - -# - Disk - - -#temp_file_limit = -1 # limits per-process temp file space - # in kB, or -1 for no limit - -# - Kernel Resources - - -#max_files_per_process = 1000 # min 25 - # (change requires restart) - -# - Cost-Based Vacuum Delay - - -#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables) -#vacuum_cost_page_hit = 1 # 0-10000 credits -#vacuum_cost_page_miss = 10 # 0-10000 credits -#vacuum_cost_page_dirty = 20 # 0-10000 credits -#vacuum_cost_limit = 200 # 1-10000 credits - -# - Background Writer - - -#bgwriter_delay = 200ms # 10-10000ms between rounds -#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables -#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round -#bgwriter_flush_after = 512kB # measured in pages, 0 disables - -# - Asynchronous Behavior - - -#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching -#max_worker_processes = 8 # (change requires restart) -#max_parallel_maintenance_workers = 2 # taken from max_parallel_workers -#max_parallel_workers_per_gather = 2 # taken from max_parallel_workers -#parallel_leader_participation = on -#max_parallel_workers = 8 # maximum number of max_worker_processes that - # can be used in parallel operations -#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate - # (change requires restart) -#backend_flush_after = 0 # measured in pages, 0 disables - - -#------------------------------------------------------------------------------ -# WRITE-AHEAD LOG -#------------------------------------------------------------------------------ - -# - Settings - - -#wal_level = replica # minimal, replica, or logical - # (change requires restart) -#fsync = on # flush data to disk for crash safety - # (turning this off can cause - # unrecoverable data corruption) -synchronous_commit = off # synchronization level; - # off, local, remote_write, remote_apply, or on -#wal_sync_method = fsync # the default is the first option - # supported by the operating system: - # open_datasync - # fdatasync (default on Linux) - # fsync - # fsync_writethrough - # open_sync -#full_page_writes = on # recover from partial page writes -#wal_compression = off # enable compression of full-page writes -#wal_log_hints = off # also do full page writes of non-critical updates - # (change requires restart) -#wal_init_zero = on # zero-fill new WAL files -#wal_recycle = on # recycle WAL files -#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers - # (change requires restart) -wal_writer_delay = 900ms # 1-10000 milliseconds -#wal_writer_flush_after = 1MB # measured in pages, 0 disables - -#commit_delay = 0 # range 0-100000, in microseconds -#commit_siblings = 5 # range 1-1000 - -# - Checkpoints - - -#checkpoint_timeout = 5min # range 30s-1d +dynamic_shared_memory_type = posix +synchronous_commit = off +wal_writer_delay = 900ms max_wal_size = 1GB min_wal_size = 80MB -#checkpoint_completion_target = 0.5 # checkpoint target duration, 0.0 - 1.0 -#checkpoint_flush_after = 256kB # measured in pages, 0 disables -#checkpoint_warning = 30s # 0 disables - -# - Archiving - - -#archive_mode = off # enables archiving; off, on, or always - # (change requires restart) -#archive_command = '' # command to use to archive a logfile segment - # placeholders: %p = path of file to archive - # %f = file name only - # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' -#archive_timeout = 0 # force a logfile segment switch after this - # number of seconds; 0 disables - -# - Archive Recovery - - -# These are only used in recovery mode. - -#restore_command = '' # command to use to restore an archived logfile segment - # placeholders: %p = path of file to restore - # %f = file name only - # e.g. 'cp /mnt/server/archivedir/%f %p' - # (change requires restart) -#archive_cleanup_command = '' # command to execute at every restartpoint -#recovery_end_command = '' # command to execute at completion of recovery - -# - Recovery Target - - -# Set these only when performing a targeted recovery. - -#recovery_target = '' # 'immediate' to end recovery as soon as a - # consistent state is reached - # (change requires restart) -#recovery_target_name = '' # the named restore point to which recovery will proceed - # (change requires restart) -#recovery_target_time = '' # the time stamp up to which recovery will proceed - # (change requires restart) -#recovery_target_xid = '' # the transaction ID up to which recovery will proceed - # (change requires restart) -#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed - # (change requires restart) -#recovery_target_inclusive = on # Specifies whether to stop: - # just after the specified recovery target (on) - # just before the recovery target (off) - # (change requires restart) -#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID - # (change requires restart) -#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' - # (change requires restart) - - -#------------------------------------------------------------------------------ -# REPLICATION -#------------------------------------------------------------------------------ - -# - Sending Servers - - -# Set these on the master and on any standby that will send replication data. - -#max_wal_senders = 10 # max number of walsender processes - # (change requires restart) -#wal_keep_segments = 0 # in logfile segments; 0 disables -#wal_sender_timeout = 60s # in milliseconds; 0 disables - -#max_replication_slots = 10 # max number of replication slots - # (change requires restart) -#track_commit_timestamp = off # collect timestamp of transaction commit - # (change requires restart) - -# - Master Server - - -# These settings are ignored on a standby server. - -#synchronous_standby_names = '' # standby servers that provide sync rep - # method to choose sync standbys, number of sync standbys, - # and comma-separated list of application_name - # from standby(s); '*' = all -#vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed - -# - Standby Servers - - -# These settings are ignored on a master server. - -#primary_conninfo = '' # connection string to sending server - # (change requires restart) -#primary_slot_name = '' # replication slot on sending server - # (change requires restart) -#promote_trigger_file = '' # file name whose presence ends recovery -#hot_standby = on # "off" disallows queries during recovery - # (change requires restart) -#max_standby_archive_delay = 30s # max delay before canceling queries - # when reading WAL from archive; - # -1 allows indefinite delay -#max_standby_streaming_delay = 30s # max delay before canceling queries - # when reading streaming WAL; - # -1 allows indefinite delay -#wal_receiver_status_interval = 10s # send replies at least this often - # 0 disables -#hot_standby_feedback = off # send info from standby to prevent - # query conflicts -#wal_receiver_timeout = 60s # time that receiver waits for - # communication from master - # in milliseconds; 0 disables -#wal_retrieve_retry_interval = 5s # time to wait before retrying to - # retrieve WAL after a failed attempt -#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery - -# - Subscribers - - -# These settings are ignored on a publisher. - -#max_logical_replication_workers = 4 # taken from max_worker_processes - # (change requires restart) -#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers - - -#------------------------------------------------------------------------------ -# QUERY TUNING -#------------------------------------------------------------------------------ - -# - Planner Method Configuration - - -#enable_bitmapscan = on -#enable_hashagg = on -#enable_hashjoin = on -#enable_indexscan = on -#enable_indexonlyscan = on -#enable_material = on -#enable_mergejoin = on -#enable_nestloop = on -#enable_parallel_append = on -#enable_seqscan = on -#enable_sort = on -#enable_tidscan = on -#enable_partitionwise_join = off -#enable_partitionwise_aggregate = off -#enable_parallel_hash = on -#enable_partition_pruning = on - -# - Planner Cost Constants - - -#seq_page_cost = 1.0 # measured on an arbitrary scale -#random_page_cost = 4.0 # same scale as above -#cpu_tuple_cost = 0.01 # same scale as above -#cpu_index_tuple_cost = 0.005 # same scale as above -#cpu_operator_cost = 0.0025 # same scale as above -#parallel_tuple_cost = 0.1 # same scale as above -#parallel_setup_cost = 1000.0 # same scale as above - -#jit_above_cost = 100000 # perform JIT compilation if available - # and query more expensive than this; - # -1 disables -#jit_inline_above_cost = 500000 # inline small functions if query is - # more expensive than this; -1 disables -#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if - # query is more expensive than this; - # -1 disables - -#min_parallel_table_scan_size = 8MB -#min_parallel_index_scan_size = 512kB -#effective_cache_size = 4GB - -# - Genetic Query Optimizer - - -#geqo = on -#geqo_threshold = 12 -#geqo_effort = 5 # range 1-10 -#geqo_pool_size = 0 # selects default based on effort -#geqo_generations = 0 # selects default based on effort -#geqo_selection_bias = 2.0 # range 1.5-2.0 -#geqo_seed = 0.0 # range 0.0-1.0 - -# - Other Planner Options - - -#default_statistics_target = 100 # range 1-10000 -#constraint_exclusion = partition # on, off, or partition -#cursor_tuple_fraction = 0.1 # range 0.0-1.0 -#from_collapse_limit = 8 -#join_collapse_limit = 8 # 1 disables collapsing of explicit - # JOIN clauses -#force_parallel_mode = off -#jit = on # allow JIT compilation -#plan_cache_mode = auto # auto, force_generic_plan or - # force_custom_plan - - -#------------------------------------------------------------------------------ -# REPORTING AND LOGGING -#------------------------------------------------------------------------------ - -# - Where to Log - - -#log_destination = 'stderr' # Valid values are combinations of - # stderr, csvlog, syslog, and eventlog, - # depending on platform. csvlog - # requires logging_collector to be on. - -# This is used when logging to stderr: -#logging_collector = off # Enable capturing of stderr and csvlog - # into log files. Required to be on for - # csvlogs. - # (change requires restart) - -# These are only used if logging_collector is on: -#log_directory = 'log' # directory where log files are written, - # can be absolute or relative to PGDATA -#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern, - # can include strftime() escapes -#log_file_mode = 0600 # creation mode for log files, - # begin with 0 to use octal notation -#log_truncate_on_rotation = off # If on, an existing log file with the - # same name as the new log file will be - # truncated rather than appended to. - # But such truncation only occurs on - # time-driven rotation, not on restarts - # or size-driven rotation. Default is - # off, meaning append to existing files - # in all cases. -#log_rotation_age = 1d # Automatic rotation of logfiles will - # happen after that time. 0 disables. -#log_rotation_size = 10MB # Automatic rotation of logfiles will - # happen after that much log output. - # 0 disables. - -# These are relevant when logging to syslog: -#syslog_facility = 'LOCAL0' -#syslog_ident = 'postgres' -#syslog_sequence_numbers = on -#syslog_split_messages = on - -# This is only relevant when logging to eventlog (win32): -# (change requires restart) -#event_source = 'PostgreSQL' - -# - When to Log - - -#log_min_messages = warning # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # info - # notice - # warning - # error - # log - # fatal - # panic - -#log_min_error_statement = error # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # info - # notice - # warning - # error - # log - # fatal - # panic (effectively off) - -#log_min_duration_statement = 0 # -1 is disabled, 0 logs all statements - # and their durations, > 0 logs only - # statements running at least this number - # of milliseconds - -#log_transaction_sample_rate = 0.0 # Fraction of transactions whose statements - # are logged regardless of their duration. 1.0 logs all - # statements from all transactions, 0.0 never logs. - -# - What to Log - - -#debug_print_parse = off -#debug_print_rewritten = off -#debug_print_plan = off -#debug_pretty_print = on -#log_checkpoints = off -#log_connections = off -#log_disconnections = off -#log_duration = off -#log_error_verbosity = default # terse, default, or verbose messages -#log_hostname = off -#log_line_prefix = '%m [%p] ' # special values: - # %a = application name - # %u = user name - # %d = database name - # %r = remote host and port - # %h = remote host - # %p = process ID - # %t = timestamp without milliseconds - # %m = timestamp with milliseconds - # %n = timestamp with milliseconds (as a Unix epoch) - # %i = command tag - # %e = SQL state - # %c = session ID - # %l = session line number - # %s = session start timestamp - # %v = virtual transaction ID - # %x = transaction ID (0 if none) - # %q = stop here in non-session - # processes - # %% = '%' - # e.g. '<%u%%%d> ' -#log_lock_waits = off # log lock waits >= deadlock_timeout -#log_statement = 'none' # none, ddl, mod, all -#log_replication_commands = off -#log_temp_files = -1 # log temporary files equal or larger - # than the specified size in kilobytes; - # -1 disables, 0 logs all temp files -log_timezone = 'Etc/UTC' - -#------------------------------------------------------------------------------ -# PROCESS TITLE -#------------------------------------------------------------------------------ - -#cluster_name = '' # added to process titles if nonempty - # (change requires restart) -#update_process_title = on - - -#------------------------------------------------------------------------------ -# STATISTICS -#------------------------------------------------------------------------------ - -# - Query and Index Statistics Collector - - -#track_activities = on -#track_counts = on -#track_io_timing = off -#track_functions = none # none, pl, all -#track_activity_query_size = 1024 # (change requires restart) -#stats_temp_directory = 'pg_stat_tmp' - - -# - Monitoring - - -#log_parser_stats = off -#log_planner_stats = off -#log_executor_stats = off -#log_statement_stats = off - - -#------------------------------------------------------------------------------ -# AUTOVACUUM -#------------------------------------------------------------------------------ - -#autovacuum = on # Enable autovacuum subprocess? 'on' - # requires track_counts to also be on. -#log_autovacuum_min_duration = -1 # -1 disables, 0 logs all actions and - # their durations, > 0 logs only - # actions running at least this number - # of milliseconds. -#autovacuum_max_workers = 3 # max number of autovacuum subprocesses - # (change requires restart) -#autovacuum_naptime = 1min # time between autovacuum runs -#autovacuum_vacuum_threshold = 50 # min number of row updates before - # vacuum -#autovacuum_analyze_threshold = 50 # min number of row updates before - # analyze -#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum -#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze -#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum - # (change requires restart) -#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age - # before forced vacuum - # (change requires restart) -#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for - # autovacuum, in milliseconds; - # -1 means use vacuum_cost_delay -#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for - # autovacuum, -1 means use - # vacuum_cost_limit - - -#------------------------------------------------------------------------------ -# CLIENT CONNECTION DEFAULTS -#------------------------------------------------------------------------------ - -# - Statement Behavior - - -#client_min_messages = notice # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # log - # notice - # warning - # error -#search_path = '"$user", public' # schema names -#row_security = on -#default_tablespace = '' # a tablespace name, '' uses the default -#temp_tablespaces = '' # a list of tablespace names, '' uses - # only default tablespace -#default_table_access_method = 'heap' -#check_function_bodies = on -#default_transaction_isolation = 'read committed' -#default_transaction_read_only = off -#default_transaction_deferrable = off -#session_replication_role = 'origin' -#statement_timeout = 0 # in milliseconds, 0 is disabled -#lock_timeout = 0 # in milliseconds, 0 is disabled -#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled -#vacuum_freeze_min_age = 50000000 -#vacuum_freeze_table_age = 150000000 -#vacuum_multixact_freeze_min_age = 5000000 -#vacuum_multixact_freeze_table_age = 150000000 -#vacuum_cleanup_index_scale_factor = 0.1 # fraction of total number of tuples - # before index cleanup, 0 always performs - # index cleanup -#bytea_output = 'hex' # hex, escape -#xmlbinary = 'base64' -#xmloption = 'content' -#gin_fuzzy_search_limit = 0 -#gin_pending_list_limit = 4MB - -# - Locale and Formatting - +# log_min_duration_statement = 0 +log_timezone = 'Europe/Madrid' datestyle = 'iso, mdy' -#intervalstyle = 'postgres' -timezone = 'Etc/UTC' -#timezone_abbreviations = 'Default' # Select the set of available time zone - # abbreviations. Currently, there are - # Default - # Australia (historical usage) - # India - # You can create your own file in - # share/timezonesets/. -#extra_float_digits = 1 # min -15, max 3; any value >0 actually - # selects precise output mode -#client_encoding = sql_ascii # actually, defaults to database - # encoding - -# These settings are initialized by initdb, but they can be changed. -lc_messages = 'en_US.utf8' # locale for system error message - # strings -lc_monetary = 'en_US.utf8' # locale for monetary formatting -lc_numeric = 'en_US.utf8' # locale for number formatting -lc_time = 'en_US.utf8' # locale for time formatting - -# default configuration for text search +timezone = 'Europe/Madrid' +lc_messages = 'en_US.utf8' +lc_monetary = 'en_US.utf8' +lc_numeric = 'en_US.utf8' +lc_time = 'en_US.utf8' default_text_search_config = 'pg_catalog.english' -# - Shared Library Preloading - - -#shared_preload_libraries = '' # (change requires restart) -#local_preload_libraries = '' -#session_preload_libraries = '' -#jit_provider = 'llvmjit' # JIT library to use - -# - Other Defaults - - -#dynamic_library_path = '$libdir' - - -#------------------------------------------------------------------------------ -# LOCK MANAGEMENT -#------------------------------------------------------------------------------ - -#deadlock_timeout = 1s -#max_locks_per_transaction = 64 # min 10 - # (change requires restart) -#max_pred_locks_per_transaction = 64 # min 10 - # (change requires restart) -#max_pred_locks_per_relation = -2 # negative values mean - # (max_pred_locks_per_transaction - # / -max_pred_locks_per_relation) - 1 -#max_pred_locks_per_page = 2 # min 0 - - -#------------------------------------------------------------------------------ -# VERSION AND PLATFORM COMPATIBILITY -#------------------------------------------------------------------------------ - -# - Previous PostgreSQL Versions - - -#array_nulls = on -#backslash_quote = safe_encoding # on, off, or safe_encoding -#escape_string_warning = on -#lo_compat_privileges = off -#operator_precedence_warning = off -#quote_all_identifiers = off -#standard_conforming_strings = on -#synchronize_seqscans = on - -# - Other Platforms and Clients - - -#transform_null_equals = off - - -#------------------------------------------------------------------------------ -# ERROR HANDLING -#------------------------------------------------------------------------------ - -#exit_on_error = off # terminate session on any error? -#restart_after_crash = on # reinitialize after backend crash? -#data_sync_retry = off # retry or panic on failure to fsync - # data? - # (change requires restart) - - -#------------------------------------------------------------------------------ -# CONFIG FILE INCLUDES -#------------------------------------------------------------------------------ - -# These options allow settings to be loaded from files other than the -# default postgresql.conf. Note that these are directives, not variable -# assignments, so they can usefully be given more than once. - -#include_dir = '...' # include files ending in '.conf' from - # a directory, e.g., 'conf.d' -#include_if_exists = '...' # include file only if it exists -#include = '...' # include file - - -#------------------------------------------------------------------------------ -# CUSTOMIZED OPTIONS -#------------------------------------------------------------------------------ - -# Add settings for extensions here - - diff --git a/docker/testenv/Dockerfile-backend b/docker/testenv/Dockerfile-backend index b84fef87d6..03906855e0 100644 --- a/docker/testenv/Dockerfile-backend +++ b/docker/testenv/Dockerfile-backend @@ -1,4 +1,4 @@ -FROM azul/zulu-openjdk-debian:14 +FROM adoptopenjdk/openjdk15:debianslim-jre LABEL maintainer="Andrey Antukh " ADD ./bundle/backend/ /opt/bundle/ WORKDIR /opt/bundle diff --git a/docker/testenv/Dockerfile-exporter b/docker/testenv/Dockerfile-exporter index 40bce1cdd6..eaeee4a9fe 100644 --- a/docker/testenv/Dockerfile-exporter +++ b/docker/testenv/Dockerfile-exporter @@ -1,24 +1,35 @@ -FROM debian:buster +FROM debian:buster-slim LABEL maintainer="Andrey Antukh " ARG DEBIAN_FRONTEND=noninteractive -ENV LANG=en_US.UTF-8 LC_ALL=C.UTF-8 +ENV LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + NODE_VERSION=v12.18.4 RUN set -ex; \ mkdir -p /etc/resolvconf/resolv.conf.d; \ - echo "nameserver 8.8.8.8" > /etc/resolvconf/resolv.conf.d/tail; - -RUN set -ex; \ - apt-get update && \ - apt-get install -yq \ + echo "nameserver 8.8.8.8" > /etc/resolvconf/resolv.conf.d/tail; \ + apt-get -qq update; \ + apt-get -qqy install --no-install-recommends \ locales \ gnupg2 \ ca-certificates \ wget \ + sudo \ + vim \ curl \ bash \ + xz-utils \ rlwrap \ + ; \ + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \ + locale-gen; \ + rm -rf /var/lib/apt/lists/*; + +RUN set -ex; \ + apt-get -qq update; \ + apt-get -qqy install \ imagemagick \ netpbm \ potrace \ @@ -69,11 +80,11 @@ RUN set -ex; \ RUN set -ex; \ mkdir -p /tmp/node; \ cd /tmp/node; \ - export PATH="$PATH:/usr/local/node-v12.18.3/bin"; \ - wget https://nodejs.org/dist/v12.18.3/node-v12.18.3-linux-x64.tar.xz; \ - tar xvf node-v12.18.3-linux-x64.tar.xz; \ - mv /tmp/node/node-v12.18.3-linux-x64 /usr/local/node-v12.18.3; \ - /usr/local/node-v12.18.3/bin/npm install -g yarn; \ + export PATH="$PATH:/usr/local/nodejs/bin"; \ + wget https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-linux-x64.tar.xz; \ + tar xvf node-$NODE_VERSION-linux-x64.tar.xz; \ + mv /tmp/node/node-$NODE_VERSION-linux-x64 /usr/local/nodejs; \ + /usr/local/nodejs/bin/npm install -g yarn; \ rm -rf /tmp/node; WORKDIR /opt/app @@ -81,7 +92,7 @@ WORKDIR /opt/app ADD ./bundle/exporter/ /opt/app/ RUN set -ex; \ - export PATH="$PATH:/usr/local/node-v12.18.3/bin"; \ + export PATH="$PATH:/usr/local/nodejs/bin"; \ yarn install; -CMD ["/usr/local/node-v12.18.3/bin/node", "app.js"] +CMD ["/usr/local/nodejs/bin/node", "app.js"] diff --git a/docs/01-Development-Environment.md b/docs/01-Development-Environment.md index 4a720a4f96..bb5771ebf3 100644 --- a/docs/01-Development-Environment.md +++ b/docs/01-Development-Environment.md @@ -1,6 +1,6 @@ # Developer Guide # -This is a generic "getting started" guide for the uxbox platform. It +This is a generic "getting started" guide for the Penpot platform. It intends to explain how to get the development environment up and running with many additional tips. @@ -148,7 +148,7 @@ For more information, please refer to: `03-Backend-Guide.md`. ## Start the testenv ## The purpose of the testenv (Test Environment) is provide an easy way -to get uxbox running in local pc without getting into the full +to get Penpot running in local pc without getting into the full development environment. As first step we still need to build devenv image because that image @@ -185,5 +185,5 @@ This will generate the necessary docker images ready to be executed. And finally, start the docker-compose: ```bash -./manage.sh start-devenv +./manage.sh start-testenv ``` diff --git a/docs/02-Frontend-Developer-Guide.md b/docs/02-Frontend-Developer-Guide.md index edcfef8d55..50b137c92d 100644 --- a/docs/02-Frontend-Developer-Guide.md +++ b/docs/02-Frontend-Developer-Guide.md @@ -26,19 +26,22 @@ app.util.debug.debug_all() app.util.debug.debug_none() ``` -## Traces and step-by-step debugging +## Logging, Tracing & Debugging -There are some useful functions to trace program execution: +As a traditional way for debugging and tracing you have the followimg approach: + + +Print data to the devtool console using clojurescript helper: +**prn**. This helper automatically formats the clojure and js data +structures as plain EDN for easy visual inspection of the data and the +type of the data. ```clojure -(println "message" expression) - ; Outputs data to the devtools console. Clojure variables are converted to string, as in (str) function. +(prn "message" expression) ``` -```clojure -(js/console.log "message" (clj->js expression)) - ; Clojure values are converted to equivalent js objects, and displayed as a foldable widget in console. -``` +An alternative is using the pprint function, usefull for pretty +printing a medium-big data sturcture for completly understand it. ```clojure (:require [cljs.pprint :refer [pprint]]) @@ -46,19 +49,82 @@ There are some useful functions to trace program execution: ; Outputs a clojure value as a string, nicely formatted and with data type information. ``` +Use the js native functions for printing data. The clj->js converts +the clojure data sturcture to js data sturcture and it is +inspeccionable in the devtools console. + +```clojure +(js/console.log "message" (clj->js expression)) +``` + + Also we can insert breakpoints in the code with this function: ```clojure (js-debugger) ``` -You can also set a breakpoint from the sources tab in devtools. One way of locating a source file is to -output a trace with (js/console.log) and then clicking in the source link that shows in the console. +You can also set a breakpoint from the sources tab in devtools. One +way of locating a source file is to output a trace with +(js/console.log) and then clicking in the source link that shows in +the console. + + +### Logging framework + +Additionally to the traditional way of putting traces in the code, we +have a logging framework with steroids. It is usefull for casual +debugging (as replacement for a `prn` and `js/console.log`) and as a +permanent traces in the code. + +You have the ability to specify the logging level per namespace and +all logging is ellided in production build. + +Lets start with a simple example: + +```clojure +(ns some.ns + (:require [app.util.logging :as log])) + +;; This function sets the level to the current namespace; messages +;; with level behind this will not be printed. +(log/set-level! :info) + + +;; Log some data; The `app.util.logging` has the following +;; functions/macros: + +(log/error :msg "error message") +(log/warn :msg "warn message") +(log/info :msg "info message") +(log/debug :msg "debug message") +(log/trace :msg "trace message") +``` + +Each macro accept arbitrary number of key values pairs: + +```clojure +(log/info :foo "bar" :msg "test" :value 1 :items #{1 2 3}) +``` + +Some keys ara treated as special cases for helping in debugging: + +```clojure +;; The special case for :js/whatever; if you namespace the key +;; with `js/`, the variable will be printed as javascript +;; inspectionable object. + +(let [foobar {:a 1 :b 2}] + (log/info :msg "Some data" :js/data foobar)) + +;; The special case for `:err`; If you attach this key, the +;; exception stack trace is printed as additional log entry. +``` ## Access to clojure from javascript console -The uxbox namespace of the main application is exported, so that is +The penpot namespace of the main application is exported, so that is accessible from javascript console in Chrome developer tools. Object names and data types are converted to javascript style. For example you can emit the event to reset zoom level by typing this at the diff --git a/docs/04-Common-Developer-Guide.md b/docs/04-Common-Developer-Guide.md index 3838edf823..366d577aef 100644 --- a/docs/04-Common-Developer-Guide.md +++ b/docs/04-Common-Developer-Guide.md @@ -6,7 +6,7 @@ and backend, such as: code style hints, architecture dicisions, etc... ## Assertions ## -UXBOX source code has this types of assertions: +Penpot source code has this types of assertions: **assert**: just using the clojure builtin `assert` macro. @@ -55,7 +55,7 @@ This macro enables you have assetions on production code. **Why don't use the `clojure.spec.alpha/assert` instead of the `app.common.spec/assert`?** -The uxbox variant does not peforms additional runtime checks for know +The Penpot variant does not peforms additional runtime checks for know if asserts are disabled in "runtime". As a result it generates much simplier code at development and production builds. diff --git a/docs/screenshot.png b/docs/screenshot.png new file mode 100644 index 0000000000..c7448c22aa Binary files /dev/null and b/docs/screenshot.png differ diff --git a/exporter/shadow-cljs.edn b/exporter/shadow-cljs.edn index 421623be4e..c8a8a5f5e1 100644 --- a/exporter/shadow-cljs.edn +++ b/exporter/shadow-cljs.edn @@ -1,9 +1,9 @@ {:dependencies - [[funcool/promesa "5.1.0"] + [[funcool/promesa "6.0.0"] [danlentz/clj-uuid "0.1.9"] [funcool/cuerdas "2020.03.26-3"] - [lambdaisland/glogi "1.0.63"] - [metosin/reitit-core "0.5.2"] + [lambdaisland/glogi "1.0.74"] + [metosin/reitit-core "0.5.9"] [com.cognitect/transit-cljs "0.8.264"] [frankiesardo/linked "1.3.0"]] diff --git a/frontend/deps.edn b/frontend/deps.edn index d5cf527299..072351097e 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -5,22 +5,20 @@ com.cognitect/transit-cljs {:mvn/version "0.8.264"} environ/environ {:mvn/version "1.2.0"} - metosin/reitit-core {:mvn/version "0.5.5"} - expound/expound {:mvn/version "0.8.5"} + metosin/reitit-core {:mvn/version "0.5.9"} + expound/expound {:mvn/version "0.8.6"} danlentz/clj-uuid {:mvn/version "0.1.9"} frankiesardo/linked {:mvn/version "1.3.0"} - funcool/lentes {:mvn/version "1.4.0-SNAPSHOT"} - funcool/beicon {:mvn/version "2020.05.08-2"} funcool/cuerdas {:mvn/version "2020.03.26-3"} funcool/okulary {:mvn/version "2020.04.14-0"} funcool/potok {:mvn/version "2020.08.10-2"} - funcool/promesa {:mvn/version "5.1.0"} - funcool/rumext {:mvn/version "2020.08.21-0"} + funcool/promesa {:mvn/version "6.0.0"} + funcool/rumext {:mvn/version "2020.10.14-1"} - lambdaisland/uri {:mvn/version "1.3.45" + lambdaisland/uri {:mvn/version "1.4.54" :exclusions [org.clojure/data.json]} } @@ -37,14 +35,14 @@ funcool/datoteka {:mvn/version "1.2.0"} binaryage/devtools {:mvn/version "RELEASE"} - thheller/shadow-cljs {:mvn/version "2.11.4"} + thheller/shadow-cljs {:mvn/version "2.11.5"} ;; i18n parsing carocad/parcera {:mvn/version "0.11.0"} org.antlr/antlr4-runtime {:mvn/version "4.7"}}} :outdated - {:extra-deps {olical/depot {:mvn/version "1.8.4"}} + {:extra-deps {olical/depot {:mvn/version "RELEASE"}} :main-opts ["-m" "depot.outdated.main"]} :repl diff --git a/frontend/package.json b/frontend/package.json index 6123343b65..1cb1fcb125 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,14 +6,14 @@ "license": "SEE LICENSE IN ", "repository": { "type": "git", - "url": "https://github.com/app/app" + "url": "https://github.com/penpot/penpot" }, "browserslist": [ "defaults" ], "scripts": {}, "devDependencies": { - "autoprefixer": "^9.8.6", + "autoprefixer": "^10.0.1", "clean-css": "^4.2.3", "gulp": "4.0.2", "gulp-gzip": "^1.4.2", @@ -22,21 +22,21 @@ "gulp-rename": "^2.0.0", "gulp-svg-sprite": "^1.5.0", "mkdirp": "^1.0.4", - "postcss": "^7.0.32", + "postcss": "^8.1.2", "rimraf": "^3.0.0", "sass": "^1.26.10", - "shadow-cljs": "^2.11.0" + "shadow-cljs": "2.11.5" }, "dependencies": { "date-fns": "^2.15.0", "map-stream": "0.0.7", "mousetrap": "^1.6.5", "randomcolor": "^0.6.2", - "react": "^16.13.1", - "react-dom": "^16.13.1", - "rxjs": "^7.0.0-beta.4", - "slate": "^0.58.4", - "slate-react": "^0.58.4", + "react": "17.0.1", + "react-dom": "17.0.1", + "rxjs": "7.0.0-beta.4", + "slate": "^0.59.0", + "slate-react": "^0.59.0", "source-map-support": "^0.5.16", "tdigest": "^0.1.1", "xregexp": "^4.3.0" diff --git a/frontend/resources/fonts/WorkSans-Black.eot b/frontend/resources/fonts/WorkSans-Black.eot new file mode 100644 index 0000000000..53bb9c861a Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Black.eot differ diff --git a/frontend/resources/fonts/WorkSans-Black.svg b/frontend/resources/fonts/WorkSans-Black.svg new file mode 100644 index 0000000000..766a534d5e --- /dev/null +++ b/frontend/resources/fonts/WorkSans-Black.svg @@ -0,0 +1,16248 @@ + + + + +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-Black.ttf b/frontend/resources/fonts/WorkSans-Black.ttf new file mode 100644 index 0000000000..64f046239a Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Black.ttf differ diff --git a/frontend/resources/fonts/WorkSans-Black.woff b/frontend/resources/fonts/WorkSans-Black.woff new file mode 100644 index 0000000000..bed6bf1b46 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Black.woff differ diff --git a/frontend/resources/fonts/WorkSans-Black.woff2 b/frontend/resources/fonts/WorkSans-Black.woff2 new file mode 100644 index 0000000000..3b24dc078b Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Black.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-BlackItalic.eot b/frontend/resources/fonts/WorkSans-BlackItalic.eot new file mode 100644 index 0000000000..de216159ec Binary files /dev/null and b/frontend/resources/fonts/WorkSans-BlackItalic.eot differ diff --git a/frontend/resources/fonts/WorkSans-BlackItalic.svg b/frontend/resources/fonts/WorkSans-BlackItalic.svg new file mode 100644 index 0000000000..fed596c151 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-BlackItalic.svg @@ -0,0 +1,16277 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-BlackItalic.ttf new file mode 100644 index 0000000000..6018540cac Binary files /dev/null and b/frontend/resources/fonts/WorkSans-BlackItalic.ttf differ diff --git a/frontend/resources/fonts/WorkSans-BlackItalic.woff b/frontend/resources/fonts/WorkSans-BlackItalic.woff new file mode 100644 index 0000000000..cb758bf784 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-BlackItalic.woff differ diff --git a/frontend/resources/fonts/WorkSans-BlackItalic.woff2 b/frontend/resources/fonts/WorkSans-BlackItalic.woff2 new file mode 100644 index 0000000000..23ff634571 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-BlackItalic.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-Bold.eot b/frontend/resources/fonts/WorkSans-Bold.eot new file mode 100644 index 0000000000..230d453b99 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Bold.eot differ diff --git a/frontend/resources/fonts/WorkSans-Bold.svg b/frontend/resources/fonts/WorkSans-Bold.svg new file mode 100644 index 0000000000..780011b827 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-Bold.svg @@ -0,0 +1,25069 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-Bold.ttf new file mode 100644 index 0000000000..1db5ef6b0c Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Bold.ttf differ diff --git a/frontend/resources/fonts/WorkSans-Bold.woff b/frontend/resources/fonts/WorkSans-Bold.woff new file mode 100644 index 0000000000..f829987ae9 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Bold.woff differ diff --git a/frontend/resources/fonts/WorkSans-Bold.woff2 b/frontend/resources/fonts/WorkSans-Bold.woff2 new file mode 100644 index 0000000000..c8961467da Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Bold.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-BoldItalic.eot b/frontend/resources/fonts/WorkSans-BoldItalic.eot new file mode 100644 index 0000000000..aab9667dcb Binary files /dev/null and b/frontend/resources/fonts/WorkSans-BoldItalic.eot differ diff --git a/frontend/resources/fonts/WorkSans-BoldItalic.svg b/frontend/resources/fonts/WorkSans-BoldItalic.svg new file mode 100644 index 0000000000..e339f34713 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-BoldItalic.svg @@ -0,0 +1,24489 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-BoldItalic.ttf new file mode 100644 index 0000000000..f045d9816f Binary files /dev/null and b/frontend/resources/fonts/WorkSans-BoldItalic.ttf differ diff --git a/frontend/resources/fonts/WorkSans-BoldItalic.woff b/frontend/resources/fonts/WorkSans-BoldItalic.woff new file mode 100644 index 0000000000..0d69d0f65b Binary files /dev/null and b/frontend/resources/fonts/WorkSans-BoldItalic.woff differ diff --git a/frontend/resources/fonts/WorkSans-BoldItalic.woff2 b/frontend/resources/fonts/WorkSans-BoldItalic.woff2 new file mode 100644 index 0000000000..bb8dfd0593 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-BoldItalic.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-ExtraBold.eot b/frontend/resources/fonts/WorkSans-ExtraBold.eot new file mode 100644 index 0000000000..8b733670f8 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraBold.eot differ diff --git a/frontend/resources/fonts/WorkSans-ExtraBold.svg b/frontend/resources/fonts/WorkSans-ExtraBold.svg new file mode 100644 index 0000000000..55e6f50e4d --- /dev/null +++ b/frontend/resources/fonts/WorkSans-ExtraBold.svg @@ -0,0 +1,25065 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-ExtraBold.ttf new file mode 100644 index 0000000000..d56644d2fd Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraBold.ttf differ diff --git a/frontend/resources/fonts/WorkSans-ExtraBold.woff b/frontend/resources/fonts/WorkSans-ExtraBold.woff new file mode 100644 index 0000000000..4741d26470 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraBold.woff differ diff --git a/frontend/resources/fonts/WorkSans-ExtraBold.woff2 b/frontend/resources/fonts/WorkSans-ExtraBold.woff2 new file mode 100644 index 0000000000..9aa1eca0b8 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraBold.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-ExtraBoldItalic.eot b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.eot new file mode 100644 index 0000000000..4b032160f9 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.eot differ diff --git a/frontend/resources/fonts/WorkSans-ExtraBoldItalic.svg b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.svg new file mode 100644 index 0000000000..4ca2b3d807 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.svg @@ -0,0 +1,24462 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.ttf new file mode 100644 index 0000000000..5a9e046b80 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.ttf differ diff --git a/frontend/resources/fonts/WorkSans-ExtraBoldItalic.woff b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.woff new file mode 100644 index 0000000000..accdcb01d0 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.woff differ diff --git a/frontend/resources/fonts/WorkSans-ExtraBoldItalic.woff2 b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.woff2 new file mode 100644 index 0000000000..7ce82c82d0 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraBoldItalic.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-ExtraLight.eot b/frontend/resources/fonts/WorkSans-ExtraLight.eot new file mode 100644 index 0000000000..5830248d7b Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraLight.eot differ diff --git a/frontend/resources/fonts/WorkSans-ExtraLight.svg b/frontend/resources/fonts/WorkSans-ExtraLight.svg new file mode 100644 index 0000000000..cf69107509 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-ExtraLight.svg @@ -0,0 +1,25774 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-ExtraLight.ttf new file mode 100644 index 0000000000..88ae413be7 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraLight.ttf differ diff --git a/frontend/resources/fonts/WorkSans-ExtraLight.woff b/frontend/resources/fonts/WorkSans-ExtraLight.woff new file mode 100644 index 0000000000..6cca5784b2 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraLight.woff differ diff --git a/frontend/resources/fonts/WorkSans-ExtraLight.woff2 b/frontend/resources/fonts/WorkSans-ExtraLight.woff2 new file mode 100644 index 0000000000..ccd3f6bb5b Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraLight.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-ExtraLightItalic.eot b/frontend/resources/fonts/WorkSans-ExtraLightItalic.eot new file mode 100644 index 0000000000..49762c9733 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraLightItalic.eot differ diff --git a/frontend/resources/fonts/WorkSans-ExtraLightItalic.svg b/frontend/resources/fonts/WorkSans-ExtraLightItalic.svg new file mode 100644 index 0000000000..b57d337cdc --- /dev/null +++ b/frontend/resources/fonts/WorkSans-ExtraLightItalic.svg @@ -0,0 +1,25162 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-ExtraLightItalic.ttf new file mode 100644 index 0000000000..816c64df5b Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraLightItalic.ttf differ diff --git a/frontend/resources/fonts/WorkSans-ExtraLightItalic.woff b/frontend/resources/fonts/WorkSans-ExtraLightItalic.woff new file mode 100644 index 0000000000..767271a33b Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraLightItalic.woff differ diff --git a/frontend/resources/fonts/WorkSans-ExtraLightItalic.woff2 b/frontend/resources/fonts/WorkSans-ExtraLightItalic.woff2 new file mode 100644 index 0000000000..09f535ef76 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ExtraLightItalic.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-Italic.eot b/frontend/resources/fonts/WorkSans-Italic.eot new file mode 100644 index 0000000000..ac5da85c3a Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Italic.eot differ diff --git a/frontend/resources/fonts/WorkSans-Italic.svg b/frontend/resources/fonts/WorkSans-Italic.svg new file mode 100644 index 0000000000..6b80809b58 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-Italic.svg @@ -0,0 +1,21510 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-Italic.ttf new file mode 100644 index 0000000000..d7b91d4c54 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Italic.ttf differ diff --git a/frontend/resources/fonts/WorkSans-Italic.woff b/frontend/resources/fonts/WorkSans-Italic.woff new file mode 100644 index 0000000000..ff7fa7e8c2 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Italic.woff differ diff --git a/frontend/resources/fonts/WorkSans-Italic.woff2 b/frontend/resources/fonts/WorkSans-Italic.woff2 new file mode 100644 index 0000000000..e80bfe69d7 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Italic.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-Light.eot b/frontend/resources/fonts/WorkSans-Light.eot new file mode 100644 index 0000000000..1fb80b6584 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Light.eot differ diff --git a/frontend/resources/fonts/WorkSans-Light.svg b/frontend/resources/fonts/WorkSans-Light.svg new file mode 100644 index 0000000000..2f8bd2d709 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-Light.svg @@ -0,0 +1,25793 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-Light.ttf new file mode 100644 index 0000000000..3a7768d742 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Light.ttf differ diff --git a/frontend/resources/fonts/WorkSans-Light.woff b/frontend/resources/fonts/WorkSans-Light.woff new file mode 100644 index 0000000000..3fe4e73bde Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Light.woff differ diff --git a/frontend/resources/fonts/WorkSans-Light.woff2 b/frontend/resources/fonts/WorkSans-Light.woff2 new file mode 100644 index 0000000000..e5095d5fd9 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Light.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-LightItalic.eot b/frontend/resources/fonts/WorkSans-LightItalic.eot new file mode 100644 index 0000000000..a2fef572a8 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-LightItalic.eot differ diff --git a/frontend/resources/fonts/WorkSans-LightItalic.svg b/frontend/resources/fonts/WorkSans-LightItalic.svg new file mode 100644 index 0000000000..d730e13d72 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-LightItalic.svg @@ -0,0 +1,25177 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-LightItalic.ttf new file mode 100644 index 0000000000..f186b05393 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-LightItalic.ttf differ diff --git a/frontend/resources/fonts/WorkSans-LightItalic.woff b/frontend/resources/fonts/WorkSans-LightItalic.woff new file mode 100644 index 0000000000..5f21a50e2e Binary files /dev/null and b/frontend/resources/fonts/WorkSans-LightItalic.woff differ diff --git a/frontend/resources/fonts/WorkSans-LightItalic.woff2 b/frontend/resources/fonts/WorkSans-LightItalic.woff2 new file mode 100644 index 0000000000..5745d422b9 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-LightItalic.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-Medium.eot b/frontend/resources/fonts/WorkSans-Medium.eot new file mode 100644 index 0000000000..67eda94082 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Medium.eot differ diff --git a/frontend/resources/fonts/WorkSans-Medium.svg b/frontend/resources/fonts/WorkSans-Medium.svg new file mode 100644 index 0000000000..2d9fdb520f --- /dev/null +++ b/frontend/resources/fonts/WorkSans-Medium.svg @@ -0,0 +1,25050 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-Medium.ttf new file mode 100644 index 0000000000..66af7dd4f5 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Medium.ttf differ diff --git a/frontend/resources/fonts/WorkSans-Medium.woff b/frontend/resources/fonts/WorkSans-Medium.woff new file mode 100644 index 0000000000..067288fbe3 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Medium.woff differ diff --git a/frontend/resources/fonts/WorkSans-Medium.woff2 b/frontend/resources/fonts/WorkSans-Medium.woff2 new file mode 100644 index 0000000000..8701f974f1 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Medium.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-MediumItalic.eot b/frontend/resources/fonts/WorkSans-MediumItalic.eot new file mode 100644 index 0000000000..43b2002f08 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-MediumItalic.eot differ diff --git a/frontend/resources/fonts/WorkSans-MediumItalic.svg b/frontend/resources/fonts/WorkSans-MediumItalic.svg new file mode 100644 index 0000000000..64377980b3 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-MediumItalic.svg @@ -0,0 +1,24313 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-MediumItalic.ttf new file mode 100644 index 0000000000..3a66a62a96 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-MediumItalic.ttf differ diff --git a/frontend/resources/fonts/WorkSans-MediumItalic.woff b/frontend/resources/fonts/WorkSans-MediumItalic.woff new file mode 100644 index 0000000000..62e9ac0a0d Binary files /dev/null and b/frontend/resources/fonts/WorkSans-MediumItalic.woff differ diff --git a/frontend/resources/fonts/WorkSans-MediumItalic.woff2 b/frontend/resources/fonts/WorkSans-MediumItalic.woff2 new file mode 100644 index 0000000000..2a906a3b65 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-MediumItalic.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-Regular.eot b/frontend/resources/fonts/WorkSans-Regular.eot new file mode 100644 index 0000000000..e3a99d1262 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Regular.eot differ diff --git a/frontend/resources/fonts/WorkSans-Regular.svg b/frontend/resources/fonts/WorkSans-Regular.svg new file mode 100644 index 0000000000..df6da12323 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-Regular.svg @@ -0,0 +1,21954 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-Regular.ttf new file mode 100644 index 0000000000..15047f2a0a Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Regular.ttf differ diff --git a/frontend/resources/fonts/WorkSans-Regular.woff b/frontend/resources/fonts/WorkSans-Regular.woff new file mode 100644 index 0000000000..9b54dc6b05 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Regular.woff differ diff --git a/frontend/resources/fonts/WorkSans-Regular.woff2 b/frontend/resources/fonts/WorkSans-Regular.woff2 new file mode 100644 index 0000000000..39db44def8 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Regular.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-SemiBold.eot b/frontend/resources/fonts/WorkSans-SemiBold.eot new file mode 100644 index 0000000000..92ca2637eb Binary files /dev/null and b/frontend/resources/fonts/WorkSans-SemiBold.eot differ diff --git a/frontend/resources/fonts/WorkSans-SemiBold.svg b/frontend/resources/fonts/WorkSans-SemiBold.svg new file mode 100644 index 0000000000..5cad7ade8c --- /dev/null +++ b/frontend/resources/fonts/WorkSans-SemiBold.svg @@ -0,0 +1,25059 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-SemiBold.ttf new file mode 100644 index 0000000000..3ef9f833aa Binary files /dev/null and b/frontend/resources/fonts/WorkSans-SemiBold.ttf differ diff --git a/frontend/resources/fonts/WorkSans-SemiBold.woff b/frontend/resources/fonts/WorkSans-SemiBold.woff new file mode 100644 index 0000000000..9ebf80f499 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-SemiBold.woff differ diff --git a/frontend/resources/fonts/WorkSans-SemiBold.woff2 b/frontend/resources/fonts/WorkSans-SemiBold.woff2 new file mode 100644 index 0000000000..6c3f6defdc Binary files /dev/null and b/frontend/resources/fonts/WorkSans-SemiBold.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-SemiBoldItalic.eot b/frontend/resources/fonts/WorkSans-SemiBoldItalic.eot new file mode 100644 index 0000000000..e59f0fcc6a Binary files /dev/null and b/frontend/resources/fonts/WorkSans-SemiBoldItalic.eot differ diff --git a/frontend/resources/fonts/WorkSans-SemiBoldItalic.svg b/frontend/resources/fonts/WorkSans-SemiBoldItalic.svg new file mode 100644 index 0000000000..de7a791c33 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-SemiBoldItalic.svg @@ -0,0 +1,24400 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-SemiBoldItalic.ttf new file mode 100644 index 0000000000..afa58c8d6b Binary files /dev/null and b/frontend/resources/fonts/WorkSans-SemiBoldItalic.ttf differ diff --git a/frontend/resources/fonts/WorkSans-SemiBoldItalic.woff b/frontend/resources/fonts/WorkSans-SemiBoldItalic.woff new file mode 100644 index 0000000000..7aed043cde Binary files /dev/null and b/frontend/resources/fonts/WorkSans-SemiBoldItalic.woff differ diff --git a/frontend/resources/fonts/WorkSans-SemiBoldItalic.woff2 b/frontend/resources/fonts/WorkSans-SemiBoldItalic.woff2 new file mode 100644 index 0000000000..137a84bfed Binary files /dev/null and b/frontend/resources/fonts/WorkSans-SemiBoldItalic.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-Thin.eot b/frontend/resources/fonts/WorkSans-Thin.eot new file mode 100644 index 0000000000..547ec1299e Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Thin.eot differ diff --git a/frontend/resources/fonts/WorkSans-Thin.svg b/frontend/resources/fonts/WorkSans-Thin.svg new file mode 100644 index 0000000000..b158ae3b66 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-Thin.svg @@ -0,0 +1,23764 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-Thin.ttf new file mode 100644 index 0000000000..6f32ad822f Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Thin.ttf differ diff --git a/frontend/resources/fonts/WorkSans-Thin.woff b/frontend/resources/fonts/WorkSans-Thin.woff new file mode 100644 index 0000000000..14df699d44 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Thin.woff differ diff --git a/frontend/resources/fonts/WorkSans-Thin.woff2 b/frontend/resources/fonts/WorkSans-Thin.woff2 new file mode 100644 index 0000000000..ede8218421 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-Thin.woff2 differ diff --git a/frontend/resources/fonts/WorkSans-ThinItalic.eot b/frontend/resources/fonts/WorkSans-ThinItalic.eot new file mode 100644 index 0000000000..5c79146567 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ThinItalic.eot differ diff --git a/frontend/resources/fonts/WorkSans-ThinItalic.svg b/frontend/resources/fonts/WorkSans-ThinItalic.svg new file mode 100644 index 0000000000..735b630f30 --- /dev/null +++ b/frontend/resources/fonts/WorkSans-ThinItalic.svg @@ -0,0 +1,23256 @@ + + + + +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.ttf b/frontend/resources/fonts/WorkSans-ThinItalic.ttf new file mode 100644 index 0000000000..71531ac9d8 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ThinItalic.ttf differ diff --git a/frontend/resources/fonts/WorkSans-ThinItalic.woff b/frontend/resources/fonts/WorkSans-ThinItalic.woff new file mode 100644 index 0000000000..57677116e0 Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ThinItalic.woff differ diff --git a/frontend/resources/fonts/WorkSans-ThinItalic.woff2 b/frontend/resources/fonts/WorkSans-ThinItalic.woff2 new file mode 100644 index 0000000000..20f3c77c7d Binary files /dev/null and b/frontend/resources/fonts/WorkSans-ThinItalic.woff2 differ diff --git a/frontend/resources/fonts/opensans-bold.eot b/frontend/resources/fonts/opensans-bold.eot deleted file mode 100644 index 5d20d91633..0000000000 Binary files a/frontend/resources/fonts/opensans-bold.eot and /dev/null differ diff --git a/frontend/resources/fonts/opensans-bold.svg b/frontend/resources/fonts/opensans-bold.svg deleted file mode 100644 index 3ed7be4bc5..0000000000 --- a/frontend/resources/fonts/opensans-bold.svg +++ /dev/null @@ -1,1830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/opensans-bold.ttf b/frontend/resources/fonts/opensans-bold.ttf deleted file mode 100644 index 2109c958e3..0000000000 Binary files a/frontend/resources/fonts/opensans-bold.ttf and /dev/null differ diff --git a/frontend/resources/fonts/opensans-bold.woff b/frontend/resources/fonts/opensans-bold.woff deleted file mode 100644 index 1205787b0e..0000000000 Binary files a/frontend/resources/fonts/opensans-bold.woff and /dev/null differ diff --git a/frontend/resources/fonts/opensans-bolditalic.eot b/frontend/resources/fonts/opensans-bolditalic.eot deleted file mode 100644 index 1f639a15ff..0000000000 Binary files a/frontend/resources/fonts/opensans-bolditalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/opensans-bolditalic.svg b/frontend/resources/fonts/opensans-bolditalic.svg deleted file mode 100644 index 6a2607b9da..0000000000 --- a/frontend/resources/fonts/opensans-bolditalic.svg +++ /dev/null @@ -1,1830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/opensans-bolditalic.ttf b/frontend/resources/fonts/opensans-bolditalic.ttf deleted file mode 100644 index 242d6b25c3..0000000000 Binary files a/frontend/resources/fonts/opensans-bolditalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/opensans-bolditalic.woff b/frontend/resources/fonts/opensans-bolditalic.woff deleted file mode 100644 index ed760c0628..0000000000 Binary files a/frontend/resources/fonts/opensans-bolditalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/opensans-extrabold.eot b/frontend/resources/fonts/opensans-extrabold.eot deleted file mode 100644 index 1e29ad5954..0000000000 Binary files a/frontend/resources/fonts/opensans-extrabold.eot and /dev/null differ diff --git a/frontend/resources/fonts/opensans-extrabold.svg b/frontend/resources/fonts/opensans-extrabold.svg deleted file mode 100644 index 27800505a5..0000000000 --- a/frontend/resources/fonts/opensans-extrabold.svg +++ /dev/null @@ -1,1830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/opensans-extrabold.woff b/frontend/resources/fonts/opensans-extrabold.woff deleted file mode 100644 index a7b99d2552..0000000000 Binary files a/frontend/resources/fonts/opensans-extrabold.woff and /dev/null differ diff --git a/frontend/resources/fonts/opensans-extrabolditalic.eot b/frontend/resources/fonts/opensans-extrabolditalic.eot deleted file mode 100644 index 77184af422..0000000000 Binary files a/frontend/resources/fonts/opensans-extrabolditalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/opensans-extrabolditalic.svg b/frontend/resources/fonts/opensans-extrabolditalic.svg deleted file mode 100644 index 8f080c1e5e..0000000000 --- a/frontend/resources/fonts/opensans-extrabolditalic.svg +++ /dev/null @@ -1,1830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/opensans-extrabolditalic.ttf b/frontend/resources/fonts/opensans-extrabolditalic.ttf deleted file mode 100644 index 26a07e9392..0000000000 Binary files a/frontend/resources/fonts/opensans-extrabolditalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/opensans-extrabolditalic.woff b/frontend/resources/fonts/opensans-extrabolditalic.woff deleted file mode 100644 index 45395d1bbe..0000000000 Binary files a/frontend/resources/fonts/opensans-extrabolditalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/opensans-italic.eot b/frontend/resources/fonts/opensans-italic.eot deleted file mode 100644 index 0c8a0ae06e..0000000000 Binary files a/frontend/resources/fonts/opensans-italic.eot and /dev/null differ diff --git a/frontend/resources/fonts/opensans-italic.svg b/frontend/resources/fonts/opensans-italic.svg deleted file mode 100644 index e1075dcc24..0000000000 --- a/frontend/resources/fonts/opensans-italic.svg +++ /dev/null @@ -1,1830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/opensans-italic.ttf b/frontend/resources/fonts/opensans-italic.ttf deleted file mode 100644 index 12d25d9a73..0000000000 Binary files a/frontend/resources/fonts/opensans-italic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/opensans-italic.woff b/frontend/resources/fonts/opensans-italic.woff deleted file mode 100644 index ff652e6435..0000000000 Binary files a/frontend/resources/fonts/opensans-italic.woff and /dev/null differ diff --git a/frontend/resources/fonts/opensans-light.eot b/frontend/resources/fonts/opensans-light.eot deleted file mode 100644 index 14868406aa..0000000000 Binary files a/frontend/resources/fonts/opensans-light.eot and /dev/null differ diff --git a/frontend/resources/fonts/opensans-light.svg b/frontend/resources/fonts/opensans-light.svg deleted file mode 100644 index 11a472ca8a..0000000000 --- a/frontend/resources/fonts/opensans-light.svg +++ /dev/null @@ -1,1831 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/opensans-light.ttf b/frontend/resources/fonts/opensans-light.ttf deleted file mode 100644 index 63af664cde..0000000000 Binary files a/frontend/resources/fonts/opensans-light.ttf and /dev/null differ diff --git a/frontend/resources/fonts/opensans-light.woff b/frontend/resources/fonts/opensans-light.woff deleted file mode 100644 index e786074813..0000000000 Binary files a/frontend/resources/fonts/opensans-light.woff and /dev/null differ diff --git a/frontend/resources/fonts/opensans-lightitalic.eot b/frontend/resources/fonts/opensans-lightitalic.eot deleted file mode 100644 index 8f445929ff..0000000000 Binary files a/frontend/resources/fonts/opensans-lightitalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/opensans-lightitalic.svg b/frontend/resources/fonts/opensans-lightitalic.svg deleted file mode 100644 index 431d7e3546..0000000000 --- a/frontend/resources/fonts/opensans-lightitalic.svg +++ /dev/null @@ -1,1835 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/opensans-lightitalic.ttf b/frontend/resources/fonts/opensans-lightitalic.ttf deleted file mode 100644 index 01dda2858a..0000000000 Binary files a/frontend/resources/fonts/opensans-lightitalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/opensans-lightitalic.woff b/frontend/resources/fonts/opensans-lightitalic.woff deleted file mode 100644 index 43e8b9e6cc..0000000000 Binary files a/frontend/resources/fonts/opensans-lightitalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/opensans-regular.eot b/frontend/resources/fonts/opensans-regular.eot deleted file mode 100644 index 6bbc3cf58c..0000000000 Binary files a/frontend/resources/fonts/opensans-regular.eot and /dev/null differ diff --git a/frontend/resources/fonts/opensans-regular.svg b/frontend/resources/fonts/opensans-regular.svg deleted file mode 100644 index 25a3952340..0000000000 --- a/frontend/resources/fonts/opensans-regular.svg +++ /dev/null @@ -1,1831 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/opensans-regular.ttf b/frontend/resources/fonts/opensans-regular.ttf deleted file mode 100644 index c537f8382a..0000000000 Binary files a/frontend/resources/fonts/opensans-regular.ttf and /dev/null differ diff --git a/frontend/resources/fonts/opensans-regular.woff b/frontend/resources/fonts/opensans-regular.woff deleted file mode 100644 index e231183dce..0000000000 Binary files a/frontend/resources/fonts/opensans-regular.woff and /dev/null differ diff --git a/frontend/resources/fonts/opensans-semibold.eot b/frontend/resources/fonts/opensans-semibold.eot deleted file mode 100644 index d8375dd0ab..0000000000 Binary files a/frontend/resources/fonts/opensans-semibold.eot and /dev/null differ diff --git a/frontend/resources/fonts/opensans-semibold.svg b/frontend/resources/fonts/opensans-semibold.svg deleted file mode 100644 index eec4db8bd7..0000000000 --- a/frontend/resources/fonts/opensans-semibold.svg +++ /dev/null @@ -1,1830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/opensans-semibold.ttf b/frontend/resources/fonts/opensans-semibold.ttf deleted file mode 100644 index b3290843a7..0000000000 Binary files a/frontend/resources/fonts/opensans-semibold.ttf and /dev/null differ diff --git a/frontend/resources/fonts/opensans-semibold.woff b/frontend/resources/fonts/opensans-semibold.woff deleted file mode 100644 index 28d6adee03..0000000000 Binary files a/frontend/resources/fonts/opensans-semibold.woff and /dev/null differ diff --git a/frontend/resources/fonts/opensans-semibolditalic.eot b/frontend/resources/fonts/opensans-semibolditalic.eot deleted file mode 100644 index 0ab1db22e6..0000000000 Binary files a/frontend/resources/fonts/opensans-semibolditalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/opensans-semibolditalic.svg b/frontend/resources/fonts/opensans-semibolditalic.svg deleted file mode 100644 index 7166ec1b90..0000000000 --- a/frontend/resources/fonts/opensans-semibolditalic.svg +++ /dev/null @@ -1,1830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/opensans-semibolditalic.ttf b/frontend/resources/fonts/opensans-semibolditalic.ttf deleted file mode 100644 index d2d6318f66..0000000000 Binary files a/frontend/resources/fonts/opensans-semibolditalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/opensans-semibolditalic.woff b/frontend/resources/fonts/opensans-semibolditalic.woff deleted file mode 100644 index d4dfca402e..0000000000 Binary files a/frontend/resources/fonts/opensans-semibolditalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-black.eot b/frontend/resources/fonts/roboto-black.eot deleted file mode 100644 index fa326d1d12..0000000000 Binary files a/frontend/resources/fonts/roboto-black.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-black.svg b/frontend/resources/fonts/roboto-black.svg deleted file mode 100644 index 945dec6cbd..0000000000 --- a/frontend/resources/fonts/roboto-black.svg +++ /dev/null @@ -1,642 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-black.ttf b/frontend/resources/fonts/roboto-black.ttf deleted file mode 100644 index 3c3b2b8ae6..0000000000 Binary files a/frontend/resources/fonts/roboto-black.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-black.woff b/frontend/resources/fonts/roboto-black.woff deleted file mode 100644 index 0229086571..0000000000 Binary files a/frontend/resources/fonts/roboto-black.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-blackitalic.eot b/frontend/resources/fonts/roboto-blackitalic.eot deleted file mode 100644 index a2aebfb7da..0000000000 Binary files a/frontend/resources/fonts/roboto-blackitalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-blackitalic.svg b/frontend/resources/fonts/roboto-blackitalic.svg deleted file mode 100644 index c9cc3cdb7b..0000000000 --- a/frontend/resources/fonts/roboto-blackitalic.svg +++ /dev/null @@ -1,642 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-blackitalic.ttf b/frontend/resources/fonts/roboto-blackitalic.ttf deleted file mode 100644 index 2020dcbc9c..0000000000 Binary files a/frontend/resources/fonts/roboto-blackitalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-blackitalic.woff b/frontend/resources/fonts/roboto-blackitalic.woff deleted file mode 100644 index 1875c0b950..0000000000 Binary files a/frontend/resources/fonts/roboto-blackitalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-bold.eot b/frontend/resources/fonts/roboto-bold.eot deleted file mode 100644 index b73776ee3b..0000000000 Binary files a/frontend/resources/fonts/roboto-bold.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-bold.svg b/frontend/resources/fonts/roboto-bold.svg deleted file mode 100644 index 43b5ed2222..0000000000 --- a/frontend/resources/fonts/roboto-bold.svg +++ /dev/null @@ -1,593 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-bold.ttf b/frontend/resources/fonts/roboto-bold.ttf deleted file mode 100644 index 1da72769a8..0000000000 Binary files a/frontend/resources/fonts/roboto-bold.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-bold.woff b/frontend/resources/fonts/roboto-bold.woff deleted file mode 100644 index 0c6994871e..0000000000 Binary files a/frontend/resources/fonts/roboto-bold.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-bolditalic.eot b/frontend/resources/fonts/roboto-bolditalic.eot deleted file mode 100644 index b803ec1687..0000000000 Binary files a/frontend/resources/fonts/roboto-bolditalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-bolditalic.svg b/frontend/resources/fonts/roboto-bolditalic.svg deleted file mode 100644 index f877a3c821..0000000000 --- a/frontend/resources/fonts/roboto-bolditalic.svg +++ /dev/null @@ -1,642 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-bolditalic.ttf b/frontend/resources/fonts/roboto-bolditalic.ttf deleted file mode 100644 index 78bab05c8c..0000000000 Binary files a/frontend/resources/fonts/roboto-bolditalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-bolditalic.woff b/frontend/resources/fonts/roboto-bolditalic.woff deleted file mode 100644 index 99de61af52..0000000000 Binary files a/frontend/resources/fonts/roboto-bolditalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-italic.eot b/frontend/resources/fonts/roboto-italic.eot deleted file mode 100644 index b708f047ff..0000000000 Binary files a/frontend/resources/fonts/roboto-italic.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-italic.svg b/frontend/resources/fonts/roboto-italic.svg deleted file mode 100644 index 49ddd4ab76..0000000000 --- a/frontend/resources/fonts/roboto-italic.svg +++ /dev/null @@ -1,642 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-italic.ttf b/frontend/resources/fonts/roboto-italic.ttf deleted file mode 100644 index ae258e8416..0000000000 Binary files a/frontend/resources/fonts/roboto-italic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-italic.woff b/frontend/resources/fonts/roboto-italic.woff deleted file mode 100644 index dd74244382..0000000000 Binary files a/frontend/resources/fonts/roboto-italic.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-light.eot b/frontend/resources/fonts/roboto-light.eot deleted file mode 100644 index 072cdc480c..0000000000 Binary files a/frontend/resources/fonts/roboto-light.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-light.svg b/frontend/resources/fonts/roboto-light.svg deleted file mode 100644 index db6a6171ed..0000000000 --- a/frontend/resources/fonts/roboto-light.svg +++ /dev/null @@ -1,641 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-light.ttf b/frontend/resources/fonts/roboto-light.ttf deleted file mode 100644 index 3b2fea0ace..0000000000 Binary files a/frontend/resources/fonts/roboto-light.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-light.woff b/frontend/resources/fonts/roboto-light.woff deleted file mode 100644 index cc534a3815..0000000000 Binary files a/frontend/resources/fonts/roboto-light.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-lightitalic.eot b/frontend/resources/fonts/roboto-lightitalic.eot deleted file mode 100644 index 77396a1ff9..0000000000 Binary files a/frontend/resources/fonts/roboto-lightitalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-lightitalic.svg b/frontend/resources/fonts/roboto-lightitalic.svg deleted file mode 100644 index 4bd14bcca7..0000000000 --- a/frontend/resources/fonts/roboto-lightitalic.svg +++ /dev/null @@ -1,641 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-lightitalic.ttf b/frontend/resources/fonts/roboto-lightitalic.ttf deleted file mode 100644 index b9b38118a3..0000000000 Binary files a/frontend/resources/fonts/roboto-lightitalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-lightitalic.woff b/frontend/resources/fonts/roboto-lightitalic.woff deleted file mode 100644 index 3071ff4f23..0000000000 Binary files a/frontend/resources/fonts/roboto-lightitalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-medium.eot b/frontend/resources/fonts/roboto-medium.eot deleted file mode 100644 index f9ad99566d..0000000000 Binary files a/frontend/resources/fonts/roboto-medium.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-medium.svg b/frontend/resources/fonts/roboto-medium.svg deleted file mode 100644 index 4ce289dfa4..0000000000 --- a/frontend/resources/fonts/roboto-medium.svg +++ /dev/null @@ -1,593 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-medium.ttf b/frontend/resources/fonts/roboto-medium.ttf deleted file mode 100644 index 8aa64d8232..0000000000 Binary files a/frontend/resources/fonts/roboto-medium.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-medium.woff b/frontend/resources/fonts/roboto-medium.woff deleted file mode 100644 index cd810ef929..0000000000 Binary files a/frontend/resources/fonts/roboto-medium.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-mediumitalic.eot b/frontend/resources/fonts/roboto-mediumitalic.eot deleted file mode 100644 index a03fe4b248..0000000000 Binary files a/frontend/resources/fonts/roboto-mediumitalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-mediumitalic.svg b/frontend/resources/fonts/roboto-mediumitalic.svg deleted file mode 100644 index 904d7c5280..0000000000 --- a/frontend/resources/fonts/roboto-mediumitalic.svg +++ /dev/null @@ -1,642 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-mediumitalic.ttf b/frontend/resources/fonts/roboto-mediumitalic.ttf deleted file mode 100644 index 6439927f16..0000000000 Binary files a/frontend/resources/fonts/roboto-mediumitalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-mediumitalic.woff b/frontend/resources/fonts/roboto-mediumitalic.woff deleted file mode 100644 index 69a1458011..0000000000 Binary files a/frontend/resources/fonts/roboto-mediumitalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-regular.eot b/frontend/resources/fonts/roboto-regular.eot deleted file mode 100644 index 9b5e8e4138..0000000000 Binary files a/frontend/resources/fonts/roboto-regular.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-regular.svg b/frontend/resources/fonts/roboto-regular.svg deleted file mode 100644 index de7d77fea5..0000000000 --- a/frontend/resources/fonts/roboto-regular.svg +++ /dev/null @@ -1,621 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-regular.ttf b/frontend/resources/fonts/roboto-regular.ttf deleted file mode 100644 index 44dd78d5e1..0000000000 Binary files a/frontend/resources/fonts/roboto-regular.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-regular.woff b/frontend/resources/fonts/roboto-regular.woff deleted file mode 100644 index bfa05d53f4..0000000000 Binary files a/frontend/resources/fonts/roboto-regular.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-thin.eot b/frontend/resources/fonts/roboto-thin.eot deleted file mode 100644 index 2284a3b3ef..0000000000 Binary files a/frontend/resources/fonts/roboto-thin.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-thin.svg b/frontend/resources/fonts/roboto-thin.svg deleted file mode 100644 index 7394e3d0a6..0000000000 --- a/frontend/resources/fonts/roboto-thin.svg +++ /dev/null @@ -1,631 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-thin.ttf b/frontend/resources/fonts/roboto-thin.ttf deleted file mode 100644 index 18919f7a96..0000000000 Binary files a/frontend/resources/fonts/roboto-thin.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-thin.woff b/frontend/resources/fonts/roboto-thin.woff deleted file mode 100644 index f10b831e85..0000000000 Binary files a/frontend/resources/fonts/roboto-thin.woff and /dev/null differ diff --git a/frontend/resources/fonts/roboto-thinitalic.eot b/frontend/resources/fonts/roboto-thinitalic.eot deleted file mode 100644 index e6291f2657..0000000000 Binary files a/frontend/resources/fonts/roboto-thinitalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/roboto-thinitalic.svg b/frontend/resources/fonts/roboto-thinitalic.svg deleted file mode 100644 index 951cccd9dc..0000000000 --- a/frontend/resources/fonts/roboto-thinitalic.svg +++ /dev/null @@ -1,631 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/roboto-thinitalic.ttf b/frontend/resources/fonts/roboto-thinitalic.ttf deleted file mode 100644 index a4e7ae08e0..0000000000 Binary files a/frontend/resources/fonts/roboto-thinitalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/roboto-thinitalic.woff b/frontend/resources/fonts/roboto-thinitalic.woff deleted file mode 100644 index 9ef17a8681..0000000000 Binary files a/frontend/resources/fonts/roboto-thinitalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-bold.eot b/frontend/resources/fonts/robotocondensed-bold.eot deleted file mode 100644 index bbc67d825b..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-bold.eot and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-bold.svg b/frontend/resources/fonts/robotocondensed-bold.svg deleted file mode 100644 index 417a2a9e3e..0000000000 --- a/frontend/resources/fonts/robotocondensed-bold.svg +++ /dev/null @@ -1,643 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/robotocondensed-bold.ttf b/frontend/resources/fonts/robotocondensed-bold.ttf deleted file mode 100644 index 87256d3a34..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-bold.ttf and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-bold.woff b/frontend/resources/fonts/robotocondensed-bold.woff deleted file mode 100644 index 235c963d69..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-bold.woff and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-bolditalic.eot b/frontend/resources/fonts/robotocondensed-bolditalic.eot deleted file mode 100644 index 5a95d1a144..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-bolditalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-bolditalic.svg b/frontend/resources/fonts/robotocondensed-bolditalic.svg deleted file mode 100644 index f468ca3320..0000000000 --- a/frontend/resources/fonts/robotocondensed-bolditalic.svg +++ /dev/null @@ -1,643 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/robotocondensed-bolditalic.ttf b/frontend/resources/fonts/robotocondensed-bolditalic.ttf deleted file mode 100644 index 059feb39c5..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-bolditalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-bolditalic.woff b/frontend/resources/fonts/robotocondensed-bolditalic.woff deleted file mode 100644 index df69b95291..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-bolditalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-italic.eot b/frontend/resources/fonts/robotocondensed-italic.eot deleted file mode 100644 index 985364ffed..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-italic.eot and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-italic.svg b/frontend/resources/fonts/robotocondensed-italic.svg deleted file mode 100644 index e3e72ddf09..0000000000 --- a/frontend/resources/fonts/robotocondensed-italic.svg +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/robotocondensed-italic.ttf b/frontend/resources/fonts/robotocondensed-italic.ttf deleted file mode 100644 index d804301e70..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-italic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-italic.woff b/frontend/resources/fonts/robotocondensed-italic.woff deleted file mode 100644 index 67804e1e4d..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-italic.woff and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-light.eot b/frontend/resources/fonts/robotocondensed-light.eot deleted file mode 100644 index d492665160..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-light.eot and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-light.svg b/frontend/resources/fonts/robotocondensed-light.svg deleted file mode 100644 index 6ce1343456..0000000000 --- a/frontend/resources/fonts/robotocondensed-light.svg +++ /dev/null @@ -1,643 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/robotocondensed-light.ttf b/frontend/resources/fonts/robotocondensed-light.ttf deleted file mode 100644 index 321c450f22..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-light.ttf and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-light.woff b/frontend/resources/fonts/robotocondensed-light.woff deleted file mode 100644 index c414478ca5..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-light.woff and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-lightitalic.eot b/frontend/resources/fonts/robotocondensed-lightitalic.eot deleted file mode 100644 index c169648374..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-lightitalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-lightitalic.svg b/frontend/resources/fonts/robotocondensed-lightitalic.svg deleted file mode 100644 index 8a8b4cb173..0000000000 --- a/frontend/resources/fonts/robotocondensed-lightitalic.svg +++ /dev/null @@ -1,643 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/robotocondensed-lightitalic.ttf b/frontend/resources/fonts/robotocondensed-lightitalic.ttf deleted file mode 100644 index 905604d021..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-lightitalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-lightitalic.woff b/frontend/resources/fonts/robotocondensed-lightitalic.woff deleted file mode 100644 index 5536e16c5d..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-lightitalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-regular.eot b/frontend/resources/fonts/robotocondensed-regular.eot deleted file mode 100644 index 5710fe6ead..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-regular.eot and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-regular.svg b/frontend/resources/fonts/robotocondensed-regular.svg deleted file mode 100644 index 0ce498562d..0000000000 --- a/frontend/resources/fonts/robotocondensed-regular.svg +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/robotocondensed-regular.ttf b/frontend/resources/fonts/robotocondensed-regular.ttf deleted file mode 100644 index be9f6f8ab9..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-regular.ttf and /dev/null differ diff --git a/frontend/resources/fonts/robotocondensed-regular.woff b/frontend/resources/fonts/robotocondensed-regular.woff deleted file mode 100644 index ec28f95c87..0000000000 Binary files a/frontend/resources/fonts/robotocondensed-regular.woff and /dev/null differ diff --git a/frontend/resources/images/icons/checkbox-checked.svg b/frontend/resources/images/icons/checkbox-checked.svg new file mode 100644 index 0000000000..21d24f176d --- /dev/null +++ b/frontend/resources/images/icons/checkbox-checked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/resources/images/icons/checkbox-unchecked.svg b/frontend/resources/images/icons/checkbox-unchecked.svg new file mode 100644 index 0000000000..68f7d0d111 --- /dev/null +++ b/frontend/resources/images/icons/checkbox-unchecked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/resources/images/icons/code.svg b/frontend/resources/images/icons/code.svg new file mode 100644 index 0000000000..d8ae15c9ef --- /dev/null +++ b/frontend/resources/images/icons/code.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/copy.svg b/frontend/resources/images/icons/copy.svg index 41863f61ef..8a65c91c70 100644 --- a/frontend/resources/images/icons/copy.svg +++ b/frontend/resources/images/icons/copy.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + diff --git a/frontend/resources/images/icons/interaction.svg b/frontend/resources/images/icons/interaction.svg index 0fd5dd946b..afc359d843 100644 --- a/frontend/resources/images/icons/interaction.svg +++ b/frontend/resources/images/icons/interaction.svg @@ -1 +1,5 @@ - + + + + + diff --git a/frontend/resources/images/icons/mask.svg b/frontend/resources/images/icons/mask.svg new file mode 100644 index 0000000000..3c2e315340 --- /dev/null +++ b/frontend/resources/images/icons/mask.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/picker-harmony.svg b/frontend/resources/images/icons/picker-harmony.svg new file mode 100644 index 0000000000..c108e28127 --- /dev/null +++ b/frontend/resources/images/icons/picker-harmony.svg @@ -0,0 +1,2 @@ + + diff --git a/frontend/resources/images/icons/picker-hsv.svg b/frontend/resources/images/icons/picker-hsv.svg new file mode 100644 index 0000000000..2218c82a17 --- /dev/null +++ b/frontend/resources/images/icons/picker-hsv.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/icons/picker-ramp.svg b/frontend/resources/images/icons/picker-ramp.svg new file mode 100644 index 0000000000..815b3a9449 --- /dev/null +++ b/frontend/resources/images/icons/picker-ramp.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/picker.svg b/frontend/resources/images/icons/picker.svg index f486028b4b..99c8edc76d 100644 --- a/frontend/resources/images/icons/picker.svg +++ b/frontend/resources/images/icons/picker.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + diff --git a/frontend/resources/images/icons/recent.svg b/frontend/resources/images/icons/recent.svg index d9d237feee..590de65173 100644 --- a/frontend/resources/images/icons/recent.svg +++ b/frontend/resources/images/icons/recent.svg @@ -1,3 +1,3 @@ - - + + diff --git a/frontend/resources/images/icons/size-horiz.svg b/frontend/resources/images/icons/size-horiz.svg index 95c19749c9..ab94565804 100644 --- a/frontend/resources/images/icons/size-horiz.svg +++ b/frontend/resources/images/icons/size-horiz.svg @@ -1 +1,3 @@ - + + + diff --git a/frontend/resources/images/icons/size-vert.svg b/frontend/resources/images/icons/size-vert.svg index 7cb95722f3..f2e807c40e 100644 --- a/frontend/resources/images/icons/size-vert.svg +++ b/frontend/resources/images/icons/size-vert.svg @@ -1 +1,3 @@ - + + + diff --git a/frontend/resources/images/icons/unchain.svg b/frontend/resources/images/icons/unchain.svg new file mode 100644 index 0000000000..5727cc3801 --- /dev/null +++ b/frontend/resources/images/icons/unchain.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index 04fd94fd05..22187701ba 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -1,6 +1,6 @@ { "auth.already-have-account" : { - "used-in" : [ "src/app/main/ui/auth/register.cljs:114" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:127" ], "translations" : { "en" : "Already have an account?", "fr" : "Vous avez déjà un compte?", @@ -8,8 +8,8 @@ "es" : "¿Tienes ya una cuenta?" } }, - "auth.confirm-password-label" : { - "used-in" : [ "src/app/main/ui/auth/recovery.cljs:76" ], + "auth.confirm-password" : { + "used-in" : [ "src/app/main/ui/auth/recovery.cljs:77" ], "translations" : { "en" : "Confirm password", "fr" : "Confirmez mot de passe", @@ -17,17 +17,17 @@ "es" : "Confirmar contraseña" } }, - "auth.create-demo-profile" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:135", "src/app/main/ui/auth/register.cljs:123" ], + "auth.create-demo-account" : { + "used-in" : [ "src/app/main/ui/auth/login.cljs:147" ], "translations" : { "en" : "Create demo account", - "fr" : "Créer un compte de démonstration", - "ru" : "Создать демо аккаунт", - "es" : "Crear cuenta de demostración" + "fr" : "Vous voulez juste essayer?", + "ru" : "Хотите попробовать?", + "es" : "Crear cuanta de prueba" } }, - "auth.create-demo-profile-label" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:132", "src/app/main/ui/auth/register.cljs:120" ], + "auth.create-demo-profile" : { + "used-in" : [ "src/app/main/ui/auth/login.cljs:144", "src/app/main/ui/auth/register.cljs:133", "src/app/main/ui/auth/register.cljs:136" ], "translations" : { "en" : "Just wanna try it?", "fr" : "Vous voulez juste essayer?", @@ -44,8 +44,8 @@ "es" : "Este es un servicio de DEMOSTRACIÓN. NO USAR para trabajo real, los proyectos serán borrados periodicamente." } }, - "auth.email-label" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:81", "src/app/main/ui/auth/register.cljs:89", "src/app/main/ui/auth/recovery_request.cljs:45" ], + "auth.email" : { + "used-in" : [ "src/app/main/ui/auth/login.cljs:92", "src/app/main/ui/auth/register.cljs:101", "src/app/main/ui/auth/recovery_request.cljs:47" ], "translations" : { "en" : "Email", "fr" : "Adresse email", @@ -54,7 +54,7 @@ } }, "auth.forgot-password" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:110" ], + "used-in" : [ "src/app/main/ui/auth/login.cljs:122" ], "translations" : { "en" : "Forgot your password?", "fr" : "Mot de passe oublié?", @@ -62,8 +62,8 @@ "es" : "¿Olvidaste tu contraseña?" } }, - "auth.fullname-label" : { - "used-in" : [ "src/app/main/ui/auth/register.cljs:83" ], + "auth.fullname" : { + "used-in" : [ "src/app/main/ui/auth/register.cljs:94" ], "translations" : { "en" : "Full Name", "fr" : "Nom complet", @@ -72,7 +72,7 @@ } }, "auth.go-back-to-login" : { - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs:66" ], + "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs:68" ], "translations" : { "en" : "Go back!", "fr" : "Retour!", @@ -81,7 +81,7 @@ } }, "auth.goodbye-title" : { - "used-in" : [ "src/app/main/ui/auth.cljs:33" ], + "used-in" : [ "src/app/main/ui/auth.cljs:35" ], "translations" : { "en" : "Goodbye!", "fr" : "Au revoir!", @@ -90,7 +90,7 @@ } }, "auth.login-here" : { - "used-in" : [ "src/app/main/ui/auth/register.cljs:117" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:130" ], "translations" : { "en" : "Login here", "fr" : "Se connecter ici", @@ -98,8 +98,8 @@ "es" : "Entra aquí" } }, - "auth.login-submit-label" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:89" ], + "auth.login-submit" : { + "used-in" : [ "src/app/main/ui/auth/login.cljs:101" ], "translations" : { "en" : "Sign in", "fr" : "Se connecter", @@ -108,7 +108,7 @@ } }, "auth.login-subtitle" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:102" ], + "used-in" : [ "src/app/main/ui/auth/login.cljs:114" ], "translations" : { "en" : "Enter your details below", "fr" : "Entrez vos informations ci-dessous", @@ -117,7 +117,7 @@ } }, "auth.login-title" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:101" ], + "used-in" : [ "src/app/main/ui/auth/login.cljs:113" ], "translations" : { "en" : "Great to see you again!", "fr" : "Ravi de vous revoir!", @@ -125,8 +125,8 @@ "es" : "Encantados de volverte a ver" } }, - "auth.login-with-gitlab-submit-label" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:128" ], + "auth.login-with-gitlab-submit" : { + "used-in" : [ "src/app/main/ui/auth/login.cljs:140" ], "translations" : { "en" : "Login with Gitlab", "fr" : "Se connecter via Gitlab", @@ -134,8 +134,8 @@ "es" : "Entrar con Gitlab" } }, - "auth.login-with-ldap-submit-label" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:93" ], + "auth.login-with-ldap-submit" : { + "used-in" : [ "src/app/main/ui/auth/login.cljs:105" ], "translations" : { "en" : "Sign in with LDAP", "fr" : "Se connecter via LDAP", @@ -143,7 +143,7 @@ "es" : "Entrar con LDAP" } }, - "auth.new-password-label" : { + "auth.new-password" : { "used-in" : [ "src/app/main/ui/auth/recovery.cljs:72" ], "translations" : { "en" : "Type a new password", @@ -153,7 +153,7 @@ } }, "auth.notifications.invalid-token-error" : { - "used-in" : [ "src/app/main/ui/auth/recovery.cljs:48" ], + "used-in" : [ "src/app/main/ui/auth/recovery.cljs:47" ], "translations" : { "en" : "The recovery token is invalid.", "fr" : "Le code de récupération n'est pas valide.", @@ -162,7 +162,7 @@ } }, "auth.notifications.password-changed-succesfully" : { - "used-in" : [ "src/app/main/ui/auth/recovery.cljs:52" ], + "used-in" : [ "src/app/main/ui/auth/recovery.cljs:51" ], "translations" : { "en" : "Password successfully changed", "fr" : "Mot de passe changé avec succès", @@ -171,7 +171,7 @@ } }, "auth.notifications.recovery-token-sent" : { - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs:32" ], + "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs:30" ], "translations" : { "en" : "Password recovery link sent to your inbox.", "fr" : "Lien de récupération de mot de passe envoyé.", @@ -179,14 +179,14 @@ "es" : "Hemos enviado a tu buzón un enlace para recuperar tu contraseña." } }, - "auth.notifications.validation-email-sent" : { - "used-in" : [ "src/app/main/ui/settings/change_email.cljs:55", "src/app/main/ui/auth/register.cljs:58" ], + "auth.notifications.team-invitation-accepted" : { + "used-in" : [ "src/app/main/ui/auth/verify_token.cljs:55", "src/app/main/ui/auth/register.cljs:50" ], "translations" : { - "en" : "Verification email sent to %s; check your email!" + "en" : "Joined the team succesfully" } }, - "auth.password-label" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:87", "src/app/main/ui/auth/register.cljs:93" ], + "auth.password" : { + "used-in" : [ "src/app/main/ui/auth/login.cljs:99", "src/app/main/ui/auth/register.cljs:106" ], "translations" : { "en" : "Password", "fr" : "Mot de passe", @@ -195,7 +195,7 @@ } }, "auth.password-length-hint" : { - "used-in" : [ "src/app/main/ui/auth/register.cljs:92" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:105" ], "translations" : { "en" : "At least 8 characters", "fr" : "Au moins 8 caractères", @@ -203,8 +203,8 @@ "es" : "8 caracteres como mínimo" } }, - "auth.recovery-request-submit-label" : { - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs:50" ], + "auth.recovery-request-submit" : { + "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs:52" ], "translations" : { "en" : "Recover Password", "fr" : "Récupérer le mot de passe", @@ -213,7 +213,7 @@ } }, "auth.recovery-request-subtitle" : { - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs:59" ], + "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs:62" ], "translations" : { "en" : "We'll send you an email with instructions", "fr" : "Nous vous enverrons un e-mail avec des instructions", @@ -222,7 +222,7 @@ } }, "auth.recovery-request-title" : { - "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs:58" ], + "used-in" : [ "src/app/main/ui/auth/recovery_request.cljs:61" ], "translations" : { "en" : "Forgot your password?", "fr" : "Vous avez oublié votre mot de passe?", @@ -230,8 +230,8 @@ "es" : "¿Olvidaste tu contraseña?" } }, - "auth.recovery-submit-label" : { - "used-in" : [ "src/app/main/ui/auth/recovery.cljs:79" ], + "auth.recovery-submit" : { + "used-in" : [ "src/app/main/ui/auth/recovery.cljs:80" ], "translations" : { "en" : "Change your password", "fr" : "Changez votre mot de passe", @@ -240,16 +240,7 @@ } }, "auth.register" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:116" ], - "translations" : { - "en" : "Sign up here", - "fr" : "Inscrivez-vous ici", - "ru" : "Зарегистрироваться", - "es" : "Regístrate aquí" - } - }, - "auth.register-label" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:113" ], + "used-in" : [ "src/app/main/ui/auth/login.cljs:125" ], "translations" : { "en" : "No account yet?", "fr" : "Pas encore de compte?", @@ -257,8 +248,8 @@ "es" : "¿No tienes una cuenta?" } }, - "auth.register-submit-label" : { - "used-in" : [ "src/app/main/ui/auth/register.cljs:97" ], + "auth.register-submit" : { + "used-in" : [ "src/app/main/ui/auth/login.cljs:128", "src/app/main/ui/auth/register.cljs:110" ], "translations" : { "en" : "Create an account", "fr" : "Créer un compte", @@ -267,7 +258,7 @@ } }, "auth.register-subtitle" : { - "used-in" : [ "src/app/main/ui/auth/register.cljs:106" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:118" ], "translations" : { "en" : "It's free, it's Open Source", "fr" : "C'est gratuit, c'est Open Source", @@ -276,7 +267,7 @@ } }, "auth.register-title" : { - "used-in" : [ "src/app/main/ui/auth/register.cljs:105" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:117" ], "translations" : { "en" : "Create an account", "fr" : "Créer un compte", @@ -285,7 +276,7 @@ } }, "auth.sidebar-tagline" : { - "used-in" : [ "src/app/main/ui/auth.cljs:42" ], + "used-in" : [ "src/app/main/ui/auth.cljs:46" ], "translations" : { "en" : "The open-source solution for design and prototyping.", "fr" : "La solution Open Source pour la conception et le prototypage.", @@ -293,8 +284,8 @@ "es" : "La solución de código abierto para diseñar y prototipar" } }, - "dashboard.grid.add-shared" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:210", "src/app/main/ui/dashboard/grid.cljs:165" ], + "dashboard.add-shared" : { + "used-in" : [ "src/app/main/ui/workspace/header.cljs:224", "src/app/main/ui/dashboard/grid.cljs:180" ], "translations" : { "en" : "Add as Shared Library", "fr" : "", @@ -302,98 +293,36 @@ "es" : "Añadir como Biblioteca Compartida" } }, - "dashboard.grid.add-shared-accept" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:104", "src/app/main/ui/dashboard/grid.cljs:94" ], + "dashboard.change-email" : { + "used-in" : [ "src/app/main/ui/settings/profile.cljs:77" ], "translations" : { - "en" : "Add as Shared Library", - "fr" : "", - "ru" : "", - "es" : "Añadir como Biblioteca Compartida" + "en" : "Change email", + "fr" : "Changer adresse e-mail", + "ru" : "Сменить email адрес", + "es" : "Cambiar correo" } }, - "dashboard.grid.add-shared-hint" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:103", "src/app/main/ui/dashboard/grid.cljs:93" ], + "dashboard.create-new-team" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:155" ], "translations" : { - "en" : "Once added as Shared Library, the assets of this file library will be available to be used among the rest of your files.", - "fr" : "", - "ru" : "", - "es" : "Una vez añadido como Biblioteca Compartida, los recursos de este archivo estarán disponibles para ser usado por el resto de tus archivos." + "en" : "+ Create new team", + "es" : "+ Crear nuevo equipo" } }, - "dashboard.grid.add-shared-message" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:102", "src/app/main/ui/dashboard/grid.cljs:92" ], + "dashboard.default-team-name" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:325" ], "translations" : { - "en" : "Add “%s” as Shared Library", - "fr" : "", - "ru" : "", - "es" : "Añadir “%s” como Biblioteca Compartida" + "en" : "Your penpot" } }, - "dashboard.grid.delete" : { - "used-in" : [ "src/app/main/ui/dashboard/project.cljs:61", "src/app/main/ui/dashboard/grid.cljs:162" ], + "dashboard.delete-team" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:309" ], "translations" : { - "en" : "Delete", - "fr" : "Supprimer", - "ru" : "Удалить", - "es" : "Borrar" + "en" : "Delete team" } }, - "dashboard.grid.empty-files" : { - "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:189" ], - "translations" : { - "en" : "You still have no files here", - "fr" : "Vous n'avez encore aucun fichier ici", - "ru" : "Файлов пока нет", - "es" : "Todavía no hay ningún archivo aquí" - } - }, - "dashboard.grid.remove-shared" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:208", "src/app/main/ui/dashboard/grid.cljs:164" ], - "translations" : { - "en" : "Remove as Shared Library", - "fr" : "", - "ru" : "", - "es" : "Eliminar como Biblioteca Compartida" - } - }, - "dashboard.grid.remove-shared-accept" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:113", "src/app/main/ui/dashboard/grid.cljs:113" ], - "translations" : { - "en" : "Remove as Shared Library", - "fr" : "", - "ru" : "", - "es" : "Eliminar como Biblioteca Compartida" - } - }, - "dashboard.grid.remove-shared-hint" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:112", "src/app/main/ui/dashboard/grid.cljs:112" ], - "translations" : { - "en" : "Once removed as Shared Library, the File Library of this file will stop being available to be used among the rest of your files.", - "fr" : "", - "ru" : "", - "es" : "Una vez eliminado como Biblioteca Compartida, la Biblioteca de este archivo dejará de estar disponible para ser usada por el resto de tus archivos." - } - }, - "dashboard.grid.remove-shared-message" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:111", "src/app/main/ui/dashboard/grid.cljs:111" ], - "translations" : { - "en" : "Remove “%s” as Shared Library", - "fr" : "", - "ru" : "", - "es" : "Añadir “%s” como Biblioteca Compartida" - } - }, - "dashboard.grid.rename" : { - "used-in" : [ "src/app/main/ui/dashboard/project.cljs:60", "src/app/main/ui/dashboard/grid.cljs:161" ], - "translations" : { - "en" : "Rename", - "fr" : "Renommer", - "ru" : "Переименовать", - "es" : "Renombrar" - } - }, - "dashboard.header.draft" : { - "used-in" : [ "src/app/main/ui/dashboard/project.cljs:54" ], + "dashboard.draft-title" : { + "used-in" : [ "src/app/main/ui/dashboard/files.cljs:72" ], "translations" : { "en" : "Draft", "fr" : "Brouillon", @@ -401,7 +330,29 @@ "es" : "Borrador" } }, - "dashboard.header.libraries" : { + "dashboard.empty-files" : { + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:187" ], + "translations" : { + "en" : "You still have no files here", + "fr" : "Vous n'avez encore aucun fichier ici", + "ru" : "Файлов пока нет", + "es" : "Todavía no hay ningún archivo aquí" + } + }, + "dashboard.invite-profile" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:69" ], + "translations" : { + "en" : "Invite to team", + "es" : "Invitar al equipo" + } + }, + "dashboard.leave-team" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:302", "src/app/main/ui/dashboard/sidebar.cljs:305" ], + "translations" : { + "en" : "Leave team" + } + }, + "dashboard.libraries-title" : { "used-in" : [ "src/app/main/ui/dashboard/libraries.cljs:40" ], "translations" : { "en" : "Shared Libraries", @@ -410,60 +361,6 @@ "es" : "Bibliotecas Compartidas" } }, - "dashboard.header.new-file" : { - "used-in" : [ "src/app/main/ui/dashboard/project.cljs:71" ], - "translations" : { - "en" : "+ New file", - "fr" : "+ Nouveau fichier", - "ru" : "+ Новый файл", - "es" : "+ Nuevo archivo" - } - }, - "dashboard.header.new-project" : { - "used-in" : [ "src/app/main/ui/dashboard/recent_files.cljs:46" ], - "translations" : { - "en" : "+ New project", - "fr" : "+ Nouveau projet", - "ru" : "+ Новый проект", - "es" : "+ Nuevo proyecto" - } - }, - "dashboard.header.profile-menu.logout" : { - "used-in" : [ "src/app/main/ui/dashboard/profile.cljs:57" ], - "translations" : { - "en" : "Exit", - "fr" : "Quitter", - "ru" : "Выход", - "es" : "Salir" - } - }, - "dashboard.header.profile-menu.password" : { - "used-in" : [ "src/app/main/ui/dashboard/profile.cljs:54" ], - "translations" : { - "en" : "Password", - "fr" : "Mot de passe", - "ru" : "Пароль", - "es" : "Contraseña" - } - }, - "dashboard.header.profile-menu.profile" : { - "used-in" : [ "src/app/main/ui/dashboard/profile.cljs:51" ], - "translations" : { - "en" : "Profile", - "fr" : "Profil", - "ru" : "Профиль", - "es" : "Perfil" - } - }, - "dashboard.header.project" : { - "used-in" : [ "src/app/main/ui/dashboard/project.cljs:56" ], - "translations" : { - "en" : "Project %s", - "fr" : "Projet %s", - "ru" : "Проект %s", - "es" : "Proyecto %s" - } - }, "dashboard.library.add-item.icons" : { "translations" : { "en" : "+ New icon", @@ -545,8 +442,26 @@ }, "unused" : true }, - "dashboard.search.no-matches-for" : { - "used-in" : [ "src/app/main/ui/dashboard/search.cljs:47" ], + "dashboard.new-file" : { + "used-in" : [ "src/app/main/ui/dashboard/projects.cljs:107", "src/app/main/ui/dashboard/files.cljs:87" ], + "translations" : { + "en" : "+ New File", + "fr" : "+ Nouveau fichier", + "ru" : "+ Новый файл", + "es" : "+ Nuevo Archivo" + } + }, + "dashboard.new-project" : { + "used-in" : [ "src/app/main/ui/dashboard/projects.cljs:35" ], + "translations" : { + "en" : "+ New project", + "fr" : "+ Nouveau projet", + "ru" : "+ Новый проект", + "es" : "+ Nuevo proyecto" + } + }, + "dashboard.no-matches-for" : { + "used-in" : [ "src/app/main/ui/dashboard/search.cljs:48" ], "translations" : { "en" : "No matches found for “%s“", "fr" : "Aucune correspondance pour “%s“", @@ -554,7 +469,97 @@ "es" : "No se encuentra “%s“" } }, - "dashboard.search.searching-for" : { + "dashboard.no-projects-placeholder" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:423" ], + "translations" : { + "en" : "Pinned projects will appear here" + } + }, + "dashboard.notifications.email-changed-successfully" : { + "used-in" : [ "src/app/main/ui/auth/verify_token.cljs:42" ], + "translations" : { + "en" : "Your email address has been updated successfully", + "fr" : "Votre adresse e-mail a été mise à jour avec succès", + "ru" : "Ваш email адрес успешно обновлен", + "es" : "Tu dirección de correo ha sido actualizada" + } + }, + "dashboard.notifications.email-verified-successfully" : { + "used-in" : [ "src/app/main/ui/auth/verify_token.cljs:36" ], + "translations" : { + "en" : "Your email address has been verified successfully", + "fr" : "Votre adresse e-mail a été vérifiée avec succès", + "ru" : "Ваш email адрес успешно подтвержден", + "es" : "Tu dirección de correo ha sido verificada" + } + }, + "dashboard.notifications.password-saved" : { + "used-in" : [ "src/app/main/ui/settings/password.cljs:36" ], + "translations" : { + "en" : "Password saved successfully!", + "fr" : "Mot de passe enregistré avec succès!", + "ru" : "Пароль успешно сохранен!", + "es" : "¡Contraseña guardada!" + } + }, + "dashboard.num-of-members" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:291" ], + "translations" : { + "en" : "%s members" + } + }, + "dashboard.password-change" : { + "used-in" : [ "src/app/main/ui/settings/password.cljs:76" ], + "translations" : { + "en" : "Change password", + "fr" : "Changement de mot de passe", + "ru" : "Изменить пароль", + "es" : "Cambiar contraseña" + } + }, + "dashboard.projects-title" : { + "used-in" : [ "src/app/main/ui/dashboard/projects.cljs:33" ], + "translations" : { + "en" : "Projects", + "fr" : "Projets", + "ru" : "Проекты", + "es" : "Proyectos" + } + }, + "dashboard.promote-to-owner" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:193" ], + "translations" : { + "en" : "Promote to owner" + } + }, + "dashboard.remove-account" : { + "used-in" : [ "src/app/main/ui/settings/profile.cljs:85" ], + "translations" : { + "en" : "Want to remove your account?", + "fr" : "Vous souhaitez supprimer votre compte?", + "ru" : "Хотите удалить свой аккаунт?", + "es" : "¿Quieres borrar tu cuenta?" + } + }, + "dashboard.remove-shared" : { + "used-in" : [ "src/app/main/ui/workspace/header.cljs:222", "src/app/main/ui/dashboard/grid.cljs:179" ], + "translations" : { + "en" : "Remove as Shared Library", + "fr" : "", + "ru" : "", + "es" : "Eliminar como Biblioteca Compartida" + } + }, + "dashboard.search-placeholder" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:110" ], + "translations" : { + "en" : "Search...", + "fr" : "Rechercher...", + "ru" : "Поиск ...", + "es" : "Buscar..." + } + }, + "dashboard.searching-for" : { "used-in" : [ "src/app/main/ui/dashboard/search.cljs:43" ], "translations" : { "en" : "Searching for “%s“...", @@ -563,8 +568,75 @@ "es" : "Buscando “%s“..." } }, - "dashboard.search.type-something" : { - "used-in" : [ "src/app/main/ui/dashboard/search.cljs:39" ], + "dashboard.select-ui-language" : { + "used-in" : [ "src/app/main/ui/settings/options.cljs:61" ], + "translations" : { + "en" : "Select UI language", + "fr" : "Sélectionner la langue de l'interface", + "ru" : "Выберите язык интерфейса", + "es" : "Cambiar el idioma de la interfaz" + } + }, + "dashboard.select-ui-theme" : { + "used-in" : [ "src/app/main/ui/settings/options.cljs:67" ], + "translations" : { + "en" : "Select theme", + "fr" : "Sélectionnez un thème", + "ru" : "Выберите тему", + "es" : "Selecciona un tema" + } + }, + "dashboard.show-all-files" : { + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:250" ], + "translations" : { + "en" : "Show all files", + "es" : "Ver todos los ficheros" + } + }, + "dashboard.sidebar.recent" : { + "translations" : { + "en" : "Recent", + "fr" : "Récent", + "ru" : "Недавние", + "es" : "Reciente" + }, + "unused" : true + }, + "dashboard.switch-team" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:140" ], + "translations" : { + "en" : "Switch Team" + } + }, + "dashboard.team-info" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:274" ], + "translations" : { + "en" : "Team info" + } + }, + "dashboard.team-members" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:285" ], + "translations" : { + "en" : "Team members" + } + }, + "dashboard.team-projects" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:294" ], + "translations" : { + "en" : "Team projects" + } + }, + "dashboard.theme-change" : { + "used-in" : [ "src/app/main/ui/settings/options.cljs:65" ], + "translations" : { + "en" : "UI theme", + "fr" : "Thème de l'interface", + "ru" : "Тема интерфейса пользователя", + "es" : "Tema visual" + } + }, + "dashboard.type-something" : { + "used-in" : [ "src/app/main/ui/dashboard/search.cljs:38" ], "translations" : { "en" : "Type to search results", "fr" : "Écrivez pour rechercher", @@ -572,31 +644,44 @@ "es" : "Escribe algo para buscar" } }, - "dashboard.sidebar.drafts" : { - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:127" ], + "dashboard.update-settings" : { + "used-in" : [ "src/app/main/ui/settings/options.cljs:72", "src/app/main/ui/settings/profile.cljs:80", "src/app/main/ui/settings/password.cljs:96" ], "translations" : { - "en" : "Drafts", - "fr" : "Brouillons", - "ru" : "Черновики", - "es" : "Borradores" + "en" : "Update settings", + "fr" : "Mettre à jour les paramètres", + "ru" : "Обновить настройки", + "es" : "Actualizar opciones" } }, - "dashboard.sidebar.libraries" : { - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:133" ], + "dashboard.your-account-title" : { + "used-in" : [ "src/app/main/ui/settings.cljs:29" ], "translations" : { - "en" : "Shared Libraries", - "fr" : "", - "ru" : "", - "es" : "Bibliotecas Compartidas" + "en" : "Your account", + "es" : "Su cuenta" } }, - "dashboard.sidebar.recent" : { - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:120" ], + "dashboard.your-email" : { + "used-in" : [ "src/app/main/ui/settings/profile.cljs:72" ], "translations" : { - "en" : "Recent", - "fr" : "Récent", - "ru" : "Недавние", - "es" : "Reciente" + "en" : "Email", + "fr" : "E-mail", + "ru" : "Email", + "es" : "Correo" + } + }, + "dashboard.your-name" : { + "used-in" : [ "src/app/main/ui/settings/profile.cljs:64" ], + "translations" : { + "en" : "Your name", + "fr" : "Votre nom complet", + "ru" : "Ваше имя", + "es" : "Tu nombre" + } + }, + "dashboard.your-penpot" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:144" ], + "translations" : { + "en" : "Your penpot" } }, "ds.accept" : { @@ -635,17 +720,8 @@ }, "unused" : true }, - "ds.cancel" : { - "translations" : { - "en" : "Cancel", - "fr" : "Annuler", - "ru" : "Отмена", - "es" : "Cancelar" - }, - "unused" : true - }, "ds.confirm-cancel" : { - "used-in" : [ "src/app/main/ui/confirm.cljs:22" ], + "used-in" : [ "src/app/main/ui/confirm.cljs:39" ], "translations" : { "en" : "Cancel", "fr" : "Annuler", @@ -654,7 +730,7 @@ } }, "ds.confirm-ok" : { - "used-in" : [ "src/app/main/ui/confirm.cljs:23" ], + "used-in" : [ "src/app/main/ui/confirm.cljs:40" ], "translations" : { "en" : "Ok", "fr" : "Ok", @@ -663,7 +739,7 @@ } }, "ds.confirm-title" : { - "used-in" : [ "src/app/main/ui/confirm.cljs:21" ], + "used-in" : [ "src/app/main/ui/confirm.cljs:38", "src/app/main/ui/confirm.cljs:42" ], "translations" : { "en" : "Are you sure?", "fr" : "Êtes-vous sûr?", @@ -671,53 +747,8 @@ "es" : "¿Seguro?" } }, - "ds.history.pinned" : { - "translations" : { - "en" : null, - "fr" : null, - "ru" : null, - "es" : null - }, - "unused" : true - }, - "ds.history.versions" : { - "translations" : { - "en" : null, - "fr" : null, - "ru" : null, - "es" : null - }, - "unused" : true - }, - "ds.new-file" : { - "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:179", "src/app/main/ui/dashboard/grid.cljs:191" ], - "translations" : { - "en" : "+ New File", - "fr" : "+ Nouveau fichier", - "ru" : "+ Новый файл", - "es" : "+ Nuevo Archivo" - } - }, - "ds.search.placeholder" : { - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:186" ], - "translations" : { - "en" : "Search...", - "fr" : "Rechercher...", - "ru" : "Поиск ...", - "es" : "Buscar..." - } - }, - "ds.settings.document-history" : { - "translations" : { - "en" : null, - "fr" : null, - "ru" : null, - "es" : null - }, - "unused" : true - }, "ds.updated-at" : { - "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:57" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:59" ], "translations" : { "en" : "Updated: %s", "fr" : "Mis à jour: %s", @@ -726,7 +757,7 @@ } }, "errors.auth.unauthorized" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:70" ], + "used-in" : [ "src/app/main/ui/auth/login.cljs:82" ], "translations" : { "en" : "Username or password seems to be wrong.", "fr" : "Le nom d'utilisateur ou le mot de passe semble être faux.", @@ -735,7 +766,7 @@ } }, "errors.email-already-exists" : { - "used-in" : [ "src/app/main/ui/settings/change_email.cljs:46", "src/app/main/ui/auth.cljs:89" ], + "used-in" : [ "src/app/main/ui/settings/change_email.cljs:47", "src/app/main/ui/auth/verify_token.cljs:80" ], "translations" : { "en" : "Email already used", "fr" : "Adresse e-mail déjà utilisée", @@ -744,7 +775,7 @@ } }, "errors.email-already-validated" : { - "used-in" : [ "src/app/main/ui/auth.cljs:94" ], + "used-in" : [ "src/app/main/ui/auth/verify_token.cljs:85" ], "translations" : { "en" : "Email already validated.", "fr" : "Adresse e-mail déjà validé.", @@ -753,7 +784,7 @@ } }, "errors.email-invalid-confirmation" : { - "used-in" : [ "src/app/main/ui/settings/change_email.cljs:36" ], + "used-in" : [ "src/app/main/ui/settings/change_email.cljs:37" ], "translations" : { "en" : "Confirmation email must match", "fr" : "L'adresse e-mail de confirmation doit correspondre", @@ -762,7 +793,7 @@ } }, "errors.generic" : { - "used-in" : [ "src/app/main/ui/settings/profile.cljs:36", "src/app/main/ui/auth.cljs:98" ], + "used-in" : [ "src/app/main/ui/settings/options.cljs:32", "src/app/main/ui/settings/profile.cljs:40", "src/app/main/ui/auth/verify_token.cljs:89" ], "translations" : { "en" : "Something wrong has happened.", "fr" : "Quelque chose c'est mal passé.", @@ -789,7 +820,7 @@ } }, "errors.media-type-mismatch" : { - "used-in" : [ "src/app/main/data/media.cljs:61", "src/app/main/data/workspace/persistence.cljs:413" ], + "used-in" : [ "src/app/main/data/media.cljs:61", "src/app/main/data/workspace/persistence.cljs:421" ], "translations" : { "en" : "Seems that the contents of the image does not match the file extension.", "fr" : "", @@ -798,7 +829,7 @@ } }, "errors.media-type-not-allowed" : { - "used-in" : [ "src/app/main/data/media.cljs:58", "src/app/main/data/workspace/persistence.cljs:410" ], + "used-in" : [ "src/app/main/data/media.cljs:58", "src/app/main/data/workspace/persistence.cljs:418" ], "translations" : { "en" : "Seems that this is not a valid image.", "fr" : "", @@ -834,7 +865,7 @@ } }, "errors.registration-disabled" : { - "used-in" : [ "src/app/main/ui/auth/register.cljs:48" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:39" ], "translations" : { "en" : "The registration is currently disabled.", "fr" : "L'enregistrement est actuellement désactivé.", @@ -843,7 +874,7 @@ } }, "errors.unexpected-error" : { - "used-in" : [ "src/app/main/data/media.cljs:64", "src/app/main/ui/settings/change_email.cljs:50", "src/app/main/ui/auth/register.cljs:54", "src/app/main/ui/workspace/sidebar/options/exports.cljs:66" ], + "used-in" : [ "src/app/main/data/media.cljs:64", "src/app/main/ui/auth/register.cljs:45", "src/app/main/ui/workspace/sidebar/options/exports.cljs:66" ], "translations" : { "en" : "An unexpected error occurred.", "fr" : "Une erreur inattendue c'est produite", @@ -869,13 +900,358 @@ "es" : "Ha ocurrido un error" } }, - "header.sitemap" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:135" ], + "handoff.attributes.blur" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/blur.cljs:33" ], "translations" : { - "en" : "Sitemap", - "fr" : null, - "ru" : "Карта сайта", - "es" : "Mapa del sitio" + "en" : "Blur" + } + }, + "handoff.attributes.blur.value" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/blur.cljs:39" ], + "translations" : { + "en" : "Value" + } + }, + "handoff.attributes.color.hex" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/common.cljs:72" ], + "translations" : { + "en" : "HEX" + } + }, + "handoff.attributes.color.hsla" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/common.cljs:78" ], + "translations" : { + "en" : "HSLA" + } + }, + "handoff.attributes.color.rgba" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/common.cljs:75" ], + "translations" : { + "en" : "RGBA" + } + }, + "handoff.attributes.content" : { + "translations" : { + "en" : "Content" + }, + "unused" : true + }, + "handoff.attributes.fill" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/fill.cljs:58" ], + "translations" : { + "en" : "Fill" + } + }, + "handoff.attributes.image.download" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/image.cljs:44" ], + "translations" : { + "en" : "Dowload source image" + } + }, + "handoff.attributes.image.height" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/image.cljs:36" ], + "translations" : { + "en" : "Height" + } + }, + "handoff.attributes.image.width" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/image.cljs:31" ], + "translations" : { + "en" : "Width" + } + }, + "handoff.attributes.layout" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/layout.cljs:76" ], + "translations" : { + "en" : "Layout" + } + }, + "handoff.attributes.layout.height" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/layout.cljs:36" ], + "translations" : { + "en" : "Height" + } + }, + "handoff.attributes.layout.left" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/layout.cljs:44" ], + "translations" : { + "en" : "Left" + } + }, + "handoff.attributes.layout.rotation" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/layout.cljs:60" ], + "translations" : { + "en" : "Rotation" + } + }, + "handoff.attributes.layout.top" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/layout.cljs:52" ], + "translations" : { + "en" : "Top" + } + }, + "handoff.attributes.layout.width" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/layout.cljs:29" ], + "translations" : { + "en" : "Width" + } + }, + "handoff.attributes.shadow" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/shadow.cljs:71" ], + "translations" : { + "en" : "Shadow" + } + }, + "handoff.attributes.shadow.shorthand.blur" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/shadow.cljs:44" ], + "translations" : { + "en" : "B" + } + }, + "handoff.attributes.shadow.shorthand.offset-x" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/shadow.cljs:36" ], + "translations" : { + "en" : "X" + } + }, + "handoff.attributes.shadow.shorthand.offset-y" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/shadow.cljs:40" ], + "translations" : { + "en" : "Y" + } + }, + "handoff.attributes.shadow.shorthand.spread" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/shadow.cljs:48" ], + "translations" : { + "en" : "S" + } + }, + "handoff.attributes.shadow.style.drop-shadow" : { + "translations" : { + "en" : "Drop" + }, + "unused" : true + }, + "handoff.attributes.shadow.style.inner-shadow" : { + "translations" : { + "en" : "Inner" + }, + "unused" : true + }, + "handoff.attributes.stroke" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/stroke.cljs:75" ], + "translations" : { + "en" : "Stroke" + } + }, + "handoff.attributes.stroke.alignment.center" : { + "translations" : { + "en" : "Center" + }, + "unused" : true + }, + "handoff.attributes.stroke.alignment.inner" : { + "translations" : { + "en" : "Inner" + }, + "unused" : true + }, + "handoff.attributes.stroke.alignment.outer" : { + "translations" : { + "en" : "Outer" + }, + "unused" : true + }, + "handoff.attributes.stroke.style.dashed" : { + "translations" : { + "en" : "Dashed" + }, + "unused" : true + }, + "handoff.attributes.stroke.style.dotted" : { + "translations" : { + "en" : "Dotted" + }, + "unused" : true + }, + "handoff.attributes.stroke.style.mixed" : { + "translations" : { + "en" : "Mixed" + }, + "unused" : true + }, + "handoff.attributes.stroke.style.none" : { + "translations" : { + "en" : "None" + }, + "unused" : true + }, + "handoff.attributes.stroke.style.solid" : { + "translations" : { + "en" : "Solid" + }, + "unused" : true + }, + "handoff.attributes.stroke.width" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/stroke.cljs:57" ], + "translations" : { + "en" : "Width" + } + }, + "handoff.attributes.typography" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/text.cljs:159" ], + "translations" : { + "en" : "Typography" + } + }, + "handoff.attributes.typography.font-family" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/text.cljs:89" ], + "translations" : { + "en" : "Font Family" + } + }, + "handoff.attributes.typography.font-size" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/text.cljs:101" ], + "translations" : { + "en" : "Font Size" + } + }, + "handoff.attributes.typography.font-style" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/text.cljs:95" ], + "translations" : { + "en" : "Font Style" + } + }, + "handoff.attributes.typography.letter-spacing" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/text.cljs:113" ], + "translations" : { + "en" : "Letter Spacing" + } + }, + "handoff.attributes.typography.line-height" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/text.cljs:107" ], + "translations" : { + "en" : "Line Height" + } + }, + "handoff.attributes.typography.text-decoration" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/text.cljs:119" ], + "translations" : { + "en" : "Text Decoration" + } + }, + "handoff.attributes.typography.text-decoration.none" : { + "translations" : { + "en" : "None" + }, + "unused" : true + }, + "handoff.attributes.typography.text-decoration.strikethrough" : { + "translations" : { + "en" : "Strikethrough" + }, + "unused" : true + }, + "handoff.attributes.typography.text-decoration.underline" : { + "translations" : { + "en" : "Underline" + }, + "unused" : true + }, + "handoff.attributes.typography.text-transform" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/text.cljs:125" ], + "translations" : { + "en" : "Text Transform" + } + }, + "handoff.attributes.typography.text-transform.lowercase" : { + "translations" : { + "en" : "Lower Case" + }, + "unused" : true + }, + "handoff.attributes.typography.text-transform.none" : { + "translations" : { + "en" : "None" + }, + "unused" : true + }, + "handoff.attributes.typography.text-transform.titlecase" : { + "translations" : { + "en" : "Title Case" + }, + "unused" : true + }, + "handoff.attributes.typography.text-transform.uppercase" : { + "translations" : { + "en" : "Upper Case" + }, + "unused" : true + }, + "handoff.tabs.code" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/right_sidebar.cljs:78" ], + "translations" : { + "en" : "Code" + } + }, + "handoff.tabs.code.selected.circle" : { + "translations" : { + "en" : "Circle" + }, + "unused" : true + }, + "handoff.tabs.code.selected.curve" : { + "translations" : { + "en" : "Curve" + }, + "unused" : true + }, + "handoff.tabs.code.selected.frame" : { + "translations" : { + "en" : "Artboard" + }, + "unused" : true + }, + "handoff.tabs.code.selected.group" : { + "translations" : { + "en" : "Group" + }, + "unused" : true + }, + "handoff.tabs.code.selected.image" : { + "translations" : { + "en" : "Image" + }, + "unused" : true + }, + "handoff.tabs.code.selected.multiple" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/right_sidebar.cljs:65" ], + "translations" : { + "en" : "%s Selected" + } + }, + "handoff.tabs.code.selected.path" : { + "translations" : { + "en" : "Path" + }, + "unused" : true + }, + "handoff.tabs.code.selected.rect" : { + "translations" : { + "en" : "Rectangle" + }, + "unused" : true + }, + "handoff.tabs.code.selected.text" : { + "translations" : { + "en" : "Text" + }, + "unused" : true + }, + "handoff.tabs.info" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/right_sidebar.cljs:74" ], + "translations" : { + "en" : "Info" } }, "history.alert-message" : { @@ -887,8 +1263,218 @@ }, "unused" : true }, + "labels.admin" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:82", "src/app/main/ui/dashboard/team.cljs:171", "src/app/main/ui/dashboard/team.cljs:187" ], + "translations" : { + "en" : "Admin" + } + }, + "labels.cancel" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:199" ], + "translations" : { + "en" : "Cancel", + "fr" : "Annuler", + "ru" : "Отмена", + "es" : "Cancelar" + } + }, + "labels.confirm-password" : { + "used-in" : [ "src/app/main/ui/settings/password.cljs:93" ], + "translations" : { + "en" : "Confirm password", + "fr" : "Confirmez mot de passe", + "ru" : "Подтвердите пароль", + "es" : "Confirmar contraseña" + } + }, + "labels.dashboard" : { + "used-in" : [ "src/app/main/ui/settings/sidebar.cljs:62" ], + "translations" : { + "en" : "Dashboard" + } + }, + "labels.delete" : { + "used-in" : [ "src/app/main/ui/dashboard/files.cljs:85", "src/app/main/ui/dashboard/grid.cljs:177" ], + "translations" : { + "en" : "Delete", + "fr" : "Supprimer", + "ru" : "Удалить", + "es" : "Borrar" + } + }, + "labels.drafts" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:402" ], + "translations" : { + "en" : "Drafts", + "fr" : "Brouillons", + "ru" : "Черновики", + "es" : "Borradores" + } + }, + "labels.editor" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:83", "src/app/main/ui/dashboard/team.cljs:174", "src/app/main/ui/dashboard/team.cljs:188" ], + "translations" : { + "en" : "Editor" + } + }, + "labels.email" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:109", "src/app/main/ui/dashboard/team.cljs:212" ], + "translations" : { + "en" : "Email", + "fr" : "Adresse email", + "ru" : "Email", + "es" : "Correo electrónico" + } + }, + "labels.language" : { + "used-in" : [ "src/app/main/ui/settings/options.cljs:54" ], + "translations" : { + "en" : "Language", + "fr" : "Langue", + "ru" : "Язык", + "es" : "Idioma" + } + }, + "labels.logout" : { + "used-in" : [ "src/app/main/ui/settings.cljs:31", "src/app/main/ui/dashboard/sidebar.cljs:457" ], + "translations" : { + "en" : "Logout", + "fr" : "Quitter", + "ru" : "Выход", + "es" : "Salir" + } + }, + "labels.members" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:295", "src/app/main/ui/dashboard/team.cljs:59", "src/app/main/ui/dashboard/team.cljs:63" ], + "translations" : { + "en" : "Members" + } + }, + "labels.name" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:211" ], + "translations" : { + "en" : "Name", + "fr" : "Nom", + "ru" : "Имя", + "es" : "Nombre" + } + }, + "labels.new-password" : { + "used-in" : [ "src/app/main/ui/settings/password.cljs:87" ], + "translations" : { + "en" : "New password", + "fr" : "Nouveau mot de passe", + "ru" : "Новый пароль", + "es" : "Nueva contraseña" + } + }, + "labels.old-password" : { + "used-in" : [ "src/app/main/ui/settings/password.cljs:81" ], + "translations" : { + "en" : "Old password", + "fr" : "Ancien mot de passe", + "ru" : "Старый пароль", + "es" : "Contraseña anterior" + } + }, + "labels.owner" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:168" ], + "translations" : { + "en" : "Owner" + } + }, + "labels.password" : { + "used-in" : [ "src/app/main/ui/settings/sidebar.cljs:75", "src/app/main/ui/dashboard/sidebar.cljs:454" ], + "translations" : { + "en" : "Password", + "fr" : "Mot de passe", + "ru" : "Пароль", + "es" : "Contraseña" + } + }, + "labels.permissions" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:213" ], + "translations" : { + "en" : "Permissions", + "es" : "Permisos" + } + }, + "labels.profile" : { + "used-in" : [ "src/app/main/ui/settings/sidebar.cljs:70", "src/app/main/ui/dashboard/sidebar.cljs:451" ], + "translations" : { + "en" : "Profile", + "fr" : "Profil", + "ru" : "Профиль", + "es" : "Perfil" + } + }, + "labels.projects" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:397" ], + "translations" : { + "en" : "Projects", + "fr" : "Projetes", + "ru" : "Проекты", + "es" : "Proyectos" + } + }, + "labels.remove" : { + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:91", "src/app/main/ui/dashboard/team.cljs:199" ], + "translations" : { + "en" : "Remove", + "fr" : "", + "ru" : "", + "es" : "Quitar" + } + }, + "labels.rename" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:298", "src/app/main/ui/dashboard/files.cljs:84", "src/app/main/ui/dashboard/grid.cljs:176" ], + "translations" : { + "en" : "Rename", + "es" : "Renombrar" + } + }, + "labels.role" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:81" ], + "translations" : { + "en" : "Role" + } + }, + "labels.settings" : { + "used-in" : [ "src/app/main/ui/settings/sidebar.cljs:80", "src/app/main/ui/dashboard/sidebar.cljs:296", "src/app/main/ui/dashboard/team.cljs:65" ], + "translations" : { + "en" : "Settings", + "fr" : "Settings", + "ru" : "Параметры", + "es" : "Configuración" + } + }, + "labels.shared-libraries" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:408" ], + "translations" : { + "en" : "Shared Libraries", + "fr" : "", + "ru" : "", + "es" : "Bibliotecas Compartidas" + } + }, + "labels.update" : { + "used-in" : [ "src/app/main/ui/settings/profile.cljs:106" ], + "translations" : { + "en" : "Update", + "fr" : "Mettre a jour", + "ru" : "Обновить", + "es" : "Actualizar" + } + }, + "labels.viewer" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:84", "src/app/main/ui/dashboard/team.cljs:177", "src/app/main/ui/dashboard/team.cljs:189" ], + "translations" : { + "en" : "Viewer", + "es" : "Visualizador" + } + }, "media.loading" : { - "used-in" : [ "src/app/main/data/media.cljs:43", "src/app/main/data/workspace/persistence.cljs:395" ], + "used-in" : [ "src/app/main/data/media.cljs:43", "src/app/main/data/workspace/persistence.cljs:402" ], "translations" : { "en" : "Loading image...", "fr" : "Chargement de l'image...", @@ -905,80 +1491,35 @@ }, "unused" : true }, - "profile.recovery.go-to-login" : { - "used-in" : [ "src/app/main/ui/auth/recovery.cljs:95" ], + "modals.add-shared-confirm.accept" : { + "used-in" : [ "src/app/main/ui/workspace/header.cljs:114", "src/app/main/ui/dashboard/grid.cljs:116" ], "translations" : { - "en" : null, - "fr" : null, - "ru" : null, - "es" : null + "en" : "Add as Shared Library", + "fr" : "", + "ru" : "", + "es" : "Añadir como Biblioteca Compartida" } }, - "settings.cancel-and-keep-my-account" : { - "used-in" : [ "src/app/main/ui/settings/delete_account.cljs:46" ], + "modals.add-shared-confirm.hint" : { + "used-in" : [ "src/app/main/ui/workspace/header.cljs:112", "src/app/main/ui/dashboard/grid.cljs:114" ], "translations" : { - "en" : "Cancel and keep my account", - "fr" : "Annuler et conserver mon compte", - "ru" : "Отменить и сохранить мой аккаунт", - "es" : "Cancelar y mantener mi cuenta" + "en" : "Once added as Shared Library, the assets of this file library will be available to be used among the rest of your files.", + "fr" : "", + "ru" : "", + "es" : "Una vez añadido como Biblioteca Compartida, los recursos de este archivo estarán disponibles para ser usado por el resto de tus archivos." } }, - "settings.cancel-email-change" : { - "used-in" : [ "src/app/main/ui/settings/profile.cljs:79" ], + "modals.add-shared-confirm.message" : { + "used-in" : [ "src/app/main/ui/workspace/header.cljs:111", "src/app/main/ui/dashboard/grid.cljs:113" ], "translations" : { - "en" : "Cancel", - "fr" : "Annuler", - "ru" : "Отмена", - "es" : "Cancelar" + "en" : "Add “%s” as Shared Library", + "fr" : "", + "ru" : "", + "es" : "Añadir “%s” como Biblioteca Compartida" } }, - "settings.change-email-info" : { - "used-in" : [ "src/app/main/ui/settings/change_email.cljs:72" ], - "translations" : { - "en" : "We'll send you an email to your current email “%s” to verify your identity.", - "fr" : "Nous vous enverrons un e-mail à votre adresse actuelle “%s” pour vérifier votre identité.", - "ru" : "Мы отправим письмо для подтверждения подлиности на текущий email адрес “%s”.", - "es" : "Enviaremos un mensaje a tu correo actual “%s” para verificar tu identidad." - } - }, - "settings.change-email-info3" : { - "used-in" : [ "src/app/main/ui/settings/profile.cljs:78" ], - "translations" : { - "en" : null, - "fr" : null, - "ru" : null, - "es" : null - } - }, - "settings.change-email-label" : { - "used-in" : [ "src/app/main/ui/settings/profile.cljs:73" ], - "translations" : { - "en" : "Change email", - "fr" : "Changer adresse e-mail", - "ru" : "Сменить email адрес", - "es" : "Cambiar correo" - } - }, - "settings.change-email-submit-label" : { - "used-in" : [ "src/app/main/ui/settings/change_email.cljs:89" ], - "translations" : { - "en" : "Change email", - "fr" : "Changer adresse e-mail", - "ru" : "Сменить email адрес", - "es" : "Cambiar correo" - } - }, - "settings.change-email-title" : { - "used-in" : [ "src/app/main/ui/settings/change_email.cljs:68" ], - "translations" : { - "en" : "Change your email", - "fr" : "Changer adresse e-mail", - "ru" : "Сменить email адрес", - "es" : "Cambiar tu correo" - } - }, - "settings.confirm-email-label" : { - "used-in" : [ "src/app/main/ui/settings/change_email.cljs:85" ], + "modals.change-email.confirm-email" : { + "used-in" : [ "src/app/main/ui/settings/change_email.cljs:103" ], "translations" : { "en" : "Verify new email", "fr" : "Vérifier la nouvelle adresse e-mail", @@ -986,89 +1527,17 @@ "es" : "Verificar el nuevo correo" } }, - "settings.confirm-password-label" : { - "used-in" : [ "src/app/main/ui/settings/password.cljs:90" ], + "modals.change-email.info" : { + "used-in" : [ "src/app/main/ui/settings/change_email.cljs:93" ], "translations" : { - "en" : "Confirm password", - "fr" : "Confirmez mot de passe", - "ru" : "Подтвердите пароль", - "es" : "Confirmar contraseña" + "en" : "We'll send you an email to your current email “%s” to verify your identity.", + "fr" : "Nous vous enverrons un e-mail à votre adresse actuelle “%s” pour vérifier votre identité.", + "ru" : "Мы отправим письмо для подтверждения подлиности на текущий email адрес “%s”.", + "es" : "Enviaremos un mensaje a tu correo actual “%s” para verificar tu identidad." } }, - "settings.delete-account-info" : { - "used-in" : [ "src/app/main/ui/settings/delete_account.cljs:36" ], - "translations" : { - "en" : "By removing your account you’ll lose all your current projects and archives.", - "fr" : "En supprimant votre compte, vous perdrez tous vos projets et archives actuels.", - "ru" : "Удалив аккаунт Вы потеряете все прокты и архивы.", - "es" : "Si borras tu cuenta perderás todos tus proyectos y archivos." - } - }, - "settings.delete-account-title" : { - "used-in" : [ "src/app/main/ui/settings/delete_account.cljs:32" ], - "translations" : { - "en" : "Are you sure you want to delete your account?", - "fr" : "Voulez-vous vraiment supprimer votre compte?", - "ru" : "Вы уверены, что хотите удалить аккаунт?", - "es" : "¿Seguro que quieres borrar tu cuenta?" - } - }, - "settings.email-label" : { - "used-in" : [ "src/app/main/ui/settings/profile.cljs:67" ], - "translations" : { - "en" : "Email", - "fr" : "E-mail", - "ru" : "Email", - "es" : "Correo" - } - }, - "settings.email-verification-pending" : { - "used-in" : [ "src/app/main/ui/settings/profile.cljs:88" ], - "translations" : { - "en" : "There is a pending email validation.", - "fr" : "Une validation par e-mail est en attente.", - "ru" : "Подтверждение email адреса не выполнено.", - "es" : "Hay una validación pendiente del correo." - } - }, - "settings.fullname-label" : { - "used-in" : [ "src/app/main/ui/settings/profile.cljs:59" ], - "translations" : { - "en" : "Your name", - "fr" : "Votre nom complet", - "ru" : "Ваше имя", - "es" : "Tu nombre" - } - }, - "settings.language-change-title" : { - "used-in" : [ "src/app/main/ui/settings/options.cljs:50" ], - "translations" : { - "en" : "Language", - "fr" : "Langue", - "ru" : "Язык", - "es" : "Idioma" - } - }, - "settings.language-label" : { - "used-in" : [ "src/app/main/ui/settings/options.cljs:56" ], - "translations" : { - "en" : "Select UI language", - "fr" : "Sélectionner la langue de l'interface", - "ru" : "Выберите язык интерфейса", - "es" : "Cambiar el idioma de la interfaz" - } - }, - "settings.multiple" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:138", "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:147", "src/app/main/ui/workspace/sidebar/options/text.cljs:124", "src/app/main/ui/workspace/sidebar/options/text.cljs:219", "src/app/main/ui/workspace/sidebar/options/text.cljs:232", "src/app/main/ui/workspace/sidebar/options/stroke.cljs:156" ], - "translations" : { - "en" : "Mixed", - "fr" : null, - "ru" : "Смешаный", - "es" : "Varios" - } - }, - "settings.new-email-label" : { - "used-in" : [ "src/app/main/ui/settings/change_email.cljs:80" ], + "modals.change-email.new-email" : { + "used-in" : [ "src/app/main/ui/settings/change_email.cljs:98" ], "translations" : { "en" : "New email", "fr" : "Nouvel e-mail", @@ -1076,53 +1545,260 @@ "es" : "Nuevo correo" } }, - "settings.new-password-label" : { - "used-in" : [ "src/app/main/ui/settings/password.cljs:85" ], + "modals.change-email.submit" : { + "used-in" : [ "src/app/main/ui/settings/change_email.cljs:109" ], "translations" : { - "en" : "New password", - "fr" : "Nouveau mot de passe", - "ru" : "Новый пароль", - "es" : "Nueva contraseña" + "en" : "Change email", + "fr" : "Changer adresse e-mail", + "ru" : "Сменить email адрес", + "es" : "Cambiar correo" } }, - "settings.notifications.email-changed-successfully" : { - "used-in" : [ "src/app/main/ui/auth.cljs:63" ], + "modals.change-email.title" : { + "used-in" : [ "src/app/main/ui/settings/change_email.cljs:86" ], "translations" : { - "en" : "Your email address has been updated successfully", - "fr" : "Votre adresse e-mail a été mise à jour avec succès", - "ru" : "Ваш email адрес успешно обновлен", - "es" : "Tu dirección de correo ha sido actualizada" + "en" : "Change your email", + "fr" : "Changer adresse e-mail", + "ru" : "Сменить email адрес", + "es" : "Cambiar tu correo" } }, - "settings.notifications.email-not-verified" : { + "modals.delete-account.cancel" : { + "used-in" : [ "src/app/main/ui/settings/delete_account.cljs:69" ], + "translations" : { + "en" : "Cancel and keep my account", + "fr" : "Annuler et conserver mon compte", + "ru" : "Отменить и сохранить мой аккаунт", + "es" : "Cancelar y mantener mi cuenta" + } + }, + "modals.delete-account.confirm" : { + "used-in" : [ "src/app/main/ui/settings/delete_account.cljs:67" ], + "translations" : { + "en" : "Yes, delete my account", + "fr" : "Oui, supprimez mon compte", + "ru" : "Да, удалить мой аккаунт", + "es" : "Si, borrar mi cuenta" + } + }, + "modals.delete-account.info" : { + "used-in" : [ "src/app/main/ui/settings/delete_account.cljs:62" ], + "translations" : { + "en" : "By removing your account you’ll lose all your current projects and archives.", + "fr" : "En supprimant votre compte, vous perdrez tous vos projets et archives actuels.", + "ru" : "Удалив аккаунт Вы потеряете все прокты и архивы.", + "es" : "Si borras tu cuenta perderás todos tus proyectos y archivos." + } + }, + "modals.delete-account.title" : { + "used-in" : [ "src/app/main/ui/settings/delete_account.cljs:55" ], + "translations" : { + "en" : "Are you sure you want to delete your account?", + "fr" : "Voulez-vous vraiment supprimer votre compte?", + "ru" : "Вы уверены, что хотите удалить аккаунт?", + "es" : "¿Seguro que quieres borrar tu cuenta?" + } + }, + "modals.delete-comment-thread.accept" : { + "used-in" : [ "src/app/main/ui/workspace/comments.cljs:236" ], "translations" : { "en" : null, "fr" : null, "ru" : null, "es" : null - }, - "unused" : true - }, - "settings.notifications.email-verified-successfully" : { - "used-in" : [ "src/app/main/ui/auth.cljs:57" ], - "translations" : { - "en" : "Your email address has been verified successfully", - "fr" : "Votre adresse e-mail a été vérifiée avec succès", - "ru" : "Ваш email адрес успешно подтвержден", - "es" : "Tu dirección de correo ha sido verificada" } }, - "settings.notifications.password-saved" : { - "used-in" : [ "src/app/main/ui/settings/password.cljs:36" ], + "modals.delete-comment-thread.message" : { + "used-in" : [ "src/app/main/ui/workspace/comments.cljs:235" ], "translations" : { - "en" : "Password saved successfully!", - "fr" : "Mot de passe enregistré avec succès!", - "ru" : "Пароль успешно сохранен!", - "es" : "¡Contraseña guardada!" + "en" : null, + "fr" : null, + "ru" : null, + "es" : null } }, - "settings.notifications.profile-deletion-not-allowed" : { - "used-in" : [ "src/app/main/data/auth.cljs:157" ], + "modals.delete-comment-thread.title" : { + "used-in" : [ "src/app/main/ui/workspace/comments.cljs:234" ], + "translations" : { + "en" : null, + "fr" : null, + "ru" : null, + "es" : null + } + }, + "modals.delete-file-confirm.accept" : { + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:82" ], + "translations" : { + "en" : "Delete file" + } + }, + "modals.delete-file-confirm.message" : { + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:81" ], + "translations" : { + "en" : "Are you sure you wan't to delete this file?" + } + }, + "modals.delete-file-confirm.title" : { + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:80" ], + "translations" : { + "en" : "Deleting file" + } + }, + "modals.delete-project-confirm.accept" : { + "used-in" : [ "src/app/main/ui/dashboard/files.cljs:58" ], + "translations" : { + "en" : "Delete project" + } + }, + "modals.delete-project-confirm.message" : { + "used-in" : [ "src/app/main/ui/dashboard/files.cljs:57" ], + "translations" : { + "en" : "Are you sure you want to delete this project?" + } + }, + "modals.delete-project-confirm.title" : { + "used-in" : [ "src/app/main/ui/dashboard/files.cljs:56" ], + "translations" : { + "en" : "Delete project" + } + }, + "modals.delete-team-confirm.accept" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:285" ], + "translations" : { + "en" : "Delete team" + } + }, + "modals.delete-team-confirm.message" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:284" ], + "translations" : { + "en" : "Are you sure you want to delete this team? All projects and files associated with team will be permanently deleted." + } + }, + "modals.delete-team-confirm.title" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:283" ], + "translations" : { + "en" : "Deleting team" + } + }, + "modals.delete-team-member-confirm.accept" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:157" ], + "translations" : { + "en" : "Delete member" + } + }, + "modals.delete-team-member-confirm.message" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:156" ], + "translations" : { + "en" : "Are you sure wan't to delete this user from team?" + } + }, + "modals.delete-team-member-confirm.title" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:155" ], + "translations" : { + "en" : "Delete team member" + } + }, + "modals.invite-member.title" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:105" ], + "translations" : { + "en" : "Invite a new team member" + } + }, + "modals.leave-and-reassign.hint1" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:188" ], + "translations" : { + "en" : "You are %s owner." + } + }, + "modals.leave-and-reassign.hint2" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:189" ], + "translations" : { + "en" : "Select an other member to promote before leave" + } + }, + "modals.leave-and-reassign.promote-and-leave" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:206" ], + "translations" : { + "en" : "Promote and leave" + } + }, + "modals.leave-and-reassign.select-memeber-to-promote" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:166" ], + "translations" : { + "en" : "Select a member to promote" + } + }, + "modals.leave-and-reassign.title" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:183" ], + "translations" : { + "en" : "Select a member to promote" + } + }, + "modals.leave-confirm.accept" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:260" ], + "translations" : { + "en" : "Leave team" + } + }, + "modals.leave-confirm.message" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:259" ], + "translations" : { + "en" : "Are you sure you want to leave this team?" + } + }, + "modals.leave-confirm.title" : { + "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:258" ], + "translations" : { + "en" : "Leaving team" + } + }, + "modals.promote-owner-confirm.accept" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:144" ], + "translations" : { + "en" : "Promote" + } + }, + "modals.promote-owner-confirm.message" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:143" ], + "translations" : { + "en" : "Are you sure you wan't to promote this user to owner?" + } + }, + "modals.promote-owner-confirm.title" : { + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:142" ], + "translations" : { + "en" : "Promote to owner" + } + }, + "modals.remove-shared-confirm.accept" : { + "used-in" : [ "src/app/main/ui/workspace/header.cljs:127", "src/app/main/ui/dashboard/grid.cljs:132" ], + "translations" : { + "en" : "Remove as Shared Library", + "fr" : "", + "ru" : "", + "es" : "Eliminar como Biblioteca Compartida" + } + }, + "modals.remove-shared-confirm.hint" : { + "used-in" : [ "src/app/main/ui/workspace/header.cljs:125", "src/app/main/ui/dashboard/grid.cljs:130" ], + "translations" : { + "en" : "Once removed as Shared Library, the File Library of this file will stop being available to be used among the rest of your files.", + "fr" : "", + "ru" : "", + "es" : "Una vez eliminado como Biblioteca Compartida, la Biblioteca de este archivo dejará de estar disponible para ser usada por el resto de tus archivos." + } + }, + "modals.remove-shared-confirm.message" : { + "used-in" : [ "src/app/main/ui/workspace/header.cljs:124", "src/app/main/ui/dashboard/grid.cljs:129" ], + "translations" : { + "en" : "Remove “%s” as Shared Library", + "fr" : "", + "ru" : "", + "es" : "Añadir “%s” como Biblioteca Compartida" + } + }, + "notifications.profile-deletion-not-allowed" : { + "used-in" : [ "src/app/main/ui/settings/delete_account.cljs:28" ], "translations" : { "en" : "You can't delete you profile. Reasign your teams before proceed.", "fr" : "Vous ne pouvez pas supprimer votre profil. Réassignez vos équipes avant de continuer.", @@ -1130,8 +1806,8 @@ "es" : "No puedes borrar tu perfil. Reasigna tus equipos antes de seguir." } }, - "settings.notifications.profile-saved" : { - "used-in" : [ "src/app/main/ui/settings/options.cljs:37", "src/app/main/ui/settings/profile.cljs:41" ], + "notifications.profile-saved" : { + "used-in" : [ "src/app/main/ui/settings/options.cljs:36", "src/app/main/ui/settings/profile.cljs:36" ], "translations" : { "en" : "Profile saved successfully!", "fr" : "Profil enregistré avec succès!", @@ -1139,68 +1815,38 @@ "es" : "Perfil guardado correctamente!" } }, - "settings.old-password-label" : { - "used-in" : [ "src/app/main/ui/settings/password.cljs:80" ], + "notifications.validation-email-sent" : { + "used-in" : [ "src/app/main/ui/settings/change_email.cljs:56", "src/app/main/ui/auth/register.cljs:54" ], "translations" : { - "en" : "Old password", - "fr" : "Ancien mot de passe", - "ru" : "Старый пароль", - "es" : "Contraseña anterior" + "en" : "Verification email sent to %s; check your email!" } }, - "settings.options" : { - "used-in" : [ "src/app/main/ui/settings/header.cljs:54" ], + "profile.recovery.go-to-login" : { + "used-in" : [ "src/app/main/ui/auth/recovery.cljs:95" ], "translations" : { - "en" : "OPTIONS", - "fr" : "OPTIONS", - "ru" : "ПАРАМЕТРЫ", - "es" : "OPCIONES" + "en" : "Go to login", + "fr" : null, + "ru" : null, + "es" : null } }, - "settings.password" : { - "used-in" : [ "src/app/main/ui/settings/header.cljs:49" ], + "settings.multiple" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:153", "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:163", "src/app/main/ui/workspace/sidebar/options/typography.cljs:99", "src/app/main/ui/workspace/sidebar/options/typography.cljs:149", "src/app/main/ui/workspace/sidebar/options/typography.cljs:162", "src/app/main/ui/workspace/sidebar/options/stroke.cljs:147" ], "translations" : { - "en" : "PASSWORD", - "fr" : "MOT DE PASSE", - "ru" : "ПАРОЛЬ", - "es" : "CONTRASEÑA" - } - }, - "settings.password-change-title" : { - "used-in" : [ "src/app/main/ui/settings/password.cljs:75" ], - "translations" : { - "en" : "Change password", - "fr" : "Changement de mot de passe", - "ru" : "Изменить пароль", - "es" : "Cambiar contraseña" + "en" : "Mixed", + "fr" : null, + "ru" : "Смешаный", + "es" : "Varios" } }, "settings.profile" : { - "used-in" : [ "src/app/main/ui/settings/header.cljs:44" ], "translations" : { "en" : "PROFILE", "fr" : "PROFIL", "ru" : "ПРОФИЛЬ", "es" : "PERFIL" - } - }, - "settings.profile-submit-label" : { - "used-in" : [ "src/app/main/ui/settings/options.cljs:67", "src/app/main/ui/settings/profile.cljs:91", "src/app/main/ui/settings/password.cljs:93" ], - "translations" : { - "en" : "Update settings", - "fr" : "Mettre à jour les paramètres", - "ru" : "Обновить настройки", - "es" : "Actualizar opciones" - } - }, - "settings.remove-account-label" : { - "used-in" : [ "src/app/main/ui/settings/profile.cljs:96" ], - "translations" : { - "en" : "Want to remove your account?", - "fr" : "Vous souhaitez supprimer votre compte?", - "ru" : "Хотите удалить свой аккаунт?", - "es" : "¿Quieres borrar tu cuenta?" - } + }, + "unused" : true }, "settings.teams" : { "translations" : { @@ -1211,44 +1857,8 @@ }, "unused" : true }, - "settings.theme-change-title" : { - "used-in" : [ "src/app/main/ui/settings/options.cljs:60" ], - "translations" : { - "en" : "UI theme", - "fr" : "Thème de l'interface", - "ru" : "Тема интерфейса пользователя", - "es" : "Tema visual" - } - }, - "settings.theme-label" : { - "used-in" : [ "src/app/main/ui/settings/options.cljs:61" ], - "translations" : { - "en" : "Select theme", - "fr" : "Sélectionnez un thème", - "ru" : "Выберите тему", - "es" : "Selecciona un tema" - } - }, - "settings.update-photo-label" : { - "used-in" : [ "src/app/main/ui/settings/profile.cljs:117" ], - "translations" : { - "en" : "UPDATE", - "fr" : "METTRE A JOUR", - "ru" : "ОБНОВИТЬ", - "es" : "ACTUALIZAR" - } - }, - "settings.yes-delete-my-account" : { - "used-in" : [ "src/app/main/ui/settings/delete_account.cljs:43" ], - "translations" : { - "en" : "Yes, delete my account", - "fr" : "Oui, supprimez mon compte", - "ru" : "Да, удалить мой аккаунт", - "es" : "Si, borrar mi cuenta" - } - }, "viewer.empty-state" : { - "used-in" : [ "src/app/main/ui/viewer.cljs:42" ], + "used-in" : [ "src/app/main/ui/viewer/handoff.cljs:56", "src/app/main/ui/viewer.cljs:42" ], "translations" : { "en" : "No frames found on the page.", "fr" : "Aucun cadre trouvé sur la page.", @@ -1257,7 +1867,7 @@ } }, "viewer.frame-not-found" : { - "used-in" : [ "src/app/main/ui/viewer.cljs:46" ], + "used-in" : [ "src/app/main/ui/viewer/handoff.cljs:60", "src/app/main/ui/viewer.cljs:46" ], "translations" : { "en" : "Frame not found.", "fr" : "Cadre introuvable.", @@ -1275,7 +1885,7 @@ } }, "viewer.header.edit-page" : { - "used-in" : [ "src/app/main/ui/viewer/header.cljs:168" ], + "used-in" : [ "src/app/main/ui/viewer/header.cljs:183" ], "translations" : { "en" : "Edit page", "fr" : "Editer la page", @@ -1284,7 +1894,7 @@ } }, "viewer.header.fullscreen" : { - "used-in" : [ "src/app/main/ui/viewer/header.cljs:179" ], + "used-in" : [ "src/app/main/ui/viewer/header.cljs:194" ], "translations" : { "en" : "Full Screen", "fr" : "Plein écran", @@ -1365,7 +1975,7 @@ } }, "viewer.header.sitemap" : { - "used-in" : [ "src/app/main/ui/viewer/header.cljs:149" ], + "used-in" : [ "src/app/main/ui/viewer/header.cljs:156" ], "translations" : { "en" : "Sitemap", "fr" : "Plan du site", @@ -1446,7 +2056,7 @@ } }, "workspace.assets.assets" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:477" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:630" ], "translations" : { "en" : "Assets", "fr" : "", @@ -1455,7 +2065,7 @@ } }, "workspace.assets.box-filter-all" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:497" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:650" ], "translations" : { "en" : "All assets", "fr" : "", @@ -1464,25 +2074,25 @@ } }, "workspace.assets.box-filter-colors" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:499" ], "translations" : { "en" : "Colors", "fr" : "", "ru" : "", "es" : "Colores" - } + }, + "unused" : true }, "workspace.assets.box-filter-graphics" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:498" ], "translations" : { "en" : "Graphics", "fr" : "", "ru" : "", "es" : "Gráficos" - } + }, + "unused" : true }, "workspace.assets.colors" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:316" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:330", "src/app/main/ui/workspace/sidebar/assets.cljs:653" ], "translations" : { "en" : "Colors", "fr" : "", @@ -1491,7 +2101,7 @@ } }, "workspace.assets.components" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:79" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:84", "src/app/main/ui/workspace/sidebar/assets.cljs:651" ], "translations" : { "en" : "Components", "fr" : "", @@ -1500,7 +2110,7 @@ } }, "workspace.assets.delete" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:98", "src/app/main/ui/workspace/sidebar/assets.cljs:186", "src/app/main/ui/workspace/sidebar/assets.cljs:292" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:104", "src/app/main/ui/workspace/sidebar/assets.cljs:192", "src/app/main/ui/workspace/sidebar/assets.cljs:306", "src/app/main/ui/workspace/sidebar/assets.cljs:434" ], "translations" : { "en" : "Delete", "fr" : "", @@ -1509,7 +2119,7 @@ } }, "workspace.assets.edit" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:291" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:305", "src/app/main/ui/workspace/sidebar/assets.cljs:433" ], "translations" : { "en" : "Edit", "fr" : "", @@ -1518,7 +2128,7 @@ } }, "workspace.assets.file-library" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:397" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:532" ], "translations" : { "en" : "File library", "fr" : "", @@ -1527,7 +2137,7 @@ } }, "workspace.assets.graphics" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:159" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:165", "src/app/main/ui/workspace/sidebar/assets.cljs:652" ], "translations" : { "en" : "Graphics", "fr" : "", @@ -1536,7 +2146,7 @@ } }, "workspace.assets.libraries" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:480" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:633" ], "translations" : { "en" : "Libraries", "fr" : "", @@ -1545,7 +2155,7 @@ } }, "workspace.assets.not-found" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:441" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:593" ], "translations" : { "en" : "No assets found", "fr" : "", @@ -1554,7 +2164,7 @@ } }, "workspace.assets.rename" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:290" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:304", "src/app/main/ui/workspace/sidebar/assets.cljs:432" ], "translations" : { "en" : "Rename", "fr" : "", @@ -1563,7 +2173,7 @@ } }, "workspace.assets.search" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:484" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:637" ], "translations" : { "en" : "Search assets", "fr" : "", @@ -1572,7 +2182,7 @@ } }, "workspace.assets.shared" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:399" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:534" ], "translations" : { "en" : "SHARED", "fr" : "", @@ -1580,8 +2190,74 @@ "es" : "COMPARTIDA" } }, + "workspace.assets.typography" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:421", "src/app/main/ui/workspace/sidebar/assets.cljs:654" ], + "translations" : { + "en" : "Typographies" + } + }, + "workspace.assets.typography.font-id" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:277" ], + "translations" : { + "en" : "Font" + } + }, + "workspace.assets.typography.font-size" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:285" ], + "translations" : { + "en" : "Size" + } + }, + "workspace.assets.typography.font-variant-id" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:281" ], + "translations" : { + "en" : "Variant" + } + }, + "workspace.assets.typography.go-to-edit" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:302" ], + "translations" : { + "en" : "Go to style library file to edit" + } + }, + "workspace.assets.typography.letter-spacing" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:293" ], + "translations" : { + "en" : "Letter Spacing" + } + }, + "workspace.assets.typography.line-height" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:289" ], + "translations" : { + "en" : "Line Height" + } + }, + "workspace.assets.typography.sample" : { + "used-in" : [ "src/app/main/ui/viewer/handoff/attributes/text.cljs:65", "src/app/main/ui/workspace/sidebar/options/typography.cljs:255" ], + "translations" : { + "en" : "Ag" + } + }, + "workspace.assets.typography.text-transform" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:297" ], + "translations" : { + "en" : "Text Transform" + } + }, + "workspace.gradients.linear" : { + "used-in" : [ "src/app/main/data/workspace/libraries.cljs:39", "src/app/main/ui/components/color_bullet.cljs:31" ], + "translations" : { + "en" : "Linear gradient" + } + }, + "workspace.gradients.radial" : { + "used-in" : [ "src/app/main/data/workspace/libraries.cljs:40", "src/app/main/ui/components/color_bullet.cljs:32" ], + "translations" : { + "en" : "Radial gradient" + } + }, "workspace.header.menu.disable-dynamic-alignment" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:202" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:216" ], "translations" : { "en" : "Disable dynamic alignment", "fr" : "Désactiver l'alignement dynamique", @@ -1590,7 +2266,7 @@ } }, "workspace.header.menu.disable-snap-grid" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:174" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:188" ], "translations" : { "en" : "Disable snap to grid", "fr" : "Désactiver l'alignement sur la grille", @@ -1599,7 +2275,7 @@ } }, "workspace.header.menu.enable-dynamic-alignment" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:203" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:217" ], "translations" : { "en" : "Enable dynamic aligment", "fr" : "Activer l'alignement dynamique", @@ -1608,7 +2284,7 @@ } }, "workspace.header.menu.enable-snap-grid" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:175" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:189" ], "translations" : { "en" : "Snap to grid", "fr" : "Aligner sur la grille", @@ -1617,7 +2293,7 @@ } }, "workspace.header.menu.hide-assets" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:195" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:209" ], "translations" : { "en" : "Hide assets", "fr" : "", @@ -1626,7 +2302,7 @@ } }, "workspace.header.menu.hide-grid" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:167" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:181" ], "translations" : { "en" : "Hide grids", "fr" : "Masquer la grille", @@ -1635,7 +2311,7 @@ } }, "workspace.header.menu.hide-layers" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:181" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:195" ], "translations" : { "en" : "Hide layers", "fr" : "Masquer les couches", @@ -1644,7 +2320,7 @@ } }, "workspace.header.menu.hide-palette" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:188" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:202" ], "translations" : { "en" : "Hide color palette", "fr" : "Masquer la palette de couleurs", @@ -1653,7 +2329,7 @@ } }, "workspace.header.menu.hide-rules" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:160" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:174" ], "translations" : { "en" : "Hide rules", "fr" : "Masquer les règles", @@ -1662,7 +2338,7 @@ } }, "workspace.header.menu.show-assets" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:196" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:210" ], "translations" : { "en" : "Show assets", "fr" : "", @@ -1671,7 +2347,7 @@ } }, "workspace.header.menu.show-grid" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:168" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:182" ], "translations" : { "en" : "Show grid", "fr" : "Montrer la grille", @@ -1680,7 +2356,7 @@ } }, "workspace.header.menu.show-layers" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:182" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:196" ], "translations" : { "en" : "Show layers", "fr" : "Montrer les couches", @@ -1689,7 +2365,7 @@ } }, "workspace.header.menu.show-palette" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:189" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:203" ], "translations" : { "en" : "Show color palette", "fr" : "Montrer la palette de couleurs", @@ -1698,7 +2374,7 @@ } }, "workspace.header.menu.show-rules" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:161" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:175" ], "translations" : { "en" : "Show rules", "fr" : "Montrer les règles", @@ -1731,7 +2407,7 @@ } }, "workspace.header.viewer" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:249" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:263" ], "translations" : { "en" : "View mode (Ctrl + P)", "fr" : "Mode visualisation (Ctrl + P)", @@ -1740,7 +2416,7 @@ } }, "workspace.libraries.add" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:111" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:115" ], "translations" : { "en" : "Add", "fr" : "", @@ -1749,7 +2425,7 @@ } }, "workspace.libraries.colors" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:42" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:43" ], "translations" : { "en" : "%s colors", "fr" : "", @@ -1758,37 +2434,37 @@ } }, "workspace.libraries.colors.big-thumbnails" : { - "used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:172" ], + "used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:170" ], "translations" : { "en" : "Big thumbnails" } }, "workspace.libraries.colors.file-library" : { - "used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:338", "src/app/main/ui/workspace/colorpalette.cljs:150" ], + "used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs:89", "src/app/main/ui/workspace/colorpalette.cljs:148" ], "translations" : { "en" : "File library" } }, "workspace.libraries.colors.recent-colors" : { - "used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:337", "src/app/main/ui/workspace/colorpalette.cljs:160" ], + "used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs:88", "src/app/main/ui/workspace/colorpalette.cljs:158" ], "translations" : { "en" : "Recent colors" } }, "workspace.libraries.colors.save-color" : { - "used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:373" ], + "used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:339" ], "translations" : { "en" : "Save color" } }, "workspace.libraries.colors.small-thumbnails" : { - "used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:177" ], + "used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:175" ], "translations" : { "en" : "Small thumbnails" } }, "workspace.libraries.components" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:36" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:37" ], "translations" : { "en" : "%s components", "fr" : "", @@ -1797,7 +2473,7 @@ } }, "workspace.libraries.file-library" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:80" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:84" ], "translations" : { "en" : "File library", "fr" : "", @@ -1806,7 +2482,7 @@ } }, "workspace.libraries.graphics" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:39" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:40" ], "translations" : { "en" : "%s graphics", "fr" : "", @@ -1815,7 +2491,7 @@ } }, "workspace.libraries.in-this-file" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:77" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:81" ], "translations" : { "en" : "LIBRARIES IN THIS FILE", "fr" : "", @@ -1824,7 +2500,7 @@ } }, "workspace.libraries.libraries" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:171" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:175" ], "translations" : { "en" : "LIBRARIES", "fr" : "", @@ -1833,7 +2509,7 @@ } }, "workspace.libraries.library" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:131" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:135" ], "translations" : { "en" : "LIBRARY", "fr" : "", @@ -1842,7 +2518,7 @@ } }, "workspace.libraries.no-libraries-need-sync" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:129" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:133" ], "translations" : { "en" : "There are no Shared Libraries that need update", "fr" : "", @@ -1851,7 +2527,7 @@ } }, "workspace.libraries.no-matches-for" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:117" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:121" ], "translations" : { "en" : "No matches found for “%s“", "fr" : "Aucune correspondance pour “%s“", @@ -1860,7 +2536,7 @@ } }, "workspace.libraries.no-shared-libraries-available" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:116" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:120" ], "translations" : { "en" : "There are no Shared Libraries available", "fr" : "", @@ -1868,17 +2544,8 @@ "es" : "No hay bibliotecas compartidas disponibles" } }, - "workspace.libraries.remove" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:87" ], - "translations" : { - "en" : "Remove", - "fr" : "", - "ru" : "", - "es" : "Quitar" - } - }, "workspace.libraries.search-shared-libraries" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:94" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:98" ], "translations" : { "en" : "Search shared libraries", "fr" : "", @@ -1887,7 +2554,7 @@ } }, "workspace.libraries.shared-libraries" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:91" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:95" ], "translations" : { "en" : "SHARED LIBRARIES", "fr" : "", @@ -1895,8 +2562,26 @@ "es" : "BIBLIOTECAS COMPARTIDAS" } }, + "workspace.libraries.text.multiple-typography" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:268" ], + "translations" : { + "en" : "Multiple typographies" + } + }, + "workspace.libraries.text.multiple-typography-tooltip" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:270" ], + "translations" : { + "en" : "Unlink all typographies" + } + }, + "workspace.libraries.typography" : { + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:46" ], + "translations" : { + "en" : "%s typographies" + } + }, "workspace.libraries.update" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:138" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:142" ], "translations" : { "en" : "Update", "fr" : "", @@ -1905,7 +2590,7 @@ } }, "workspace.libraries.updates" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:175" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:179" ], "translations" : { "en" : "UPDATES", "fr" : "", @@ -1967,6 +2652,24 @@ }, "unused" : true }, + "workspace.options.blur-options.background-blur" : { + "translations" : { + "en" : "Background" + }, + "unused" : true + }, + "workspace.options.blur-options.layer-blur" : { + "translations" : { + "en" : "Layer" + }, + "unused" : true + }, + "workspace.options.blur-options.title" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/blur.cljs:56" ], + "translations" : { + "en" : "Blur" + } + }, "workspace.options.canvas-background" : { "used-in" : [ "src/app/main/ui/workspace/sidebar/options/page.cljs:45" ], "translations" : { @@ -1977,7 +2680,7 @@ } }, "workspace.options.design" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options.cljs:68" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options.cljs:69" ], "translations" : { "en" : "Design", "fr" : "Conception", @@ -2007,7 +2710,7 @@ } }, "workspace.options.fill" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/fill.cljs:51" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/fill.cljs:53" ], "translations" : { "en" : "Fill", "fr" : "Remplissage", @@ -2025,7 +2728,7 @@ } }, "workspace.options.grid.column" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:129" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:132" ], "translations" : { "en" : "Columns", "fr" : "Colonnes", @@ -2034,7 +2737,7 @@ } }, "workspace.options.grid.params.columns" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:170" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:173" ], "translations" : { "en" : "Columns", "fr" : "Colonnes", @@ -2043,7 +2746,7 @@ } }, "workspace.options.grid.params.gutter" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:203" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:206" ], "translations" : { "en" : "Gutter", "fr" : "Gouttière", @@ -2052,7 +2755,7 @@ } }, "workspace.options.grid.params.height" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:194" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:197" ], "translations" : { "en" : "Height", "fr" : "Hauteur", @@ -2061,7 +2764,7 @@ } }, "workspace.options.grid.params.margin" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:209" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:212" ], "translations" : { "en" : "Margin", "fr" : "Marge", @@ -2070,7 +2773,7 @@ } }, "workspace.options.grid.params.rows" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:161" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:164" ], "translations" : { "en" : "Rows", "fr" : "Lignes", @@ -2079,7 +2782,7 @@ } }, "workspace.options.grid.params.set-default" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:222" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:226" ], "translations" : { "en" : "Set as default", "fr" : "Définir par défaut", @@ -2088,7 +2791,7 @@ } }, "workspace.options.grid.params.size" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:154" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:157" ], "translations" : { "en" : "Size", "fr" : "Taille", @@ -2097,7 +2800,7 @@ } }, "workspace.options.grid.params.type" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:179" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:182" ], "translations" : { "en" : "Type", "fr" : "Type", @@ -2106,7 +2809,7 @@ } }, "workspace.options.grid.params.type.bottom" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:187" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:190" ], "translations" : { "en" : "Bottom", "fr" : "Bas", @@ -2115,7 +2818,7 @@ } }, "workspace.options.grid.params.type.center" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:185" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:188" ], "translations" : { "en" : "Center", "fr" : "Centre", @@ -2124,7 +2827,7 @@ } }, "workspace.options.grid.params.type.left" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:184" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:187" ], "translations" : { "en" : "Left", "fr" : "Gauche", @@ -2133,7 +2836,7 @@ } }, "workspace.options.grid.params.type.right" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:188" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:191" ], "translations" : { "en" : "Right", "fr" : "Droite", @@ -2142,7 +2845,7 @@ } }, "workspace.options.grid.params.type.stretch" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:181" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:184" ], "translations" : { "en" : "Stretch", "fr" : "Étirer", @@ -2151,7 +2854,7 @@ } }, "workspace.options.grid.params.type.top" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:183" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:186" ], "translations" : { "en" : "Top", "fr" : "Haut", @@ -2160,7 +2863,7 @@ } }, "workspace.options.grid.params.use-default" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:220" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:224" ], "translations" : { "en" : "Use default", "fr" : "Utiliser la valeur par défaut", @@ -2169,7 +2872,7 @@ } }, "workspace.options.grid.params.width" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:195" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:198" ], "translations" : { "en" : "Width", "fr" : "Largeur", @@ -2178,7 +2881,7 @@ } }, "workspace.options.grid.row" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:130" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:133" ], "translations" : { "en" : "Rows", "fr" : "Lignes", @@ -2187,7 +2890,7 @@ } }, "workspace.options.grid.square" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:128" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:131" ], "translations" : { "en" : "Square", "fr" : "Carré", @@ -2196,7 +2899,7 @@ } }, "workspace.options.grid.title" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:234" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame_grid.cljs:238" ], "translations" : { "en" : "Grid & Layouts", "fr" : "Grille & couches", @@ -2205,7 +2908,7 @@ } }, "workspace.options.group-fill" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/fill.cljs:50" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/fill.cljs:52" ], "translations" : { "en" : "Group fill", "fr" : null, @@ -2214,7 +2917,7 @@ } }, "workspace.options.group-stroke" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:70" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:63" ], "translations" : { "en" : "Group stroke", "fr" : null, @@ -2241,7 +2944,7 @@ } }, "workspace.options.position" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame.cljs:126", "src/app/main/ui/workspace/sidebar/options/measures.cljs:146" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame.cljs:127", "src/app/main/ui/workspace/sidebar/options/measures.cljs:146" ], "translations" : { "en" : "Position", "fr" : "Position", @@ -2250,7 +2953,7 @@ } }, "workspace.options.prototype" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options.cljs:80" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options.cljs:81" ], "translations" : { "en" : "Prototype", "fr" : "Prototype", @@ -2295,7 +2998,7 @@ } }, "workspace.options.selection-fill" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/fill.cljs:49" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/fill.cljs:51" ], "translations" : { "en" : "Selection fill", "fr" : null, @@ -2304,7 +3007,7 @@ } }, "workspace.options.selection-stroke" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:69" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:62" ], "translations" : { "en" : "Selection stroke", "fr" : null, @@ -2313,49 +3016,49 @@ } }, "workspace.options.shadow-options.blur" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:163" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:161" ], "translations" : { "en" : "Blur" } }, "workspace.options.shadow-options.drop-shadow" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:129" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:127" ], "translations" : { "en" : "Drop shadow" } }, "workspace.options.shadow-options.inner-shadow" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:130" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:128" ], "translations" : { "en" : "Inner shadow" } }, "workspace.options.shadow-options.offsetx" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:141" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:139" ], "translations" : { "en" : "X" } }, "workspace.options.shadow-options.offsety" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:151" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:149" ], "translations" : { "en" : "Y" } }, "workspace.options.shadow-options.spread" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:174" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:172" ], "translations" : { "en" : "Spread" } }, "workspace.options.shadow-options.title" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:190" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:192" ], "translations" : { "en" : "Shadow" } }, "workspace.options.size" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame.cljs:99", "src/app/main/ui/workspace/sidebar/options/measures.cljs:116" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame.cljs:100", "src/app/main/ui/workspace/sidebar/options/measures.cljs:116" ], "translations" : { "en" : "Size", "fr" : "Taille", @@ -2364,7 +3067,7 @@ } }, "workspace.options.size-presets" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame.cljs:81" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame.cljs:82" ], "translations" : { "en" : "Size presets", "fr" : "Tailles prédéfinies", @@ -2373,7 +3076,7 @@ } }, "workspace.options.stroke" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:71" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:64" ], "translations" : { "en" : "Stroke", "fr" : "Bordure", @@ -2382,7 +3085,7 @@ } }, "workspace.options.stroke.center" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:163" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:154" ], "translations" : { "en" : "Center", "fr" : "Centre", @@ -2391,7 +3094,7 @@ } }, "workspace.options.stroke.dashed" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:173" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:164" ], "translations" : { "en" : "Dashed", "fr" : "Tiré", @@ -2400,7 +3103,7 @@ } }, "workspace.options.stroke.dotted" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:172" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:163" ], "translations" : { "en" : "Dotted", "fr" : "Pointillé", @@ -2409,7 +3112,7 @@ } }, "workspace.options.stroke.inner" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:164" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:155" ], "translations" : { "en" : "Inside", "fr" : "Intérieur", @@ -2418,7 +3121,7 @@ } }, "workspace.options.stroke.mixed" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:174" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:165" ], "translations" : { "en" : "Mixed", "fr" : "Mixte", @@ -2427,7 +3130,7 @@ } }, "workspace.options.stroke.outer" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:165" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:156" ], "translations" : { "en" : "Outside", "fr" : "Extérieur", @@ -2436,7 +3139,7 @@ } }, "workspace.options.stroke.solid" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:171" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:162" ], "translations" : { "en" : "Solid", "fr" : "Solide", @@ -2445,7 +3148,7 @@ } }, "workspace.options.text-options.align-bottom" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:270" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:119" ], "translations" : { "en" : "Align bottom", "fr" : "Aligner en bas", @@ -2454,7 +3157,7 @@ } }, "workspace.options.text-options.align-center" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:175" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:72" ], "translations" : { "en" : "Align center", "fr" : "Aligner au centre", @@ -2463,7 +3166,7 @@ } }, "workspace.options.text-options.align-justify" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:185" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:82" ], "translations" : { "en" : "Justify", "fr" : "Justifier", @@ -2472,7 +3175,7 @@ } }, "workspace.options.text-options.align-left" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:170" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:67" ], "translations" : { "en" : "Align left", "fr" : "Aligner à gauche", @@ -2481,7 +3184,7 @@ } }, "workspace.options.text-options.align-middle" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:265" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:114" ], "translations" : { "en" : "Align middle", "fr" : "Aligner au milieu", @@ -2490,7 +3193,7 @@ } }, "workspace.options.text-options.align-right" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:180" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:77" ], "translations" : { "en" : "Align right", "fr" : "Aligner à droite", @@ -2499,7 +3202,7 @@ } }, "workspace.options.text-options.align-top" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:260" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:109" ], "translations" : { "en" : "Align top", "fr" : "Aligner en haut", @@ -2508,7 +3211,7 @@ } }, "workspace.options.text-options.decoration" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:306" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:151" ], "translations" : { "en" : "Decoration", "fr" : "Décoration", @@ -2517,25 +3220,25 @@ } }, "workspace.options.text-options.grow-auto-height" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:287" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:136" ], "translations" : { "en" : "Auto height" } }, "workspace.options.text-options.grow-auto-width" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:282" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:131" ], "translations" : { "en" : "Auto width" } }, "workspace.options.text-options.grow-fixed" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:277" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:126" ], "translations" : { "en" : "Fixed" } }, "workspace.options.text-options.letter-spacing" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:224" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:154" ], "translations" : { "en" : "Letter Spacing", "fr" : "Espacement de caractères", @@ -2544,7 +3247,7 @@ } }, "workspace.options.text-options.line-height" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:211" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:141" ], "translations" : { "en" : "Line height", "fr" : "Hauteur de ligne", @@ -2553,7 +3256,7 @@ } }, "workspace.options.text-options.lowercase" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:353" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:188" ], "translations" : { "en" : "Lowercase", "fr" : "Minuscule", @@ -2562,7 +3265,7 @@ } }, "workspace.options.text-options.none" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:309", "src/app/main/ui/workspace/sidebar/options/text.cljs:343" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:178", "src/app/main/ui/workspace/sidebar/options/text.cljs:154" ], "translations" : { "en" : "None", "fr" : "Aucune", @@ -2571,7 +3274,7 @@ } }, "workspace.options.text-options.strikethrough" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:321" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:166" ], "translations" : { "en" : "Strikethrough", "fr" : "Barré", @@ -2580,7 +3283,7 @@ } }, "workspace.options.text-options.text-case" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:340" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:175" ], "translations" : { "en" : "Case", "fr" : "Casse", @@ -2589,7 +3292,7 @@ } }, "workspace.options.text-options.title" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:379" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:190" ], "translations" : { "en" : "Text", "fr" : "Texte", @@ -2598,7 +3301,7 @@ } }, "workspace.options.text-options.title-group" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:378" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:189" ], "translations" : { "en" : "Group text", "ru" : "Текст группы", @@ -2606,7 +3309,7 @@ } }, "workspace.options.text-options.title-selection" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:377" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:188" ], "translations" : { "en" : "Selection text", "ru" : "Выбранный текст", @@ -2614,7 +3317,7 @@ } }, "workspace.options.text-options.titlecase" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:358" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:193" ], "translations" : { "en" : "Titlecase", "fr" : "Titre", @@ -2623,7 +3326,7 @@ } }, "workspace.options.text-options.underline" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:315" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:160" ], "translations" : { "en" : "Underline", "fr" : "Souligner", @@ -2632,7 +3335,7 @@ } }, "workspace.options.text-options.uppercase" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:348" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:183" ], "translations" : { "en" : "Uppercase", "fr" : "Majuscule", @@ -2658,6 +3361,138 @@ "es" : "Usa el botón de play de la cabecera para arrancar la vista de prototipo." } }, + "workspace.shape.menu.back" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:103" ], + "translations" : { + "en" : "Send to back" + } + }, + "workspace.shape.menu.backward" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:100" ], + "translations" : { + "en" : "Send backward" + } + }, + "workspace.shape.menu.copy" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:81" ], + "translations" : { + "en" : "Copy" + } + }, + "workspace.shape.menu.create-component" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:145" ], + "translations" : { + "en" : "Create component" + } + }, + "workspace.shape.menu.cut" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:84" ], + "translations" : { + "en" : "Cut" + } + }, + "workspace.shape.menu.delete" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:163" ], + "translations" : { + "en" : "Delete" + } + }, + "workspace.shape.menu.detach-instance" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:152" ], + "translations" : { + "en" : "Detach instance" + } + }, + "workspace.shape.menu.duplicate" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:90" ], + "translations" : { + "en" : "Duplicate" + } + }, + "workspace.shape.menu.forward" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:94" ], + "translations" : { + "en" : "Bring forward" + } + }, + "workspace.shape.menu.front" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:97" ], + "translations" : { + "en" : "Bring to front" + } + }, + "workspace.shape.menu.go-master" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:159" ], + "translations" : { + "en" : "Go to master component file" + } + }, + "workspace.shape.menu.group" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:110" ], + "translations" : { + "en" : "Group" + } + }, + "workspace.shape.menu.hide" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:133" ], + "translations" : { + "en" : "Hide" + } + }, + "workspace.shape.menu.lock" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:139" ], + "translations" : { + "en" : "Lock" + } + }, + "workspace.shape.menu.mask" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:113" ], + "translations" : { + "en" : "Mask" + } + }, + "workspace.shape.menu.paste" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:87", "src/app/main/ui/workspace/context_menu.cljs:172" ], + "translations" : { + "en" : "Paste" + } + }, + "workspace.shape.menu.reset-overrides" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:154" ], + "translations" : { + "en" : "Reset overrides" + } + }, + "workspace.shape.menu.show" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:131" ], + "translations" : { + "en" : "Show" + } + }, + "workspace.shape.menu.ungroup" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:119" ], + "translations" : { + "en" : "Ungroup" + } + }, + "workspace.shape.menu.unlock" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:137" ], + "translations" : { + "en" : "Unlock" + } + }, + "workspace.shape.menu.unmask" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:123" ], + "translations" : { + "en" : "Unmask" + } + }, + "workspace.shape.menu.update-master" : { + "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:157" ], + "translations" : { + "en" : "Update master component" + } + }, "workspace.sidebar.icons" : { "translations" : { "en" : "Icons", @@ -2668,7 +3503,7 @@ "unused" : true }, "workspace.sidebar.sitemap" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:156" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:164" ], "translations" : { "en" : "Pages", "fr" : "Pages", @@ -2676,8 +3511,17 @@ "es" : "Páginas" } }, + "workspace.sitemap" : { + "used-in" : [ "src/app/main/ui/workspace/header.cljs:149" ], + "translations" : { + "en" : "Sitemap", + "fr" : null, + "ru" : "Карта сайта", + "es" : "Mapa del sitio" + } + }, "workspace.toolbar.assets" : { - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs:105" ], + "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs:111" ], "translations" : { "en" : "Assets (Ctrl + I)", "fr" : "", @@ -2695,7 +3539,7 @@ } }, "workspace.toolbar.color-palette" : { - "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs:115" ], + "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs:121" ], "translations" : { "en" : "Color Palette (---)", "fr" : "Palette de couleurs (---)", @@ -2703,6 +3547,13 @@ "es" : "Paleta de colores (---)" } }, + "workspace.toolbar.comments" : { + "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs:99" ], + "translations" : { + "en" : "Comments", + "es" : "Comentarios" + } + }, "workspace.toolbar.curve" : { "used-in" : [ "src/app/main/ui/workspace/left_toolbar.cljs:88" ], "translations" : { @@ -2766,8 +3617,230 @@ "es" : "Texto (T)" } }, + "workspace.undo.empty" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs:294" ], + "translations" : { + "en" : "There are no history changes so far" + } + }, + "workspace.undo.entry.delete" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs:121" ], + "translations" : { + "en" : "Deleted %s" + } + }, + "workspace.undo.entry.modify" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs:120" ], + "translations" : { + "en" : "Modified %s" + } + }, + "workspace.undo.entry.move" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs:122" ], + "translations" : { + "en" : "Moved objects" + } + }, + "workspace.undo.entry.multiple.circle" : { + "translations" : { + "en" : "circles" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.color" : { + "translations" : { + "en" : "color assets" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.component" : { + "translations" : { + "en" : "components" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.curve" : { + "translations" : { + "en" : "curves" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.frame" : { + "translations" : { + "en" : "artboard" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.group" : { + "translations" : { + "en" : "groups" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.image" : { + "translations" : { + "en" : "images" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.media" : { + "translations" : { + "en" : "graphic assets" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.multiple" : { + "translations" : { + "en" : "objects" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.page" : { + "translations" : { + "en" : "pages" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.path" : { + "translations" : { + "en" : "paths" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.rect" : { + "translations" : { + "en" : "rectangles" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.shape" : { + "translations" : { + "en" : "shapes" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.text" : { + "translations" : { + "en" : "texts" + }, + "unused" : true + }, + "workspace.undo.entry.multiple.typography" : { + "translations" : { + "en" : "typography assets" + }, + "unused" : true + }, + "workspace.undo.entry.new" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs:119" ], + "translations" : { + "en" : "New %s" + } + }, + "workspace.undo.entry.single.circle" : { + "translations" : { + "en" : "circle" + }, + "unused" : true + }, + "workspace.undo.entry.single.color" : { + "translations" : { + "en" : "color asset" + }, + "unused" : true + }, + "workspace.undo.entry.single.component" : { + "translations" : { + "en" : "component" + }, + "unused" : true + }, + "workspace.undo.entry.single.curve" : { + "translations" : { + "en" : "curve" + }, + "unused" : true + }, + "workspace.undo.entry.single.frame" : { + "translations" : { + "en" : "frame" + }, + "unused" : true + }, + "workspace.undo.entry.single.group" : { + "translations" : { + "en" : "group" + }, + "unused" : true + }, + "workspace.undo.entry.single.image" : { + "translations" : { + "en" : "image" + }, + "unused" : true + }, + "workspace.undo.entry.single.media" : { + "translations" : { + "en" : "graphic asset" + }, + "unused" : true + }, + "workspace.undo.entry.single.multiple" : { + "translations" : { + "en" : "object" + }, + "unused" : true + }, + "workspace.undo.entry.single.page" : { + "translations" : { + "en" : "page" + }, + "unused" : true + }, + "workspace.undo.entry.single.path" : { + "translations" : { + "en" : "path" + }, + "unused" : true + }, + "workspace.undo.entry.single.rect" : { + "translations" : { + "en" : "rectangle" + }, + "unused" : true + }, + "workspace.undo.entry.single.shape" : { + "translations" : { + "en" : "shape" + }, + "unused" : true + }, + "workspace.undo.entry.single.text" : { + "translations" : { + "en" : "text" + }, + "unused" : true + }, + "workspace.undo.entry.single.typography" : { + "translations" : { + "en" : "typography asset" + }, + "unused" : true + }, + "workspace.undo.entry.unknown" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs:123" ], + "translations" : { + "en" : "Operation over %s" + } + }, + "workspace.undo.title" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/history.cljs:290" ], + "translations" : { + "en" : "History" + } + }, "workspace.updates.dismiss" : { - "used-in" : [ "src/app/main/data/workspace/libraries.cljs:488" ], + "used-in" : [ "src/app/main/data/workspace/libraries.cljs:541" ], "translations" : { "en" : "Dismiss", "fr" : "", @@ -2776,7 +3849,7 @@ } }, "workspace.updates.there-are-updates" : { - "used-in" : [ "src/app/main/data/workspace/libraries.cljs:484" ], + "used-in" : [ "src/app/main/data/workspace/libraries.cljs:537" ], "translations" : { "en" : "There are updates in shared libraries", "fr" : "", @@ -2785,7 +3858,7 @@ } }, "workspace.updates.update" : { - "used-in" : [ "src/app/main/data/workspace/libraries.cljs:486" ], + "used-in" : [ "src/app/main/data/workspace/libraries.cljs:539" ], "translations" : { "en" : "Update", "fr" : "", diff --git a/frontend/resources/styles/common/base.scss b/frontend/resources/styles/common/base.scss index f7f75c2a47..a9fcacdee2 100644 --- a/frontend/resources/styles/common/base.scss +++ b/frontend/resources/styles/common/base.scss @@ -10,7 +10,7 @@ body { color: $color-gray-20; display: flex; flex-direction: column; - font-family: "sourcesanspro", sans-serif; + font-family: 'worksans', sans-serif; height: 100vh; overflow: hidden; } diff --git a/frontend/resources/styles/common/dependencies/colors.scss b/frontend/resources/styles/common/dependencies/colors.scss index 0e8a21688e..bd995bc8c9 100644 --- a/frontend/resources/styles/common/dependencies/colors.scss +++ b/frontend/resources/styles/common/dependencies/colors.scss @@ -9,6 +9,7 @@ $color-white: #ffffff; $color-black: #000000; $color-canvas: #E8E9EA; +$color-dashboard: #F6F6F6; // Main color $color-primary: #31EFB8; diff --git a/frontend/resources/styles/common/dependencies/fonts.scss b/frontend/resources/styles/common/dependencies/fonts.scss index 17484a07fd..d0dfc685af 100644 --- a/frontend/resources/styles/common/dependencies/fonts.scss +++ b/frontend/resources/styles/common/dependencies/fonts.scss @@ -40,6 +40,20 @@ $base-lh-sm: 1.33; $title-lh: 1.25; $title-lh-sm: 1.15; +// Work Sans +@include font-face("worksans","WorkSans-ExtraLight", "100"); +@include font-face("worksans","WorkSans-ExtraLightitalic","100",italic); +@include font-face("worksans","WorkSans-Light","200"); +@include font-face("worksans","WorkSans-LightItalic","200",italic); +@include font-face("worksans","WorkSans-Regular",normal); +@include font-face("worksans","WorkSans-Italic",normal,italic); +@include font-face("worksans","WorkSans-SemiBold","500"); +@include font-face("worksans","WorkSans-SemiBoldItalic","500",italic); +@include font-face("worksans","WorkSans-Bold",bold); +@include font-face("worksans","WorkSans-BoldItalic",bold,italic); +@include font-face("worksans","WorkSans-Black","900"); +@include font-face("worksans","WorkSans-BlackItalic","900",italic); + // Source Sans Pro @include font-face("sourcesanspro","sourcesanspro-extralight", "100"); @include font-face("sourcesanspro","sourcesanspro-extralightitalic","100",italic); diff --git a/frontend/resources/styles/common/dependencies/helpers.scss b/frontend/resources/styles/common/dependencies/helpers.scss index e23f58b39e..ed0f805c1b 100644 --- a/frontend/resources/styles/common/dependencies/helpers.scss +++ b/frontend/resources/styles/common/dependencies/helpers.scss @@ -6,11 +6,11 @@ // Copyright (c) 2015-2016 Juan de la Cruz // Padding & Margin sizes -$x-small: 5px; -$small: 10px; -$medium: 15px; +$x-small: 4px; +$small: 8px; +$medium: 16px; $big: 20px; -$x-big: 30px; +$x-big: 32px; // New sizes $size-1: 0.25rem; @@ -24,6 +24,7 @@ $size-6: 2rem; $br-small: 3px; $br-medium: 5px; $br-big: 8px; +$br-huge: 12px; // Alignments .text-left { diff --git a/frontend/resources/styles/common/dependencies/mixin.scss b/frontend/resources/styles/common/dependencies/mixin.scss index c8b7eefeb2..0b553dcbc2 100644 --- a/frontend/resources/styles/common/dependencies/mixin.scss +++ b/frontend/resources/styles/common/dependencies/mixin.scss @@ -112,19 +112,6 @@ } } -// Collection fonts - -@mixin font-face-collection($style-name, $file, $weight: unquote("normal"), $style: unquote("normal") ) { - $filepath: "/fonts/collection/" + $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'); - font-weight: unquote($weight); - font-style: unquote($style); - } -} - @mixin tooltipShow { &:hover { .icon-tooltip { diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss index c19e4a8ccc..815343e43b 100644 --- a/frontend/resources/styles/common/framework.scss +++ b/frontend/resources/styles/common/framework.scss @@ -17,6 +17,7 @@ font-size: $fs13; height: 30px; justify-content: center; + min-width: 25px; padding: 0 1rem; transition: all .4s; svg { @@ -49,7 +50,7 @@ .btn-secondary { @extend %btn; background: $color-white; - border: 1px solid $color-black; + border: 1px solid $color-gray-20; color: $color-black; &:hover { background: $color-primary; @@ -97,16 +98,18 @@ .btn-icon-light { @extend %btn; - background: $color-gray-10; - color: $color-gray-40; + background: $color-white; + border: 1px solid $color-gray-20; + color: $color-black; padding: $x-small; svg { - fill: $color-gray-40; + fill: $color-black; } &:hover { background: $color-primary; + border-color: $color-primary; svg { - fill: $color-gray-60; + fill: $color-black; } } } @@ -447,7 +450,7 @@ select { background-color: $color-white; box-sizing: border-box; color: $color-gray-60; - font-family: "sourcesanspro", sans-serif; + font-family: "worksans", sans-serif; font-size: $fs14; margin-bottom: $medium; -webkit-appearance: none; @@ -1043,7 +1046,7 @@ input[type=range]:focus::-ms-fill-upper { @include animation(0, .6s, fadeOutUp); } - & .icon { + .icon { display: flex; svg { @@ -1053,11 +1056,11 @@ input[type=range]:focus::-ms-fill-upper { } } - & .content { + .content { &.bottom-actions { flex-direction: column; - & .actions { + .actions { margin-top: $medium; display: flex; justify-content: flex-start; @@ -1069,7 +1072,7 @@ input[type=range]:focus::-ms-fill-upper { align-items: center; justify-content: space-between; - & .actions { + .actions { display: flex; justify-content: flex-start; @@ -1080,12 +1083,12 @@ input[type=range]:focus::-ms-fill-upper { } } - & .btn-close { + .btn-close { position: absolute; right: 0px; top: 0px; - width: 40px; - height: 40px; + width: 48px; + height: 48px; display: flex; justify-content: center; @@ -1104,223 +1107,118 @@ input[type=range]:focus::-ms-fill-upper { opacity: .8; } } -} -.banner.fixed { - position: fixed; - top: 0; - left: 0px; - width: 100%; - height: 40px; - z-index: 13; + &.fixed { + position: fixed; + top: 0; + left: 0px; + width: 100%; + height: 48px; + z-index: 13; - display: flex; - justify-content: center; - align-items: center; - - & .wrapper { display: flex; justify-content: center; align-items: center; - max-width: 60%; - & .icon { - margin-right: $medium; - } - - & .content { - color: $color-white; + .wrapper { display: flex; - align-items: center; justify-content: center; - font-size: $fs15; + align-items: center; + max-width: 60%; + + .icon { + margin-right: $medium; + } + + .content { + color: $color-white; + display: flex; + align-items: center; + justify-content: center; + font-size: $fs15; + } } } -} -.banner.floating, -.banner.inline { - min-height: 40px; + &.floating, &.inline { + min-height: 40px; - & .wrapper { - display: flex; - - & .icon { - padding: $small; - width: 40px; - } - - & .content { - color: $color-black; + .wrapper { display: flex; - font-size: $fs14; - padding: $small; - width: 100%; + + .icon { + padding: $small; + width: 40px; + } + + .content { + color: $color-black; + display: flex; + font-size: $fs14; + padding: $small; + width: 100%; + } + } + + &.error { + .content { + background-color: lighten($color-danger,30%); + } + } + + &.success { + .content { + background-color: lighten($color-success,30%); + } + } + + &.warning { + .content { + background-color: lighten($color-warning,30%); + } + } + + &.info { + .content { + background-color: lighten($color-info,30%); + } } } - &.error { - & .content { - background-color: lighten($color-danger,30%); + &.floating { + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.18); + position: absolute; + top: 70px; + left: 0; + right: 0; + width: 35rem; + margin-left: auto; + margin-right: auto; + z-index: 20; + + &.error { + border: 1px solid $color-danger; + } + + &.success { + border: 1px solid $color-success; + } + + &.warning { + border: 1px solid $color-warning; + } + + &.info { + border: 1px solid $color-info; } } - &.success { - & .content { - background-color: lighten($color-success,30%); - } - } - - &.warning { - & .content { - background-color: lighten($color-warning,30%); - } - } - - &.info { - & .content { - background-color: lighten($color-info,30%); - } + &.inline { + width: 100%; + margin-bottom: $big; } } -.banner.floating { - box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.18); - position: absolute; - top: 70px; - left: 0; - right: 0; - width: 35rem; - margin-left: auto; - margin-right: auto; - z-index: 20; - - &.error { - border: 1px solid $color-danger; - } - - &.success { - border: 1px solid $color-success; - } - - &.warning { - border: 1px solid $color-warning; - } - - &.info { - border: 1px solid $color-info; - } -} - -.banner.inline { - width: 100%; - margin-bottom: $big; -} - -// .inline-banner { -// // display: flex; -// // margin-bottom: $big; -// // min-height: 40px; -// // width: 100%; -// -// // .icon { -// // display: flex; -// // flex-shrink: 0; -// // justify-content: center; -// // margin-right: $small; -// // padding: $small; -// // width: 40px; -// // -// // svg { -// // fill: $color-white; -// // height: 20px; -// // width: 20px; -// // } -// // } -// -// .content { -// display: flex; -// flex-direction: column; -// } -// -// .main { -// display: flex; -// } -// -// .extra { -// display: flex; -// justify-content: flex-end; -// padding: $small; -// -// > div:not(:last-child) { -// margin-right: $small; -// } -// } -// -// .text { -// display: flex; -// font-size: $fs14; -// color: $color-black; -// padding: $small; -// } -// -// &.error { -// background-color: lighten($color-danger,30%); -// .icon { -// background-color: $color-danger; -// } -// } -// -// &.success { -// background-color: lighten($color-success,30%); -// .icon { -// background-color: $color-success; -// } -// } -// -// &.warning { -// background-color: lighten($color-warning,30%); -// .icon { -// background-color: $color-warning; -// } -// } -// -// &.info { -// background-color: lighten($color-info,30%); -// .icon { -// background-color: $color-info; -// } -// } -// -// .btn-close { -// width: 40px; -// height: 40px; -// flex-shrink: 0; -// display: flex; -// justify-content: center; -// align-items: center; -// cursor: pointer; -// opacity: .35; -// -// svg { -// fill: $color-black; -// height: 18px; -// width: 18px; -// transform: rotate(45deg); -// } -// -// &:hover { -// opacity: .8; -// } -// } -// -// &.quick { -// .btn-close { -// display: none; -// } -// } -// } - .close-bezier { fill: $color-danger; stroke: $color-danger-dark; diff --git a/frontend/resources/styles/main-default.scss b/frontend/resources/styles/main-default.scss index 10410d6730..a438f64642 100644 --- a/frontend/resources/styles/main-default.scss +++ b/frontend/resources/styles/main-default.scss @@ -2,10 +2,12 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2016 Andrey Antukh -// Copyright (c) 2016 Juan de la Cruz +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL -// app MAIN STYLES +// MAIN STYLES //################################################# // //################################################# @@ -25,25 +27,25 @@ @import 'common/base'; @import 'main/layouts/login'; @import 'main/layouts/main-layout'; -@import 'main/layouts/projects-page'; -@import 'main/layouts/libraries-page'; -@import 'main/layouts/recent-files-page'; -@import 'main/layouts/search-page'; @import "main/layouts/not-found"; @import "main/layouts/viewer"; +@import "main/layouts/handoff"; //################################################# // Commons //################################################# @import 'common/framework'; +@import 'main/partials/modal'; +@import 'main/partials/forms'; +@import "main/partials/texts"; +@import 'main/partials/context-menu'; +@import 'main/partials/dropdown'; //################################################# // Partials //################################################# -@import "main/partials/login"; -@import "main/partials/texts"; @import "main/partials/viewer"; @import "main/partials/viewer-header"; @import "main/partials/viewer-thumbnails"; @@ -51,19 +53,20 @@ @import 'main/partials/activity-bar'; @import 'main/partials/color-palette'; @import 'main/partials/colorpicker'; -@import 'main/partials/context-menu'; +@import 'main/partials/dashboard'; +@import 'main/partials/dashboard-header'; @import 'main/partials/dashboard-grid'; +@import 'main/partials/dashboard-sidebar'; +@import 'main/partials/dashboard-team'; +@import 'main/partials/dashboard-settings'; @import 'main/partials/debug-icons-preview'; @import 'main/partials/editable-label'; -@import 'main/partials/forms'; @import 'main/partials/left-toolbar'; -@import 'main/partials/dashboard-sidebar'; @import 'main/partials/loader'; -@import 'main/partials/main-bar'; -@import 'main/partials/modal'; @import 'main/partials/project-bar'; @import 'main/partials/sidebar'; @import 'main/partials/sidebar-align-options'; +@import 'main/partials/sidebar-assets'; @import 'main/partials/sidebar-document-history'; @import 'main/partials/sidebar-element-options'; @import 'main/partials/sidebar-icons'; @@ -71,9 +74,11 @@ @import 'main/partials/sidebar-layers'; @import 'main/partials/sidebar-sitemap'; @import 'main/partials/sidebar-tools'; -@import 'main/partials/sidebar-assets'; @import 'main/partials/tab-container'; @import 'main/partials/tool-bar'; @import 'main/partials/user-settings'; @import 'main/partials/workspace'; @import 'main/partials/workspace-header'; +@import 'main/partials/workspace-comments'; +@import 'main/partials/color-bullet'; +@import "main/partials/handoff"; diff --git a/frontend/resources/styles/main/layouts/handoff.scss b/frontend/resources/styles/main/layouts/handoff.scss new file mode 100644 index 0000000000..2eb5c2548d --- /dev/null +++ b/frontend/resources/styles/main/layouts/handoff.scss @@ -0,0 +1,33 @@ +.handoff-layout { + display: grid; + grid-template-rows: 40px auto; + grid-template-columns: 1fr; + user-select: none; + + &.fullscreen { + .viewer-header { + opacity: 0; + &:hover { + opacity: 1; + } + } + + .viewer-content { + grid-row: 1 / span 2; + } + } + + .viewer-header { + grid-column: 1 / span 1; + grid-row: 1 / span 1; + } + + .viewer-content { + grid-column: 1 / span 1; + grid-row: 2 / span 1; + } +} + +.handoff-layout .settings-bar.settings-bar-left { + left: 0; +} diff --git a/frontend/resources/styles/main/layouts/libraries-page.scss b/frontend/resources/styles/main/layouts/libraries-page.scss deleted file mode 100644 index dc614aafc5..0000000000 --- a/frontend/resources/styles/main/layouts/libraries-page.scss +++ /dev/null @@ -1,6 +0,0 @@ -.libraries-page { - padding: 1rem; - background-color: $color-white; - flex: 1 0 0; - overflow-y: auto; -} diff --git a/frontend/resources/styles/main/layouts/login.scss b/frontend/resources/styles/main/layouts/login.scss index e8d89e8464..9442c515e5 100644 --- a/frontend/resources/styles/main/layouts/login.scss +++ b/frontend/resources/styles/main/layouts/login.scss @@ -10,7 +10,7 @@ .auth { display: grid; grid-template-rows: auto; - grid-template-columns: 388px auto; + grid-template-columns: 510px auto; } .auth-sidebar { @@ -52,11 +52,15 @@ background-color: $color-white; .form-container { - width: 368px; + width: 412px; .btn-ocean { margin-top: $x-big; } + + form { + margin: 2rem 0; + } } .btn-google-auth { diff --git a/frontend/resources/styles/main/layouts/main-layout.scss b/frontend/resources/styles/main/layouts/main-layout.scss index 0852beb6f4..edabb7c481 100644 --- a/frontend/resources/styles/main/layouts/main-layout.scss +++ b/frontend/resources/styles/main/layouts/main-layout.scss @@ -2,8 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2015-2016 Andrey Antukh -// Copyright (c) 2015-2016 Juan de la Cruz +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL .main-content { display: flex; @@ -14,23 +16,22 @@ .dashboard-layout { background-color: $color-white; display: grid; - grid-template-rows: 40px 1fr; + grid-template-rows: 50px 1fr; grid-template-columns: 40px 180px 1fr; height: 100vh; - & .dashboard-sidebar { - grid-row: 2; + .dashboard-sidebar { + grid-row: 1 / span 2; grid-column: 1 / span 2; overflow: hidden; } - & .dashboard-content { + .dashboard-content { grid-row: 1 / span 2; } } .dashboard-content { - background-color: lighten($color-gray-10, 5%); display: flex; flex-direction: column; } diff --git a/frontend/resources/styles/main/layouts/projects-page.scss b/frontend/resources/styles/main/layouts/projects-page.scss deleted file mode 100644 index 3b93940580..0000000000 --- a/frontend/resources/styles/main/layouts/projects-page.scss +++ /dev/null @@ -1,6 +0,0 @@ -.projects-page { - padding: 1rem; - background-color: $color-white; - flex: 1 0 0; - overflow-y: auto; -} diff --git a/frontend/resources/styles/main/layouts/recent-files-page.scss b/frontend/resources/styles/main/layouts/recent-files-page.scss deleted file mode 100644 index b87226bb82..0000000000 --- a/frontend/resources/styles/main/layouts/recent-files-page.scss +++ /dev/null @@ -1,41 +0,0 @@ -.recent-files-page { - background-color: $color-white; - flex: 1 0 0; - overflow-y: auto; -} - -.recent-files-row { - padding: 1rem; - border-top: 1px solid $color-gray-10; - - &.first { - border-top: none; - } -} - -.recent-files-row-title { - display: flex; - flex-direction: row; - margin-left: $medium; - margin-top: $medium; -} - -.recent-files-row-title-name, .recent-files-row-title-info { - font-size: 15px; - line-height: 1rem; - font-weight: unset; -} - -.recent-files-row-title-name { - color: black; - margin-right: $medium; -} - -.recent-files-row-title-info { - color: $color-gray-30 -} - -.recent-files-empty { - margin: 30px; - font-size: 20px -} diff --git a/frontend/resources/styles/main/layouts/search-page.scss b/frontend/resources/styles/main/layouts/search-page.scss deleted file mode 100644 index 37e17f2a9e..0000000000 --- a/frontend/resources/styles/main/layouts/search-page.scss +++ /dev/null @@ -1,6 +0,0 @@ -.search-page { - padding: 1rem; - background-color: $color-white; - flex: 1 0 0; - overflow-y: auto; -} diff --git a/frontend/resources/styles/main/partials/activity-bar.scss b/frontend/resources/styles/main/partials/activity-bar.scss index c4bd911c93..5948b738c1 100644 --- a/frontend/resources/styles/main/partials/activity-bar.scss +++ b/frontend/resources/styles/main/partials/activity-bar.scss @@ -2,8 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2015-2016 Andrey Antukh -// Copyright (c) 2015-2016 Juan de la Cruz +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL .activity-bar { background-color: $color-gray-50; diff --git a/frontend/resources/styles/main/partials/color-bullet.scss b/frontend/resources/styles/main/partials/color-bullet.scss new file mode 100644 index 0000000000..b4ee15997d --- /dev/null +++ b/frontend/resources/styles/main/partials/color-bullet.scss @@ -0,0 +1,184 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL + +.color-cell { + .color-bullet { + background-color: $color-white; + // Creates strange artifacts + border: 2px solid $color-gray-60; + // box-shadow: 0 0 0 2px $color-gray-60; + border-radius: 50%; + } + + &.cell-big .color-bullet { + width: 50px; + height: 50px; + } + + &.cell-small .color-bullet { + width: 40px; + height: 40px; + } + + .color-bullet.color-big { + width: 50px; + height: 50px; + } + +} + +.color-cell.current { + .color-bullet { + border-color: $color-gray-50; + } +} + +ul.palette-menu .color-bullet { + width: 20px; + height: 20px; + border-radius: 12px; + border: 1px solid $color-gray-10; + margin-right: 5px; + background-size: 8px; +} +.color-cell.add-color .color-bullet { + align-items: center; + background-color: $color-gray-50; + border: 3px dashed $color-gray-10; + cursor: pointer; + display: flex; + justify-content: center; + margin-bottom: 1rem; + padding: .6rem; + + svg { + fill: $color-gray-10; + height: 30px; + width: 30px; + } +} + +.colorpicker-content .color-bullet { + grid-area: color; + width: 20px; + height: 20px; + border-radius: 12px; + border: 1px solid $color-gray-10; + background-size: 8px; +} + +.asset-group .group-list-item .color-bullet { + width: 20px; + height: 20px; + border-radius: 10px; + margin-right: $x-small; +} + +.color-cell.add-color:hover .color-bullet { + border-color: $color-gray-30; + + svg { + fill: $color-gray-30; + } +} + +.color-bullet { + display: flex; + flex-direction: row; + overflow: hidden; + + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center; + background-color: $color-white; + + & > * { + width: 100%; + height: 100%; + } +} + +.color-data .color-bullet.multiple { + background: transparent; + + &::before { + content: "?" + } +} + +.color-data .color-bullet.is-library-color { + border-radius: 50%; +} + +.color-data .color-bullet { + background-color: $color-gray-30; + border: 1px solid $color-gray-30; + border-radius: $br-small; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: $color-gray-10; + flex-shrink: 0; + height: 20px; + margin: 5px 4px 0 0; + width: 20px; + + &.color-name { + border-radius: 10px; + } + + &.palette-th { + align-items: center; + border: 1px solid $color-gray-30; + display: flex; + justify-content: center; + + svg { + fill: $color-gray-30; + height: 16px; + width: 16px; + } + + &:hover { + border-color: $color-primary; + svg { + fill: $color-primary; + } + + } + } +} + +.colorpicker-content .libraries .selected-colors .color-bullet { + grid-area: auto; + margin-bottom: 0.25rem; + cursor: pointer; + + &:hover { + border-color: $color-primary; + } + + &.button { + background: $color-white; + display: flex; + align-items: center; + justify-content: center; + } + + &.button svg { + width: 12px; + height: 12px; + fill: $color-gray-30; + } + + &.plus-button svg { + width: 8px; + height: 8px; + fill: $color-black; + } +} diff --git a/frontend/resources/styles/main/partials/color-palette.scss b/frontend/resources/styles/main/partials/color-palette.scss index 9009156284..6bff34acba 100644 --- a/frontend/resources/styles/main/partials/color-palette.scss +++ b/frontend/resources/styles/main/partials/color-palette.scss @@ -2,8 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2015-2016 Andrey Antukh -// Copyright (c) 2015-2016 Juan de la Cruz +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL .color-palette { @include animation(0,.5s,fadeInUp); @@ -49,8 +51,8 @@ } &.left-sidebar-open { - left: 280px; - width: calc(100% - 280px); + left: 303px; + width: calc(100% - 303px); } & .context-menu-items { @@ -121,7 +123,7 @@ display: flex; overflow: hidden; width: 100%; - height: 4.8rem; + height: 5rem; padding: 0.25rem; &.size-small { @@ -154,28 +156,6 @@ flex-basis: 52px; } - .color { - background-color: $color-gray-10; - border: 2px solid $color-gray-60; - border-radius: 50%; - flex-shrink: 0; - } - - &.cell-big .color { - width: 50px; - height: 50px; - } - - &.cell-small .color { - width: 40px; - height: 40px; - } - - .color.color-big { - width: 50px; - height: 50px; - } - .color-text { color: $color-gray-20; font-size: $fs12; @@ -184,11 +164,9 @@ text-overflow: ellipsis; width: 66px; text-align: center; + margin-top: 0.25rem; } &.current { - .color { - border-color: $color-gray-50; - } .color-text { color: $color-gray-50; font-weight: bold; @@ -215,31 +193,11 @@ } &.add-color { margin-left: 1.5rem; - .color { - align-items: center; - background-color: $color-gray-50; - border: 3px dashed $color-gray-10; - cursor: pointer; - display: flex; - justify-content: center; - margin-bottom: 1rem; - padding: .6rem; - svg { - fill: $color-gray-10; - height: 30px; - width: 30px; - } - } + .color-text { font-weight: bold; } &:hover { - .color { - border-color: $color-gray-30; - svg { - fill: $color-gray-30; - } - } .color-text { color: $color-gray-40; } @@ -334,12 +292,5 @@ ul.palette-menu { margin-top: 0.5rem; } - .color-bullet { - width: 20px; - height: 20px; - border-radius: 12px; - border: 1px solid $color-gray-10; - margin-right: 5px; - } } diff --git a/frontend/resources/styles/main/partials/colorpicker.scss b/frontend/resources/styles/main/partials/colorpicker.scss index 2ff6badc4c..90323f6779 100644 --- a/frontend/resources/styles/main/partials/colorpicker.scss +++ b/frontend/resources/styles/main/partials/colorpicker.scss @@ -6,257 +6,475 @@ // Copyright (c) 2015-2016 Juan de la Cruz .colorpicker { + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + background-color: $color-white; +} + +.colorpicker-content { + display: flex; + flex-direction: column; + padding: $small; + + & > * { + width: 200px; + } + + .top-actions { display: flex; - flex-direction: column; - padding: 0.5rem; - background-color: $color-white; - box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + margin-bottom: $x-small; + justify-content: space-between; - & > * { - width: 200px; + .picker-btn { + background: none; + border: none; + cursor: pointer; + + &.active svg, + &:hover svg { + fill: $color-primary; + } + + svg { + width: 14px; + height: 14px; + } + } + } + + .gradients-buttons { + .gradient { + cursor: pointer; + width: 15px; + height: 15px; + padding: 0; + margin: 0; + border: 1px solid $color-gray-20; + border-radius: 2px; + margin-left: $x-small; } - .top-actions { - display: flex; - margin-bottom: 0.25rem; - - .picker-btn { - background: none; - border: none; - cursor: pointer; - - &.active, - &:hover svg { - fill: $color-primary; - } - - svg { - width: 14px; - height: 14px; - } - } + .active { + border-color: $color-primary; } - .picker-detail-wrapper { - position: relative; + .linear-gradient { + background: linear-gradient(180deg, $color-gray-20, transparent); + } - .center-circle { - width: 14px; - height: 14px; - border: 2px solid $color-white; - border-radius: 8px; - position: absolute; - left: 50%; - top: 50%; - transform: translate(-7px, -7px); - filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25)); - } + .radial-gradient { + background: radial-gradient(transparent, $color-gray-20); } - #picker-detail { - border: 1px solid $color-gray-10; + } + + .gradient-stops { + height: 10px; + display: flex; + margin-top: $small; + margin-bottom: $medium; + + .gradient-background-wrapper { + height: 100%; + width: 100%; + border: 1px solid $color-gray-10; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center; } + .gradient-background { + height: 100%; + width: 100%; + } + + .gradient-stop-wrapper { + position: absolute; + width: calc(100% - 2rem); + margin-left: 0.5rem; + } + + .gradient-stop { + display: grid; + grid-template-columns: 50% 50%; + position: absolute; + width: 15px; + height: 15px; + border-radius: 2px; + border: 1px solid $color-gray-20; + margin-top: -2px; + margin-left: -7px; + box-shadow: 0 2px 2px rgb(0 0 0 / 15%); + + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center; + background-color: $color-white; + + &.active { + border-color: $color-primary; + } + } + } + + .picker-detail-wrapper { + position: relative; + + .center-circle { + width: 14px; + height: 14px; + border: 2px solid $color-white; + border-radius: 8px; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-7px, -7px); + filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25)); + } + } + + #picker-detail { + border: 1px solid $color-gray-10; + } + + .slider-selector { + --gradient-direction: 90deg; + --background-repeat: left; + + &.vertical { + --gradient-direction: 0deg; + --background-repeat: top; + } + + border: 1px solid $color-gray-10; + + background: linear-gradient(var(--gradient-direction), rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%); + align-self: center; + position: relative; + cursor: pointer; + + width: 100%; + height: calc(0.5rem + 1px); + + &.vertical { + width: calc(0.5rem + 1px); + height: 100%; + } + + &.hue { + background: linear-gradient( + var(--gradient-direction), + #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, + #00f 67%, #f0f 83%, #f00 100%); + } + + &.saturation { + background: linear-gradient( + var(--gradient-direction), + var(--saturation-grad-from) 0%, + var(--saturation-grad-to) 100% + ) + } + + &.opacity { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") var(--background-repeat) center; + + &::after { + content: ""; + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(var(--gradient-direction), rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%); + } + + } + + &.value { + background: linear-gradient(var(--gradient-direction), #FFF 0%, #000 100%); + } + + .handler { + background-color: $color-white;; + box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px; + transform: translate(-6px, -2px); + left: 50%; + position: absolute; + width: 12px; + height: 12px; + border-radius: 6px; + z-index: 1; + } + + &.vertical .handler { + transform: translate(-6px, 6px); + } + } + + .value-saturation-selector { + background-color: rgba(var(--hue-rgb)); + position: relative; + height: 6.75rem; + cursor: pointer; + + .handler { + position: absolute; + width: 12px; + height: 12px; + 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; + transform: translate(-6px, -6px); + left: 50%; + top: 50%; + } + + &::before { + content: ""; + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(to right, #fff, rgba(255,255,255,0)); + } + + &::after { + content: ""; + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(to top, #000, rgba(0,0,0,0)); + } + } + + .shade-selector { + display: grid; + justify-items: center; + align-items: center; + grid-template-areas: "color hue" + "color opacity"; + grid-template-columns: 2.5rem 1fr; + height: 3.5rem; + grid-row-gap: 0.5rem; + cursor: pointer; + margin-bottom: 0.25rem; + + .slider-selector.hue { + grid-area: "hue"; + align-self: end; + } + + .slider-selector.opacity { + grid-area: "opacity"; + align-self: start; + } + } + + .color-values { + display: grid; + grid-template-columns: 3.5rem repeat(4, 1fr); + grid-row-gap: 0.25rem; + justify-items: center; + grid-column-gap: 0.25rem; + + &.disable-opacity { + grid-template-columns: 3.5rem repeat(3, 1fr); + } + + input { + width: 100%; + margin: 0; + border: 1px solid $color-gray-10; + border-radius: 2px; + font-size: $fs11; + height: 1.5rem; + padding: 0 $x-small; + color: $color-gray-40; + } + + label { + font-size: $fs11; + } + } + + .libraries { + border-top: 1px solid $color-gray-10; + padding-top: 0.5rem; + margin-top: 0.25rem; + width: 200px; + + select { + background-image: url(/images/icons/arrow-down.svg); + background-repeat: no-repeat; + background-position: 95% 48%; + background-size: 10px; + margin: 0; + margin-bottom: $small; + width: 100%; + padding: $x-small 0.25rem; + font-size: 0.75rem; + color: $color-gray-40; + cursor: pointer; + border-color: $color-gray-10; + border-radius: 2px; + + option { + padding: 0; + } + } + + .selected-colors { + display: grid; + grid-template-columns: repeat(8, 1fr); + justify-content: space-between; + margin-right: -8px; + overflow-x: hidden; + overflow-y: auto; + max-height: 5.5rem; + } + + + .selected-colors::after { + content: ""; + flex: auto; + } + } + + .actions { + margin-top: 0.5rem; + display: flex; + flex-direction: row; + justify-content: center; + + .btn-primary { + height: 1.5rem; + padding: 0 2.5rem; + font-size: $fs12; + } + } + + .harmony-selector { + display: flex; + flex-direction: row; + margin-bottom: 0.5rem; + + .hue-wheel-wrapper { + position: relative; + + .hue-wheel { + width: 152px; + height: 152px; + } + + .handler { position: absolute; width: 12px; height: 12px; border-radius: 6px; z-index: 1; - } - - .value-selector { - background-color: rgba(var(--hue)); - position: relative; - height: 6.75rem; - cursor: pointer; - - .handler { - box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset; - transform: translate(-6px, -6px); - left: 50%; - top: 50%; - } - } - - .value-selector::before { - content: ""; - position: absolute; - width: 100%; - height: 100%; - background: linear-gradient(to right, #fff, rgba(255,255,255,0)); - } - - .value-selector::after { - content: ""; - position: absolute; - width: 100%; - height: 100%; - background: linear-gradient(to top, #000, rgba(0,0,0,0)); - } - - .shade-selector { - display: grid; - justify-items: center; - align-items: center; - grid-template-areas: "color hue" "color opacity"; - grid-template-columns: 2.5rem 1fr; - height: 3.5rem; - grid-row-gap: 0.5rem; - cursor: pointer; - } - - .color-bullet { - grid-area: color; - width: 20px; - height: 20px; - background-color: rgba(var(--color)); - border-radius: 12px; - border: 1px solid $color-gray-10; - } - - .hue-selector { - align-self: end; - grid-area: hue; - height: 0.5rem; - width: 100%; - background: linear-gradient( - to right, - #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, - #00f 67%, #f0f 83%, #f00 100%); - position: relative; - cursor: pointer; - } - - .hue-selector .handler, - .opacity-selector .handler { - background-color: rgb(248, 248, 248); - box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px; - transform: translate(-6px, -2px); + 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; + transform: translate(-6px, -6px); left: 50%; + top: 50%; + } + + .handler.complement { + background-color: $color-white; + box-shadow: rgb(0 0 0 / 0.25) 0px 4px 4px; + } } - .opacity-selector { - align-self: start; - grid-area: opacity; - height: 0.5rem; - width: 100%; - position: relative; - background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center; - } - - .opacity-selector::after { - content: ""; - background: linear-gradient(to right, rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%); - position: absolute; - width: 100%; + .handlers-wrapper { + height: 152px; + display: flex; + flex-direction: row; + flex-grow: 1; + justify-content: space-around; + padding-top: 0.5rem; + + & > * { height: 100%; + } + } + } + + .hsva-selector { + display: grid; + padding: 0.25rem; + grid-template-columns: 20px 1fr; + grid-template-rows: repeat(4, 2rem); + grid-row-gap: 0.5rem; + margin-bottom: 0.5rem; + + .hue, + .saturation, + .value, + .opacity { + border-radius: 10px; } - .color-values { - display: grid; - grid-template-columns: 3.5rem repeat(4, 1fr); - grid-row-gap: 0.25rem; - justify-items: center; - grid-column-gap: 0.25rem; + .hsva-selector-label { + grid-column: 1; + align-self: center; + } + } +} - input { - width: 100%; - margin: 0; - border: 1px solid $color-gray-10; - border-radius: 2px; - font-size: $fs11; - height: 1.5rem; - padding: 0 $x-small; - color: $color-gray-40; - } +.colorpicker-tooltip { + border-radius: $br-small; + display: flex; + flex-direction: column; + left: 1400px; + top: 100px; + position: absolute; + z-index: 11; + width: auto; - label { - font-size: $fs11; - } + span { + color: $color-gray-20; + font-size: $fs12; + } + + .inputs-area { + + .input-text { + color: $color-gray-60; + font-size: $fs13; + margin: 5px; + padding: 5px; + width: 100%; } - .libraries { - border-top: 1px solid $color-gray-10; - padding-top: 0.5rem; - margin-top: 0.25rem; - width: 200px; - - select { - background-image: url(/images/icons/arrow-down.svg); - background-repeat: no-repeat; - background-position: 95% 48%; - background-size: 10px; - margin: 0; - margin-bottom: 0.5rem; - width: 100%; - padding: 2px 0.25rem; - font-size: 0.75rem; - color: $color-gray-40; - border-color: $color-gray-10; - border-radius: 2px; + } - option { - padding: 0; - } - } + .colorpicker-tabs { + display: flex; + margin-bottom: $small; + border-radius: 5px; + border: 1px solid $color-gray-10; + height: 2rem; - .selected-colors { - display: grid; - grid-template-columns: repeat(8, 1fr); - justify-content: space-between; - margin-right: -8px; - overflow-x: hidden; - overflow-y: auto; - max-height: 5.5rem; - } + .colorpicker-tab { + cursor: pointer; + display: flex; + flex-grow: 1; + justify-content: center; + align-items: center; - - .selected-colors::after { - content: ""; - flex: auto; - } - - .selected-colors .color-bullet { - grid-area: auto; - margin-bottom: 0.25rem; - cursor: pointer; - - &:hover { - border-color: $color-primary; - } - - &.button { - display: flex; - align-items: center; - justify-content: center; - } - - &.button svg { - width: 12px; - height: 12px; - fill: $color-gray-30; - } - - &.plus-button svg { - width: 8px; - height: 8px; - fill: $color-black; - } - } + svg { + width: 16px; + height: 16px; + fill: $color-gray-20; + } } - .actions { - margin-top: 0.5rem; - display: flex; - flex-direction: row; - justify-content: center; - - .btn-primary { - height: 1.5rem; - padding: 0 2.5rem; - font-size: $fs12; - } + .active { + background-color: $color-gray-10; + svg { + fill: $color-gray-60; + } } + + :hover svg { + fill: $color-primary; + } + + } } .color-data { @@ -265,8 +483,8 @@ position: relative; .color-name { - font-size: $fs13; - margin: 5px 6px 0px 6px; + font-size: $fs13; + margin: 5px 6px 0px 6px; } .color-info { @@ -274,7 +492,7 @@ background-color: $color-gray-50; border: 1px solid $color-gray-30; border-radius: $br-small; - color: $color-gray-20; + color: $color-white; height: 20px; margin: 5px 0 0 0; padding: 0 $x-small; @@ -310,30 +528,3 @@ } } -.colorpicker-tooltip { - border-radius: $br-small; - display: flex; - flex-direction: column; - left: 1400px; - top: 100px; - position: absolute; - z-index: 11; - width: auto; - - span { - color: $color-gray-20; - font-size: $fs12; - } - - .inputs-area { - - .input-text { - color: $color-gray-60; - font-size: $fs13; - margin: 5px; - padding: 5px; - width: 100%; - } - - } -} diff --git a/frontend/resources/styles/main/partials/context-menu.scss b/frontend/resources/styles/main/partials/context-menu.scss index 9bff5e57f5..7a585c7fd5 100644 --- a/frontend/resources/styles/main/partials/context-menu.scss +++ b/frontend/resources/styles/main/partials/context-menu.scss @@ -1,3 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL + .context-menu { position: relative; visibility: hidden; @@ -27,7 +36,7 @@ .context-menu-action { color: $color-black; display: block; - font-size: $fs12; + font-size: $fs14; padding: $size-2 $size-4; text-align: left; white-space: nowrap; @@ -35,7 +44,7 @@ &:hover { color: $color-black; background-color: $color-primary-lighter; - } + } } .context-menu.is-selectable { @@ -49,6 +58,6 @@ background-position: 5% 48%; background-size: 10px; font-weight: bold; - } + } } diff --git a/frontend/resources/styles/main/partials/dashboard-grid.scss b/frontend/resources/styles/main/partials/dashboard-grid.scss index 0ed99bda19..76cda8a6b6 100644 --- a/frontend/resources/styles/main/partials/dashboard-grid.scss +++ b/frontend/resources/styles/main/partials/dashboard-grid.scss @@ -2,74 +2,23 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2015-2016 Andrey Antukh -// Copyright (c) 2015-2016 Juan de la Cruz +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL .dashboard-grid { font-size: $fs14; - .dashboard-title { - position: relative; - width: 100%; - - h2 { - text-align: center; - width: 100%; - .edit { - padding: 5px 10px; - background: $color-gray-50; - border: none; - height: 100%; - } - .close { - padding: 5px 10px; - background: $color-gray-50; - cursor: pointer; - svg { - transform: rotate(45deg); - fill: $color-gray-30; - height: 20px; - width: 20px; - } - } - } - - .edition { - align-items: center; - display: flex; - position: absolute; - right: 40px; - top: 0; - - span { - cursor: pointer; - - svg { - fill: $color-gray-30; - height: 20px; - margin: 0 10px; - width: 20px; - } - - &:hover { - - svg { - fill: $color-gray-50; - } - - } - - } - - } - - } - - .dashboard-grid-row { + .grid-row { display: flex; flex-wrap: wrap; width: 100%; align-content: flex-start; + + &.no-wrap { + flex-wrap: nowrap; + } } .grid-item { @@ -82,14 +31,37 @@ flex-shrink: 0; height: 200px; margin: $medium; - max-width: 300px; + max-width: 260px; min-width: 260px; position: relative; text-align: center; width: 18%; box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); - & .overlay { + &.placeholder { + min-width: 115px; + max-width: 115px; + + display: flex; + flex-direction: column; + justify-content: center; + + .placeholder-icon { + svg { + transform: rotate(-90deg); + width: 20px; + height: 20px; + fill: $color-gray-30; + } + } + + .placeholder-label { + font-size: $fs14; + } + + } + + &.overlay { border-radius: 4px; border: 2px solid $color-primary; height: 100%; @@ -99,6 +71,7 @@ width: 100%; z-index: 1; } + &:hover .overlay { display: block; opacity: 1; @@ -146,6 +119,13 @@ width: 100%; } + .edit-wrapper { + .element-title { + padding: 3px; + height: 25px; + } + } + } .item-badge { @@ -347,128 +327,52 @@ padding: $medium; } -} - -.grid-item-th { - background-position: center; - background-size: auto 80%; - background-repeat: no-repeat; - border-top-left-radius: $br-small; - border-top-right-radius: $br-small; - height: 70%; - overflow: hidden; - position: relative; - width: 100%; - - background-color: $color-canvas; - - .img-th { - height: auto; + .grid-item-th { + background-position: center; + background-size: auto 80%; + background-repeat: no-repeat; + border-top-left-radius: $br-small; + border-top-right-radius: $br-small; + height: 70%; + overflow: hidden; + position: relative; width: 100%; - } - svg { - height: 100%; + background-color: $color-canvas; + + .img-th { + height: auto; width: 100%; - } - -} - -// MULTISELECT OPTIONS BAR -.multiselect-bar { - @include animation(0,.5s,fadeInUp); - align-items: center; - background-color: $color-gray-50; - display: flex; - left: 0; - justify-content: center; - padding: $medium; - position: absolute; - width: 100%; - bottom: 0; - - .multiselect-nav { - align-items: center; - display: flex; - justify-content: center; - margin-left: 10%; - width: 110px; - - span { - margin-right: 1.5rem; - &:last-child { - margin-right: 0; - } } svg { - cursor: pointer; - fill: $color-gray-30; - height: 20px; - width: 20px; - - &:hover { - fill: $color-gray-20; - } - + height: 100%; + width: 100%; } - - span.delete { - - &:hover { - - svg{ - fill: $color-danger-light; - } - - } - - } - - } - -} - -.move-item { - position: relative; - - .move-list { - background-color: $color-gray-10; - border-radius: $br-small; - bottom: 30px; - display: flex; - flex-direction: column; - left: -30px; - max-height: 260px; - overflow-y: scroll; - padding: $medium; - position: absolute; - width: 260px; - - li { - padding-bottom: $medium; - - &.title { - color: $color-gray-50; - } - - } - } } -.grid-files-empty { - align-items: center; - border: 1px dashed $color-gray-20; - border-radius: $br-small; - display: flex; - flex-direction: column; - height: fit-content; - margin: $size-4; - padding: 3rem; +.grid-empty-placeholder { + align-items: center; + border: 1px dashed $color-gray-20; + border-radius: $br-small; + display: flex; + flex-direction: column; + height: 200px; + margin: $size-4; + padding: 3rem; + justify-content: center; - .grid-files-desc { - color: $color-gray-60; - margin-bottom: $medium; - } + svg { + width: 36px; + height: 36px; + fill: $color-gray-20; + } + + .text { + margin-top: 10px; + color: $color-gray-30; + font-size: $fs16; + } } + diff --git a/frontend/resources/styles/main/partials/dashboard-header.scss b/frontend/resources/styles/main/partials/dashboard-header.scss new file mode 100644 index 0000000000..30fae12c71 --- /dev/null +++ b/frontend/resources/styles/main/partials/dashboard-header.scss @@ -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/. +// +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL + +.dashboard-header { + align-items: center; + background-color: $color-white; + display: flex; + height: 63px; + padding: $x-small $small; + position: relative; + z-index: 10; + justify-content: space-between; + + .element-name { + margin-right: $small; + } + + .btn-secondary { + flex-shrink: 0; + z-index: 10; + height: 32px; + } + + svg { + fill: $color-black; + height: 14px; + margin-right: $x-small; + width: 14px; + } + + nav { + display: flex; + width: 300px; + justify-content: center; + z-index: 1; + margin-top: 39px; + + ul { + display: flex; + align-items: center; + font-size: $fs15; + justify-content: center; + } + + li { + a { + align-items: center; + border-bottom: 3px solid transparent; + color: $color-gray-30; + display: flex; + height: 40px; + padding: $x-small $big; + flex-basis: 140px; + + &:hover { + color: $color-black; + } + + } + + &.active { + a { + color: $color-black; + border-color: $color-primary; + } + } + } + } + + .dashboard-title { + display: flex; + h1 { + color: $color-black; + display: flex; + flex-shrink: 0; + font-size: $fs18; + z-index: 10; + } + + .context-menu.is-open { + margin-top: 10px; + } + } + + .icon { + display: flex; + align-items: center; + cursor: pointer; + margin-left: $small; + z-index: 10; + + svg { + fill: $color-gray-40; + width: 15px; + height: 15px; + + &:hover { + fill: $color-primary-dark; + } + } + } +} diff --git a/frontend/resources/styles/main/partials/dashboard-settings.scss b/frontend/resources/styles/main/partials/dashboard-settings.scss new file mode 100644 index 0000000000..c09e6d40a0 --- /dev/null +++ b/frontend/resources/styles/main/partials/dashboard-settings.scss @@ -0,0 +1,124 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL + +.dashboard-sidebar { + &.settings { + .back-to-dashboard { + padding: 12px 18px; + font-size: $fs14; + cursor: pointer; + display: flex; + + .icon { + display: flex; + align-items: center; + margin-right: 14px; + } + + .text { + color: $color-gray-60; + } + + svg { + fill: $color-gray-60; + transform: rotate(90deg); + width: 12px; + height: 12px; + } + } + } +} + + +.dashboard-settings { + display: flex; + width: 100%; + justify-content: center; + align-items: center; + + .form-container { + margin-top: 50px; + display: flex; + max-width: 368px; + width: 100%; + + &.two-columns { + max-width: 536px; + justify-content: space-between; + flex-direction: row; + } + } + + .avatar-form { + display: flex; + flex-direction: column; + width: 120px; + min-width: 120px; + + img { + border-radius: 50%; + flex-shrink: 0; + height: 120px; + margin-right: $medium; + width: 120px; + } + + .image-change-field { + position: relative; + width: 120px; + height: 120px; + + .update-overlay { + opacity: 0; + cursor: pointer; + position: absolute; + width: 121px; + height: 121px; + border-radius: 50%; + font-size: $fs24; + color: $color-white; + line-height: 120px; + text-align: center; + background: $color-primary-dark; + z-index: 14; + } + + input[type=file] { + width: 120px; + height: 120px; + position: absolute; + opacity: 0; + cursor: pointer; + top: 0; + z-index: 15; + } + + &:hover { + img {display: none;} + .update-overlay {opacity: 1}; + } + } + } + + .profile-form { + display: flex; + flex-direction: column; + max-width: 368px; + width: 100%; + } + + .options-form, + .password-form { + h2 { + font-size: $fs14; + margin-bottom: 20px; + } + } + +} diff --git a/frontend/resources/styles/main/partials/dashboard-sidebar.scss b/frontend/resources/styles/main/partials/dashboard-sidebar.scss index 49e86f4165..3a100db1bd 100644 --- a/frontend/resources/styles/main/partials/dashboard-sidebar.scss +++ b/frontend/resources/styles/main/partials/dashboard-sidebar.scss @@ -2,102 +2,297 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2015-2016 Andrey Antukh -// Copyright (c) 2015-2016 Juan de la Cruz +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL .dashboard-sidebar { background-color: $color-white; - .sidebar-team { + .sidebar-inside { display: flex; flex-direction: column; - padding: $size-4 0; - border-top: 1px solid $color-gray-10; height: 100%; - padding-bottom: 2.8rem; + padding-top: $size-2; } - .dashboard-sidebar-inside { + .sidebar-content { display: flex; flex-direction: column; height: 100%; - border-right: 1px solid $color-gray-10; + overflow-y: auto; + padding: 0; - .dashboard-elements { + hr { + border-color: $color-gray-10; + margin: 1rem 15px; + } + } + + .sidebar-team-switch { + position: relative; + display: flex; + margin: 5px 15px; + + .teams-dropdown { + left: 0; + top: 50px; + z-index: 12; + max-height: 30rem; + min-width: 189px; + } + + .options-dropdown { + left: 80px; + top: 50px; + z-index: 12; + max-height: 30rem; + min-width: 116px; + } + + .switch-content { + height: 40px; display: flex; - flex-direction: column; - overflow-y: auto; - margin: 0; + width: 100%; + border: 1px solid $color-gray-10; + border-radius: 6px; + align-items: center; + } - &.dashboard-common { - overflow: unset; + .switch-options { + display: flex; + max-width: 22px; + min-width: 22px; + border-left: 1px solid $color-gray-10; + justify-content: center; + align-items: center; + cursor: pointer; + + svg { + width: 15px; + height: 13px; + fill: $color-gray-60; } + } - li { - align-items: center; - cursor: pointer; + .current-team { + display: flex; + flex-grow: 1; + font-size: $fs14; + padding: 0px 10px; + } + + .team-name { + flex-grow: 1; + display: flex; + height: 48px; + align-items: center; + + .team-icon { display: flex; - flex-shrink: 0; - padding: $size-2; + align-items: center; + padding-right: 10px; svg { - border-radius: 3px; - fill: $color-black; - margin-right: 8px; - height: $size-3; - width: $size-3; + width: 23px; + height: 23px; + fill: $color-gray-60; } - span.element-title { - color: $color-gray-60; - font-size: $fs14; - overflow-x: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - &.recent-projects { - svg { - fill: $color-white; - } + img { + border-radius: 50%; + flex-shrink: 0; + height: 23px; + width: 23px; } + } - & .edit-wrapper { - display: flex; - } + .team-text { + color: $color-gray-60; + @include text-ellipsis; + width: 100px; + } + } - input.element-title { - border: 0; - height: 30px; - padding: 5px; + .switch-icon { + display: flex; + align-items: center; + cursor: pointer; + + svg { + width: 10px; + height: 10px; + fill: $color-gray-60; + } + } + } + + .sidebar-empty-placeholder { + padding: 10px 12px; + color: $color-gray-30; + display: flex; + align-items: flex-start; + + .icon { + padding: 0px 10px; + svg { + fill: $color-gray-30; + width: 12px; + height: 12px; + } + } + .text { + font-size: $fs13; + } + } + + .sidebar-nav { + display: flex; + flex-direction: column; + overflow-y: auto; + margin: 0; + + // TODO: should be deprecated / unclear name + &.dashboard-common { + overflow: unset; + } + + &.no-overflow { + overflow: unset; + } + + li { + align-items: center; + cursor: pointer; + display: flex; + flex-shrink: 0; + padding: $size-2; + + svg { + border-radius: 3px; + fill: $color-black; + margin-right: 8px; + height: $size-3; + width: $size-3; + } + + span.element-title { + color: $color-gray-60; + font-size: $fs14; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + user-select: none; + } + + &::before { + background-color: transparent; + border-radius: $br-small; + content: ""; + height: 26px; + margin-right: 6px; + width: 4px; + } + + &.recent-projects { + svg { + fill: $color-white; + } + } + + & .edit-wrapper { + border: 1px solid $color-gray-10; + border-radius: $br-small; + display: flex; + width: 100%; + } + + input.element-title { + border: 0; + height: 30px; + padding: 5px; + margin: 0; + width: 100%; + background-color: $color-white; + } + + .close { + background-color: $color-white; + cursor: pointer; + padding: 3px 5px; + + svg { + fill: $color-gray-30; + height: 15px; + transform: rotate(45deg) translateY(7px); + width: 15px; margin: 0; - width: 100%; - background-color: $color-white; } + } - .close { - background-color: $color-white; - cursor: pointer; - padding: 3px 5px; + .element-subtitle { + color: $color-gray-20; + font-style: italic; + } - svg { - fill: $color-gray-30; - height: 15px; - transform: rotate(45deg) translateY(7px); - width: 15px; - margin: 0; - } + &:hover { + &::before { + background-color: $color-gray-10; } + } - .element-subtitle { - color: $color-gray-20; - font-style: italic; + &.current { + font-weight: bold; + + &::before { + background-color: $color-primary; } + } + } + } - &:hover, - &.current { - background-color: $color-primary-lighter; - color: $color-gray-60; + .sidebar-search { + align-items: center; + border: 1px solid $color-gray-10; + border-radius: 4px; + display: flex; + margin: 5px 15px; + + .input-text { + background: $color-white; + border: 0; + color: $color-gray-60; + font-size: $fs14; + padding: 6px; + margin: 0; + max-width: 170px; + width: 100%; + height: 40px; + } + + &:focus, + &:focus-within { + border-color: $color-black; + } + + .clear-search { + align-items: center; + cursor: pointer; + display: flex; + height: 22px; + margin-left: auto; + padding: 0 $small; + width: 32px; + + svg { + fill: $color-gray-30; + height: 15px; + transform: rotate(45deg); + width: 15px; + + &:hover { + fill: $color-danger; } } } @@ -117,6 +312,7 @@ display: flex; margin-top: 1rem; padding: $size-2; + position: relative; span { color: $color-gray-30; @@ -126,50 +322,80 @@ .btn-icon-light { margin-left: auto; } + + &::before { + background-color: $color-gray-10; + content: ""; + height: 1px; + left: 4%; + position: absolute; + right: 4%; + top: -5px; + width: 92%; + } } -.dashboard-search { - align-items: center; - border: 1px solid $color-gray-10; - display: flex; - margin: $size-2; - - .input-text { - background: $color-white; - border: 0; - color: $color-gray-60; - font-size: $fs14; - padding: 4px 8px; - margin: 0; - max-width: 170px; - width: 100%; +.team-form-modal { + h2 { + font-weight: 400; + color: $color-gray-40; + font-size: 28px; + margin-bottom: 30px; } - &:focus, - &:focus-within { - border-color: $color-black; - } - - .clear-search { - align-items: center; - cursor: pointer; + .buttons-row { + margin-top: 20px; display: flex; - height: 22px; - padding: 0 5px; - width: 22px; - - svg { - fill: $color-gray-30; - height: 15px; - transform: rotate(45deg); - width: 15px; - - &:hover { - fill: $color-danger; - } - - } - + justify-content: center; } + input[type=submit] { + width: 120px; + margin-bottom: 0px; + } +} + + +.profile-section { + align-items: center; + cursor: pointer; + display: flex; + padding: 10px 15px; + position: relative; + + span { + @include text-ellipsis; + color: $color-black; + margin: 10px 5px; + font-size: $fs14; + max-width: 135px; + } + + img { + border-radius: 50%; + flex-shrink: 0; + height: 25px; + width: 25px; + } + + .dropdown { + left: 15px; + bottom: 45px; + min-width: 189px; + width: 170px; + + @include animation(0,.2s,fadeInUp); + + li { + height: 40px; + font-size: $fs14; + padding: 5px 10px; + + svg { + fill: $color-gray-20; + height: 12px; + width: 12px; + } + } + } } diff --git a/frontend/resources/styles/main/partials/dashboard-team.scss b/frontend/resources/styles/main/partials/dashboard-team.scss new file mode 100644 index 0000000000..f4ad20e5dd --- /dev/null +++ b/frontend/resources/styles/main/partials/dashboard-team.scss @@ -0,0 +1,241 @@ + +.dashboard-invite-modal { + top: 65px; + right: 13px; + padding: 14px; + box-shadow: 0px 4px 8px rgba($color-black, 0.25); + border-radius: 8px; + width: 414px; + position: fixed; + + form { + width: 100%; + } + + .form-row { + display: flex; + flex-direction: row; + margin: 15px 0px; + } + + .custom-input { + width: 272px; + margin-right: 10px; + } + + .custom-select { + width: 103px + } + + .action-buttons { + display: flex; + justify-content: center; + input[type=submit] { + margin-bottom: 0px; + } + } + + .title { + color: $color-black; + } +} + +.dashboard-team-members { + .table-field { + // border: 1px solid red; + &.name { + width: 43%; + min-width: 300px; + } + + &.email { + width: 43%; + min-width: 300px; + } + + &.permissions { + min-width: 120px; + user-select: none; + cursor: default; + position: relative; + } + } + + .dropdown { + position: absolute; + max-height: 30rem; + overflow-y: auto; + background-color: $color-white; + border-radius: 4px; + box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25); + z-index: 12; + top: 30px; + left: 0px; + width: 125px; + + hr { + margin: 0; + border-color: $color-gray-10; + } + + li { + display: flex; + align-items: center; + color: $color-gray-60; + cursor: pointer; + font-size: $fs12; + height: 31px; + padding: 5px 16px; + + &.title { + font-weight: 600; + cursor: default; + } + + &:hover { + background-color: $color-primary-lighter; + } + } + } + +} + +.dashboard-team-settings { + + .team-settings { + display: flex; + justify-content: center; + margin-top: 16px; + + svg { + width: 20px; + height: 20px; + } + + .horizontal-blocks { + display: flex; + max-width: 1010px; + justify-content: space-between; + width: 100%; + } + + .block { + display: flex; + max-width: 324px; + width: 324px; + height: 100px; + background-color: $color-white; + flex-direction: column; + padding: 12px; + + .label { + font-size: 13px; + color: $color-gray-30; + } + } + + .info-block { + position: relative; + + .name { + margin-top: 10px; + font-size: $fs32; + color: $color-black; + @include text-ellipsis; + margin-right: 90px; + } + + .icon { + position: absolute; + padding: 15px; + width: 100px; + height: 100px; + right: 0px; + top: 0px; + + img { + border-radius: 50%; + width: 70px; + height: 70px; + } + + .update-overlay { + opacity: 0; + cursor: pointer; + position: absolute; + display: flex; + justify-content: center; + align-items: center; + width: 70px; + height: 70px; + border-radius: 50%; + color: $color-white; + background: $color-primary-dark; + z-index: 14; + + svg { fill: $color-white; } + } + + &:hover { + .update-overlay { + opacity: 1; + } + } + } + } + + .owner-block { + img { + width: 30px; + height: 30px; + border-radius: 50%; + } + + svg { + width: 12px; + height: 12px; + fill: $color-primary-dark; + } + + .owner { + margin-top: 5px; + display: flex; + align-items: center; + color: $color-black; + .icon { + margin-right: 12px; + } + } + + .summary { + margin-top: 5px; + color: $color-primary-dark; + .icon { + padding: 0px 10px; + margin-right: 12px; + } + } + } + + .stats-block { + svg { + fill: $color-black; + } + + .projects, + .files { + margin-top: 7px; + display: flex; + align-items: center; + color: $color-black; + + .icon { + display: flex; + align-items: center; + padding: 0px 2px; + margin-right: 14px; + } + } + } + } +} diff --git a/frontend/resources/styles/main/partials/dashboard.scss b/frontend/resources/styles/main/partials/dashboard.scss new file mode 100644 index 0000000000..58cf587998 --- /dev/null +++ b/frontend/resources/styles/main/partials/dashboard.scss @@ -0,0 +1,164 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL + +.dashboard-container { + background-color: $color-dashboard; + border-top-right-radius: $br-huge; + border-top-left-radius: $br-huge; + flex: 1 0 0; + margin-right: $small; + overflow-y: auto; + + &.search { + margin-top: 10px; + } +} + +.dashboard-project-row { + margin-bottom: $medium; + + .project { + align-items: center; + background: $color-white; + border-radius: $br-small; + display: flex; + flex-direction: row; + margin-left: $medium; + margin-top: $medium; + padding: $x-small $x-small $x-small $small; + width: fit-content; + height: 40px; + + .btn-secondary { + margin-left: $big; + height: 32px; + } + + h2 { + cursor: pointer; + font-size: 15px; + line-height: 1rem; + font-weight: unset; + color: $color-black; + margin-right: $medium; + } + + .info { + font-size: 15px; + line-height: 1rem; + font-weight: unset; + color: $color-gray-30; + } + + .pin-icon { + cursor: pointer; + display: flex; + align-items: center; + margin-right: 10px; + svg { + width: 15px; + height: 15px; + fill: $color-gray-20; + } + + &.active { + svg { fill: $color-gray-50; } + } + } + } +} + +.recent-files-row-title-info { + font-size: $fs15; +} + +.dashboard-table { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 20px; + font-size: $fs16; + + .table-header { + max-width: 1040px; + display: flex; + background-color: $color-white; + color: $color-gray-30; + width: 100%; + height: 40px; + align-items: center; + padding: 0px 16px; + } + + .table-rows { + display: flex; + flex-direction: column; + max-width: 1040px; + width: 100%; + margin-top: 20px; + color: $color-black; + } + + .table-row { + display: flex; + width: 100%; + height: 45px; + align-items: center; + padding: 0px 16px; + } + + .table-field { + display: flex; + align-items: center; + + .icon { + padding-left: 10px; + cursor: pointer; + } + } + + svg { + width: 10px; + height: 10px; + fill: $color-black; + } +} + + +.edit-wrapper { + border: 1px solid $color-gray-10; + border-radius: $br-small; + display: flex; + position: relative; + + input.element-title { + border: 0; + height: 30px; + padding: 5px; + margin: 0; + width: 100%; + background-color: $color-white; + } + + .close { + cursor: pointer; + position: absolute; + + top: 1px; + right: 2px; + + svg { + fill: $color-gray-30; + height: 15px; + transform: rotate(45deg) translateY(7px); + width: 15px; + margin: 0; + } + } +} diff --git a/frontend/resources/styles/main/partials/dropdown.scss b/frontend/resources/styles/main/partials/dropdown.scss new file mode 100644 index 0000000000..496cc78fdb --- /dev/null +++ b/frontend/resources/styles/main/partials/dropdown.scss @@ -0,0 +1,32 @@ +.dropdown { + position: absolute; + max-height: 30rem; + background-color: $color-white; + border-radius: 2px; + box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25); + z-index: 12; + + hr { + margin: 0 !important; + border-color: $color-gray-10; + } + + li { + display: flex; + align-items: center; + color: $color-gray-60; + cursor: pointer; + font-size: $fs14; + height: 40px; + padding: 5px 16px; + + &.title { + font-weight: 600; + cursor: default; + } + + &:hover { + background-color: $color-primary-lighter; + } + } +} diff --git a/frontend/resources/styles/main/partials/forms.scss b/frontend/resources/styles/main/partials/forms.scss index dedf5ef6b0..b65fd5583a 100644 --- a/frontend/resources/styles/main/partials/forms.scss +++ b/frontend/resources/styles/main/partials/forms.scss @@ -14,9 +14,11 @@ textarea { } } +.form-container, .generic-form { display: flex; justify-content: center; + flex-direction: column; .forms-container { display: flex; @@ -31,6 +33,18 @@ textarea { // flex-basis: 368px; } + .fields-row { + margin-bottom: 20px; + flex-direction: column; + + .options { + display: flex; + justify-content: flex-end; + font-size: $fs14; + margin-top: 13px; + } + } + .field { margin-bottom: 20px; } @@ -48,7 +62,7 @@ textarea { } h2 { - font-size: $fs14; + font-size: $fs24; color: $color-gray-60; // height: 40px; display: flex; @@ -61,7 +75,7 @@ textarea { .links { display: flex; - font-size: $fs11; + font-size: $fs14; justify-content: space-between; margin-bottom: $medium; @@ -72,13 +86,13 @@ textarea { } .link-entry { - font-size: $fs12; + font-size: $fs14; color: $color-gray-40; margin-bottom: 10px; } .link-entry a { - font-size: $fs12; + font-size: $fs14; color: $color-primary-dark; } } @@ -93,7 +107,7 @@ textarea { border-radius: 2px; border: 1px solid $color-gray-20; color: $color-gray-60; - font-size: $fs12; + font-size: $fs14; height: 40px; margin: 0; padding: 15px 15px 0 15px; @@ -109,7 +123,7 @@ textarea { } label { - font-size: $fs10; + font-size: $fs12; color: $color-gray-30; position: absolute; left: 15px; @@ -181,13 +195,13 @@ textarea { .hint { padding: 4px; - font-size: $fs10; + font-size: $fs12; } .error { color: $color-danger; padding: 4px; - font-size: $fs10; + font-size: $fs12; } } @@ -198,13 +212,13 @@ textarea { justify-content: center; label { - font-size: $fs10; + font-size: $fs12; color: $color-gray-30; } select { cursor: pointer; - font-size: $fs12; + font-size: $fs14; border: 0px; opacity: 0; z-index: 10; @@ -224,6 +238,7 @@ textarea { justify-content: center; padding-top: 6px; padding-bottom: 6px; + padding-left: 15px; } @@ -235,8 +250,6 @@ textarea { border-radius: 2px; border: 1px solid $color-gray-20; height: 40px; - padding-left: 15px; - padding-right: 15px; &.invalid { border-color: $color-danger; @@ -261,7 +274,7 @@ textarea { .value { color: $color-gray-60; - font-size: $fs12; + font-size: $fs14; width: 100%; border: 0px; padding: 0px; @@ -273,7 +286,8 @@ textarea { justify-content: center; align-items: center; padding-left: 10px; - + padding-right: 10px; + pointer-events: none; svg { fill: $color-gray-30; @@ -283,3 +297,4 @@ textarea { } } } + diff --git a/frontend/resources/styles/main/partials/handoff.scss b/frontend/resources/styles/main/partials/handoff.scss new file mode 100644 index 0000000000..adac8714ee --- /dev/null +++ b/frontend/resources/styles/main/partials/handoff.scss @@ -0,0 +1,256 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// This Source Code Form is "Incompatible With Secondary Licenses", as +// defined by the Mozilla Public License, v. 2.0. +// +// Copyright (c) 2020 UXBOX Labs SL + +.handoff-svg-wrapper { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.attributes-block { + user-select: text; + + border-bottom: 1px solid $color-gray-60; + padding-bottom: 0.5rem; + font-size: $fs12; + + .attributes-text-block { + border-bottom: 1px solid $color-gray-60; + } + + & :last-child{ + border-bottom: none; + } + + .attributes-copy-button { + visibility: hidden; + opacity: 0; + transition: opacity 0.3s; + position: absolute; + right: 0; + top: 0; + background: none; + border: none; + padding: 0; + cursor: pointer; + + svg { + width: 16px; + height: 16px; + fill: $color-gray-20; + transition: fill 0.3s; + + &:hover { + fill: $color-primary; + } + } + } + + .attributes-label { + color: $color-gray-20; + } + + .attributes-value { + color: $color-white; + } + + .attributes-block-title { + position: relative; + color: $color-gray-10; + padding: 0.5rem; + font-size: $fs14; + + .attributes-copy-button { + padding: 0.5rem; + margin-top: 0.25rem; + } + } + + .attributes-unit-row { + position: relative; + display: flex; + flex-direction: row; + padding: 1rem 0.5rem; + + .attributes-label, + .attributes-value { + width: 50%; + } + .attributes-copy-button { + padding: 1rem 0.5rem; + margin-top: 0.25rem; + } + } + + .attributes-color-row { + display: flex; + padding: 1rem 0; + position: relative; + align-items: center; + + .color-text { + width: 3rem; + text-transform: uppercase; + } + + .attributes-color-display { + display: flex; + } + + .color-bullet { + width: 24px; + height: 24px; + } + .attributes-copy-button { + padding: 1rem 0.5rem; + margin-top: 0.25rem; + } + + & > * { + margin: 0 0.5rem; + } + + & :last-child { + margin-right: 0; + } + + select { + font-size: $fs12; + margin: 0; + background: none; + color: $color-gray-20; + border: none; + border-bottom: 1px solid $color-gray-30; + padding: 0 1rem 0.25rem 0.25rem; + margin-top: 2px; + background-image: url("/images/icons/arrow-down-white.svg"); + background-repeat: no-repeat; + background-position: 95% 48%; + background-size: 10px; + cursor: pointer; + + option { + padding: 1rem; + background-color: $color-gray-50; + border: none; + } + } + } + + .attributes-content-row { + position: relative; + margin: 0.5rem; + width: calc(100% - 1rem); + + .attributes-content { + overflow-y: auto; + max-height: 5rem; + background: $color-gray-60; + border-radius: 4px; + padding: 1rem 0.5rem; + color: $color-gray-10; + white-space: pre-wrap; + } + + .attributes-copy-button { + padding: 0.5rem; + margin-top: 0.25rem; + } + } + + .attributes-image-row { + position: relative; + display: flex; + padding: 0.25rem; + align-items: center; + justify-content: center; + margin: 0.5rem; + background: $color-gray-60; + border-radius: 4px; + + width: calc(100% - 1rem); + min-height: 5rem; + img { + max-height: 8rem; + width: auto; + } + } + + .attributes-shadow-row { + position: relative; + display: flex; + margin: 0.5rem; + padding-right: 2rem; + justify-content: space-between; + + & > :first-child { + color: $color-gray-10; + } + + .attributes-shadow { + display: flex; + + .attributes-label { + margin-right: 2px; + } + } + } + + .attributes-stroke-row { + position: relative; + display: flex; + margin: 0.5rem; + padding-right: 2rem; + justify-content: space-between; + } + + .attributes-typography-row { + position: relative; + margin: 0.5rem; + padding-right: 2rem; + + .typography-sample { + font-size: $fs16; + } + } + + .download-button { + display: block; + text-align: center; + border: 1px solid $color-gray-60; + background-color: $color-gray-60; + padding: 0.5rem 1rem; + color: $color-gray-10; + width: calc(100% - 1rem); + border-radius: 4px; + margin: 0.5rem; + cursor: pointer; + + &:hover { + background-color: $color-primary; + color: $color-black; + } + } + + .attributes-block-title, + .attributes-unit-row, + .attributes-color-row, + .attributes-shadow-row, + .attributes-stroke-row, + .attributes-typography-row, + .attributes-content-row { + &:hover .attributes-copy-button { + visibility: visible; + opacity: 1; + } + } + +} diff --git a/frontend/resources/styles/main/partials/left-toolbar.scss b/frontend/resources/styles/main/partials/left-toolbar.scss index b3666a2239..1fab2b9b31 100644 --- a/frontend/resources/styles/main/partials/left-toolbar.scss +++ b/frontend/resources/styles/main/partials/left-toolbar.scss @@ -5,7 +5,7 @@ // Copyright (c) 2015-2020 Andrey Antukh // Copyright (c) 2015-2020 Juan de la Cruz -$width-left-toolbar: 40px; +$width-left-toolbar: 48px; .left-toolbar { background-color: $color-gray-50; @@ -23,7 +23,7 @@ $width-left-toolbar: 40px; display: flex; flex-direction: column; overflow: visible; - padding-top: 40px; + padding-top: 48px; height: 100%; } @@ -40,11 +40,11 @@ $width-left-toolbar: 40px; cursor: pointer; display: flex; flex-shrink: 0; - height: 40px; + height: 48px; justify-content: center; margin: $x-small 0; position: relative; - width: 40px; + width: 48px; svg { fill: $color-gray-20; diff --git a/frontend/resources/styles/main/partials/login.scss b/frontend/resources/styles/main/partials/login.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frontend/resources/styles/main/partials/main-bar.scss b/frontend/resources/styles/main/partials/main-bar.scss deleted file mode 100644 index 3ae31ac5b7..0000000000 --- a/frontend/resources/styles/main/partials/main-bar.scss +++ /dev/null @@ -1,167 +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) 2015-2016 Andrey Antukh -// Copyright (c) 2015-2016 Juan de la Cruz - -.main-bar { - align-items: center; - background-color: $color-white; - border-bottom: 1px solid $color-gray-10; - display: flex; - height: 40px; - padding: $x-small $small; - padding-left: $x-big; - position: relative; - z-index: 10; - - .element-name { - margin-right: $small; - } - - .btn-secondary { - flex-shrink: 0; - margin-left: auto; - } - - svg { - fill: $color-black; - height: 14px; - margin-right: $x-small; - width: 14px; - } - -} - -.main-logo { - border-right: 1px solid $color-gray-10; - border-bottom: 1px solid $color-gray-10; - text-align: center; - padding-top: $x-small; - - svg { - fill: $color-black; - height: 30px; - width: 30px; - } -} - -.main-nav { - align-items: center; - display: flex; - font-size: $fs15; - height: 35px; - margin: 0 0 0 120px; - - li { - - a { - border-bottom: 2px solid transparent; - color: $color-gray-10; - margin: $x-small $big; - - &:hover { - border-color: $color-primary; - } - - } - - &.current { - - a { - border-color: $color-primary; - } - - } - - } - -} - -.dashboard-title { - color: $color-black; - display: flex; - font-size: $fs15; -} - -.main-bar-icon { - cursor: pointer; - margin-left: $small; - - svg { - fill: $color-gray-40; - width: 10px; - - &:hover { - fill: $color-primary-dark; - } - } -} - -.user-zone { - align-items: center; - border-right: 1px solid $color-gray-10; - border-bottom: 1px solid $color-gray-10; - cursor: pointer; - display: flex; - padding: 0 $x-small 0 $small; - position: relative; - width: 180px; - - span { - @include text-ellipsis; - color: $color-black; - margin: $small; - font-size: $fs12; - } - - img { - border-radius: 50%; - flex-shrink: 0; - height: 25px; - width: 25px; - } - - ul.profile-menu { - position: absolute; - top: 0; - left: 0; - z-index: 12; - width: 180px; - background-color: $color-gray-60; - border-radius: $br-small; - padding: 0 $small; - - @include animation(0,.2s,fadeInDown); - - li { - font-size: $fs13; - padding: $small 0; - - svg { - fill: $color-gray-20; - height: 12px; - width: 12px; - } - - span { - color: $color-white; - } - - &:hover { - - span { - color: $color-primary; - } - - svg { - fill: $color-primary; - } - - } - - } - } - -} diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index ddde643011..92100c35ea 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -40,7 +40,7 @@ display: flex; flex-grow: 1; flex-direction: column; - padding: 100px; + padding: 60px 100px; } .button-row { @@ -56,84 +56,151 @@ } } -.change-email-modal { - h2 { - font-size: $fs14; - margin-bottom: 20px; +// NEW GEN MODALS + +.modal-container { + border-radius: 8px; + display: flex; + flex-direction: column; + width: 448px; + background-color: $color-dashboard; + + .modal-header { + align-items: center; + background-color: $color-white; + border-radius: 8px 8px 0px 0px; + color: $color-black; + display: flex; + height: 63px; + justify-content: space-between; } - .confirmation { - .btn-primary { - margin-bottom: 30px; - } + .modal-header-title { + display: flex; + align-items: center; + font-size: $fs24; + padding-left: 16px; - .featured-note .icon svg { - fill: $color-success; + h2 { + margin: 0; } + } + .modal-close-button { + align-items: center; + cursor: pointer; + display: flex; + height: 30px; + justify-content: center; + margin-right: 16px; + width: 30px; + + svg { + transform: rotate(45deg); + width: 16px; + height: 16px; + } + } + + .modal-content { + display: flex; + flex-direction: column; + padding: 32px; + border-top: 1px solid $color-gray-10; + h3 { + color: $color-gray-40; + font-size: $fs16; + font-weight: 400; + } + } + + .modal-footer { + display: flex; + height: 63px; + padding: 0px 16px; + border-top: 1px solid $color-gray-10; + + .action-buttons { + display: flex; + width: 100%; + height: 100%; + justify-content: flex-end; + // border: 1px solid red; + align-items: center; + + input { + margin-bottom: 0px; + } + } + } +} + +.change-email-modal { + h2 { + font-size: $fs18 + } + + h3 { + margin-bottom: 15px; + } + + .modal-footer .action-buttons { + justify-content: space-around; } } .confirm-dialog { background-color: $color-white; - width: 23rem; - .modal-content { - padding: 20px 40px; + p { + font-size: $fs14; + color: $color-gray-40; } - .dialog-title { - font-size: 24px; - color: $color-black; - font-weight: normal; - text-align: center; - } - - .dialog-buttons { + .action-buttons { display: flex; flex-direction: row; - margin-top: 3rem; width: 100%; + font-size: $fs14; } - .dialog-cancel-button { + .cancel-button { border: 1px solid $color-gray-30; background: $color-canvas; - border-radius: 2px; - padding: 0.5rem; - margin-right: 1rem; - justify-content: space-evenly; - margin-bottom: 0; - width: 100%; + border-radius: 3px; + padding: 0.5rem 1rem; cursor: pointer; + margin-right: 8px; &:hover { background: $color-gray-20; } } - .dialog-accept-button { - width: 100%; - padding: 0.5rem; - border: 1px solid $color-danger; - background: $color-danger; - color: $color-white; - margin-bottom: 0; + .accept-button { + border-radius: 3px; cursor: pointer; + padding: 0.5rem 1rem; - &:hover { - background: $color-danger-dark; + &.danger { + background: $color-danger; + border: 1px solid $color-danger; + color: $color-white; + &:hover { + background: $color-danger-dark; + } } - &.not-danger { + &.primary { background: $color-primary; - color: $color-gray-60; - } - - &.not-danger:hover { - background: $color-primary-dark; + border: 1px solid $color-primary; + color: $color-black; + &:hover { + background: $color-primary-dark; + } } } + } .libraries-dialog { diff --git a/frontend/resources/styles/main/partials/sidebar-align-options.scss b/frontend/resources/styles/main/partials/sidebar-align-options.scss index 9688ba574a..7b97504262 100644 --- a/frontend/resources/styles/main/partials/sidebar-align-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-align-options.scss @@ -8,7 +8,7 @@ .align-options { display: flex; border-bottom: solid 1px $color-gray-60; - height: 37px; + height: 40px; padding: 0 $x-small; .align-group { @@ -22,7 +22,9 @@ } .align-button { + align-items: center; cursor: pointer; + display: flex; padding: $small $x-small; svg { height: 16px; diff --git a/frontend/resources/styles/main/partials/sidebar-assets.scss b/frontend/resources/styles/main/partials/sidebar-assets.scss index d4d6ff4ed9..8917dfaed0 100644 --- a/frontend/resources/styles/main/partials/sidebar-assets.scss +++ b/frontend/resources/styles/main/partials/sidebar-assets.scss @@ -118,7 +118,7 @@ .asset-group { background-color: $color-gray-60; padding: $small; - font-size: $fs11; + font-size: $fs12; color: $color-gray-20; /* TODO: see if this is useful, or is better to leave only one scroll bar in the whole sidebar @@ -236,18 +236,11 @@ .group-list-item { display: flex; align-items: center; - margin-top: $x-small; - font-size: $fs11; + margin-top: $small; + font-size: $fs12; color: $color-white; cursor: pointer; - & .color-block { - width: 20px; - height: 20px; - border-radius: 10px; - margin-right: $x-small; - } - & span { margin-left: $x-small; color: $color-gray-30; diff --git a/frontend/resources/styles/main/partials/sidebar-document-history.scss b/frontend/resources/styles/main/partials/sidebar-document-history.scss index 8172906603..ec00f2f739 100644 --- a/frontend/resources/styles/main/partials/sidebar-document-history.scss +++ b/frontend/resources/styles/main/partials/sidebar-document-history.scss @@ -8,37 +8,122 @@ // Copyright (c) 2020 UXBOX Labs SL .history-toolbox { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .history-toolbox-title { - color: $color-gray-10; - font-size: $fs14; - padding: 0.5rem; + color: $color-gray-10; + font-size: $fs14; + padding: 0.5rem; } -.undo-history { +.history-entry-empty { + display: flex; + flex-direction: column; + align-items: center; + padding: 1rem; + + .history-entry-empty-icon { + margin-bottom: 1rem; + svg { + width: 32px; + height: 32px; + fill: $color-gray-40; + } + } + + .history-entry-empty-msg { + color: $color-gray-30; font-size: $fs12; - color: $color-gray-10; - - .undo-entry { - max-height: 10rem; - overflow: auto; - margin: 0.5rem; - - &.transaction { - border: 2px solid $color-primary; - } - } - - .undo-entry-change { - background-color: #1F1F1F; - padding: 0.5rem; - } - - .separator { - margin: 0.5rem; - border-color: $color-primary; - } + } +} + +.history-entries { + font-size: $fs12; + color: $color-gray-20; + fill: $color-gray-20; +} + +.history-entry { + border: 1px solid $color-gray-60; + border-radius: 4px; + margin: 0.5rem; + display: flex; + flex-direction: column; + padding: 0.5rem; + cursor: pointer; + + transition: border 0.2s; + + &.disabled { + opacity: 0.5; + } + + &.current { + background-color: $color-gray-60; + } + + &.hover { + border-color: $color-primary; + } +} + +.history-entry-summary { + display: flex; + flex-direction: row; + align-items: center; + + * { + display: flex; + } +} + +.history-entry-summary-icon { + svg { + width: 16px; + height: 16px; + } +} + +.history-entry-summary-text { + flex: 1; + margin: 0 0.5rem; + margin-top: 2px; +} + +.history-entry-summary-button { + opacity: 0; + transition: transform 0.2s; + + svg { + width: 12px; + height: 12px; + } + + .show-detail &, + .hover & { + opacity: 1; + } + + .show-detail & { + transform: rotate(90deg); + } +} + +.history-entry-detail { + display: none; + + .show-detail & { + display: block; + padding: 1rem 0 0.5rem 0; + } + + .history-entry-details-list { + margin: 0; + + li { + margin-bottom: 0.5rem; + } + } } diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 4e6201959a..11275e5c28 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -61,16 +61,22 @@ .element-set { border-bottom: 1px solid $color-gray-60; color: $color-gray-20; - margin: 0 $x-small; + padding: $small $x-small; .element-set-title { - color: $color-gray-10; + color: $color-gray-20; display: flex; font-size: $fs13; - padding: $small $x-small; + padding: $x-small; width: 100%; + align-items: center; } + &:hover { + .element-set-title { + color: $color-gray-10; + } + } } .element-list { @@ -185,7 +191,7 @@ background-color: $color-gray-50; border: 1px solid transparent; border-bottom-color: $color-gray-40; - color: $color-gray-10; + color: $color-white; font-size: $fs12; margin: $x-small; padding: $x-small; @@ -217,7 +223,7 @@ } .element-set-subtitle { - color: $color-gray-10; + color: $color-gray-20; font-size: $fs11; width: 12rem; } @@ -262,8 +268,9 @@ } .custom-select { - cursor: pointer; border: 1px solid $color-gray-40; + border-radius: $br-small; + cursor: pointer; padding: $x-small $big $x-small $x-small; position: relative; @@ -289,16 +296,17 @@ } .custom-select-dropdown { - position: absolute; - left: 0; - z-index: 12; - max-height: 30rem; - min-width: 7rem; - overflow-y: auto; - background-color: $color-white; border-radius: $br-small; box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25); + left: 0; + max-height: 30rem; + min-width: 7rem; + position: absolute; + overflow-y: auto; + top: 30px; + z-index: 12; + .presets { width: 200px; @@ -467,47 +475,6 @@ margin-bottom: 0; } -.color-th { - background-color: $color-gray-30; - border: 1px solid $color-gray-30; - border-radius: $br-small; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - color: $color-gray-10; - flex-shrink: 0; - height: 20px; - margin: 5px 4px 0 0; - width: 20px; - - &.color-name { - border-radius: 10px; - } - - &.palette-th { - align-items: center; - border: 1px solid $color-gray-30; - display: flex; - justify-content: center; - - svg { - fill: $color-gray-30; - height: 16px; - width: 16px; - } - - &:hover { - border-color: $color-primary; - svg { - fill: $color-primary; - } - - } - - } -} - .presets { .custom-select-dropdown { width: 200px; @@ -604,14 +571,13 @@ svg { cursor: pointer; height: 20px; - stroke: $color-gray-40; - stroke-width: 30px; + fill: $color-gray-40; width: 20px; } &:hover { svg { - stroke: $color-gray-10; + fill: $color-gray-10; } } } @@ -768,7 +734,18 @@ z-index: 10; } -.element-set-content .advanced-options { +.advanced-options-wrapper { + position: absolute; + width: 100%; + padding-right: 1.5rem; + padding-left: 0.25rem; +} + +.element-options .advanced-options-wrapper { + padding-right: 1rem; +} + +.advanced-options { background-color: #303236; border-radius: 4px; left: -8px; @@ -858,8 +835,8 @@ align-items: center; cursor: pointer; svg { - width: 16px; - height: 16px; + width: 12px; + height: 12px; fill: $color-gray-20; } @@ -876,3 +853,155 @@ .element-set-options-group:hover .element-set-actions { visibility: visible; } + + +.typography-entry { + margin: 0.5rem 0.3rem; + display: flex; + flex-direction: row; + align-items: center; + + .typography-selection-wrapper { + display: flex; + flex-direction: row; + align-items: center; + flex: 1; + height: 100%; + + &.is-selectable { + cursor: pointer; + } + } + + .typography-sample { + font-size: 17px; + color: $color-white; + margin: 0 0.5rem; + + font-family: sourcesanspro; + font-style: normal; + font-weight: normal; + } + + .typography-name { + flex-grow: 1; + font-size: 11px; + margin-top: 4px; + } + + .element-set-actions-button svg { + width: 10px; + height: 10px; + } +} + +.asset-group { + .typography-entry { + margin: 0.25rem 0; + } + + .element-set-content .font-option, + .element-set-content .size-option { + margin: 0.5rem 0; + } + .element-set-content .variant-option { + margin-left: 0.5rem; + } +} + +.row-flex input.adv-typography-name { + font-size: 14px; + color: $color-gray-10; + width: 100%; + max-width: none; + margin: 0; + background: #303236; + border-top: none; + border-left: none; + border-right: none; +} + + + +.size-option .custom-select-dropdown { + position: fixed; + max-height: 16rem; + min-width: 6rem; + margin-top: 25px; + left: initial; +} + +.typography-read-only-data { + font-size: 12px; + color: $color-white; + + .typography-name { + font-size: 14px; + } + + .row-flex { + padding: 0.5rem 0; + } + + .label { + color: $color-gray-30; + + &::after { + content: ':'; + margin-right: 0.25rem; + } + } + + .go-to-lib-button { + transition: border 0.3s, color 0.3s; + text-align: center; + background: $color-gray-60; + padding: 0.5rem; + border-radius: 2px; + cursor: pointer; + font-size: 14px; + margin-top: 1rem; + border: 1px solid $color-gray-60; + + &:hover { + border: 1px solid $color-primary; + color: $color-primary; + } + } +} + +.multiple-typography { + margin: 0.5rem; + padding: 0.5rem; + border: 1px dashed $color-gray-30; + border-radius: 4px; + display: flex; + justify-content: space-between; + + .multiple-typography-text, + .multiple-typography-button { + font-size: $fs13; + display: flex; + align-items: center; + } + + .multiple-typography-button { + cursor: pointer; + svg { + transition: fill 0.3s; + width: 16px; + height: 16px; + fill: $color-gray-10; + } + + &:hover svg { + fill: $color-primary; + } + } + + svg { + } + + .multiple-typography-button:hover svg { + } +} diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss index ea8e23fd40..dced031cc7 100644 --- a/frontend/resources/styles/main/partials/sidebar-layers.scss +++ b/frontend/resources/styles/main/partials/sidebar-layers.scss @@ -64,6 +64,7 @@ } &.selected { + background-color: $color-gray-60; svg { fill: $color-primary; @@ -143,6 +144,42 @@ } } +.element-list li.masked { + .element-children { + li:first-child { + position: relative; + + &::before { + content: " "; + border-right: 1px solid $color-gray-40; + border-top: 1px solid $color-gray-40; + position: absolute; + width: 0.5rem; + height: 0.5rem; + transform: rotate(-45deg); + top: -1px; + left: -5px; + } + } + + li:last-child { + border-left: none; + position: relative; + + &::before { + content: " "; + border-left: 1px solid $color-gray-40; + border-bottom: 1px solid $color-gray-40; + height: 1rem; + width: 0.3rem; + position: absolute; + top: 0; + left: 0; + } + } + } +} + .element-icon { svg { fill: $color-gray-30; diff --git a/frontend/resources/styles/main/partials/sidebar-sitemap.scss b/frontend/resources/styles/main/partials/sidebar-sitemap.scss index a94829d0af..b04d09fe79 100644 --- a/frontend/resources/styles/main/partials/sidebar-sitemap.scss +++ b/frontend/resources/styles/main/partials/sidebar-sitemap.scss @@ -163,6 +163,17 @@ } } +.element-set-title-actions { + display: flex; + flex-direction: row; + flex: 1; + justify-content: flex-end; + + .add-page { + margin: 0; + } +} + .collapse-pages { margin-left: $small; diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index 8d3b0cde9a..bc3e60c6d3 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -5,7 +5,7 @@ // Copyright (c) 2015-2020 Andrey Antukh // Copyright (c) 2015-2020 Juan de la Cruz -$width-settings-bar: 15rem; +$width-settings-bar: 16rem; // This width is also used in update-viewport-size at frontend/src/app/main/data/workspace.cljs .settings-bar { @@ -22,7 +22,7 @@ $width-settings-bar: 15rem; &.settings-bar-left { border-left: none; border-right: 1px solid $color-gray-60; - left: 40px; + left: 48px; } .settings-bar-inside { @@ -60,7 +60,7 @@ $width-settings-bar: 15rem; flex-direction: column; - padding-top: 40px; + padding-top: 48px; height: 100%; .tool-window { @@ -115,6 +115,25 @@ $width-settings-bar: 15rem; } } + .tool-window-bar-icon { + height: 15px; + + svg { + width: 15px; + height: 15px; + } + } + + &.big { + height: 3rem; + padding-bottom: 1rem; + } + + .tool-window-bar-title { + font-size: $fs14; + margin-left: 0.5rem; + } + .tool-window-icon { margin-right: $small; display: none; @@ -158,7 +177,7 @@ $width-settings-bar: 15rem; ul { border-left: 9px solid $color-gray-50; - margin: 0; + margin: 0 0 0 0.4rem; li { border-left: 1px solid $color-gray-40; diff --git a/frontend/resources/styles/main/partials/texts.scss b/frontend/resources/styles/main/partials/texts.scss index 67cd1658d1..24ebc8f0f6 100644 --- a/frontend/resources/styles/main/partials/texts.scss +++ b/frontend/resources/styles/main/partials/texts.scss @@ -1,4 +1,5 @@ foreignObject .rich-text { color: $color-black; height: 100%; + white-space: pre-wrap; } diff --git a/frontend/resources/styles/main/partials/user-settings.scss b/frontend/resources/styles/main/partials/user-settings.scss index 7b64072708..a06c5b1506 100644 --- a/frontend/resources/styles/main/partials/user-settings.scss +++ b/frontend/resources/styles/main/partials/user-settings.scss @@ -153,7 +153,7 @@ .change-email { display: flex; flex-direction: row; - font-size: $fs12; + font-size: $fs14; color: $color-primary-dark; justify-content: flex-end; margin-bottom: 20px; diff --git a/frontend/resources/styles/main/partials/viewer-header.scss b/frontend/resources/styles/main/partials/viewer-header.scss index 20fda06d5d..a123a74713 100644 --- a/frontend/resources/styles/main/partials/viewer-header.scss +++ b/frontend/resources/styles/main/partials/viewer-header.scss @@ -4,7 +4,7 @@ border-bottom: 1px solid $color-gray-60; display: flex; height: 40px; - padding: $x-small $medium $x-small 55px; + padding: 0 $medium 0 55px; position: relative; z-index: 12; justify-content: space-between; @@ -109,10 +109,40 @@ } } + .mode-zone { + display: flex; + height: 100%; + + .mode-zone-button { + background: inherit; + border: none; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + + height: 100%; + width: 40px; + + svg { + fill: $color-gray-20; + width: 24px; + height: 24px; + } + + &.active { + background: $color-gray-60; + svg { + fill: $color-primary; + } + } + } + } + .options-zone { align-items: center; display: flex; - width: 365px; + width: 384px; justify-content: flex-end; position: relative; @@ -248,7 +278,7 @@ } .zoom-dropdown { - left : 150px; + left : 116px; top: 45px; } diff --git a/frontend/resources/styles/main/partials/workspace-comments.scss b/frontend/resources/styles/main/partials/workspace-comments.scss new file mode 100644 index 0000000000..6c51747747 --- /dev/null +++ b/frontend/resources/styles/main/partials/workspace-comments.scss @@ -0,0 +1,340 @@ +.workspace-comments { + width: 100%; + height: 100%; + grid-column: 1/span 2; + grid-row: 1/span 2; + z-index: 1000; + pointer-events: none; + overflow: hidden; + + .threads { + position: relative; + } + + .thread-bubble { + position: absolute; + display: flex; + transform: translate(-15px, -15px); + + cursor: pointer; + pointer-events: auto; + background-color: $color-gray-10; + color: $color-gray-60; + border: 1px solid #B1B2B5; + box-sizing: border-box; + box-shadow: 0px 4px 4px rgba($color-black, 0.25); + + font-size: $fs13; + width: 30px; + height: 30px; + border-radius: 50%; + + display: flex; + align-items: center; + justify-content: center; + + &.resolved { + color: $color-gray-10; + background-color: $color-gray-50; + } + + &.unread { + background-color: $color-primary; + } + } + + .thread-content { + position: absolute; + pointer-events: auto; + margin-left: 10px; + background: $color-white; + border: 1px solid $color-gray-20; + box-sizing: border-box; + box-shadow: 0px 2px 8px rgba($color-black, 0.25); + border-radius: 2px; + min-width: 250px; + max-width: 250px; + + .comments { + max-height: 320px; + overflow-y: auto; + } + + hr { + border: 0; + height: 1px; + background-color: #e3e3e3; + margin: 0px 10px; + } + } + + .reply-form { + display: flex; + padding: 10px; + flex-direction: column; + + &.edit-form { + padding-bottom: 0px; + } + + textarea { + font-family: "worksans", sans-serif; + font-size: $fs13; + min-height: 32px; + outline: none; + overflow: hidden; + padding: $small; + resize: none; + width: 100%; + } + + .buttons { + margin-top: 10px; + display: flex; + justify-content: flex-end; + + input { + margin: 0px; + font-size: $fs12; + + &:not(:last-child) { + margin-right: 6px; + } + } + } + } + + + + .comment-container { + position: relative; + } + + .comment { + display: flex; + flex-direction: column; + padding: 10px; + + .author { + display: flex; + align-items: center; + height: 26px; + max-height: 26px; + position: relative; + + .name { + display: flex; + flex-direction: column; + + .fullname { + font-weight: 700; + color: $color-gray-60; + font-size: $fs13; + + @include text-ellipsis; + width: 110px; + + } + .timeago { + margin-top: -2px; + font-size: $fs11; + color: $color-gray-30; + } + } + + .avatar { + display: flex; + align-items: center; + padding-right: 6px; + + img { + border-radius: 50%; + flex-shrink: 0; + height: 24px; + width: 24px; + } + } + + .options-resolve { + position: absolute; + right: 20px; + top: 0px; + width: 16px; + height: 16px; + + cursor: pointer; + + svg { + width: 16px; + height: 16px; + fill: $color-gray-30; + } + } + + .options { + position: absolute; + right: 0px; + top: 0px; + height: 16px; + display: flex; + align-items: center; + cursor: pointer; + + .options-icon { + svg { + width: 10px; + height: 10px; + fill: $color-black; + } + } + + } + } + + .content { + margin: $medium 0; + // margin-left: 26px; + font-size: $fs13; + color: $color-black; + .text { + margin-left: 26px; + white-space: pre-wrap; + display: inline-block; + } + } + } + + + .comment-options-dropdown { + top: 7px; + right: 7px; + width: 150px; + + border: 1px solid #B1B2B5; + } + +} + +.workspace-comments-sidebar { + pointer-events: auto; + + .sidebar-title { + display: flex; + background-color: $color-black; + height: 34px; + align-items: center; + padding: 0px 9px; + color: $color-gray-10; + font-size: $fs12; + justify-content: space-between; + + .options { + display: flex; + margin-right: 3px; + cursor: pointer; + + .label { + padding-right: 8px; + } + + .icon { + display: flex; + align-items: center; + } + + svg { + fill: $color-gray-10; + width: 10px; + height: 10px; + } + } + } + + .sidebar-options-dropdown { + top: 80px; + right: 7px; + } + + .threads { + + hr { + border: 0; + height: 1px; + background-color: #1f1f2f; + margin: 0px 0px; + } + } + + .page-section { + display: flex; + flex-direction: column; + font-size: $fs12; + + .section-title { + margin: 0px 10px; + margin-top: 15px; + + .icon { + margin-right: 4px; + } + + .label { + } + + svg { + fill: $color-gray-10; + height: 10px; + width: 10px; + } + } + } + + .thread-bubble { + position: unset; + transform: unset; + width: 20px; + height: 20px; + margin-right: 6px; + box-shadow: unset; + } + + .comment { + .author { + margin-bottom: 10px; + .name { + display: flex; + flex-direction: row; + align-items: center; + + .fullname { + width: unset; + max-width: 100px; + color: $color-gray-20; + padding-right: 3px; + } + .timeago { + margin-top: unset; + color: $color-gray-20; + } + } + } + + .content { + margin-top: 0px; + color: $color-white; + + &.replies { + margin-left: 26px; + display: flex; + .total-replies { + margin-right: 9px; + color: $color-info; + } + + .new-replies { + color: $color-primary; + } + } + } + } +} diff --git a/frontend/resources/styles/main/partials/workspace-header.scss b/frontend/resources/styles/main/partials/workspace-header.scss index fa3c42de3f..fb340c3f40 100644 --- a/frontend/resources/styles/main/partials/workspace-header.scss +++ b/frontend/resources/styles/main/partials/workspace-header.scss @@ -12,7 +12,7 @@ background-color: $color-gray-50; border-bottom: 1px solid $color-gray-60; display: flex; - height: 40px; + height: 48px; padding: $x-small $medium $x-small 55px; position: relative; z-index: 12; @@ -28,7 +28,7 @@ left: 0; position: absolute; top: 0; - width: 40px; + width: 48px; a { height: 30px; @@ -88,7 +88,7 @@ .zoom-dropdown { top: 45px; - left: -40px; + left: 98px; } } @@ -138,7 +138,7 @@ position: absolute; top: 40px; left: 40px; - width: 230px; + width: 270px; z-index: 12; @include animation(0,.2s,fadeInDown); @@ -148,7 +148,7 @@ li { cursor: pointer; - font-size: $fs12; + font-size: $fs14; padding: $small $x-small; display: flex; justify-content: space-between; diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index 74bfa41d4c..add2038780 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -16,7 +16,7 @@ left: 740px; position: absolute; top: 40px; - width: 216px; + width: 240px; z-index: 12; padding: $x-small 0; diff --git a/frontend/resources/styles/main/partials/zoom-widget.scss b/frontend/resources/styles/main/partials/zoom-widget.scss index 8bd3ebdb2e..4e68d88072 100644 --- a/frontend/resources/styles/main/partials/zoom-widget.scss +++ b/frontend/resources/styles/main/partials/zoom-widget.scss @@ -17,7 +17,7 @@ .zoom-dropdown { position: absolute; z-index: 12; - width: 160px; + width: 210px; background-color: $color-white; border-radius: $br-small; @@ -26,13 +26,13 @@ li { color: $color-gray-60; cursor: pointer; - font-size: $fs12; + font-size: $fs14; display: flex; padding: $small; span { - color: $color-gray-40; - font-size: $fs12; + color: $color-gray-20; + font-size: $fs14; margin-left: auto; } diff --git a/frontend/resources/templates/index.mustache b/frontend/resources/templates/index.mustache index ec4ef388ff..def3b53d10 100644 --- a/frontend/resources/templates/index.mustache +++ b/frontend/resources/templates/index.mustache @@ -3,7 +3,7 @@ - UXBOX - The Open-Source prototyping tool + PENPOT - The Open-Source prototyping tool diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index a2e7c0d1be..408003d67c 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -1,7 +1,7 @@ {:deps {:aliases [:dev]} :http {:port 3448} :nrepl {:port 3447} - :jvm-opts ["-Xmx512m" "-Xms512m"] + :jvm-opts ["-Xmx1g" "-Xms1g"] :builds {:main diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index e882e54b84..6281a10f5b 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -8,7 +8,9 @@ ;; Copyright (c) 2020 UXBOX Labs SL (ns app.config - (:require [app.util.object :as obj])) + (:require + [app.util.object :as obj] + [cuerdas.core :as str])) (this-as global (def default-language "en") @@ -24,4 +26,7 @@ (defn resolve-media-path [path] - (str media-uri "/" path)) + (when path + (if (str/starts-with? path "data:") + path + (str media-uri "/" path)))) diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 1a4621504d..70053ffc4b 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -9,31 +9,29 @@ (ns app.main (:require - [hashp.core :include-macros true] - [cljs.spec.alpha :as s] - [beicon.core :as rx] - [rumext.alpha :as mf] [app.common.uuid :as uuid] [app.main.data.auth :refer [logout]] [app.main.data.users :as udu] [app.main.store :as st] [app.main.ui :as ui] + [app.main.ui.confirm] [app.main.ui.modal :refer [modal]] [app.main.worker] [app.util.dom :as dom] [app.util.i18n :as i18n] - [app.util.theme :as theme] - [app.util.router :as rt] [app.util.object :as obj] + [app.util.router :as rt] [app.util.storage :refer [storage]] + [app.util.theme :as theme] [app.util.timers :as ts] + [app.util.logging :as log] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [rumext.alpha :as mf])) - ;; MODALS - [app.main.ui.settings.delete-account] - [app.main.ui.settings.change-email] - [app.main.ui.confirm] - [app.main.ui.workspace.colorpicker] - [app.main.ui.workspace.libraries])) +(log/initialize!) +(log/set-level! :root :warn) +(log/set-level! :app :info) (declare reinit) @@ -43,7 +41,6 @@ profile (:profile storage) authed? (and (not (nil? profile)) (not= (:id profile) uuid/zero))] - (cond (and (or (= path "") (nil? match)) @@ -51,7 +48,7 @@ (st/emit! (rt/nav :auth-login)) (and (nil? match) authed?) - (st/emit! (rt/nav :dashboard-team {:team-id (:default-team-id profile)})) + (st/emit! (rt/nav :dashboard-projects {:team-id (:default-team-id profile)})) (nil? match) (st/emit! (rt/nav :not-found)) @@ -86,4 +83,3 @@ (defn ^:dev/after-load after-load [] (reinit)) - diff --git a/frontend/src/app/main/data/auth.cljs b/frontend/src/app/main/data/auth.cljs index 41bb2cbd37..2209cdd995 100644 --- a/frontend/src/app/main/data/auth.cljs +++ b/frontend/src/app/main/data/auth.cljs @@ -151,19 +151,17 @@ ;; --- Request Account Deletion -(def request-account-deletion - (letfn [(on-error [{:keys [code] :as error}] - (if (= :app.services.mutations.profile/owner-teams-with-people code) - (let [msg (tr "settings.notifications.profile-deletion-not-allowed")] - (rx/of (dm/error msg))) - (rx/empty)))] - (ptk/reify ::request-account-deletion - ptk/WatchEvent - (watch [_ state stream] - (rx/concat - (->> (rp/mutation :delete-profile {}) - (rx/map #(rt/nav :auth-goodbye)) - (rx/catch on-error))))))) +(defn request-account-deletion + [params] + (ptk/reify ::request-account-deletion + ptk/WatchEvent + (watch [_ state stream] + (let [{:keys [on-error on-success] + :or {on-error identity + on-success identity}} (meta params)] + (->> (rp/mutation :delete-profile {}) + (rx/tap on-success) + (rx/catch on-error)))))) ;; --- Recovery Request diff --git a/frontend/src/app/main/data/colors.cljs b/frontend/src/app/main/data/colors.cljs index ac1d1513e3..ae866dd50c 100644 --- a/frontend/src/app/main/data/colors.cljs +++ b/frontend/src/app/main/data/colors.cljs @@ -25,29 +25,6 @@ [app.main.data.modal :as md] [app.common.pages-helpers :as cph])) -(declare create-color-result) - -(defn create-color - [file-id color] - (s/assert (s/nilable uuid?) file-id) - (ptk/reify ::create-color - ptk/WatchEvent - (watch [_ state s] - - (->> (rp/mutation! :create-color {:file-id file-id - :content color - :name color}) - (rx/map (partial create-color-result file-id)))))) - -(defn create-color-result - [file-id color] - (ptk/reify ::create-color-result - ptk/UpdateEvent - (update [_ state] - (-> state - (update-in [:workspace-file :colors] #(conj % color)) - (assoc-in [:workspace-local :color-for-rename] (:id color)))))) - (def clear-color-for-rename (ptk/reify ::clear-color-for-rename ptk/UpdateEvent @@ -73,44 +50,6 @@ (-> state (update-in [:workspace-file :colors] #(d/replace-by-id % color)))))) -(declare update-color-result) - -(defn update-color - [file-id color-id content] - (ptk/reify ::update-color - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/mutation! :update-color {:id color-id - :content content}) - (rx/map (partial update-color-result file-id)))))) - -(defn update-color-result - [file-id color] - (ptk/reify ::update-color-result - ptk/UpdateEvent - (update [_ state] - (-> state - (update-in [:workspace-file :colors] #(d/replace-by-id % color)))))) - -(declare delete-color-result) - -(defn delete-color - [file-id color-id] - (ptk/reify ::delete-color - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/mutation! :delete-color {:id color-id}) - (rx/map #(delete-color-result file-id color-id)))))) - -(defn delete-color-result - [file-id color-id] - (ptk/reify ::delete-color-result - ptk/UpdateEvent - (update [_ state] - (-> state - (update-in [:workspace-file :colors] - (fn [colors] (filter #(not= (:id %) color-id) colors))))))) - (defn change-palette-size [size] (s/assert #{:big :small} size) (ptk/reify ::change-palette-size @@ -165,51 +104,53 @@ (assoc-in [:workspace-local :picked-color-select] value) (assoc-in [:workspace-local :picked-shift?] shift?))))) - (defn change-fill - ([ids color id file-id] - (change-fill ids color 1 id file-id)) - ([ids color opacity id file-id] + ([ids color] (ptk/reify ::change-fill ptk/WatchEvent (watch [_ state s] (let [pid (:current-page-id state) objects (get-in state [:workspace-data :pages-index pid :objects]) - children (mapcat #(cph/get-children % objects) ids) + not-frame (fn [shape-id] (not= (get-in objects [shape-id :type]) :frame)) + children (->> ids (filter not-frame) (mapcat #(cph/get-children % objects))) ids (into ids children) is-text? #(= :text (:type (get objects %))) text-ids (filter is-text? ids) shape-ids (filter (comp not is-text?) ids) - update-fn (fn [shape] (assoc shape - :fill-color color - :fill-opacity opacity - :fill-color-ref-id id - :fill-color-ref-file file-id)) - editor (get-in state [:workspace-local :editor]) - converted-attrs {:fill color} + attrs (cond-> {:fill-color (:color color) + :fill-color-ref-id (:id color) + :fill-color-ref-file (:file-id color) + :fill-color-gradient (:gradient color) + :fill-opacity (:opacity color)}) + + update-fn (fn [shape] (merge shape attrs)) + editors (get-in state [:workspace-local :editors]) reduce-fn (fn [state id] (update-in state [:workspace-data :pages-index pid :objects id] update-fn))] (rx/from (conj - (map #(dwt/update-text-attrs {:id % :editor editor :attrs converted-attrs}) text-ids) + (map #(dwt/update-text-attrs {:id % :editor (get editors %) :attrs attrs}) text-ids) (dwc/update-shapes shape-ids update-fn)))))))) -(defn change-stroke [ids color id file-id] +(defn change-stroke [ids color] (ptk/reify ::change-stroke ptk/WatchEvent (watch [_ state s] - (let [objects (get-in state [:workspace-data :pages-index (:current-page-id state) :objects]) - children (mapcat #(cph/get-children % objects) ids) + (let [pid (:current-page-id state) + objects (get-in state [:workspace-data :pages-index pid :objects]) + not-frame (fn [shape-id] (not= (get-in objects [shape-id :type]) :frame)) + children (->> ids (filter not-frame) (mapcat #(cph/get-children % objects))) ids (into ids children) update-fn (fn [s] (cond-> s true - (assoc :stroke-color color - :stroke-color-ref-id id - :stroke-color-ref-file file-id) + (assoc :stroke-color (:color color) + :stroke-color-gradient (:gradient color) + :stroke-color-ref-id (:id color) + :stroke-color-ref-file (:file-id color)) (= (:stroke-style s) :none) (assoc :stroke-style :solid @@ -218,20 +159,67 @@ (rx/of (dwc/update-shapes ids update-fn)))))) (defn picker-for-selected-shape [] - ;; TODO: replace st/emit! by a subject push and set that in the WatchEvent - (let [handle-change-color (fn [color opacity id file-id shift?] - (let [ids (get-in @st/state [:workspace-local :selected])] - (st/emit! - (if shift? - (change-stroke ids color nil nil) - (change-fill ids color nil nil)) - (md/hide-modal))))] - (ptk/reify ::start-picker + (let [sub (rx/subject)] + (ptk/reify ::picker-for-selected-shape + ptk/WatchEvent + (watch [_ state stream] + (let [ids (get-in state [:workspace-local :selected]) + stop? (->> stream + (rx/filter (ptk/type? ::stop-picker))) + + update-events (fn [[color shift?]] + (rx/of (if shift? + (change-stroke ids color) + (change-fill ids color)) + (stop-picker)))] + (rx/merge + ;; Stream that updates the stroke/width and stops if `esc` pressed + (->> sub + (rx/take-until stop?) + (rx/flat-map update-events)) + + ;; Hide the modal if the stop event is emitted + (->> stop? + (rx/first) + (rx/map #(md/hide)))))) + ptk/UpdateEvent (update [_ state] + (let [handle-change-color (fn [color shift?] (rx/push! sub [color shift?]))] + (-> state + (assoc-in [:workspace-local :picking-color?] true) + (assoc ::md/modal {:id (random-uuid) + :data {:color "#000000" :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 (first (get-in state [:workspace-local :selected]))] (-> state - (assoc-in [:workspace-local :picking-color?] true) - (assoc ::md/modal {:id (random-uuid) - :type :colorpicker - :props {:on-change handle-change-color} - :allow-click-outside true})))))) + (assoc-in [:workspace-local :current-gradient] gradient) + (assoc-in [:workspace-local :current-gradient :shape-id] id)))))) + +(defn stop-gradient [] + (ptk/reify ::stop-gradient + ptk/UpdateEvent + (update [_ state] + (-> state + (update :workspace-local dissoc :current-gradient))))) + +(defn update-gradient [changes] + (ptk/reify ::update-gradient + ptk/UpdateEvent + (update [_ state] + (-> state + (update-in [:workspace-local :current-gradient] merge changes))))) + +(defn select-gradient-stop [spot] + (ptk/reify ::select-gradient-stop + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:workspace-local :editing-stop] spot))))) diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index edf22fa881..52ae6f79ab 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -6,18 +6,21 @@ (ns app.main.data.dashboard (:require - [beicon.core :as rx] - [cljs.spec.alpha :as s] - [cuerdas.core :as str] - [potok.core :as ptk] [app.common.data :as d] [app.common.pages :as cp] [app.common.spec :as us] + [app.common.uuid :as uuid] [app.main.repo :as rp] [app.util.router :as rt] [app.util.time :as dt] [app.util.timers :as ts] - [app.common.uuid :as uuid])) + [app.util.avatars :as avatars] + [app.main.data.media :as di] + [app.main.data.messages :as dm] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [potok.core :as ptk])) ;; --- Specs @@ -28,10 +31,13 @@ (s/def ::project-id ::us/uuid) (s/def ::created-at ::us/inst) (s/def ::modified-at ::us/inst) +(s/def ::is-pinned ::us/boolean) +(s/def ::photo ::us/string) (s/def ::team (s/keys :req-un [::id ::name + ::photo ::created-at ::modified-at])) @@ -41,7 +47,8 @@ ::team-id ::profile-id ::created-at - ::modified-at])) + ::modified-at + ::is-pinned])) (s/def ::file (s/keys :req-un [::id @@ -50,188 +57,140 @@ ::modified-at ::project-id])) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Initialization -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(declare search-files) - -(defn initialize-search - [team-id search-term] - (ptk/reify ::initialize-search - ptk/UpdateEvent - (update [_ state] - (update state :dashboard-local assoc - :search-result nil)) - - ptk/WatchEvent - (watch [_ state stream] - (let [local (:dashboard-local state)] - (when-not (empty? search-term) - (rx/of (search-files team-id search-term))))))) - - -(declare fetch-files) -(declare fetch-projects) -(declare fetch-recent-files) -(declare fetch-shared-files) - -(def initialize-drafts - (ptk/reify ::initialize-drafts - ptk/UpdateEvent - (update [_ state] - (let [profile (:profile state)] - (update state :dashboard-local assoc - :project-for-edit nil - :team-id (:default-team-id profile) - :project-id (:default-project-id profile)))) - - ptk/WatchEvent - (watch [_ state stream] - (let [local (:dashboard-local state)] - (rx/of (fetch-files (:project-id local)) - (fetch-projects (:team-id local) nil)))))) - - -(defn initialize-recent - [team-id] - (us/verify ::us/uuid team-id) - (ptk/reify ::initialize-recent - ptk/UpdateEvent - (update [_ state] - (update state :dashboard-local assoc - :project-for-edit nil - :project-id nil - :team-id team-id)) - - ptk/WatchEvent - (watch [_ state stream] - (let [local (:dashboard-local state)] - (rx/of (fetch-projects (:team-id local) nil) - (fetch-recent-files (:team-id local))))))) - - -(defn initialize-project - [team-id project-id] - (us/verify ::us/uuid team-id) - (us/verify ::us/uuid project-id) - (ptk/reify ::initialize-project - ptk/UpdateEvent - (update [_ state] - (update state :dashboard-local assoc - :project-for-edit nil - :team-id team-id - :project-id project-id)) - - ptk/WatchEvent - (watch [_ state stream] - (let [local (:dashboard-local state)] - (rx/of (fetch-projects (:team-id local) nil) - (fetch-files (:project-id local))))))) - - -(defn initialize-libraries - [team-id] - (us/verify ::us/uuid team-id) - (ptk/reify ::initialize-libraries - ptk/UpdateEvent - (update [_ state] - (update state :dashboard-local assoc - :project-for-edit nil - :project-id nil - :team-id team-id)) - - ptk/WatchEvent - (watch [_ state stream] - (let [local (:dashboard-local state)] - (rx/of (fetch-projects (:team-id local) nil) - (fetch-shared-files (:team-id local))))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Fetching ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; --- Fetch Team + +(defn assoc-team-avatar + [{:keys [photo name] :as team}] + (us/assert ::team team) + (cond-> team + (or (nil? photo) (empty? photo)) + (assoc :photo (avatars/generate {:name name})))) + +(defn fetch-team + [{:keys [id] :as params}] + (letfn [(fetched [team state] + (update state :teams assoc id team))] + (ptk/reify ::fetch-team + ptk/WatchEvent + (watch [_ state stream] + (let [profile (:profile state)] + (->> (rp/query :team params) + (rx/map assoc-team-avatar) + (rx/map #(partial fetched %)))))))) + +(defn fetch-team-members + [{:keys [id] :as params}] + (us/assert ::us/uuid id) + (letfn [(fetched [members state] + (assoc-in state [:team-members id] (d/index-by :id members)))] + (ptk/reify ::fetch-team-members + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :team-members {:team-id id}) + (rx/map #(partial fetched %))))))) + ;; --- Fetch Projects -(declare projects-fetched) - (defn fetch-projects - [team-id project-id] + [{:keys [team-id] :as params}] (us/assert ::us/uuid team-id) - (us/assert (s/nilable ::us/uuid) project-id) - (ptk/reify ::fetch-projects + (letfn [(fetched [projects state] + (assoc-in state [:projects team-id] (d/index-by :id projects)))] + (ptk/reify ::fetch-projects + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :projects {:team-id team-id}) + (rx/map #(partial fetched %))))))) + +(defn fetch-bundle + [{:keys [id] :as params}] + (us/assert ::us/uuid id) + (ptk/reify ::fetch-team ptk/WatchEvent (watch [_ state stream] - (->> (rp/query :projects {:team-id team-id}) - (rx/map projects-fetched) - #_(rx/catch (fn [error] - (rx/of (rt/nav' :auth-login)))))))) + (let [profile (:profile state)] + (->> (rx/merge (ptk/watch (fetch-team params) state stream) + (ptk/watch (fetch-projects {:team-id id}) state stream)) + (rx/catch (fn [{:keys [type code] :as error}] + (cond + (and (= :not-found type) + (not= id (:default-team-id profile))) + (rx/of (rt/nav :dashboard-projects {:team-id (:default-team-id profile)}) + (dm/error "Team does not found")) + + (and (= :validation type) + (= :not-authorized code) + (not= id (:default-team-id profile))) + (rx/of (rt/nav :dashboard-projects {:team-id (:default-team-id profile)}) + (dm/error "Team does not found")) + + :else + (rx/throw error))))))))) -(defn projects-fetched - [projects] - (us/verify (s/every ::project) projects) - (ptk/reify ::projects-fetched - ptk/UpdateEvent - (update [_ state] - (assoc state :projects (d/index-by :id projects))))) ;; --- Search Files -(declare files-searched) +(s/def :internal.event.search-files/team-id ::us/uuid) +(s/def :internal.event.search-files/search-term (s/nilable ::us/string)) + +(s/def :internal.event/search-files + (s/keys :req-un [:internal.event.search-files/search-term + :internal.event.search-files/team-id])) (defn search-files - [team-id search-term] - (us/assert ::us/uuid team-id) - (us/assert ::us/string search-term) - (ptk/reify ::search-files - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/query :search-files {:team-id team-id :search-term search-term}) - (rx/map files-searched))))) + [params] + (us/assert :internal.event/search-files params) + (letfn [(fetched [result state] + (update state :dashboard-local + assoc :search-result result))] + (ptk/reify ::search-files + ptk/UpdateEvent + (update [_ state] + (update state :dashboard-local + assoc :search-result nil)) -(defn files-searched - [files] - (us/verify (s/every ::file) files) - (ptk/reify ::files-searched - ptk/UpdateEvent - (update [_ state] - (update state :dashboard-local assoc - :search-result files)))) + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :search-files params) + (rx/map #(partial fetched %))))))) ;; --- Fetch Files (defn fetch-files - [project-id] + [{:keys [project-id] :as params}] (us/assert ::us/uuid project-id) - (letfn [(on-fetched [files state] - (assoc state :files (d/index-by :id files)))] + (letfn [(fetched [files state] + (update state :files assoc project-id (d/index-by :id files)))] (ptk/reify ::fetch-files ptk/WatchEvent (watch [_ state stream] - (let [params {:project-id project-id}] - (->> (rp/query :files params) - (rx/map #(partial on-fetched %)))))))) + (->> (rp/query :files params) + (rx/map #(partial fetched %))))))) ;; --- Fetch Shared Files (defn fetch-shared-files - [team-id] - (letfn [(on-fetched [files state] - (let [files (d/index-by :id files)] - (assoc state :files files)))] + [{:keys [team-id] :as params}] + (us/assert ::us/uuid team-id) + (letfn [(fetched [files state] + (update state :shared-files assoc team-id (d/index-by :id files)))] (ptk/reify ::fetch-shared-files ptk/WatchEvent (watch [_ state stream] (->> (rp/query :shared-files {:team-id team-id}) - (rx/map #(partial on-fetched %))))))) + (rx/map #(partial fetched %))))))) ;; --- Fetch recent files (declare recent-files-fetched) (defn fetch-recent-files - [team-id] + [{:keys [team-id] :as params}] (us/assert ::us/uuid team-id) (ptk/reify ::fetch-recent-files ptk/WatchEvent @@ -241,21 +200,16 @@ (rx/map recent-files-fetched)))))) (defn recent-files-fetched - [recent-files] + [files] (ptk/reify ::recent-files-fetched ptk/UpdateEvent (update [_ state] - (let [flatten-files #(reduce (fn [acc [project-id files]] - (merge acc (d/index-by :id files))) - {} - %1) - extract-ids #(reduce (fn [acc [project-id files]] - (assoc acc project-id (map :id files))) - {} - %1)] - (assoc state - :files (flatten-files recent-files) - :recent-file-ids (extract-ids recent-files)))))) + (reduce-kv (fn [state project-id files] + (-> state + (update-in [:files project-id] merge (d/index-by :id files)) + (assoc-in [:recent-files project-id] (into #{} (map :id) files)))) + state + (group-by :project-id files))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Modification @@ -263,30 +217,145 @@ ;; --- Create Project -(declare project-created) - -(def create-project - (ptk/reify ::create-project +(defn create-team + [{:keys [name] :as params}] + (us/assert string? name) + (ptk/reify ::create-team ptk/WatchEvent (watch [_ state stream] - (let [name (name (gensym "New Project ")) - team-id (get-in state [:dashboard-local :team-id])] - (->> (rp/mutation! :create-project {:name name :team-id team-id}) - (rx/map project-created)))))) + (let [{:keys [on-success on-error] + :or {on-success identity + on-error identity}} (meta params)] + (->> (rp/mutation! :create-team {:name name}) + (rx/tap on-success) + (rx/catch on-error)))))) -(defn project-created - [data] - (us/verify ::project data) - (ptk/reify ::project-created +(defn update-team + [{:keys [id name] :as params}] + (us/assert ::team params) + (ptk/reify ::update-team ptk/UpdateEvent (update [_ state] - (-> state - (update :projects assoc (:id data) data) - (update :dashboard-local assoc :project-for-edit (:id data)))) + (assoc-in state [:teams id :name] name)) ptk/WatchEvent (watch [_ state stream] - (rx/of (rt/nav :dashboard-project {:team-id (:team-id data) :project-id (:id data)}))))) + (->> (rp/mutation! :update-team params) + (rx/ignore))))) + +(defn update-team-photo + [{:keys [file team-id] :as params}] + (us/assert ::di/js-file file) + (us/assert ::us/uuid team-id) + (ptk/reify ::update-team-photo + ptk/WatchEvent + (watch [_ state stream] + (let [on-success di/notify-finished-loading + + on-error #(do (di/notify-finished-loading) + (di/process-error %)) + + prepare #(hash-map :file % :team-id team-id)] + + (di/notify-start-loading) + + (->> (rx/of file) + (rx/map di/validate-file) + (rx/map prepare) + (rx/mapcat #(rp/mutation :update-team-photo %)) + (rx/do on-success) + (rx/map #(fetch-team %)) + (rx/catch on-error)))))) + +(defn update-team-member-role + [{:keys [team-id role member-id] :as params}] + (us/assert ::us/uuid team-id) + (us/assert ::us/uuid member-id) + (us/assert ::us/keyword role) + (ptk/reify ::update-team-member-role + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/mutation! :update-team-member-role params) + (rx/mapcat #(rx/of (fetch-team-members {:id team-id}) + (fetch-team {:id team-id}))))))) + +(defn delete-team-member + [{:keys [team-id member-id] :as params}] + (us/assert ::us/uuid team-id) + (us/assert ::us/uuid member-id) + (ptk/reify ::delete-team-member + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/mutation! :delete-team-member params) + (rx/mapcat #(rx/of (fetch-team-members {:id team-id}) + (fetch-team {:id team-id}))))))) + +(defn leave-team + [{:keys [id reassign-to] :as params}] + (us/assert ::team params) + (us/assert (s/nilable ::us/uuid) reassign-to) + (ptk/reify ::leave-team + ptk/WatchEvent + (watch [_ state stream] + (let [{:keys [on-success on-error] + :or {on-success identity + on-error identity}} (meta params)] + (rx/concat + (when (uuid? reassign-to) + (->> (rp/mutation! :update-team-member-role {:team-id id + :role :owner + :member-id reassign-to}) + (rx/ignore))) + (->> (rp/mutation! :leave-team {:id id}) + (rx/tap on-success) + (rx/catch on-error))))))) + +(defn invite-team-member + [{:keys [team-id email role] :as params}] + (us/assert ::us/uuid team-id) + (us/assert ::us/email email) + (us/assert ::us/keyword role) + (ptk/reify ::invite-team-member + ptk/WatchEvent + (watch [_ state stream] + (let [{:keys [on-success on-error] + :or {on-success identity + on-error identity}} (meta params)] + (->> (rp/mutation! :invite-team-member params) + (rx/tap on-success) + (rx/catch on-error)))))) + +(defn delete-team + [{:keys [id] :as params}] + (us/assert ::team params) + (ptk/reify ::delete-team + ptk/WatchEvent + (watch [_ state stream] + (let [{:keys [on-success on-error] + :or {on-success identity + on-error identity}} (meta params)] + (->> (rp/mutation! :delete-team {:id id}) + (rx/tap on-success) + (rx/catch on-error)))))) + +(defn create-project + [{:keys [team-id] :as params}] + (us/assert ::us/uuid team-id) + (letfn [(created [project state] + (-> state + (assoc-in [:projects team-id (:id project)] project) + (assoc-in [:dashboard-local :project-for-edit] (:id project))))] + (ptk/reify ::create-project + ptk/WatchEvent + (watch [_ state stream] + (let [name (name (gensym "New Project ")) + {:keys [on-success on-error] + :or {on-success identity + on-error identity}} (meta params)] + (->> (rp/mutation! :create-project {:name name :team-id team-id}) + (rx/tap on-success) + (rx/map #(partial created %)) + (rx/catch on-error))))))) (def clear-project-for-edit (ptk/reify ::clear-project-for-edit @@ -294,15 +363,31 @@ (update [_ state] (assoc-in state [:dashboard-local :project-for-edit] nil)))) + +(defn toggle-project-pin + [{:keys [id is-pinned team-id] :as params}] + (us/assert ::project params) + (ptk/reify ::toggle-project-pin + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:projects team-id id :is-pinned] (not is-pinned))) + + ptk/WatchEvent + (watch [_ state stream] + (let [project (get-in state [:projects team-id id]) + params (select-keys project [:id :is-pinned :team-id])] + (->> (rp/mutation :update-project-pin params) + (rx/ignore)))))) + ;; --- Rename Project (defn rename-project - [id name] - {:pre [(uuid? id) (string? name)]} + [{:keys [id name team-id] :as params}] + (us/assert ::project params) (ptk/reify ::rename-project ptk/UpdateEvent (update [_ state] - (assoc-in state [:projects id :name] name)) + (assoc-in state [:projects team-id id :name] name)) ptk/WatchEvent (watch [_ state stream] @@ -313,12 +398,12 @@ ;; --- Delete Project (by id) (defn delete-project - [id] - (us/verify ::us/uuid id) + [{:keys [id team-id] :as params}] + (us/assert ::project params) (ptk/reify ::delete-project ptk/UpdateEvent (update [_ state] - (update state :projects dissoc id)) + (update-in state [:projects team-id] dissoc id)) ptk/WatchEvent (watch [_ state s] @@ -328,16 +413,14 @@ ;; --- Delete File (by id) (defn delete-file - [id] - (us/verify ::us/uuid id) + [{:keys [id project-id] :as params}] + (us/assert ::file params) (ptk/reify ::delete-file ptk/UpdateEvent (update [_ state] - (let [project-id (get-in state [:files id :project-id]) - recent-project-files (get-in state [:recent-file-ids project-id] [])] - (-> state - (update :files dissoc id) - (assoc-in [:recent-file-ids project-id] (remove #(= % id) recent-project-files))))) + (-> state + (update-in [:files project-id] dissoc id) + (update-in [:recent-files project-id] (fnil disj #{}) id))) ptk/WatchEvent (watch [_ state s] @@ -347,28 +430,28 @@ ;; --- Rename File (defn rename-file - [id name] - {:pre [(uuid? id) (string? name)]} + [{:keys [id name project-id] :as params}] + (us/assert ::file params) (ptk/reify ::rename-file ptk/UpdateEvent (update [_ state] - (assoc-in state [:files id :name] name)) + (assoc-in state [:files project-id id :name] name)) ptk/WatchEvent (watch [_ state stream] - (let [params {:id id :name name}] + (let [params (select-keys params [:id :name])] (->> (rp/mutation :rename-file params) (rx/ignore)))))) ;; --- Set File shared (defn set-file-shared - [id is-shared] - {:pre [(uuid? id) (boolean? is-shared)]} + [{:keys [id project-id is-shared] :as params}] + (us/assert ::file params) (ptk/reify ::set-file-shared ptk/UpdateEvent (update [_ state] - (assoc-in state [:files id :is-shared] is-shared)) + (assoc-in state [:files project-id id :is-shared] is-shared)) ptk/WatchEvent (watch [_ state stream] @@ -381,48 +464,29 @@ (declare file-created) (defn create-file - [project-id] + [{:keys [project-id] :as params}] + (us/assert ::us/uuid project-id) (ptk/reify ::create-file ptk/WatchEvent (watch [_ state stream] - (let [name (name (gensym "New File ")) - params {:name name :project-id project-id}] + (let [{:keys [on-success on-error] + :or {on-success identity + on-error identity}} (meta params) + + name (name (gensym "New File ")) + params (assoc params :name name)] + (->> (rp/mutation! :create-file params) - (rx/map file-created)))))) + (rx/tap on-success) + (rx/map file-created) + (rx/catch on-error)))))) (defn file-created - [data] - (us/verify ::file data) + [{:keys [project-id id] :as file}] + (us/verify ::file file) (ptk/reify ::file-created ptk/UpdateEvent (update [_ state] - (let [project-id (:project-id data) - file-id (:id data) - recent-project-files (get-in state [:recent-file-ids project-id] [])] - (-> state - (assoc-in [:files file-id] data) - (assoc-in [:recent-file-ids project-id] (conj recent-project-files file-id))))) - - ptk/WatchEvent - (watch [_ state stream] - (let [pparams {:project-id (:project-id data) - :file-id (:id data)} - qparams {:page-id (get-in data [:data :pages 0])}] - (rx/of (rt/nav :workspace pparams qparams)))))) - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; UI State Handling -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; --- Update Opts (Filtering & Ordering) - -;; (defn update-opts -;; [& {:keys [order filter] :as opts}] -;; (ptk/reify ::update-opts -;; ptk/UpdateEvent -;; (update [_ state] -;; (update state :dashboard-local merge -;; (when order {:order order}) -;; (when filter {:filter filter}))))) - + (-> state + (assoc-in [:files project-id id] file) + (update-in [:recent-files project-id] (fnil conj #{}) id))))) diff --git a/frontend/src/app/main/data/messages.cljs b/frontend/src/app/main/data/messages.cljs index 53517d16df..c4837c0fbf 100644 --- a/frontend/src/app/main/data/messages.cljs +++ b/frontend/src/app/main/data/messages.cljs @@ -27,6 +27,7 @@ (s/def ::message-position #{:fixed :floating :inline}) (s/def ::message-status #{:visible :hide}) (s/def ::message-controls #{:none :close :inline-actions :bottom-actions}) +(s/def ::message-tag string?) (s/def ::label string?) (s/def ::callback fn?) (s/def ::message-action (s/keys :req-un [::label ::callback])) @@ -52,7 +53,7 @@ (ptk/reify ::hide ptk/UpdateEvent (update [_ state] - (update state :message assoc :status :hide)) + (d/update-when state :message assoc :status :hide)) ptk/WatchEvent (watch [_ state stream] @@ -61,6 +62,15 @@ (rx/delay +animation-timeout+) (rx/take-until stoper)))))) +(defn hide-tag + [tag] + (ptk/reify ::hide-tag + ptk/WatchEvent + (watch [_ state stream] + (let [message (get state :message)] + (when (= (:tag message) tag) + (rx/of hide)))))) + (defn error ([content] (error content {})) ([content {:keys [timeout] :or {timeout 3000}}] @@ -94,9 +104,13 @@ :timeout timeout}))) (defn info-dialog - [content controls actions] - (show {:content content - :type :info - :controls controls - :actions actions})) + ([content controls actions] + (info-dialog content controls actions nil)) + ([content controls actions tag] + (show {:content content + :type :info + :position :floating + :controls controls + :actions actions + :tag tag}))) diff --git a/frontend/src/app/main/data/modal.cljs b/frontend/src/app/main/data/modal.cljs index 42f02a97e7..cd0a7c2c54 100644 --- a/frontend/src/app/main/data/modal.cljs +++ b/frontend/src/app/main/data/modal.cljs @@ -8,30 +8,68 @@ ;; Copyright (c) 2020 UXBOX Labs SL (ns app.main.data.modal + (:refer-clojure :exclude [update]) (:require - [potok.core :as ptk])) + [potok.core :as ptk] + [app.main.store :as st] + [app.common.uuid :as uuid] + [cljs.core :as c])) -(defn show-modal [id type props] - (ptk/reify ::show-modal - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc ::modal {:id id - :type type - :props props - :allow-click-outside false}))))) +(defonce components (atom {})) -(defn hide-modal [] +(defn show + ([props] + (show (uuid/next) (:type props) props)) + ([type props] (show (uuid/next) type props)) + ([id type props] + (ptk/reify ::show-modal + ptk/UpdateEvent + (update [_ state] + (assoc state ::modal {:id id + :type type + :props props + :allow-click-outside false}))))) +(defn update-props + ([type props] + (ptk/reify ::show-modal + ptk/UpdateEvent + (update [_ state] + (cond-> state + (::modal state) + (update-in [::modal :props] merge props)))))) + +(defn hide + [] (ptk/reify ::hide-modal ptk/UpdateEvent (update [_ state] - (-> state - (dissoc ::modal))))) + (dissoc state ::modal)))) -(defn update-modal [options] +(defn update + [options] (ptk/reify ::update-modal ptk/UpdateEvent (update [_ state] - (-> state - (update ::modal merge options))))) + (cond-> state + (::modal state) + (c/update ::modal merge options))))) +(defn show! + [type props] + (st/emit! (show type props))) + +(defn update-props! + [type props] + (st/emit! (update-props type props))) + +(defn allow-click-outside! + [] + (st/emit! (update {:allow-click-outside true}))) + +(defn disallow-click-outside! + [] + (st/emit! (update {:allow-click-outside false}))) + +(defn hide! + [] + (st/emit! (hide))) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index b068345bf3..2e990f7d09 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -106,6 +106,7 @@ (defn request-email-change [{:keys [email] :as data}] + (us/assert ::us/email email) (ptk/reify ::request-email-change ptk/WatchEvent (watch [_ state stream] diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index 7fc1a5f460..285286a7d4 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -20,7 +20,8 @@ [app.common.data :as d] [app.common.exceptions :as ex] [app.util.router :as rt] - [app.common.uuid :as uuid])) + [app.common.uuid :as uuid] + [app.common.pages-helpers :as cph])) ;; --- Specs @@ -51,7 +52,11 @@ :page-id page-id :file-id file-id :interactions-mode :hide - :show-interactions? false})) + :show-interactions? false + + :selected #{} + :collapsed #{} + :hover #{}})) ptk/WatchEvent (watch [_ state stream] @@ -89,12 +94,13 @@ (update [_ state] (let [objects (:objects page) frames (extract-frames objects)] - (assoc state :viewer-data {:project project - :objects objects - :file file - :page page - :frames frames - :share-token share-token}))))) + (-> state + (assoc :viewer-data {:project project + :objects objects + :file file + :page page + :frames frames + :share-token share-token})))))) (def create-share-link (ptk/reify ::create-share-link @@ -170,24 +176,25 @@ ptk/WatchEvent (watch [_ state stream] (let [route (:route state) + screen (-> route :data :name keyword) qparams (get-in route [:params :query]) pparams (get-in route [:params :path]) index (d/parse-integer (:index qparams))] (when (pos? index) - (rx/of (rt/nav :viewer pparams (assoc qparams :index (dec index))))))))) + (rx/of (rt/nav screen pparams (assoc qparams :index (dec index))))))))) (def select-next-frame (ptk/reify ::select-prev-frame ptk/WatchEvent (watch [_ state stream] (let [route (:route state) + screen (-> route :data :name keyword) qparams (get-in route [:params :query]) pparams (get-in route [:params :path]) index (d/parse-integer (:index qparams)) - total (count (get-in state [:viewer-data :frames]))] (when (< index (dec total)) - (rx/of (rt/nav :viewer pparams (assoc qparams :index (inc index))))))))) + (rx/of (rt/nav screen pparams (assoc qparams :index (inc index))))))))) (defn set-interactions-mode [mode] @@ -236,16 +243,92 @@ frames (get-in state [:viewer-data :frames]) share-token (get-in state [:viewer-data :share-token]) index (d/index-of-pred frames #(= (:id %) frame-id))] - (rx/of (rt/nav :viewer {:page-id page-id :file-id file-id} {:token share-token - :index index})))))) + (rx/of (rt/nav :viewer + {:page-id page-id + :file-id file-id} + {:token share-token + :index index})))))) + +(defn set-current-frame [frame-id] + (ptk/reify ::current-frame + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:viewer-data :current-frame-id] frame-id)))) + +(defn deselect-all [] + (ptk/reify ::deselect-all + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:viewer-local :selected] #{})))) + +(defn select-shape + ([id] + (ptk/reify ::select-shape + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:viewer-local :selected] #{id})))))) + +(defn toggle-selection + [id] + (ptk/reify ::toggle-selection + ptk/UpdateEvent + (update [_ state] + (let [selected (get-in state [:viewer-local :selected])] + (cond-> state + (not (selected id)) (update-in [:viewer-local :selected] conj id) + (selected id) (update-in [:viewer-local :selected] disj id)))))) + +(defn shift-select-to + [id] + (ptk/reify ::shift-select-to + ptk/UpdateEvent + (update [_ state] + (let [objects (get-in state [:viewer-data :objects]) + selection (-> state + (get-in [:viewer-local :selected] #{}) + (conj id))] + (-> state + (assoc-in [:viewer-local :selected] + (cph/expand-region-selection objects selection))))))) + +(defn select-all + [] + (ptk/reify ::select-all + ptk/UpdateEvent + (update [_ state] + (let [objects (get-in state [:viewer-data :objects]) + frame-id (get-in state [:viewer-data :current-frame-id]) + selection (->> objects + (filter #(= (:frame-id (second %)) frame-id)) + (map first) + (into #{frame-id}))] + (-> state + (assoc-in [:viewer-local :selected] selection)))))) + +(defn toggle-collapse [id] + (ptk/reify ::toggle-collapse + ptk/UpdateEvent + (update [_ state] + (let [toggled? (contains? (get-in state [:viewer-local :collapsed]) id)] + (update-in state [:viewer-local :collapsed] (if toggled? disj conj) id))))) + +(defn hover-shape [id hover?] + (ptk/reify ::hover-shape + ptk/UpdateEvent + (update [_ state] + (update-in state [:viewer-local :hover] (if hover? conj disj) id)))) + ;; --- Shortcuts (def shortcuts {"+" #(st/emit! increase-zoom) "-" #(st/emit! decrease-zoom) + "ctrl+a" #(st/emit! (select-all)) "shift+0" #(st/emit! zoom-to-50) "shift+1" #(st/emit! reset-zoom) "shift+2" #(st/emit! zoom-to-200) "left" #(st/emit! select-prev-frame) "right" #(st/emit! select-next-frame)}) + diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 48a73d2b5d..66d4269fef 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -9,7 +9,6 @@ (ns app.main.data.workspace (:require - [cuerdas.core :as str] [app.common.data :as d] [app.common.exceptions :as ex] [app.common.geom.matrix :as gmt] @@ -22,6 +21,7 @@ [app.common.uuid :as uuid] [app.config :as cfg] [app.main.constants :as c] + [app.main.data.colors :as mdc] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.notifications :as dwn] @@ -29,11 +29,11 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.texts :as dwtxt] [app.main.data.workspace.transforms :as dwt] - [app.main.data.colors :as mdc] [app.main.repo :as rp] [app.main.store :as st] [app.main.streams :as ms] [app.main.worker :as uw] + [app.util.logging :as log] [app.util.router :as rt] [app.util.timers :as ts] [app.util.transit :as t] @@ -41,8 +41,11 @@ [beicon.core :as rx] [cljs.spec.alpha :as s] [clojure.set :as set] + [clojure.set :as set] + [cuerdas.core :as str] [potok.core :as ptk])) +;; (log/set-level! :trace) ;; --- Specs (s/def ::shape-attrs ::cp/shape-attrs) @@ -59,8 +62,8 @@ (s/def ::layout-flag #{:sitemap - :sitemap-pages :layers + :comments :assets :document-history :colorpalette @@ -74,7 +77,6 @@ (def default-layout #{:sitemap - :sitemap-pages :layers :element-options :rules @@ -188,9 +190,15 @@ (ptk/reify ::initialize-page ptk/UpdateEvent (update [_ state] - (let [local (get-in state [:workspace-cache page-id] workspace-local-default)] + (let [prev-local (get state :workspace-local) + local (-> state + (get-in [:workspace-cache page-id] workspace-local-default) + (merge (select-keys prev-local [:vbox :vport :zoom]))) + page (-> (get-in state [:workspace-data :pages-index page-id]) + (select-keys [:id :name]))] (assoc state :current-page-id page-id ; mainly used by events + :trimmed-page page :workspace-local local))))) (defn finalize-page @@ -202,7 +210,7 @@ (let [local (:workspace-local state)] (-> state (assoc-in [:workspace-cache page-id] local) - (dissoc :current-page-id)))))) + (dissoc :current-page-id :workspace-local :trimmed-page)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Workspace Page CRUD @@ -306,7 +314,7 @@ objects (dwc/lookup-page-objects state page-id) shapes (cph/select-toplevel-shapes objects {:include-frames? true}) srect (geom/selection-rect shapes) - local (assoc local :vport size)] + local (assoc local :vport size :zoom 1)] (cond (or (not (mth/finite? (:width srect))) (not (mth/finite? (:height srect)))) @@ -380,53 +388,47 @@ ;; --- Toggle layout flag -(defn- toggle-layout-flag - [state flag] - (us/assert ::layout-flag flag) - (update state :workspace-layout - (fn [flags] - (if (contains? flags flag) - (disj flags flag) - (conj flags flag))))) +(def layout-flags + {:assets + {:del #{:sitemap :layers :document-history } + :add #{:assets}} -(defn- check-sidebars - [state] - (let [layout (:workspace-layout state) - left-sidebar? (not (empty? (keep layout [:layers - :sitemap - :document-history - :assets]))) - right-sidebar? (not (empty? (keep layout [:element-options])))] - (update state :workspace-local - assoc :left-sidebar? left-sidebar? - :right-sidebar? right-sidebar?))) + :document-history + {:del #{:assets :layers :sitemap} + :add #{:document-history}} -(defn- check-auto-flags - [state flags-to-toggle] - (update state :workspace-layout - (fn [flags] - (cond - (contains? (set flags-to-toggle) :assets) - (disj flags :sitemap :layers :document-history) + :layers + {:del #{:document-history :assets} + :add #{:sitemap :layers}}}) - (contains? (set flags-to-toggle) :sitemap) - (disj flags :assets :document-history) +(defn- ensure-layout + [layout] + (assert (contains? layout-flags layout) + (str "unexpected layout name: " layout)) + (ptk/reify ::ensure-layout + ptk/UpdateEvent + (update [_ state] + (update state :workspace-layout + (fn [stored] + (let [todel (get-in layout-flags [layout :del] #{}) + toadd (get-in layout-flags [layout :add] #{})] + (-> stored + (set/difference todel) + (set/union toadd)))))))) - (contains? (set flags-to-toggle) :document-history) - (disj flags :assets :sitemap :layers) - - :else - flags)))) - -(defn toggle-layout-flags +(defn- toggle-layout-flags [& flags] - (us/assert ::layout-flags flags) (ptk/reify ::toggle-layout-flags ptk/UpdateEvent (update [_ state] - (-> (reduce toggle-layout-flag state flags) - (check-auto-flags flags) - (check-sidebars))))) + (update state :workspace-layout + (fn [stored] + (reduce (fn [flags flag] + (if (contains? flags flag) + (disj flags flag) + (conj flags flag))) + stored + (into #{} flags))))))) ;; --- Set element options mode @@ -547,11 +549,8 @@ unames (dwc/retrieve-used-names objects) name (dwc/generate-unique-name unames (:name shape)) - frames (cph/select-frames objects) - - frame-id (if (= :frame (:type shape)) - uuid/zero - (dwc/calculate-frame-overlap frames shape)) + frame-id (or (:frame-id attrs) + (cph/frame-id-by-position objects attrs)) shape (merge (if (= :frame (:type shape)) @@ -559,8 +558,7 @@ cp/default-shape-attrs) (assoc shape :id id - :name name - :frame-id frame-id)) + :name name)) rchange {:type :add-obj :id id @@ -584,20 +582,26 @@ [(+ x (/ width 2)) (+ y (/ height 2))])) (defn create-and-add-shape - [type data] + [type frame-x frame-y data] (ptk/reify ::create-and-add-shape ptk/WatchEvent (watch [_ state stream] (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 (-> (dwc/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}) - (geom/setup-selrect))] + (assoc :frame-id frame-id) + (geom/setup-selrect))] (rx/of (add-shape shape)))))) ;; --- Update Shape Attrs @@ -677,10 +681,8 @@ (let [page-id (:current-page-id state) objects (dwc/lookup-page-objects state page-id) - del-change #(array-map :type :del-obj :page-id page-id :id %) - get-empty-parents - (fn get-empty-parents [parents] + (fn [parents] (->> parents (map (fn [id] (let [obj (get objects id)] @@ -690,43 +692,78 @@ (take-while (complement nil?)) (map :id))) - rchanges - (reduce (fn [res id] - (let [children (cph/get-children id objects) - parents (cph/get-parents id objects)] - (d/concat res - (map del-change (reverse children)) - [(del-change id)] - (map del-change (get-empty-parents parents)) - [{:type :reg-objects - :page-id page-id - :shapes (vec parents)}]))) - [] + 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) - uchanges - (reduce (fn [res id] - (let [children (cph/get-children id objects) - parents (cph/get-parents id objects) - add-chg (fn [id] - (let [item (get objects id)] - {:type :add-obj - :id (:id item) + rchanges + (d/concat + (reduce (fn [res id] + (let [children (cph/get-children id objects) + parents (cph/get-parents id objects) + del-change #(array-map + :type :del-obj + :page-id page-id + :id %)] + (d/concat res + (map del-change (reverse children)) + [(del-change id)] + (map del-change (get-empty-parents parents)) + [{:type :reg-objects :page-id page-id - :index (cph/position-on-parent id objects) - :frame-id (:frame-id item) - :parent-id (:parent-id item) - :obj item}))] - (d/concat res - (map add-chg (reverse (get-empty-parents parents))) - [(add-chg id)] - (map add-chg children) - [{:type :reg-objects - :page-id page-id - :shapes (vec parents)}]))) - [] - ids) - ] + :shapes (vec parents)}]))) + [] + ids) + (map #(array-map + :type :mod-obj + :page-id page-id + :id % + :operations [{:type :set + :attr :masked-group? + :val false}]) + groups-to-unmask)) + + uchanges + (d/concat + (reduce (fn [res id] + (let [children (cph/get-children id objects) + parents (cph/get-parents id objects) + add-change (fn [id] + (let [item (get objects id)] + {:type :add-obj + :id (:id item) + :page-id page-id + :index (cph/position-on-parent id objects) + :frame-id (:frame-id item) + :parent-id (:parent-id item) + :obj item}))] + (d/concat res + (map add-change (reverse (get-empty-parents parents))) + [(add-change id)] + (map add-change children) + [{:type :reg-objects + :page-id page-id + :shapes (vec parents)}]))) + [] + ids) + (map #(array-map + :type :mod-obj + :page-id page-id + :id % + :operations [{:type :set + :attr :masked-group? + :val true}]) + groups-to-unmask))] ;; (println "================ rchanges") ;; (cljs.pprint/pprint rchanges) @@ -741,7 +778,7 @@ (watch [_ state stream] (let [selected (get-in state [:workspace-local :selected])] (rx/of (delete-shapes selected) - dws/deselect-all))))) + (dws/deselect-all)))))) ;; --- Shape Vertical Ordering @@ -808,29 +845,60 @@ (conj res (cph/get-parent (first ids) objects)) (next ids)))) - rchanges [{:type :mov-objects - :parent-id parent-id - :page-id page-id - :index to-index - :shapes (vec (reverse ids))} - {:type :reg-objects - :page-id page-id - :shapes parents}] + groups-to-unmask + (reduce (fn [group-ids id] + ;; When a masked group loses its mask shape, because it's + ;; moved outside the 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))) + (not= (:id parent) parent-id)) + (conj group-ids (:id parent)) + group-ids))) + #{} + ids) - uchanges - (reduce (fn [res id] - (let [obj (get objects id)] - (conj res - {:type :mov-objects - :parent-id (:parent-id obj) + rchanges (d/concat + [{:type :mov-objects + :parent-id parent-id + :page-id page-id + :index to-index + :shapes (vec (reverse ids))} + {:type :reg-objects + :page-id page-id + :shapes parents}] + (map (fn [group-id] + {:type :mod-obj :page-id page-id - :index (cph/position-on-parent id objects) - :shapes [id]}))) - [] (reverse ids)) - uchanges (conj uchanges - {:type :reg-objects + :id group-id + :operations [{:type :set + :attr :masked-group? + :val false}]}) + groups-to-unmask)) + + uchanges (d/concat + (reduce (fn [res id] + (let [obj (get objects id)] + (conj res + {:type :mov-objects + :parent-id (:parent-id obj) + :page-id page-id + :index (cph/position-on-parent id objects) + :shapes [id]}))) + [] (reverse ids)) + [{:type :reg-objects :page-id page-id - :shapes parents})] + :shapes parents}] + (map (fn [group-id] + {:type :mod-obj + :page-id page-id + :id group-id + :operations [{:type :set + :attr :masked-group? + :val true}]}) + groups-to-unmask))] ;; (println "================ rchanges") ;; (cljs.pprint/pprint rchanges) @@ -895,7 +963,7 @@ (let [curr (first moved) prev (get objects (:id curr)) ops1 (dwc/generate-operations prev curr) - ops2 (dwc/generate-operations curr prev)] + ops2 (dwc/generate-operations curr prev true)] (recur (next moved) (conj rchanges {:type :mod-obj :page-id page-id @@ -942,7 +1010,7 @@ (let [curr (first moved) prev (get objects (:id curr)) ops1 (dwc/generate-operations prev curr) - ops2 (dwc/generate-operations curr prev)] + ops2 (dwc/generate-operations curr prev true)] (recur (next moved) (conj rchanges {:type :mod-obj :page-id page-id @@ -996,13 +1064,18 @@ ptk/WatchEvent (watch [_ state stream] - (let [cancel-event? (fn [event] - (dwc/interrupt? event)) - stoper (rx/filter (ptk/type? ::clear-drawing) stream)] - (->> (rx/filter cancel-event? stream) - (rx/take 1) - (rx/map (constantly clear-drawing)) - (rx/take-until stoper))))))) + (let [stoper (rx/filter (ptk/type? ::clear-drawing) stream)] + (rx/merge + (rx/of (dws/deselect-all)) + + ;; NOTE: comments are a special case and they manage they + ;; own interrupt cycle. + (when (not= tool :comments) + (->> stream + (rx/filter dwc/interrupt?) + (rx/take 1) + (rx/map (constantly clear-drawing)) + (rx/take-until stoper))))))))) ;; --- Update Dimensions @@ -1148,12 +1221,9 @@ (update [_ state] (let [page-id (:current-page-id state) objects (dwc/lookup-page-objects state page-id) - root-id (cph/get-root-component (:id shape) objects) - root-shape (get objects root-id) mdata {:position position :shape shape - :root-shape root-shape :selected (get-in state [:workspace-local :selected])}] (-> state (assoc-in [:workspace-local :context-menu] mdata)))) @@ -1175,16 +1245,26 @@ (def copy-selected (letfn [(prepare-selected [objects selected] - (let [data (reduce #(prepare %1 objects %2) {} selected)] + (let [data (reduce #(prepare %1 objects selected %2) {} selected)] {:type :copied-shapes :selected selected :objects data})) - (prepare [result objects id] - (let [obj (get objects id)] + (maybe-translate [shape objects selected] + (if (and (not= (:type shape) :frame) + (not (contains? selected (:frame-id shape)))) + ;; When the parent frame is not selected we change to relative + ;; coordinates + (let [frame (get objects (:frame-id shape))] + (geom/translate-to-frame shape frame)) + shape)) + + (prepare [result objects selected id] + (let [obj (-> (get objects id) + (maybe-translate objects selected))] (as-> result $$ (assoc $$ id obj) - (reduce #(prepare %1 objects %2) $$ (:shapes obj))))) + (reduce #(prepare %1 objects selected %2) $$ (:shapes obj))))) (on-copy-error [error] (js/console.error "Clipboard blocked:" error) @@ -1202,6 +1282,13 @@ (rx/catch on-copy-error) (rx/ignore))))))) +(defn selected-frame? [state] + (let [selected (get-in state [:workspace-local :selected]) + page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id)] + (and (and (= 1 (count selected)) + (= :frame (get-in objects [(first selected) :type])))))) + (defn- paste-impl [{:keys [selected objects] :as data}] (ptk/reify ::paste-impl @@ -1211,7 +1298,19 @@ wrapper (geom/selection-rect selected-objs) orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper)) mouse-pos @ms/mouse-position - delta (gpt/subtract mouse-pos orig-pos) + + page-id (:current-page-id state) + + page-objects (dwc/lookup-page-objects state page-id) + page-selected (get-in state [:workspace-local :selected]) + + [frame-id delta] (if (selected-frame? state) + [(first page-selected) + (get page-objects (first page-selected))] + [(cph/frame-id-by-position page-objects mouse-pos) + (gpt/subtract mouse-pos orig-pos)]) + + objects (d/mapm (fn [_ v] (assoc v :frame-id frame-id :parent-id frame-id)) objects) page-id (:current-page-id state) unames (-> (dwc/lookup-page-objects state page-id) @@ -1241,7 +1340,7 @@ :height height :id (:id image) :path (:path image)}}] - (st/emit! (create-and-add-shape :image shape)))) + (st/emit! (create-and-add-shape :image x y shape)))) (defn- paste-image-impl [image] @@ -1310,20 +1409,23 @@ {:keys [x y]} @ms/mouse-position width (min (* 7 (count text)) 700) height 16 + page-id (:current-page-id state) + frame-id (-> (dwc/lookup-page-objects state page-id) + (cph/frame-id-by-position @ms/mouse-position)) shape (geom/setup-selrect {:id id :type :text :name "Text" :x x :y y + :frame-id frame-id :width width :height height :grow-type (if (> (count text) 100) :auto-height :auto-width) :content (as-content text)})] (rx/of dwc/start-undo-transaction - dws/deselect-all + (dws/deselect-all) (add-shape shape) - (dwc/rehash-shape-frame-relationship [id]) dwc/commit-undo-transaction))))) (defn update-shape-flags @@ -1372,6 +1474,96 @@ (dws/prepare-remove-group page-id group objects)] (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))))) +(def mask-group + (ptk/reify ::mask-group + ptk/WatchEvent + (watch [_ state stream] + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + selected (get-in state [:workspace-local :selected]) + shapes (dws/shapes-for-grouping objects selected)] + (when-not (empty? shapes) + (let [;; If the selected shape is a group, we can use it. If not, + ;; create a new group and set it as masked. + [group rchanges uchanges] + (if (and (= (count shapes) 1) + (= (:type (first shapes)) :group)) + [(first shapes) [] []] + (dws/prepare-create-group page-id shapes "Group-" true)) + + rchanges (d/concat rchanges + [{:type :mod-obj + :page-id page-id + :id (:id group) + :operations [{:type :set + :attr :masked-group? + :val true}]} + {:type :reg-objects + :page-id page-id + :shapes [(:id group)]}]) + + uchanges (conj rchanges + {:type :mod-obj + :page-id page-id + :id (:id group) + :operations [{:type :set + :attr :masked-group? + :val nil}]}) + + ;; If the mask has the default color, change it automatically + ;; to white, to have an opaque mask by default (user may change + ;; it later to have different degrees of transparency). + mask (first shapes) + rchanges (if (not= (:fill-color mask) cp/default-color) + rchanges + (conj rchanges + {:type :mod-obj + :page-id page-id + :id (:id mask) + :operations [{:type :set + :attr :fill-color + :val "#ffffff"}]})) + + uchanges (if (not= (:fill-color mask) cp/default-color) + uchanges + (conj uchanges + {:type :mod-obj + :page-id page-id + :id (:id mask) + :operations [{:type :set + :attr :fill-color + :val (:fill-color mask)}]}))] + + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) + (dws/select-shapes (d/ordered-set (:id group)))))))))) + +(def unmask-group + (ptk/reify ::unmask-group + ptk/WatchEvent + (watch [_ state stream] + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + selected (get-in state [:workspace-local :selected])] + (when (= (count selected) 1) + (let [group (get objects (first selected)) + + rchanges [{:type :mod-obj + :page-id page-id + :id (:id group) + :operations [{:type :set + :attr :masked-group? + :val nil}]}] + + uchanges [{:type :mod-obj + :page-id page-id + :id (:id group) + :operations [{:type :set + :attr :masked-group? + :val (:masked-group? group)}]}]] + + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) + (dws/select-shapes (d/ordered-set (:id group)))))))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Interactions @@ -1447,22 +1639,23 @@ (defn change-canvas-color [color] - (s/assert string? color) + ;; TODO: Create a color spec + #_(s/assert string? color) (ptk/reify ::change-canvas-color ptk/WatchEvent (watch [_ state stream] (let [page-id (get state :current-page-id) options (dwc/lookup-page-options state page-id) - ccolor (:background options)] + previus-color (:background options)] (rx/of (dwc/commit-changes [{:type :set-option :page-id page-id :option :background - :value color}] + :value (:color color)}] [{:type :set-option :page-id page-id :option :background - :value ccolor}] + :value previus-color}] {:commit-local? true})))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1516,8 +1709,10 @@ "+" #(st/emit! (increase-zoom nil)) "-" #(st/emit! (decrease-zoom nil)) "ctrl+g" #(st/emit! group-selected) - "ctrl+k" #(st/emit! dwl/add-component) "shift+g" #(st/emit! ungroup-selected) + "ctrl+m" #(st/emit! mask-group) + "shift+m" #(st/emit! unmask-group) + "ctrl+k" #(st/emit! dwl/add-component) "shift+0" #(st/emit! reset-zoom) "shift+1" #(st/emit! zoom-to-fit-all) "shift+2" #(st/emit! zoom-to-selected-shape) @@ -1533,7 +1728,8 @@ (select-for-drawing :text)) "ctrl+c" #(st/emit! copy-selected) "ctrl+v" #(st/emit! paste) - "escape" #(st/emit! :interrupt deselect-all) + "ctrl+x" #(st/emit! copy-selected delete-selected) + "escape" #(st/emit! :interrupt (deselect-all true)) "del" #(st/emit! delete-selected) "backspace" #(st/emit! delete-selected) "ctrl+up" #(st/emit! (vertical-order-selected :up)) diff --git a/frontend/src/app/main/data/workspace/comments.cljs b/frontend/src/app/main/data/workspace/comments.cljs new file mode 100644 index 0000000000..b355be375d --- /dev/null +++ b/frontend/src/app/main/data/workspace/comments.cljs @@ -0,0 +1,307 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.data.workspace.comments + (:require + [cuerdas.core :as str] + [app.common.data :as d] + [app.common.exceptions :as ex] + [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as geom] + [app.common.math :as mth] + [app.common.pages :as cp] + [app.common.pages-helpers :as cph] + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.config :as cfg] + [app.main.constants :as c] + [app.main.data.workspace.common :as dwc] + [app.main.repo :as rp] + [app.main.store :as st] + [app.main.streams :as ms] + [app.main.worker :as uw] + [app.util.router :as rt] + [app.util.timers :as ts] + [app.util.transit :as t] + [app.util.webapi :as wapi] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [clojure.set :as set] + [potok.core :as ptk])) + +(s/def ::comment-thread any?) +(s/def ::comment any?) + +(declare create-draft-thread) +(declare retrieve-comment-threads) +(declare refresh-comment-thread) +(declare handle-interrupt) +(declare handle-comment-layer-click) + +(defn initialize-comments + [file-id] + (us/assert ::us/uuid file-id) + (ptk/reify ::start-commenting + ptk/UpdateEvent + (update [_ state] + (update state :workspace-local assoc :commenting true)) + + ptk/WatchEvent + (watch [_ state stream] + (let [stoper (rx/filter #(= ::finalize %) stream)] + (rx/merge + (rx/of (retrieve-comment-threads file-id)) + (->> stream + (rx/filter ms/mouse-click?) + (rx/switch-map #(rx/take 1 ms/mouse-position)) + (rx/mapcat #(rx/take 1 ms/mouse-position)) + (rx/map handle-comment-layer-click) + (rx/take-until stoper)) + (->> stream + (rx/filter dwc/interrupt?) + (rx/map handle-interrupt) + (rx/take-until stoper))))))) + +(defn- handle-interrupt + [] + (ptk/reify ::handle-interrupt + ptk/UpdateEvent + (update [_ state] + (let [local (:workspace-comments state) + drawing (:workspace-drawing state)] + (cond + (:comment drawing) + (update state :workspace-drawing dissoc :comment) + + (:open local) + (update state :workspace-comments dissoc :open) + + :else + (dissoc state :workspace-drawing)))))) + +;; Event responsible of the what should be executed when user clicked +;; on the comments layer. An option can be create a new draft thread, +;; an other option is close previously open thread or cancel the +;; latest opened thread draft. +(defn- handle-comment-layer-click + [position] + (ptk/reify ::handle-comment-layer-click + ptk/UpdateEvent + (update [_ state] + (let [local (:workspace-comments state)] + (if (:open local) + (update state :workspace-comments dissoc :open) + (update state :workspace-drawing assoc + :comment {:position position :content ""})))))) + +(defn create-thread + [data] + (letfn [(created [{:keys [id comment] :as thread} state] + (-> state + (update :comment-threads assoc id (dissoc thread :comment)) + (update :workspace-comments assoc :open id) + (update :workspace-drawing dissoc :comment) + (update-in [:comments id] assoc (:id comment) comment)))] + + (ptk/reify ::create-thread + ptk/WatchEvent + (watch [_ state stream] + (let [file-id (get-in state [:workspace-file :id]) + page-id (:current-page-id state) + params (assoc data + :page-id page-id + :file-id file-id)] + (->> (rp/mutation :create-comment-thread params) + (rx/map #(partial created %)))))))) + +(defn update-comment-thread-status + [{:keys [id] :as thread}] + (us/assert ::comment-thread thread) + (ptk/reify ::update-comment-thread-status + ptk/UpdateEvent + (update [_ state] + (d/update-in-when state [:comment-threads id] assoc :count-unread-comments 0)) + + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/mutation :update-comment-thread-status {:id id}) + (rx/ignore))))) + + +(defn update-comment-thread + [{:keys [id is-resolved] :as thread}] + (us/assert ::comment-thread thread) + (ptk/reify ::update-comment-thread + + ptk/UpdateEvent + (update [_ state] + (d/update-in-when state [:comment-threads id] assoc :is-resolved is-resolved)) + + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/mutation :update-comment-thread {:id id :is-resolved is-resolved}) + (rx/ignore))))) + + +(defn add-comment + [thread content] + (us/assert ::comment-thread thread) + (us/assert ::us/string content) + (letfn [(created [comment state] + (update-in state [:comments (:id thread)] assoc (:id comment) comment))] + (ptk/reify ::create-comment + ptk/WatchEvent + (watch [_ state stream] + (rx/concat + (->> (rp/mutation :add-comment {:thread-id (:id thread) :content content}) + (rx/map #(partial created %))) + (rx/of (refresh-comment-thread thread))))))) + +(defn update-comment + [{:keys [id content thread-id] :as comment}] + (us/assert ::comment comment) + (ptk/reify :update-comment + ptk/UpdateEvent + (update [_ state] + (d/update-in-when state [:comments thread-id id] assoc :content content)) + + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/mutation :update-comment {:id id :content content}) + (rx/ignore))))) + +(defn delete-comment-thread + [{:keys [id] :as thread}] + (us/assert ::comment-thread thread) + (ptk/reify :delete-comment-thread + ptk/UpdateEvent + (update [_ state] + (-> state + (update :comments dissoc id) + (update :comment-threads dissoc id))) + + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/mutation :delete-comment-thread {:id id}) + (rx/ignore))))) + +(defn delete-comment + [{:keys [id thread-id] :as comment}] + (us/assert ::comment comment) + (ptk/reify :delete-comment + ptk/UpdateEvent + (update [_ state] + (d/update-in-when state [:comments thread-id] dissoc id)) + + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/mutation :delete-comment {:id id}) + (rx/ignore))))) + +(defn refresh-comment-thread + [{:keys [id file-id] :as thread}] + (us/assert ::comment-thread thread) + (letfn [(fetched [thread state] + (assoc-in state [:comment-threads id] thread))] + (ptk/reify ::refresh-comment-thread + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :comment-thread {:file-id file-id :id id}) + (rx/map #(partial fetched %))))))) + +(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)))] + (ptk/reify ::retrieve-comment-threads + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :comment-threads {:file-id file-id}) + (rx/map #(partial fetched %))))))) + +(defn retrieve-comments + [thread-id] + (us/assert ::us/uuid thread-id) + (letfn [(fetched [comments state] + (update state :comments assoc thread-id (d/index-by :id comments)))] + (ptk/reify ::retrieve-comments + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :comments {:thread-id thread-id}) + (rx/map #(partial fetched %))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Workspace (local) events +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn open-thread + [{:keys [id] :as thread}] + (us/assert ::comment-thread thread) + (ptk/reify ::open-thread + ptk/UpdateEvent + (update [_ state] + (-> state + (update :workspace-comments assoc :open id) + (update :workspace-drawing dissoc :comment))))) + +(defn close-thread + [] + (ptk/reify ::open-thread + ptk/UpdateEvent + (update [_ state] + (-> state + (update :workspace-comments dissoc :open) + (update :workspace-drawing dissoc :comment))))) + +(defn update-draft-thread + [data] + (ptk/reify ::update-draft-thread + ptk/UpdateEvent + (update [_ state] + (update state :workspace-drawing assoc :comment data)))) + +(defn update-filters + [{:keys [main resolved]}] + (ptk/reify ::update-filters + ptk/UpdateEvent + (update [_ state] + (update state :workspace-comments + (fn [local] + (cond-> local + (some? main) + (assoc :filter main) + + (some? resolved) + (assoc :filter-resolved resolved))))))) + + +(defn center-to-comment-thread + [{:keys [id position] :as thread}] + (us/assert ::comment-thread thread) + (ptk/reify :center-to-comment-thread + ptk/UpdateEvent + (update [_ state] + (update state :workspace-local + (fn [{:keys [vbox vport zoom] :as local}] + ;; (prn "position=" position) + ;; (prn "vbox=" vbox) + ;; (prn "vport=" vport) + (let [pw (/ 50 zoom) + ph (/ 200 zoom) + nw (mth/round (- (/ (:width vbox) 2) pw)) + nh (mth/round (- (/ (:height vbox) 2) ph)) + nx (- (:x position) nw) + ny (- (:y position) nh)] + (update local :vbox assoc :x nx :y ny)))) + + ))) + + diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 422b4a379f..282efc5002 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -86,28 +86,33 @@ (rx/of (append-undo entry)))))))))) (defn generate-operations - [ma mb] - (let [ma-keys (set (keys ma)) - mb-keys (set (keys mb)) - added (set/difference mb-keys ma-keys) - removed (set/difference ma-keys mb-keys) - both (set/intersection ma-keys mb-keys)] - (d/concat - (mapv #(array-map :type :set :attr % :val (get mb %)) added) - (mapv #(array-map :type :set :attr % :val nil) removed) - (loop [items (seq both) - result []] - (if items - (let [k (first items) - vma (get ma k) - vmb (get mb k)] - (if (= vma vmb) - (recur (next items) result) - (recur (next items) - (conj result {:type :set - :attr k - :val vmb})))) - result))))) + ([ma mb] (generate-operations ma mb false)) + ([ma mb undo?] + (let [ops (let [ma-keys (set (keys ma)) + mb-keys (set (keys mb)) + added (set/difference mb-keys ma-keys) + removed (set/difference ma-keys mb-keys) + both (set/intersection ma-keys mb-keys)] + (d/concat + (mapv #(array-map :type :set :attr % :val (get mb %)) added) + (mapv #(array-map :type :set :attr % :val nil) removed) + (loop [items (seq both) + result []] + (if items + (let [k (first items) + vma (get ma k) + vmb (get mb k)] + (if (= vma vmb) + (recur (next items) result) + (recur (next items) + (conj result {:type :set + :attr k + :val vmb + :ignore-touched undo?})))) + result))))] + (if undo? + (conj ops {:type :set-touched :touched (:touched mb)}) + ops)))) (defn generate-changes [page-id objects1 objects2] @@ -150,55 +155,6 @@ ;; --- Common Helpers & Events -(defn- calculate-frame-overlap - [frames shape] - (let [xf (comp - (filter #(geom/overlaps? % (:selrect shape))) - (take 1)) - frame (first (into [] xf frames))] - (or (:id frame) uuid/zero))) - -(defn- calculate-shape-to-frame-relationship-changes - [page-id frames shapes] - (loop [shape (first shapes) - shapes (rest shapes) - rch [] - uch []] - (if (nil? shape) - [rch uch] - (let [fid (calculate-frame-overlap frames shape)] - (if (not= fid (:frame-id shape)) - (recur (first shapes) - (rest shapes) - (conj rch {:type :mov-objects - :page-id page-id - :parent-id fid - :shapes [(:id shape)]}) - (conj uch {:type :mov-objects - :page-id page-id - :parent-id (:frame-id shape) - :shapes [(:id shape)]})) - (recur (first shapes) - (rest shapes) - rch - uch)))))) - -(defn rehash-shape-frame-relationship - [ids] - (ptk/reify ::rehash-shape-frame-relationship - ptk/WatchEvent - (watch [_ state stream] - (let [page-id (:current-page-id state) - objects (lookup-page-objects state page-id) - - shapes (cph/select-toplevel-shapes objects) - frames (cph/select-frames objects) - - [rch uch] (calculate-shape-to-frame-relationship-changes page-id frames shapes)] - (when-not (empty? rch) - (rx/of (commit-changes rch uch {:commit-local? true}))))))) - - (defn get-frame-at-point [objects point] (let [frames (cph/select-frames objects)] @@ -278,7 +234,8 @@ items (conj-undo-entry items entry)] (-> state (update :workspace-undo assoc :items items - :index (inc index)))) + :index (min (inc index) + (dec MAX-UNDO-SIZE))))) state)) (defn- accumulate-undo-entry @@ -415,7 +372,7 @@ obj1 (get objects id) obj2 (f obj1) rch-operations (generate-operations obj1 obj2) - uch-operations (generate-operations obj2 obj1) + uch-operations (generate-operations obj2 obj1 true) rchg {:type :mod-obj :page-id page-id :operations rch-operations @@ -456,7 +413,7 @@ obj1 (get objects id) obj2 (f obj1) rops (generate-operations obj1 obj2) - uops (generate-operations obj2 obj1) + uops (generate-operations obj2 obj1 true) rchg {:type :mod-obj :page-id page-id :operations rops diff --git a/frontend/src/app/main/data/workspace/drawing.cljs b/frontend/src/app/main/data/workspace/drawing.cljs index c61420901d..bfe1144180 100644 --- a/frontend/src/app/main/data/workspace/drawing.cljs +++ b/frontend/src/app/main/data/workspace/drawing.cljs @@ -299,7 +299,7 @@ (rx/of dwc/start-undo-transaction) (rx/empty)) - (rx/of dw/deselect-all + (rx/of (dw/deselect-all) (dw/add-shape shape)))))))))) (def close-drawing-path diff --git a/frontend/src/app/main/data/workspace/grid.cljs b/frontend/src/app/main/data/workspace/grid.cljs index 9d70190873..feacd4fbff 100644 --- a/frontend/src/app/main/data/workspace/grid.cljs +++ b/frontend/src/app/main/data/workspace/grid.cljs @@ -21,7 +21,7 @@ (defonce ^:private default-square-params {:size 16 - :color {:value "#59B9E2" + :color {:color "#59B9E2" :opacity 0.2}}) (defonce ^:private default-layout-params @@ -30,7 +30,7 @@ :item-length nil :gutter 8 :margin 0 - :color {:value "#DE4762" + :color {:color "#DE4762" :opacity 0.1}}) (defonce default-grid-params diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 7e1c0b1892..44aa7881f0 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -18,6 +18,7 @@ [app.main.data.messages :as dm] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.libraries-helpers :as dwlh] [app.common.pages :as cp] [app.main.repo :as rp] [app.main.store :as st] @@ -30,25 +31,34 @@ [cljs.spec.alpha :as s] [potok.core :as ptk])) +(declare sync-file) + +(defn default-color-name [color] + (or (:color color) + (case (get-in color [:gradient :type]) + :linear (tr "workspace.gradients.linear") + :radial (tr "workspace.gradients.radial")))) + (defn add-color [color] - (us/assert ::us/string color) - (ptk/reify ::add-color - ptk/WatchEvent - (watch [_ state s] - (let [id (uuid/next) - rchg {:type :add-color - :color {:id id - :name color - :value color}} - uchg {:type :del-color - :id id}] - (rx/of #(assoc-in % [:workspace-local :color-for-rename] id) - (dwc/commit-changes [rchg] [uchg] {:commit-local? true})))))) + (let [id (uuid/next) + color (assoc color + :id id + :name (default-color-name color))] + (us/assert ::cp/color color) + (ptk/reify ::add-color + ptk/WatchEvent + (watch [_ state s] + (let [rchg {:type :add-color + :color color} + uchg {:type :del-color + :id id}] + (rx/of #(assoc-in % [:workspace-local :color-for-rename] id) + (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))) (defn add-recent-color [color] - (us/assert ::us/string color) + (us/assert ::cp/recent-color color) (ptk/reify ::add-recent-color ptk/WatchEvent (watch [_ state s] @@ -73,7 +83,8 @@ :color color} uchg {:type :mod-color :color prev}] - (rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})))))) + (rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}) + (sync-file nil)))))) (defn delete-color [{:keys [id] :as params}] @@ -114,8 +125,6 @@ :object prev}] (rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})))))) -(declare make-component-shape) - (def add-component (ptk/reify ::add-component ptk/WatchEvent @@ -134,7 +143,7 @@ (dws/prepare-create-group page-id shapes "Component-" true)) [new-shape new-shapes updated-shapes] - (make-component-shape group nil objects) + (dwlh/make-component-shape group objects) rchanges (conj rchanges {:type :add-component @@ -153,9 +162,15 @@ {:type :set :attr :component-file :val nil} + {:type :set + :attr :component-root? + :val (:component-root? updated-shape)} {:type :set :attr :shape-ref - :val (:shape-ref updated-shape)}]}) + :val (:shape-ref updated-shape)} + {:type :set + :attr :touched + :val (:touched updated-shape)}]}) updated-shapes)) uchanges (conj uchanges @@ -164,41 +179,31 @@ uchanges (into uchanges (map (fn [updated-shape] - {:type :mod-obj - :page-id page-id - :id (:id updated-shape) - :operations [{:type :set - :attr :component-id - :val nil} - {:type :set - :attr :component-file - :val nil} - {:type :set - :attr :shape-ref - :val nil}]}) + (let [original-shape (get objects (:id updated-shape))] + {:type :mod-obj + :page-id page-id + :id (:id updated-shape) + :operations [{:type :set + :attr :component-id + :val (:component-id original-shape)} + {:type :set + :attr :component-file + :val (:component-file original-shape)} + {:type :set + :attr :component-root? + :val (:component-root? original-shape)} + {:type :set + :attr :shape-ref + :val (:shape-ref original-shape)} + {:type :set + :attr :touched + :val (:touched original-shape)}]})) updated-shapes))] + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) (dws/select-shapes (d/ordered-set (:id group)))))))))) -(defn- make-component-shape - "Clone the shape and all children. Generate new ids and detach - from parent and frame. Update the original shapes to have links - to the new ones." - [shape parent-id objects] - (let [update-new-shape (fn [new-shape original-shape] - (assoc new-shape :frame-id nil)) - - update-original-shape (fn [original-shape new-shape] - (cond-> original-shape - true - (assoc :shape-ref (:id new-shape)) - - (nil? (:parent-id new-shape)) - (assoc :component-id (:id new-shape))))] - - (cph/clone-object shape parent-id objects update-new-shape update-original-shape))) - (defn delete-component [{:keys [id] :as params}] (us/assert ::us/uuid id) @@ -218,7 +223,7 @@ (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) (defn instantiate-component - [file-id component-id] + [file-id component-id position] (us/assert (s/nilable ::us/uuid) file-id) (us/assert ::us/uuid component-id) (ptk/reify ::instantiate-component @@ -230,18 +235,13 @@ component-shape (get-in component [:objects (:id component)]) orig-pos (gpt/point (:x component-shape) (:y component-shape)) - mouse-pos @ms/mouse-position - delta (gpt/subtract mouse-pos orig-pos) - - _ (js/console.log "orig-pos" (clj->js orig-pos)) - _ (js/console.log "mouse-pos" (clj->js mouse-pos)) - _ (js/console.log "delta" (clj->js delta)) + delta (gpt/subtract position orig-pos) page-id (:current-page-id state) objects (dwc/lookup-page-objects state page-id) unames (atom (dwc/retrieve-used-names objects)) - all-frames (cph/select-frames objects) + frame-id (cph/frame-id-by-position objects (gpt/add orig-pos delta)) update-new-shape (fn [new-shape original-shape] @@ -255,17 +255,24 @@ (as-> $ (assoc $ :name new-name) (geom/move $ delta) - (assoc $ :frame-id - (dwc/calculate-frame-overlap all-frames $)) + (assoc $ :frame-id frame-id) (assoc $ :parent-id (or (:parent-id $) (:frame-id $))) - (assoc $ :shape-ref (:id original-shape))) + (assoc $ :shape-ref (:id original-shape)) + (dissoc $ :touched)) (nil? (:parent-id original-shape)) - (assoc :component-id (:id original-shape)) + (assoc :component-id (:id original-shape) + :component-root? true) (and (nil? (:parent-id original-shape)) (some? file-id)) - (assoc :component-file file-id)))) + (assoc :component-file file-id) + + (and (nil? (:parent-id original-shape)) (nil? file-id)) + (dissoc :component-file) + + (some? (:parent-id original-shape)) + (dissoc :component-root?)))) [new-shape new-shapes _] (cph/clone-object component-shape @@ -299,9 +306,7 @@ (watch [_ state stream] (let [page-id (:current-page-id state) objects (dwc/lookup-page-objects state page-id) - root-id (cph/get-root-component id objects) - - shapes (cph/get-object-with-children root-id objects) + shapes (cph/get-object-with-children id objects) rchanges (map (fn [obj] {:type :mod-obj @@ -359,36 +364,43 @@ (d/update-in-when [:workspace-libraries file-id :data] cp/process-changes changes))))) -(declare generate-sync-file) -(declare generate-sync-page) -(declare generate-sync-shape-and-children) -(declare generate-sync-shape) -(declare remove-component-and-ref) -(declare remove-ref) -(declare update-attrs) -(declare sync-attrs) -(declare calc-new-pos) - (defn reset-component [id] (us/assert ::us/uuid id) (ptk/reify ::reset-component ptk/WatchEvent (watch [_ state stream] - (let [page-id (:current-page-id state) - page (get-in state [:workspace-data :pages-index page-id]) - objects (dwc/lookup-page-objects state page-id) - root-id (cph/get-root-component id objects) - root-shape (get objects id) - file-id (get root-shape :component-file) + ;; ===== Uncomment this to debug ===== + ;; (js/console.info "##### RESET-COMPONENT of shape" (str id)) + (let [page-id (:current-page-id state) + page (get-in state [:workspace-data :pages-index page-id]) + objects (dwc/lookup-page-objects state page-id) + shape (get objects id) + file-id (get shape :component-file) - components - (if (nil? file-id) - (get-in state [:workspace-data :components]) - (get-in state [:workspace-libraries file-id :data :components])) + [all-shapes component root-component] + (dwlh/resolve-shapes-and-components shape + objects + state + true) + + ;; ===== Uncomment this to debug ===== + ;; _ (js/console.info "shape" (:name shape) "<- component" (:name component)) + ;; _ (js/console.debug "all-shapes" (clj->js all-shapes)) + ;; _ (js/console.debug "component" (clj->js component)) + ;; _ (js/console.debug "root-component" (clj->js root-component)) [rchanges uchanges] - (generate-sync-shape-and-children root-shape page components)] + (dwlh/generate-sync-shape-and-children-components shape + all-shapes + component + root-component + (:id page) + nil + true)] + + ;; ===== Uncomment this to debug ===== + ;; (js/console.debug "rchanges" (clj->js rchanges)) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) @@ -398,41 +410,39 @@ (ptk/reify ::update-component ptk/WatchEvent (watch [_ state stream] - (let [page-id (:current-page-id state) - objects (dwc/lookup-page-objects state page-id) - root-id (cph/get-root-component id objects) - root-shape (get objects id) + ;; ===== Uncomment this to debug ===== + ;; (js/console.info "##### UPDATE-COMPONENT of shape" (str id)) + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + shape (get objects id) + file-id (get shape :component-file) - component-id (get root-shape :component-id) - component-objs (dwc/lookup-component-objects state component-id) - component-obj (get component-objs component-id) + [all-shapes component root-component] + (dwlh/resolve-shapes-and-components shape + objects + state + true) - ;; Clone again the original shape and its children, maintaing - ;; the ids of the cloned shapes. If the original shape has some - ;; new child shapes, the cloned ones will have new generated ids. - update-new-shape (fn [new-shape original-shape] - (cond-> new-shape - true - (assoc :frame-id nil) + ;; ===== Uncomment this to debug ===== + ;; _ (js/console.info "shape" (:name shape) "-> component" (:name component)) + ;; _ (js/console.debug "all-shapes" (clj->js all-shapes)) + ;; _ (js/console.debug "component" (clj->js component)) + ;; _ (js/console.debug "root-component" (clj->js root-component)) - (some? (:shape-ref original-shape)) - (assoc :id (:shape-ref original-shape)))) + [rchanges uchanges] + (dwlh/generate-sync-shape-inverse shape + all-shapes + component + root-component + page-id)] - [new-shape new-shapes _] - (cph/clone-object root-shape nil objects update-new-shape) - - rchanges [{:type :mod-component - :id component-id - :name (:name new-shape) - :shapes new-shapes}] - - uchanges [{:type :mod-component - :id component-id - :name (:name component-obj) - :shapes (vals component-objs)}]] + ;; ===== Uncomment this to debug ===== + ;; (js/console.debug "rchanges" (clj->js rchanges)) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) +(declare sync-file-2nd-stage) + (defn sync-file [file-id] (us/assert (s/nilable ::us/uuid) file-id) @@ -445,14 +455,57 @@ ptk/WatchEvent (watch [_ state stream] - (let [[rchanges uchanges] (generate-sync-file state file-id)] + ;; ===== Uncomment this to debug ===== + ;; (js/console.info "##### SYNC-FILE" (str (or file-id "local"))) + (let [library-changes [(dwlh/generate-sync-library :components file-id state) + (dwlh/generate-sync-library :colors file-id state) + (dwlh/generate-sync-library :typographies file-id state)] + file-changes [(dwlh/generate-sync-file :components file-id state) + (dwlh/generate-sync-file :colors file-id state) + (dwlh/generate-sync-file :typographies file-id state)] + rchanges (d/concat [] + (->> library-changes (remove nil?) (map first) (flatten)) + (->> file-changes (remove nil?) (map first) (flatten))) + uchanges (d/concat [] + (->> library-changes (remove nil?) (map second) (flatten)) + (->> file-changes (remove nil?) (map second) (flatten)))] + ;; ===== Uncomment this to debug ===== + ;; (js/console.debug "rchanges" (clj->js rchanges)) (rx/concat + (rx/of (dm/hide-tag :sync-dialog)) (when rchanges (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))) (when file-id (rp/mutation :update-sync {:file-id (get-in state [:workspace-file :id]) - :library-id file-id}))))))) + :library-id file-id})) + (when (some? library-changes) + (rx/of (sync-file-2nd-stage file-id)))))))) + +(defn sync-file-2nd-stage + "If some components have been modified, we need to launch another synchronization + to update the instances of the changed components." + ;; TODO: this does not work if there are multiple nested components. Only the + ;; first level will be updated. + ;; To solve this properly, it would be better to launch another sync-file + ;; recursively. But for this not to cause an infinite loop, we need to + ;; implement updated-at at component level, to detect what components have + ;; not changed, and then not to apply sync and terminate the loop. + [file-id] + (us/assert (s/nilable ::us/uuid) file-id) + (ptk/reify ::sync-file-2nd-stage + ptk/WatchEvent + (watch [_ state stream] + ;; ===== Uncomment this to debug ===== + ;; (js/console.info "##### SYNC-FILE" (str (or file-id "local")) "(2nd stage)") + (let [[rchanges1 uchanges1] (dwlh/generate-sync-file :components nil state) + [rchanges2 uchanges2] (dwlh/generate-sync-library :components file-id state) + rchanges (d/concat rchanges1 rchanges2) + uchanges (d/concat uchanges1 uchanges2)] + (when rchanges + ;; ===== Uncomment this to debug ===== + ;; (js/console.debug "rchanges" (clj->js rchanges)) + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))) (def ignore-sync (ptk/reify ::sync-file @@ -486,191 +539,50 @@ [{:label (tr "workspace.updates.update") :callback do-update} {:label (tr "workspace.updates.dismiss") - :callback do-dismiss}])))))) + :callback do-dismiss}] + :sync-dialog)))))) -(defn- generate-sync-file - [state file-id] - (let [components - (if (nil? file-id) - (get-in state [:workspace-data :components]) - (get-in state [:workspace-libraries file-id :data :components]))] - (when (some? components) - (loop [pages (seq (vals (get-in state [:workspace-data :pages-index]))) - rchanges [] - uchanges []] - (let [page (first pages)] - (if (nil? page) - [rchanges uchanges] - (let [[page-rchanges page-uchanges] - (generate-sync-page file-id page components)] - (recur (next pages) - (concat rchanges page-rchanges) - (concat uchanges page-uchanges))))))))) +(defn add-typography + ([typography] (add-typography typography true)) + ([typography edit?] + (let [typography (update typography :id #(or % (uuid/next)))] + (us/assert ::cp/typography typography) + (ptk/reify ::add-typography + ptk/WatchEvent + (watch [_ state s] + (let [rchg {:type :add-typography + :typography (assoc typography :ts (.now js/Date))} + uchg {:type :del-typography + :id (:id typography)}] + (rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}) + #(cond-> % + edit? + (assoc-in [:workspace-local :rename-typography] (:id typography)))))))))) -(defn- generate-sync-page - [file-id page components] - (let [linked-shapes - (cph/select-objects #(and (some? (:component-id %)) - (= (:component-file %) file-id)) - page)] - (loop [shapes (seq linked-shapes) - rchanges [] - uchanges []] - (let [shape (first shapes)] - (if (nil? shape) - [rchanges uchanges] - (let [[shape-rchanges shape-uchanges] - (generate-sync-shape-and-children shape page components)] - (recur (next shapes) - (concat rchanges shape-rchanges) - (concat uchanges shape-uchanges)))))))) +(defn update-typography + [typography] + (us/assert ::cp/typography typography) -(defn- generate-sync-shape-and-children - [root-shape page components] - (let [objects (get page :objects) - all-shapes (cph/get-object-with-children (:id root-shape) objects) - component (get components (:component-id root-shape)) - root-component (get-in component [:objects (:shape-ref root-shape)])] - (loop [shapes (seq all-shapes) - rchanges [] - uchanges []] - (let [shape (first shapes)] - (if (nil? shape) - [rchanges uchanges] - (let [[shape-rchanges shape-uchanges] - (generate-sync-shape shape root-shape root-component page component)] - (recur (next shapes) - (concat rchanges shape-rchanges) - (concat uchanges shape-uchanges)))))))) - -(defn- generate-sync-shape - [shape root-shape root-component page component] - (if (nil? component) - (remove-component-and-ref shape page) - (let [component-shape (get (:objects component) (:shape-ref shape))] - (if (nil? component-shape) - (remove-ref shape page) - (update-attrs shape component-shape root-shape root-component page))))) - -(defn- remove-component-and-ref - [shape page] - [[{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations [{:type :set - :attr :component-id - :val nil} - {:type :set - :attr :component-file - :val nil} - {:type :set - :attr :shape-ref - :val nil}]}] - [{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations [{:type :set - :attr :component-id - :val (:component-id shape)} - {:type :set - :attr :component-file - :val (:component-file shape)} - {:type :set - :attr :shape-ref - :val (:shape-ref shape)}]}]]) - -(defn- remove-ref - [shape page] - [[{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations [{:type :set - :attr :shape-ref - :val nil}]}] - [{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations [{:type :set - :attr :shape-ref - :val (:shape-ref shape)}]}]]) - -(defn- update-attrs - [shape component-shape root-shape root-component page] - (let [new-pos (calc-new-pos shape component-shape root-shape root-component)] - (loop [attrs (seq sync-attrs) - roperations [{:type :set - :attr :x - :val (:x new-pos)} - {:type :set - :attr :y - :val (:y new-pos)}] - uoperations [{:type :set - :attr :x - :val (:x shape)} - {:type :set - :attr :y - :val (:y shape)}]] - - (let [attr (first attrs)] - (if (nil? attr) - (let [rchanges [{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations roperations}] - uchanges [{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations uoperations}]] - [rchanges uchanges]) - (if-not (contains? shape attr) - (recur (next attrs) - roperations - uoperations) - (let [roperation {:type :set - :attr attr - :val (get component-shape attr)} - uoperation {:type :set - :attr attr - :val (get shape attr)}] - (recur (next attrs) - (conj roperations roperation) - (conj uoperations uoperation))))))))) - -(def sync-attrs [:content - :fill-color - :fill-color-ref-file - :fill-color-ref-id - :fill-opacity - :font-family - :font-size - :font-style - :font-weight - :letter-spacing - :line-height - :proportion - :rx - :ry - :stroke-color - :stroke-color-ref-file - :stroke-color-ref-id - :stroke-opacity - :stroke-style - :stroke-width - :stroke-alignment - :text-align - :width - :height - :interactions - :points - :transform]) - -(defn- calc-new-pos - [shape component-shape root-shape root-component] - (let [root-pos (gpt/point (:x root-shape) (:y root-shape)) - root-component-pos (gpt/point (:x root-component) (:y root-component)) - component-pos (gpt/point (:x component-shape) (:y component-shape)) - delta (gpt/subtract component-pos root-component-pos) - shape-pos (gpt/point (:x shape) (:y shape)) - new-pos (gpt/add root-pos delta)] - new-pos)) + (ptk/reify ::update-typography + ptk/WatchEvent + (watch [_ state stream] + (let [prev (get-in state [:workspace-data :typographies (:id typography)]) + rchg {:type :mod-typography + :typography typography} + uchg {:type :mod-typography + :typography prev}] + (rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}) + (sync-file nil)))))) +(defn delete-typography + [id] + (us/assert ::us/uuid id) + (ptk/reify ::delete-typography + ptk/WatchEvent + (watch [_ state stream] + (let [prev (get-in state [:workspace-data :typographies id]) + rchg {:type :del-typography + :id id} + uchg {:type :add-typography + :typography prev}] + (rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})))))) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs new file mode 100644 index 0000000000..6144cbcfb6 --- /dev/null +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -0,0 +1,640 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.data.workspace.libraries-helpers + (:require + [cljs.spec.alpha :as s] + [app.common.spec :as us] + [app.common.data :as d] + [app.common.pages-helpers :as cph] + [app.common.geom.point :as gpt] + [app.common.pages :as cp] + [app.util.text :as ut])) + +(defonce empty-changes [[] []]) + +(defonce color-sync-attrs + [[:fill-color-ref-id :color :fill-color] + [:fill-color-ref-id :gradient :fill-color-gradient] + [:fill-color-ref-id :opacity :fill-opacity] + + [:stroke-color-ref-id :color :stroke-color] + [:stroke-color-ref-id :gradient :stroke-color-gradient] + [:stroke-color-ref-id :opacity :stroke-opacity]]) + +(declare generate-sync-container) +(declare generate-sync-shape) +(declare has-asset-reference-fn) + +(declare get-assets) +(declare resolve-shapes-and-components) +(declare generate-sync-shape-and-children-components) +(declare generate-sync-shape-inverse) +(declare generate-sync-shape<-component) +(declare generate-sync-shape->component) +(declare remove-component-and-ref) +(declare remove-ref) +(declare reset-touched) +(declare update-attrs) +(declare calc-new-pos) + + +;; ---- Create a new component ---- + +(defn make-component-shape + "Clone the shape and all children. Generate new ids and detach + from parent and frame. Update the original shapes to have links + to the new ones." + [shape objects] + (assert (nil? (:component-id shape))) + (assert (nil? (:component-file shape))) + (assert (nil? (:shape-ref shape))) + (let [update-new-shape (fn [new-shape original-shape] + (cond-> new-shape + true + (assoc :frame-id nil) + + (nil? (:parent-id new-shape)) + (dissoc :component-id + :component-file + :component-root? + :shape-ref))) + + ;; Make the original shape an instance of the new component. + ;; If one of the original shape children already was a component + ;; instance, the 'instanceness' is copied into the new component. + update-original-shape (fn [original-shape new-shape] + (cond-> original-shape + true + (-> (assoc :shape-ref (:id new-shape)) + (dissoc :touched)) + + (nil? (:parent-id new-shape)) + (assoc :component-id (:id new-shape) + :component-file nil + :component-root? true) + + (some? (:parent-id new-shape)) + (dissoc :component-root?)))] + + (cph/clone-object shape nil objects update-new-shape update-original-shape))) + + +;; ---- General library synchronization functions ---- + +(defn generate-sync-file + "Generate changes to synchronize all shapes in all pages of the current file, + with the given asset of the given library." + [asset-type library-id state] + + (s/assert #{:colors :components :typographies} asset-type) + (s/assert (s/nilable ::us/uuid) library-id) + + (let [library-items + (if (nil? library-id) + (get-in state [:workspace-data asset-type]) + (get-in state [:workspace-libraries library-id :data asset-type]))] + + (if (empty? library-items) + empty-changes + + (loop [pages (vals (get-in state [:workspace-data :pages-index])) + rchanges [] + uchanges []] + (if-let [page (first pages)] + (let [[page-rchanges page-uchanges] + (generate-sync-container asset-type + library-id + state + page + (:id page) + nil)] + (recur (next pages) + (d/concat rchanges page-rchanges) + (d/concat uchanges page-uchanges))) + [rchanges uchanges]))))) + +(defn generate-sync-library + "Generate changes to synchronize all shapes inside components of the current + file library, that use the given type of asset of the given library." + [asset-type library-id state] + (let [library-items + (if (nil? library-id) + (get-in state [:workspace-data asset-type]) + (get-in state [:workspace-libraries library-id :data asset-type]))] + (if (empty? library-items) + empty-changes + + (loop [local-components (seq (vals (get-in state [:workspace-data :components]))) + rchanges [] + uchanges []] + (if-let [local-component (first local-components)] + (let [[comp-rchanges comp-uchanges] + (generate-sync-container asset-type + library-id + state + local-component + nil + (:id local-component))] + (recur (next local-components) + (d/concat rchanges comp-rchanges) + (d/concat uchanges comp-uchanges))) + [rchanges uchanges]))))) + +(defn- generate-sync-container + "Generate changes to synchronize all shapes in a particular container + (a page or a component) that are linked to the given library." + [asset-type library-id state container page-id component-id] + (let [has-asset-reference? (has-asset-reference-fn asset-type library-id) + linked-shapes (cph/select-objects has-asset-reference? container)] + (loop [shapes (seq linked-shapes) + rchanges [] + uchanges []] + (if-let [shape (first shapes)] + (let [[shape-rchanges shape-uchanges] + (generate-sync-shape asset-type + library-id + state + (get container :objects) + page-id + component-id + shape)] + (recur (next shapes) + (d/concat rchanges shape-rchanges) + (d/concat uchanges shape-uchanges))) + [rchanges uchanges])))) + +(defn- has-asset-reference-fn + "Gets a function that checks if a shape uses some asset of the given type + in the given library." + [asset-type library-id] + (case asset-type + :components + (fn [shape] (and (:component-root? shape) + (= (:component-file shape) library-id))) + + :colors + (fn [shape] (if (= (:type shape) :text) + (->> shape + :content + ;; Check if any node in the content has a reference for the library + (ut/some-node + #(or (and (some? (:stroke-color-ref-id %)) + (= library-id (:stroke-color-ref-file %))) + (and (some? (:fill-color-ref-id %)) + (= library-id (:fill-color-ref-file %)))))) + (some + #(let [attr (name %) + attr-ref-id (keyword (str attr "-ref-id")) + attr-ref-file (keyword (str attr "-ref-file"))] + (and (get shape attr-ref-id) + (= library-id (get shape attr-ref-file)))) + (map #(nth % 2) color-sync-attrs)))) + + :typographies + (fn [shape] + (and (= (:type shape) :text) + (->> shape + :content + ;; Check if any node in the content has a reference for the library + (ut/some-node + #(and (some? (:typography-ref-id %)) + (= library-id (:typography-ref-file %))))))))) + +(defmulti generate-sync-shape + "Generate changes to synchronize one shape, that use the given type + of asset of the given library." + (fn [type _ _ _ _ _ _ _] type)) + +(defmethod generate-sync-shape :components + [_ library-id state objects page-id component-id shape] + (let [[all-shapes component root-component] + (resolve-shapes-and-components shape + objects + state + false)] + + (generate-sync-shape-and-children-components shape + all-shapes + component + root-component + page-id + component-id + false))) + +(defn- generate-sync-text-shape [shape page-id component-id update-node] + (let [old-content (:content shape) + new-content (ut/map-node update-node old-content) + rchanges [(d/without-nils {:type :mod-obj + :page-id page-id + :component-id component-id + :id (:id shape) + :operations [{:type :set + :attr :content + :val new-content}]})] + lchanges [(d/without-nils {:type :mod-obj + :page-id page-id + :component-id component-id + :id (:id shape) + :operations [{:type :set + :attr :content + :val old-content}]})]] + (if (= new-content old-content) + empty-changes + [rchanges lchanges]))) + + +(defmethod generate-sync-shape :colors + [_ library-id state _ page-id component-id shape] + + ;; Synchronize a shape that uses some colors of the library. The value of the + ;; color in the library is copied to the shape. + (let [colors (get-assets library-id :colors state)] + (if (= :text (:type shape)) + (let [update-node (fn [node] + (if-let [color (get colors (:fill-color-ref-id node))] + (assoc node + :fill-color (:color color) + :fill-opacity (:opacity color) + :fill-color-gradient (:gradient color)) + node))] + (generate-sync-text-shape shape page-id component-id update-node)) + (loop [attrs (seq color-sync-attrs) + roperations [] + uoperations []] + (let [[attr-ref-id color-attr attr] (first attrs)] + (if (nil? attr) + (if (empty? roperations) + empty-changes + (let [rchanges [(d/without-nils {:type :mod-obj + :page-id page-id + :component-id component-id + :id (:id shape) + :operations roperations})] + uchanges [(d/without-nils {:type :mod-obj + :page-id page-id + :component-id component-id + :id (:id shape) + :operations uoperations})]] + [rchanges uchanges])) + (if-not (contains? shape attr-ref-id) + (recur (next attrs) + roperations + uoperations) + (let [color (get colors (get shape attr-ref-id)) + roperation {:type :set + :attr attr + :val (color-attr color) + :ignore-touched true} + uoperation {:type :set + :attr attr + :val (get shape attr) + :ignore-touched true}] + (recur (next attrs) + (conj roperations roperation) + (conj uoperations uoperation)))))))))) + +(defmethod generate-sync-shape :typographies + [_ library-id state _ page-id component-id shape] + + ;; Synchronize a shape that uses some typographies of the library. The attributes + ;; of the typography are copied to the shape." + (let [typographies (get-assets library-id :typographies state) + update-node (fn [node] + (if-let [typography (get typographies (:typography-ref-id node))] + (merge node (d/without-keys typography [:name :id])) + node))] + (generate-sync-text-shape shape page-id component-id update-node))) + + +;; ---- Component synchronization helpers ---- + +(defn- get-assets + [library-id asset-type state] + (if (nil? library-id) + (get-in state [:workspace-data asset-type]) + (get-in state [:workspace-libraries library-id :data asset-type]))) + +(defn- get-component + [state file-id component-id] + (let [components (if (nil? file-id) + (get-in state [:workspace-data :components]) + (get-in state [:workspace-libraries file-id :data :components]))] + (get components component-id))) + +(defn resolve-shapes-and-components + "Get all shapes inside a component instance, and the component they are + linked with. If follow-indirection? is true, and the shape corresponding + to the root shape is also a component instance, follow the link and get + the final component." + [shape objects state follow-indirection?] + (loop [all-shapes (cph/get-object-with-children (:id shape) objects) + local-objects objects + local-shape shape] + + (let [root-shape (cph/get-root-shape local-shape local-objects) + component (get-component state + (get root-shape :component-file) + (get root-shape :component-id)) + component-shape (get-in component [:objects (:shape-ref local-shape)])] + + (if (or (nil? (:component-id component-shape)) + (not follow-indirection?)) + [all-shapes component component-shape] + (let [resolve-indirection + (fn [shape] + (let [component-shape (get-in component [:objects (:shape-ref shape)])] + (-> shape + (assoc :shape-ref (:shape-ref component-shape)) + (d/assoc-when :component-id (:component-id component-shape)) + (d/assoc-when :component-file (:component-file component-shape))))) + new-shapes (map resolve-indirection all-shapes)] + (recur new-shapes + (:objects component) + component-shape)))))) + +(defn generate-sync-shape-and-children-components + "Generate changes to synchronize one shape that the root of a component + instance, and all its children, from the given component. + If reset? is false, all atributes of each component shape that have + changed, and whose group has not been touched in the instance shape will + be copied to this one. + If reset? is true, all changed attributes will be copied and the 'touched' + flags in the instance shape will be cleared." + [root-shape all-shapes component root-component page-id component-id reset?] + (loop [shapes (seq all-shapes) + rchanges [] + uchanges []] + (let [shape (first shapes)] + (if (nil? shape) + [rchanges uchanges] + (let [[shape-rchanges shape-uchanges] + (generate-sync-shape<-component + shape + root-shape + root-component + component + page-id + component-id + reset?)] + (recur (next shapes) + (d/concat rchanges shape-rchanges) + (d/concat uchanges shape-uchanges))))))) + +(defn- generate-sync-shape-inverse + "Generate changes to update the component a shape is linked to, from + the values in the shape and all its children. + All atributes of each instance shape that have changed, will be copied + to the component shape. Also clears the 'touched' flags in the source + shapes. + And if the component shapes are, in turn, instances of a second component, + their 'touched' flags will be set accordingly." + [root-shape all-shapes component root-component page-id] + (loop [shapes (seq all-shapes) + rchanges [] + uchanges []] + (let [shape (first shapes)] + (if (nil? shape) + [rchanges uchanges] + (let [[shape-rchanges shape-uchanges] + (generate-sync-shape->component + shape + root-shape + root-component + component + page-id)] + (recur (next shapes) + (d/concat rchanges shape-rchanges) + (d/concat uchanges shape-uchanges))))))) + +(defn- generate-sync-shape<-component + "Generate changes to synchronize one shape that is linked to other shape + inside a component. Same considerations as above about reset-touched?" + [shape root-shape root-component component page-id component-id reset?] + (if (nil? component) + (remove-component-and-ref shape page-id component-id) + (let [component-shape (get (:objects component) (:shape-ref shape))] + (if (nil? component-shape) + (remove-ref shape page-id component-id) + (update-attrs shape + component-shape + root-shape + root-component + page-id + component-id + {:omit-touched? (not reset?) + :reset-touched? reset? + :set-touched? false}))))) + +(defn- generate-sync-shape->component + "Generate changes to synchronize one shape inside a component, with other + shape that is linked to it." + [shape root-shape root-component component page-id] + ;; ===== Uncomment this to debug ===== + ;; (js/console.log "component" (clj->js component)) + (if (nil? component) + empty-changes + (let [component-shape (get (:objects component) (:shape-ref shape))] + ;; ===== Uncomment this to debug ===== + ;; (js/console.log "component-shape" (clj->js component-shape)) + (if (nil? component-shape) + empty-changes + (let [;; ===== Uncomment this to debug ===== + ;; _(js/console.info "update" (:name shape) "->" (:name component-shape)) + [rchanges1 uchanges1] + (update-attrs component-shape + shape + root-component + root-shape + nil + (:id root-component) + {:omit-touched? false + :reset-touched? false + :set-touched? true}) + [rchanges2 uchanges2] + (reset-touched shape + page-id + nil)] + [(d/concat rchanges1 rchanges2) + (d/concat uchanges2 uchanges2)]))))) + + +; ---- Operation generation helpers ---- + +(defn- remove-component-and-ref + [shape page-id component-id] + [[(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :operations [{:type :set + :attr :component-root? + :val nil} + {:type :set + :attr :component-id + :val nil} + {:type :set + :attr :component-file + :val nil} + {:type :set + :attr :shape-ref + :val nil} + {:type :set-touched + :touched nil}]})] + [(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :operations [{:type :set + :attr :component-root? + :val (:component-root? shape)} + {:type :set + :attr :component-id + :val (:component-id shape)} + {:type :set + :attr :component-file + :val (:component-file shape)} + {:type :set + :attr :shape-ref + :val (:shape-ref shape)} + {:type :set-touched + :touched (:touched shape)}]})]]) + +(defn- -remove-ref + [shape page-id component-id] + [[(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :operations [{:type :set + :attr :shape-ref + :val nil} + {:type :set-touched + :touched nil}]})] + [(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :operations [{:type :set + :attr :shape-ref + :val (:shape-ref shape)} + {:type :set-touched + :touched (:touched shape)}]})]]) + +(defn- reset-touched + [shape page-id component-id] + [[(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :operations [{:type :set-touched + :touched nil}]})] + [(d/without-nils {:type :mod-obj + :id (:id shape) + :page-id page-id + :component-id component-id + :operations [{:type :set-touched + :touched (:touched shape)}]})]]) + +(defn- update-attrs + "The main function that implements the sync algorithm. Copy + attributes that have changed in the origin shape to the dest shape. + If omit-touched? is true, attributes whose group has been touched + in the destination shape will be ignored. + If reset-touched? is true, the 'touched' flags will be cleared in + the dest shape. + If set-touched? is true, the corresponding 'touched' flags will be + set in dest shape if they are different than their current values." + [dest-shape origin-shape dest-root origin-root page-id component-id + {:keys [omit-touched? reset-touched? set-touched?] :as options}] + + ;; === Uncomment this to debug synchronization === + ;; (println "SYNC" + ;; "[C]" (:name origin-shape) + ;; "->" + ;; (if page-id "[W]" ["C"]) + ;; (:name dest-shape) + ;; (str options)) + + (let [; The position attributes need a special sync algorith, because we do + ; not synchronize the absolute position, but the position relative of + ; the container shape of the component. + new-pos (calc-new-pos dest-shape origin-shape dest-root origin-root) + touched (get dest-shape :touched #{})] + + (loop [attrs (seq (keys (dissoc cp/component-sync-attrs :x :y))) + roperations (if (or (not= (:x new-pos) (:x dest-shape)) + (not= (:y new-pos) (:y dest-shape))) + [{:type :set :attr :x :val (:x new-pos)} + {:type :set :attr :y :val (:y new-pos)}] + []) + uoperations (if (or (not= (:x new-pos) (:x dest-shape)) + (not= (:y new-pos) (:y dest-shape))) + [{:type :set :attr :x :val (:x dest-shape)} + {:type :set :attr :y :val (:y dest-shape)}] + [])] + + (let [attr (first attrs)] + (if (nil? attr) + (let [roperations (if reset-touched? + (conj roperations + {:type :set-touched + :touched nil}) + roperations) + + uoperations (if reset-touched? + (conj uoperations + {:type :set-touched + :touched (:touched dest-shape)}) + uoperations) + + rchanges [(d/without-nils {:type :mod-obj + :id (:id dest-shape) + :page-id page-id + :component-id component-id + :operations roperations})] + uchanges [(d/without-nils {:type :mod-obj + :id (:id dest-shape) + :page-id page-id + :component-id component-id + :operations uoperations})]] + [rchanges uchanges]) + + (if-not (contains? dest-shape attr) + (recur (next attrs) + roperations + uoperations) + (let [roperation {:type :set + :attr attr + :val (get origin-shape attr) + :ignore-touched (not set-touched?)} + uoperation {:type :set + :attr attr + :val (get dest-shape attr) + :ignore-touched (not set-touched?)} + + attr-group (get cp/component-sync-attrs attr)] + (if (and (touched attr-group) omit-touched?) + (recur (next attrs) + roperations + uoperations) + (recur (next attrs) + (conj roperations roperation) + (conj uoperations uoperation)))))))))) + +(defn- calc-new-pos + [dest-shape origin-shape dest-root origin-root] + (let [root-pos (gpt/point (:x dest-root) (:y dest-root)) + origin-root-pos (gpt/point (:x origin-root) (:y origin-root)) + origin-pos (gpt/point (:x origin-shape) (:y origin-shape)) + delta (gpt/subtract origin-pos origin-root-pos) + shape-pos (gpt/point (:x dest-shape) (:y dest-shape)) + new-pos (gpt/add root-pos delta)] + new-pos)) + diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index fb1366ee99..a35c0ae67b 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -26,6 +26,7 @@ [app.util.router :as rt] [app.util.time :as dt] [app.util.transit :as t] + [app.util.avatars :as avatars] [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk])) @@ -208,7 +209,7 @@ (watch [_ state stream] (->> (rx/zip (rp/query :file {:id file-id}) (rp/query :file-users {:id file-id}) - (rp/query :project-by-id {:project-id project-id}) + (rp/query :project {:id project-id}) (rp/query :file-libraries {:file-id file-id})) (rx/first) (rx/map (fn [bundle] (apply bundle-fetched bundle))) @@ -224,6 +225,12 @@ :else (throw error)))))))) +(defn assoc-profile-avatar + [{:keys [photo fullname] :as profile}] + (cond-> profile + (or (nil? photo) (empty? photo)) + (assoc :photo (avatars/generate {:name fullname})))) + (defn- bundle-fetched [file users project libraries] (ptk/reify ::bundle-fetched @@ -236,13 +243,14 @@ ptk/UpdateEvent (update [_ state] - (assoc state - :workspace-undo {} - :workspace-project project - :workspace-file file - :workspace-data (:data file) - :workspace-users (d/index-by :id users) - :workspace-libraries (d/index-by :id libraries))))) + (let [users (map assoc-profile-avatar users)] + (assoc state + :workspace-undo {} + :workspace-project project + :workspace-file file + :workspace-data (:data file) + :workspace-users (d/index-by :id users) + :workspace-libraries (d/index-by :id libraries)))))) ;; --- Set File shared @@ -300,8 +308,7 @@ (rx/mapcat #(rx/zip (rp/query :file-library {:file-id library-id}) (rp/query :media-objects {:file-id library-id - :is-local false}) - (rp/query :colors {:file-id library-id})))) + :is-local false})))) (rx/map file-linked)))))) (defn file-linked @@ -394,7 +401,8 @@ (rx/concat (rx/of (dm/show {:content (tr "media.loading") :type :info - :timeout nil})) + :timeout nil + :tag :media-loading})) (->> (if (string? uri) (->> (rx/of uri) (rx/map prepare-uri) @@ -420,7 +428,7 @@ :else (rx/throw error)))) (rx/finalize (fn [] - (st/emit! dm/hide))))))))) + (st/emit! (dm/hide-tag :media-loading)))))))))) ;; --- Delete media object diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 27b32ac5bf..136333ef6b 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -12,6 +12,7 @@ [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk] + [linked.set :as lks] [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes :as geom] @@ -21,6 +22,7 @@ [app.common.spec :as us] [app.common.uuid :as uuid] [app.main.data.workspace.common :as dwc] + [app.main.data.modal :as md] [app.main.streams :as ms] [app.main.worker :as uw])) @@ -65,7 +67,7 @@ (ms/mouse-up? %)) stream)] (rx/concat - (rx/of deselect-all) + (rx/of (deselect-all)) (->> ms/mouse-position (rx/scan (fn [data pos] (if data @@ -116,15 +118,26 @@ objects (dwc/lookup-page-objects state page-id)] (rx/of (dwc/expand-all-parents ids objects)))))) -(def deselect-all +(defn deselect-all "Clear all possible state of drawing, edition - or any similar action taken by the user." - (ptk/reify ::deselect-all - ptk/UpdateEvent - (update [_ state] - (update state :workspace-local #(-> % - (assoc :selected (d/ordered-set)) - (dissoc :selected-frame)))))) + or any similar action taken by the user. + When `check-modal` the method will check if a modal is opened + and not deselect if it's true" + ([] (deselect-all false)) + + ([check-modal] + (ptk/reify ::deselect-all + ptk/UpdateEvent + (update [_ state] + + ;; Only deselect if there is no modal openned + (cond-> state + (or (not check-modal) + (not (::md/modal state))) + (update :workspace-local + #(-> % + (assoc :selected (d/ordered-set)) + (dissoc :selected-frame)))))))) ;; --- Select Shapes (By selrect) @@ -133,13 +146,18 @@ ptk/WatchEvent (watch [_ state stream] (let [page-id (:current-page-id state) - selrect (get-in state [:workspace-local :selrect])] + selrect (get-in state [:workspace-local :selrect]) + is-not-blocked (fn [shape-id] (not (get-in state [:workspace-data + :pages-index page-id + :objects shape-id + :blocked] false)))] (rx/merge (rx/of (update-selrect nil)) (when selrect (->> (uw/ask! {:cmd :selection/query :page-id page-id :rect selrect}) + (rx/map #(into lks/empty-linked-set (filter is-not-blocked) %)) (rx/map select-shapes)))))))) (defn select-inside-group @@ -151,9 +169,15 @@ objects (dwc/lookup-page-objects state page-id) group (get objects group-id) children (map #(get objects %) (:shapes group)) - selected (d/seek #(geom/has-point? % position) children)] + + ;; We need to reverse the children because if two children + ;; overlap we want to select the one that's over (and it's + ;; in the later vector position + selected (->> children + reverse + (d/seek #(geom/has-point? % position)))] (when selected - (rx/of deselect-all (select-shape (:id selected)))))))) + (rx/of (deselect-all) (select-shape (:id selected)))))))) ;; --- Group shapes @@ -220,7 +244,10 @@ :page-id page-id :parent-id parent-id :shapes shapes - :index index-in-parent}] + :index index-in-parent} + {:type :del-obj + :page-id page-id + :id (:id group)}] uchanges [{:type :add-obj :page-id page-id :id (:id group) @@ -245,7 +272,7 @@ (def ^:private change->name #(get-in % [:obj :name])) -(defn- prepare-duplicate-changes +(defn prepare-duplicate-changes "Prepare objects to paste: generate new id, give them unique names, move to the position of mouse pointer, and find in what frame they fit." @@ -277,10 +304,6 @@ renamed-obj (assoc obj :id id :name name) moved-obj (geom/move renamed-obj delta) frames (cph/select-frames objects) - frame-id (if frame-id - frame-id - (dwc/calculate-frame-overlap frames moved-obj)) - parent-id (or parent-id frame-id) children-changes diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 3e2725848a..34616e94c4 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -19,7 +19,8 @@ [app.common.geom.shapes :as geom] [app.main.data.workspace.common :as dwc] [app.main.fonts :as fonts] - [app.util.object :as obj])) + [app.util.object :as obj] + [app.util.text :as ut])) (defn create-editor [] @@ -120,7 +121,10 @@ (defn- shape-current-values [shape pred attrs] (let [root (:content shape) - nodes (nodes-seq pred root)] + nodes (->> (nodes-seq pred root) + (map #(if (is-text-node? %) + (merge ut/default-text-attrs %) + %)))] (geom/get-attrs-multi nodes attrs))) (defn current-text-values diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 54491833d3..56985d95c8 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -233,6 +233,38 @@ (rx/first) (rx/map #(start-move from-position)))))) +(defn calculate-frame-for-move [ids] + (ptk/reify ::calculate-frame-for-move + ptk/WatchEvent + (watch [_ state stream] + (let [position @ms/mouse-position + page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + frame-id (cph/frame-id-by-position objects position) + + moving-shapes (->> ids + (map #(get objects %)) + (remove #(= (:frame-id %) frame-id))) + + rch [{:type :mov-objects + :page-id page-id + :parent-id frame-id + :shapes (mapv :id moving-shapes)}] + + moving-shapes-by-frame-id (group-by :frame-id moving-shapes) + + uch (->> moving-shapes-by-frame-id + (mapv (fn [[frame-id shapes]] + {:type :mov-objects + :page-id page-id + :parent-id frame-id + :shapes (mapv :id shapes)})))] + + (when-not (empty? rch) + (rx/of dwc/pop-undo-into-transaction + (dwc/commit-changes rch uch {:commit-local? true}) + dwc/commit-undo-transaction)))))) + (defn start-move ([from-position] (start-move from-position nil)) ([from-position ids] @@ -257,9 +289,12 @@ (rx/switch-map #(snap/closest-snap-move page-id shapes objects layout %)) (rx/map #(gpt/round % 0)) (rx/map gmt/translate-matrix) - (rx/map #(set-modifiers ids {:displacement %}))) + (rx/map #(fn [state] (assoc-in state [:workspace-local :modifiers] {:displacement %})))) - (rx/of (apply-modifiers ids) + (rx/of (set-modifiers ids) + (apply-modifiers ids) + (calculate-frame-for-move ids) + (fn [state] (update state :workspace-local dissoc :modifiers)) finish-transform))))))) (defn- get-displacement-with-grid @@ -339,13 +374,15 @@ ;; -- Apply modifiers (defn set-modifiers + ([ids] (set-modifiers ids nil true)) ([ids modifiers] (set-modifiers ids modifiers true)) ([ids modifiers recurse-frames?] (us/verify (s/coll-of uuid?) ids) (ptk/reify ::set-modifiers ptk/UpdateEvent (update [_ state] - (let [page-id (:current-page-id state) + (let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {})) + page-id (:current-page-id state) objects (dwc/lookup-page-objects state page-id) not-frame-id? @@ -429,10 +466,8 @@ ;; we have 3 different objects references). rchanges (conj (dwc/generate-changes page-id objects1 objects2) regchg) - uchanges (conj (dwc/generate-changes page-id objects2 objects0) regchg) - ] + uchanges (conj (dwc/generate-changes page-id objects2 objects0) regchg)] (rx/of dwc/start-undo-transaction (dwc/commit-changes rchanges uchanges {:commit-local? true}) - (dwc/rehash-shape-frame-relationship ids) dwc/commit-undo-transaction))))) diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index 7169782d6f..7146814df8 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -26,7 +26,8 @@ [app.main.ui.shapes.path :as path] [app.main.ui.shapes.rect :as rect] [app.main.ui.shapes.text :as text] - [app.main.ui.shapes.group :as group])) + [app.main.ui.shapes.group :as group] + [app.main.ui.shapes.shape :refer [shape-container]])) (def ^:private default-color "#E8E9EA") ;; $color-canvas @@ -56,7 +57,8 @@ [{:keys [shape] :as props}] (let [childs (mapv #(get objects %) (:shapes shape)) shape (geom/transform-shape shape)] - [:& frame-shape {:shape shape :childs childs}])))) + [:> shape-container {:shape shape} + [:& frame-shape {:shape shape :childs childs}]])))) (defn group-wrapper-factory [objects] @@ -78,10 +80,8 @@ frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))] (when (and shape (not (:hidden shape))) (let [shape (geom/transform-shape frame shape) - opts #js {:shape shape} - filter-id (filters/get-filter-id)] - [:g {:filter (filters/filter-str filter-id shape)} - [:& filters/filters {:filter-id filter-id :shape shape}] + opts #js {:shape shape}] + [:> shape-container {:shape shape} (case (:type shape) :curve [:> path/path-shape opts] :text [:> text/text-shape opts] diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index cbdf0d35ae..a20d94d6e1 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -21,8 +21,6 @@ [clojure.set :as set] [app.util.object :as obj])) -(defonce default-font "sourcesanspro") - (def google-fonts (preload-gfonts "fonts/gfonts.2020.04.23.json")) @@ -96,6 +94,9 @@ (register! :builtin local-fonts) (register! :google google-fonts) +(defn get-font-data [id] + (get @fontsdb id)) + (defn resolve-variants [id] (get-in @fontsdb [id :variants])) @@ -164,3 +165,8 @@ (defn ready [cb] (-> (obj/get-in js/document ["fonts" "ready"]) (p/then cb))) + +(defn get-default-variant [{:keys [variants]}] + (or + (d/seek #(or (= (:id %) "regular") (= (:name %) "regular")) variants) + (first variants))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 1de3066b3f..06b44ffda2 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -49,6 +49,10 @@ (def selected-shapes (l/derived :selected workspace-local)) +(defn make-selected-ref + [id] + (l/derived #(contains? % id) selected-shapes)) + (def selected-zoom (l/derived :zoom workspace-local)) @@ -97,6 +101,12 @@ (get-in state [:workspace-data :recent-colors] [])) st/state)) +(def workspace-file-typography + (l/derived (fn [state] + (when-let [file (:workspace-file state)] + (get-in file [:data :typographies]))) + st/state)) + (def workspace-project (l/derived :workspace-project st/state)) @@ -146,10 +156,9 @@ (defn is-child-selected? [id] (letfn [(selector [state] - (let [page-id :current-page-id + (let [page-id (:current-page-id state) objects (get-in state [:workspace-data :pages-index page-id :objects]) selected (get-in state [:workspace-local :selected]) - shape (get objects id) children (cph/get-children id objects)] (some selected children)))] (l/derived selector st/state))) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index bce56562cc..823d1d3734 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -20,20 +20,24 @@ (http/success? response) (rx/of (:body response)) - (or (http/client-error? response) - (= 500 (:status response))) + (= (:status response) 400) (rx/throw (:body response)) - (= 502 (:status response)) - (rx/throw {:type :bad-gateway - :body (:body response)}) + (= (:status response) 401) + (rx/throw {:type :authentication + :code :not-authenticated}) + + (= (:status response) 403) + (rx/throw {:type :authorization + :code :not-authorized}) (= 0 (:status response)) (rx/throw {:type :offline}) :else - (rx/throw {:type :unexpected - :response response}))) + (rx/throw {:type :internal-error + :status (:status response) + :body (:body response)}))) (defn send-query! [id params] @@ -98,6 +102,14 @@ (seq params)) (send-mutation! id form))) +(defmethod mutation :update-team-photo + [id params] + (let [form (js/FormData.)] + (run! (fn [[key val]] + (.append form (name key) val)) + (seq params)) + (send-mutation! id form))) + (defmethod mutation :login [id params] (let [uri (str cfg/public-uri "/api/login")] diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index ca62522ffd..3843021794 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -159,17 +159,18 @@ (defn closest-distance-snap [page-id shapes objects movev] - (->> (rx/of shapes) - (rx/map #(vector (->> % first :frame-id (get objects)) - (-> % gsh/selection-rect (gsh/move movev)))) - (rx/merge-map - (fn [[frame selrect]] - (let [areas (->> (gsh/selrect->areas (or (:selrect frame) - (gsh/rect->rect-shape @refs/vbox)) selrect) - (d/mapm #(select-shapes-area page-id shapes objects %2))) - snap-x (search-snap-distance selrect :x (:left areas) (:right areas)) - snap-y (search-snap-distance selrect :y (:top areas) (:bottom areas))] - (rx/combine-latest snap->vector snap-y snap-x)))))) + (let [frame-id (snap-frame-id shapes) + frame (get objects frame-id) + selrect (->> shapes (map #(gsh/move % movev)) gsh/selection-rect)] + (->> (rx/of (vector frame selrect)) + (rx/merge-map + (fn [[frame selrect]] + (let [areas (->> (gsh/selrect->areas (or (:selrect frame) + (gsh/rect->rect-shape @refs/vbox)) selrect) + (d/mapm #(select-shapes-area page-id shapes objects %2))) + snap-x (search-snap-distance selrect :x (:left areas) (:right areas)) + snap-y (search-snap-distance selrect :y (:top areas) (:bottom areas))] + (rx/combine-latest snap->vector snap-y snap-x))))))) (defn closest-snap-point [page-id shapes layout point] @@ -193,10 +194,12 @@ (not (contains? layout :snap-grid))) (or (filter-shapes id) (not (contains? layout :dynamic-alignment))))) - shapes-points (->> shapes - ;; Unroll all the possible snap-points - (mapcat (partial sp/shape-snap-points)) + shape (if (> (count shapes) 1) + (->> shapes (map gsh/transform-shape) gsh/selection-rect) + (->> shapes (first))) + shapes-points (->> shape + (sp/shape-snap-points) ;; Move the points in the translation vector (map #(gpt/add % movev)))] (->> (rx/merge (closest-snap page-id frame-id shapes-points filter-shapes) @@ -205,6 +208,5 @@ (rx/reduce gpt/min) (rx/map #(or % (gpt/point 0 0))) (rx/map #(gpt/add movev %)) - (rx/map #(gpt/round % 0)) - ))) + (rx/map #(gpt/round % 0))))) diff --git a/frontend/src/app/main/store.clj b/frontend/src/app/main/store.clj new file mode 100644 index 0000000000..f8510983d0 --- /dev/null +++ b/frontend/src/app/main/store.clj @@ -0,0 +1,13 @@ +;; 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) 2020 UXBOX Labs SL + +(ns app.main.store) + +(defmacro emitf + [& events] + `(fn [] + (app.main.store/emit! ~@events))) + diff --git a/frontend/src/app/main/store.cljs b/frontend/src/app/main/store.cljs index cf645ca837..5814c4a5d9 100644 --- a/frontend/src/app/main/store.cljs +++ b/frontend/src/app/main/store.cljs @@ -5,10 +5,14 @@ ;; Copyright (c) 2020 UXBOX Labs SL (ns app.main.store + (:require-macros [app.main.store]) (:require [beicon.core :as rx] [okulary.core :as l] [potok.core :as ptk] + [cuerdas.core :as str] + [app.common.data :as d] + [app.common.pages-helpers :as cph] [app.common.uuid :as uuid] [app.util.storage :refer [storage]] [app.util.debug :refer [debug? logjs]])) @@ -51,6 +55,10 @@ (apply ptk/emit! store (cons event events)) nil)) +(defn emitf + [& events] + #(apply ptk/emit! store events)) + (def initial-state {:session-id (uuid/next) :profile (:profile storage)}) @@ -68,3 +76,77 @@ (defn ^:export dump-objects [] (let [page-id (get @state :current-page-id)] (logjs "state" (get-in @state [:workspace-data :pages-index page-id :objects])))) + +(defn ^:export dump-object [name] + (let [page-id (get @state :current-page-id)] + (let [objects (get-in @state [:workspace-data :pages-index page-id :objects]) + target (d/seek (fn [[id shape]] (= name (:name shape))) objects)] + (->> target + (logjs "state"))))) + +(defn ^:export dump-tree + ([] (dump-tree false)) + ([show-touched] + (let [page-id (get @state :current-page-id) + objects (get-in @state [:workspace-data :pages-index page-id :objects]) + components (get-in @state [:workspace-data :components]) + libraries (get-in @state [:workspace-libraries]) + root (d/seek #(nil? (:parent-id %)) (vals objects))] + + (letfn [(show-shape [shape-id level objects] + (let [shape (get objects shape-id)] + (println (str/pad (str (str/repeat " " level) + (:name shape) + (when (seq (:touched shape)) "*") + {:length 20 + :type :right}) + (show-component shape objects)) + (when (and show-touched (seq (:touched shape))) + (println (str (str/repeat " " level) + " " + (str (:touched shape))))) + (when (:shapes shape) + (dorun (for [shape-id (:shapes shape)] + (show-shape shape-id (inc level) objects))))))) + + (show-component [shape objects] + (if (nil? (:shape-ref shape)) + "" + (let [root-shape (cph/get-root-shape shape objects) + component-id (when root-shape (:component-id root-shape)) + component-file-id (when root-shape (:component-file root-shape)) + component-file (when component-file-id (get libraries component-file-id)) + component (when component-id + (if component-file + (get-in component-file [:data :components component-id]) + (get components component-id))) + component-shape (when (and component (:shape-ref shape)) + (get-in component [:objects (:shape-ref shape)]))] + (str/format " %s--> %s%s%s" + (cond (:component-root? shape) "#" + (:component-id shape) "@" + :else "-") + (when component-file (str/format "<%s> " (:name component-file))) + (:name component-shape) + (if (or (:component-root? shape) + (nil? (:component-id shape))) + "" + (let [component-id (:component-id shape) + component-file-id (:component-file shape) + component-file (when component-file-id (get libraries component-file-id)) + component (if component-file + (get-in component-file [:data :components component-id]) + (get components component-id))] + (str/format " (%s%s)" + (when component-file (str/format "<%s> " (:name component-file))) + (:name component))))))))] + + (println "[Workspace]") + (show-shape (:id root) 0 objects) + + (dorun (for [component (vals components)] + (do + (println) + (println (str/format "[%s]" (:name component))) + (show-shape (:id component) 0 (:objects component))))))))) + diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index c982d023aa..66fddcf8e6 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -9,11 +9,7 @@ (ns app.main.ui (:require - [expound.alpha :as expound] - [beicon.core :as rx] - [cuerdas.core :as str] - [potok.core :as ptk] - [rumext.alpha :as mf] + [app.config :as cfg] [app.common.data :as d] [app.common.exceptions :as ex] [app.common.uuid :as uuid] @@ -21,18 +17,25 @@ [app.main.data.messages :as dm] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.auth :refer [auth verify-token]] + [app.main.ui.auth :refer [auth]] + [app.main.ui.auth.verify-token :refer [verify-token]] + [app.main.ui.cursors :as c] [app.main.ui.dashboard :refer [dashboard]] [app.main.ui.icons :as i] - [app.main.ui.cursors :as c] [app.main.ui.messages :as msgs] + [app.main.ui.render :as render] [app.main.ui.settings :as settings] [app.main.ui.static :refer [not-found-page not-authorized-page]] [app.main.ui.viewer :refer [viewer-page]] - [app.main.ui.render :as render] + [app.main.ui.viewer.handoff :refer [handoff]] [app.main.ui.workspace :as workspace] [app.util.i18n :as i18n :refer [tr t]] - [app.util.timers :as ts])) + [app.util.timers :as ts] + [cuerdas.core :as str] + [cljs.spec.alpha :as s] + [expound.alpha :as expound] + [potok.core :as ptk] + [rumext.alpha :as mf])) ;; --- Routes @@ -51,6 +54,7 @@ ["/options" :settings-options]] ["/view/:file-id/:page-id" :viewer] + ["/handoff/:file-id/:page-id" :handoff] ["/not-found" :not-found] ["/not-authorized" :not-authorized] @@ -60,12 +64,13 @@ ;; Used for export ["/render-object/:file-id/:page-id/:object-id" :render-object] - ["/dashboard" - ["/team/:team-id" - ["/" :dashboard-team] - ["/search" :dashboard-search] - ["/project/:project-id" :dashboard-project] - ["/libraries" :dashboard-libraries]]] + ["/dashboard/team/:team-id" + ["/members" :dashboard-team-members] + ["/settings" :dashboard-team-settings] + ["/projects" :dashboard-projects] + ["/search" :dashboard-search] + ["/libraries" :dashboard-libraries] + ["/projects/:project-id" :dashboard-files]] ["/workspace/:project-id/:file-id" :workspace]]) @@ -74,7 +79,9 @@ (let [data (ex-data error)] (case (:type data) :not-found [:& not-found-page {:error data}] - [:span "Internal application errror"]))) + (do + (ptk/handle-error error) + [:span "Internal application errror"])))) (mf/defc app {::mf/wrap [#(mf/catch % {:fallback app-error})]} @@ -105,9 +112,11 @@ ]) (:dashboard-search - :dashboard-team - :dashboard-project - :dashboard-libraries) + :dashboard-projects + :dashboard-files + :dashboard-libraries + :dashboard-team-members + :dashboard-team-settings) [:& dashboard {:route route}] :viewer @@ -120,6 +129,14 @@ :index index :token token}]) + :handoff + (let [index (d/parse-integer (get-in route [:params :query :index])) + file-id (uuid (get-in route [:params :path :file-id])) + page-id (uuid (get-in route [:params :path :page-id]))] + [:& handoff {:page-id page-id + :file-id file-id + :index index}]) + :render-object (do (let [file-id (uuid (get-in route [:params :path :file-id])) @@ -158,29 +175,74 @@ (defmethod ptk/handle-error :validation [error] - (js/console.error "handle-error(validation):" (if (map? error) (pr-str error) error)) + (ts/schedule + (st/emitf (dm/show {:content "Unexpected validation error." + :type :error + :timeout 5000}))) (when-let [explain (:explain error)] - (println "============ SERVER RESPONSE ERROR ================") - (println explain) - (println "============ END SERVER RESPONSE ERROR ================"))) + (js/console.group "Server Error") + (js/console.error (if (map? error) (pr-str error) error)) + (js/console.error explain) + (js/console.endGroup "Server Error"))) + (defmethod ptk/handle-error :authentication [error] (ts/schedule 0 #(st/emit! logout))) +(defmethod ptk/handle-error :authorization + [error] + (ts/schedule 0 #(st/emit! logout))) + (defmethod ptk/handle-error :assertion - [{:keys [data stack] :as error}] - (js/console.error stack) - (js/console.error (with-out-str - (expound/printer data)))) + [{:keys [data stack message context] :as error}] + (js/console.group message) + (js/console.info (str/format "ns: '%s'\nname: '%s'\nfile: '%s:%s'" + (:ns context) + (:name context) + (str cfg/public-uri "/js/cljs-runtime/" (:file context)) + (:line context))) + (js/console.groupCollapsed "Stack Trace") + (js/console.info stack) + (js/console.groupEnd "Stack Trace") + + (js/console.error (with-out-str (expound/printer data))) + (js/console.groupEnd message)) (defmethod ptk/handle-error :default [error] + (js/console.log error) (if (instance? ExceptionInfo error) (ptk/handle-error (ex-data error)) (do - (js/console.error "handle-error(default):" - (if (map? error) (pr-str error) error)) - (ts/schedule 100 #(st/emit! (dm/show {:content "Something wrong has happened." - :type :error - :timeout 5000})))))) + (js/console.group "Generic Error:") + (ex/ignoring + (js/console.error (pr-str error)) + (js/console.error (.-stack error))) + (js/console.groupEnd "Generic error") + (ts/schedule (st/emitf (dm/show + {:content "Something wrong has happened." + :type :error + :timeout 5000})))))) + +(defmethod ptk/handle-error :internal-error + [{:keys [status] :as error}] + (cond + (= status 429) + (ts/schedule + (st/emitf (dm/show {:content "Too many requests, wait a little bit and retry." + :type :error + :timeout 5000}))) + + :else + (ts/schedule + (st/emitf (dm/show {:content "Unable to connect to backend, wait a little bit and refresh." + :type :error}))))) + +(defonce uncaught-error-handler + (letfn [(on-error [event] + (ptk/handle-error (unchecked-get event "error")) + (.preventDefault ^js event))] + (.addEventListener js/window "error" on-error) + (fn [] + (.removeEventListener js/window "error" on-error)))) diff --git a/frontend/src/app/main/ui/auth.cljs b/frontend/src/app/main/ui/auth.cljs index 8d636175e5..143e0ad67b 100644 --- a/frontend/src/app/main/ui/auth.cljs +++ b/frontend/src/app/main/ui/auth.cljs @@ -9,6 +9,7 @@ (ns app.main.ui.auth (:require + [app.common.uuid :as uuid] [app.main.data.auth :as da] [app.main.data.messages :as dm] [app.main.data.users :as du] @@ -20,6 +21,7 @@ [app.main.ui.auth.register :refer [register-page]] [app.main.ui.icons :as i] [app.util.forms :as fm] + [app.util.storage :refer [cache]] [app.util.i18n :as i18n :refer [tr t]] [app.util.router :as rt] [app.util.timers :as ts] @@ -35,7 +37,9 @@ (mf/defc auth [{:keys [route] :as props}] (let [section (get-in route [:data :name]) - locale (mf/deref i18n/locale)] + locale (mf/deref i18n/locale) + params (:query-params route)] + [:div.auth [:section.auth-sidebar [:a.logo {:href "/#/"} i/logo] @@ -43,61 +47,9 @@ [:section.auth-content (case section - :auth-register [:& register-page {:locale locale}] - :auth-login [:& login-page {:locale locale}] + :auth-register [:& register-page {:locale locale :params params}] + :auth-login [:& login-page {:locale locale :params params}] :auth-goodbye [:& goodbye-page {:locale locale}] :auth-recovery-request [:& recovery-request-page {:locale locale}] :auth-recovery [:& recovery-page {:locale locale :params (:query-params route)}])]])) - -(defmulti handle-token (fn [token] (:iss token))) - -(defmethod handle-token :verify-email - [data] - (let [msg (tr "settings.notifications.email-verified-successfully")] - (ts/schedule 100 #(st/emit! (dm/success msg))) - (st/emit! (rt/nav :auth-login)))) - -(defmethod handle-token :change-email - [data] - (let [msg (tr "settings.notifications.email-changed-successfully")] - (ts/schedule 100 #(st/emit! (dm/success msg))) - (st/emit! (rt/nav :settings-profile) - du/fetch-profile))) - -(defmethod handle-token :auth - [tdata] - (st/emit! (da/login-from-token tdata))) - -(defmethod handle-token :default - [tdata] - (js/console.log "Unhandled token:" (pr-str tdata)) - (st/emit! (rt/nav :auth-login))) - -(mf/defc verify-token - [{:keys [route] :as props}] - (let [token (get-in route [:query-params :token])] - (mf/use-effect - (fn [] - (->> (rp/mutation :verify-profile-token {:token token}) - (rx/subs - (fn [tdata] - (handle-token tdata)) - (fn [error] - (case (:code error) - :email-already-exists - (let [msg (tr "errors.email-already-exists")] - (ts/schedule 100 #(st/emit! (dm/error msg))) - (st/emit! (rt/nav :auth-login))) - - :email-already-validated - (let [msg (tr "errors.email-already-validated")] - (ts/schedule 100 #(st/emit! (dm/warn msg))) - (st/emit! (rt/nav :auth-login))) - - (let [msg (tr "errors.generic")] - (ts/schedule 100 #(st/emit! (dm/error msg))) - (st/emit! (rt/nav :auth-login))))))))) - - [:div.verify-token - i/loader-pencil])) diff --git a/frontend/src/app/main/ui/auth/login.cljs b/frontend/src/app/main/ui/auth/login.cljs index 31114f80f2..051440d929 100644 --- a/frontend/src/app/main/ui/auth/login.cljs +++ b/frontend/src/app/main/ui/auth/login.cljs @@ -20,10 +20,9 @@ [app.main.store :as st] [app.main.ui.messages :as msgs] [app.main.data.messages :as dm] - [app.main.ui.components.forms :refer [input submit-button form]] + [app.main.ui.components.forms :as fm] [app.util.object :as obj] [app.util.dom :as dom] - [app.util.forms :as fm] [app.util.i18n :refer [tr t]] [app.util.router :as rt])) @@ -50,18 +49,31 @@ (mf/defc login-form [{:keys [locale] :as props}] (let [error? (mf/use-state false) - submit-event (mf/use-var da/login) + form (fm/use-form :spec ::login-form + :inital {}) on-error (fn [form event] + (js/console.log error?) (reset! error? true)) on-submit - (fn [form event] - (reset! error? false) - (let [params (with-meta (:clean-data form) - {:on-error on-error})] - (st/emit! (@submit-event params))))] + (mf/use-callback + (mf/deps form) + (fn [event] + (reset! error? false) + (let [params (with-meta (:clean-data @form) + {:on-error on-error})] + (st/emit! (da/login params))))) + + on-submit-ldap + (mf/use-callback + (mf/deps form) + (fn [event] + (reset! error? false) + (let [params (with-meta (:clean-data @form) + {:on-error on-error})] + (st/emit! (da/login-with-ldap params)))))] [:* (when @error? @@ -70,28 +82,28 @@ :content (t locale "errors.auth.unauthorized") :on-close #(reset! error? false)}]) - [:& form {:on-submit on-submit - :spec ::login-form - :initial {}} - [:& input - {:name :email - :type "text" - :tab-index "2" - :help-icon i/at - :label (t locale "auth.email-label")}] - [:& input - {:type "password" - :name :password - :tab-index "3" - :help-icon i/eye - :label (t locale "auth.password-label")}] - [:& submit-button - {:label (t locale "auth.login-submit-label") - :on-click #(reset! submit-event da/login)}] + [:& fm/form {:on-submit on-submit :form form} + [:div.fields-row + [:& fm/input + {:name :email + :type "text" + :tab-index "2" + :help-icon i/at + :label (t locale "auth.email")}]] + [:div.fields-row + [:& fm/input + {:type "password" + :name :password + :tab-index "3" + :help-icon i/eye + :label (t locale "auth.password")}]] + [:& fm/submit-button + {:label (t locale "auth.login-submit") + :on-click on-submit}] (when cfg/login-with-ldap - [:& submit-button - {:label (t locale "auth.login-with-ldap-submit-label") - :on-click #(reset! submit-event da/login-with-ldap)}])]])) + [:& fm/submit-button + {:label (t locale "auth.login-with-ldap-submit") + :on-click on-submit}])]])) (mf/defc login-page [{:keys [locale] :as props}] @@ -110,10 +122,10 @@ (t locale "auth.forgot-password")]] [:div.link-entry - [:span (t locale "auth.register-label") " "] + [:span (t locale "auth.register") " "] [:a {:on-click #(st/emit! (rt/nav :auth-register)) :tab-index "6"} - (t locale "auth.register")]]] + (t locale "auth.register-submit")]]] (when cfg/google-client-id [:a.btn-ocean.btn-large.btn-google-auth @@ -125,11 +137,11 @@ {:on-click login-with-gitlab} [:img.logo {:src "/images/icons/brand-gitlab.svg"}] - (t locale "auth.login-with-gitlab-submit-label")]) + (t locale "auth.login-with-gitlab-submit")]) [:div.links.demo [:div.link-entry - [:span (t locale "auth.create-demo-profile-label") " "] + [:span (t locale "auth.create-demo-profile") " "] [:a {:on-click #(st/emit! da/create-demo-profile) :tab-index "6"} - (t locale "auth.create-demo-profile")]]]]]) + (t locale "auth.create-demo-account")]]]]]) diff --git a/frontend/src/app/main/ui/auth/recovery.cljs b/frontend/src/app/main/ui/auth/recovery.cljs index 87d19fbf47..0def99d089 100644 --- a/frontend/src/app/main/ui/auth/recovery.cljs +++ b/frontend/src/app/main/ui/auth/recovery.cljs @@ -17,15 +17,14 @@ [app.main.data.auth :as uda] [app.main.data.messages :as dm] [app.main.store :as st] - [app.main.ui.components.forms :refer [input submit-button form]] + [app.main.ui.components.forms :as fm] [app.util.dom :as dom] - [app.util.forms :as fm] [app.util.i18n :as i18n :refer [t tr]] [app.util.router :as rt])) -(s/def ::password-1 ::fm/not-empty-string) -(s/def ::password-2 ::fm/not-empty-string) -(s/def ::token ::fm/not-empty-string) +(s/def ::password-1 ::us/not-empty-string) +(s/def ::password-2 ::us/not-empty-string) +(s/def ::token ::us/not-empty-string) (s/def ::recovery-form (s/keys :req-un [::password-1 @@ -54,29 +53,31 @@ (defn- on-submit [form event] - (let [params (with-meta {:token (get-in form [:clean-data :token]) - :password (get-in form [:clean-data :password-2])} - {:on-error (partial on-error form) - :on-success (partial on-success form)})] - (st/emit! (uda/recover-profile params)))) + (let [mdata {:on-error on-error + :on-success on-success} + params {:token (get-in @form [:clean-data :token]) + :password (get-in @form [:clean-data :password-2])}] + (st/emit! (uda/recover-profile (with-meta params mdata))))) (mf/defc recovery-form [{:keys [locale params] :as props}] - [:& form {:on-submit on-submit - :spec ::recovery-form - :validators [password-equality] - :initial params} + (let [form (fm/use-form :spec ::recovery-form + :validators [password-equality] + :initial params)] + [:& fm/form {:on-submit on-submit + :form form} + [:div.fields-row + [:& fm/input {:type "password" + :name :password-1 + :label (t locale "auth.new-password")}]] - [:& input {:type "password" - :name :password-1 - :label (t locale "auth.new-password-label")}] + [:div.fields-row + [:& fm/input {:type "password" + :name :password-2 + :label (t locale "auth.confirm-password")}]] - [:& input {:type "password" - :name :password-2 - :label (t locale "auth.confirm-password-label")}] - - [:& submit-button - {:label (t locale "auth.recovery-submit-label")}]]) + [:& fm/submit-button + {:label (t locale "auth.recovery-submit")}]])) ;; --- Recovery Request Page @@ -86,7 +87,6 @@ [:div.form-container [:h1 "Forgot your password?"] [:div.subtitle "Please enter your new password"] - [:& recovery-form {:locale locale :params params}] [:div.links diff --git a/frontend/src/app/main/ui/auth/recovery_request.cljs b/frontend/src/app/main/ui/auth/recovery_request.cljs index cccb74e465..4b99e342d6 100644 --- a/frontend/src/app/main/ui/auth/recovery_request.cljs +++ b/frontend/src/app/main/ui/auth/recovery_request.cljs @@ -9,45 +9,48 @@ (ns app.main.ui.auth.recovery-request (:require - [cljs.spec.alpha :as s] - [cuerdas.core :as str] - [rumext.alpha :as mf] [app.common.spec :as us] [app.main.data.auth :as uda] [app.main.data.messages :as dm] [app.main.store :as st] - [app.main.ui.components.forms :refer [input submit-button form]] + [app.main.ui.components.forms :as fm] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.forms :as fm] [app.util.i18n :as i18n :refer [tr t]] - [app.util.router :as rt])) + [app.util.router :as rt] + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [rumext.alpha :as mf])) (s/def ::email ::us/email) (s/def ::recovery-request-form (s/keys :req-un [::email])) +(defn- on-success + [] + (st/emit! (dm/info (tr "auth.notifications.recovery-token-sent")) + (rt/nav :auth-login))) + (defn- on-submit [form event] - (let [on-success #(st/emit! - (dm/info (tr "auth.notifications.recovery-token-sent")) - (rt/nav :auth-login)) - params (with-meta (:clean-data form) - {:on-success on-success})] + (let [params (with-meta (:clean-data @form) + {:on-success on-success})] (st/emit! (uda/request-profile-recovery params)))) (mf/defc recovery-form [{:keys [locale] :as props}] - [:& form {:on-submit on-submit - :spec ::recovery-request-form - :initial {}} + (let [form (fm/use-form :spec ::recovery-request-form + :initial {})] + [:& fm/form {:on-submit on-submit + :form form} + [:div.fields-row + [:& fm/input {:name :email + :label (t locale "auth.email") + :help-icon i/at + :type "text"}]] - [:& input {:name :email - :label (t locale "auth.email-label") - :help-icon i/at - :type "text"}] + [:& fm/submit-button + {:label (t locale "auth.recovery-request-submit")}]])) - [:& submit-button - {:label (t locale "auth.recovery-request-submit-label")}]]) ;; --- Recovery Request Page @@ -57,7 +60,6 @@ [:div.form-container [:h1 (t locale "auth.recovery-request-title")] [:div.subtitle (t locale "auth.recovery-request-subtitle")] - [:& recovery-form {:locale locale}] [:div.links diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 5511f3ab1a..81674ebf1b 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -9,16 +9,16 @@ (ns app.main.ui.auth.register (:require + [app.common.spec :as us] [app.config :as cfg] [app.main.data.auth :as da] - [app.main.data.auth :as uda] + [app.main.data.users :as du] [app.main.data.messages :as dm] [app.main.store :as st] - [app.main.ui.components.forms :refer [input submit-button form]] + [app.main.ui.components.forms :as fm] [app.main.ui.icons :as i] [app.main.ui.messages :as msgs] [app.util.dom :as dom] - [app.util.forms :as fm] [app.util.i18n :refer [tr t]] [app.util.router :as rt] [app.util.timers :as tm] @@ -32,15 +32,6 @@ {:type :warning :content (tr "auth.demo-warning")}]) -(s/def ::fullname ::fm/not-empty-string) -(s/def ::password ::fm/not-empty-string) -(s/def ::email ::fm/email) - -(s/def ::register-form - (s/keys :req-un [::password - ::fullname - ::email])) - (defn- on-error [form error] (case (:code error) @@ -55,9 +46,14 @@ (defn- on-success [form data] - (let [msg (tr "auth.notifications.validation-email-sent" (:email data))] - (st/emit! (rt/nav :auth-login) - (dm/success msg)))) + (if (and (:is-active data) (:claims data)) + (let [message (tr "auth.notifications.team-invitation-accepted")] + (st/emit! (rt/nav :dashboard-projects {:team-id (get-in data [:claims :team-id])}) + du/fetch-profile + (dm/success message))) + (let [message (tr "notifications.validation-email-sent" (:email data))] + (st/emit! (rt/nav :auth-login) + (dm/success message))))) (defn- validate [data] @@ -67,57 +63,74 @@ (defn- on-submit [form event] - (let [data (with-meta (:clean-data form) + (let [data (with-meta (:clean-data @form) {:on-error (partial on-error form) :on-success (partial on-success form)})] - (st/emit! (uda/register data)))) + (st/emit! (da/register data)))) + +(s/def ::fullname ::us/not-empty-string) +(s/def ::password ::us/not-empty-string) +(s/def ::email ::us/email) +(s/def ::token ::us/not-empty-string) + +(s/def ::register-form + (s/keys :req-un [::password + ::fullname + ::email] + :opt-un [::token])) (mf/defc register-form - [{:keys [locale] :as props}] - [:& form {:on-submit on-submit - :spec ::register-form - :validators [validate] - :initial {}} - [:& input {:name :fullname - :tab-index "1" - :label (t locale "auth.fullname-label") - :type "text"}] - [:& input {:type "email" - :name :email - :tab-index "2" - :help-icon i/at - :label (t locale "auth.email-label")}] - [:& input {:name :password - :tab-index "3" - :hint (t locale "auth.password-length-hint") - :label (t locale "auth.password-label") - :type "password"}] + [{:keys [locale params] :as props}] + (let [initial (mf/use-memo (mf/deps params) (constantly params)) + form (fm/use-form :spec ::register-form + :validators [validate] + :initial initial)] - [:& submit-button - {:label (t locale "auth.register-submit-label")}]]) + [:& fm/form {:on-submit on-submit + :form form} + [:div.fields-row + [:& fm/input {:name :fullname + :tab-index "1" + :label (t locale "auth.fullname") + :type "text"}]] + [:div.fields-row + [:& fm/input {:type "email" + :name :email + :tab-index "2" + :help-icon i/at + :label (t locale "auth.email")}]] + [:div.fields-row + [:& fm/input {:name :password + :tab-index "3" + :hint (t locale "auth.password-length-hint") + :label (t locale "auth.password") + :type "password"}]] + + [:& fm/submit-button + {:label (t locale "auth.register-submit")}]])) ;; --- Register Page (mf/defc register-page - [{:keys [locale] :as props}] - [:section.generic-form - [:div.form-container - [:h1 (t locale "auth.register-title")] - [:div.subtitle (t locale "auth.register-subtitle")] - (when cfg/demo-warning - [:& demo-warning]) + [{:keys [locale params] :as props}] + [:div.form-container + [:h1 (t locale "auth.register-title")] + [:div.subtitle (t locale "auth.register-subtitle")] + (when cfg/demo-warning + [:& demo-warning]) - [:& register-form {:locale locale}] + [:& register-form {:locale locale + :params params}] - [:div.links - [:div.link-entry - [:span (t locale "auth.already-have-account") " "] - [:a {:on-click #(st/emit! (rt/nav :auth-login)) - :tab-index "4"} - (t locale "auth.login-here")]] + [:div.links + [:div.link-entry + [:span (t locale "auth.already-have-account") " "] + [:a {:on-click #(st/emit! (rt/nav :auth-login)) + :tab-index "4"} + (t locale "auth.login-here")]] - [:div.link-entry - [:span (t locale "auth.create-demo-profile-label") " "] - [:a {:on-click #(st/emit! da/create-demo-profile) - :tab-index "5"} - (t locale "auth.create-demo-profile")]]]]]) + [:div.link-entry + [:span (t locale "auth.create-demo-profile") " "] + [:a {:on-click #(st/emit! da/create-demo-profile) + :tab-index "5"} + (t locale "auth.create-demo-profile")]]]]) diff --git a/frontend/src/app/main/ui/auth/verify_token.cljs b/frontend/src/app/main/ui/auth/verify_token.cljs new file mode 100644 index 0000000000..670a1d4df7 --- /dev/null +++ b/frontend/src/app/main/ui/auth/verify_token.cljs @@ -0,0 +1,94 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.auth.verify-token + (:require + [app.common.uuid :as uuid] + [app.main.data.auth :as da] + [app.main.data.messages :as dm] + [app.main.data.users :as du] + [app.main.repo :as rp] + [app.main.store :as st] + [app.main.ui.auth.login :refer [login-page]] + [app.main.ui.auth.recovery :refer [recovery-page]] + [app.main.ui.auth.recovery-request :refer [recovery-request-page]] + [app.main.ui.auth.register :refer [register-page]] + [app.main.ui.icons :as i] + [app.util.forms :as fm] + [app.util.storage :refer [cache]] + [app.util.i18n :as i18n :refer [tr t]] + [app.util.router :as rt] + [app.util.timers :as ts] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [rumext.alpha :as mf])) + +(defmulti handle-token (fn [token] (:iss token))) + +(defmethod handle-token :verify-email + [data] + (let [msg (tr "dashboard.notifications.email-verified-successfully")] + (ts/schedule 100 #(st/emit! (dm/success msg))) + (st/emit! (rt/nav :auth-login)))) + +(defmethod handle-token :change-email + [data] + (let [msg (tr "dashboard.notifications.email-changed-successfully")] + (ts/schedule 100 #(st/emit! (dm/success msg))) + (st/emit! (rt/nav :settings-profile) + du/fetch-profile))) + +(defmethod handle-token :auth + [tdata] + (st/emit! (da/login-from-token tdata))) + +(defmethod handle-token :team-invitation + [tdata] + (case (:state tdata) + :created + (let [message (tr "auth.notifications.team-invitation-accepted")] + (st/emit! du/fetch-profile + (rt/nav :dashboard-projects {:team-id (:team-id tdata)}) + (dm/success message))) + + :pending + (st/emit! (rt/nav :auth-register {} {:token (:token tdata)})))) + +(defmethod handle-token :default + [tdata] + (js/console.log "Unhandled token:" (pr-str tdata)) + (st/emit! (rt/nav :auth-login))) + +(mf/defc verify-token + [{:keys [route] :as props}] + (let [token (get-in route [:query-params :token])] + (mf/use-effect + (fn [] + (->> (rp/mutation :verify-token {:token token}) + (rx/subs + (fn [tdata] + (handle-token tdata)) + (fn [error] + (case (:code error) + :email-already-exists + (let [msg (tr "errors.email-already-exists")] + (ts/schedule 100 #(st/emit! (dm/error msg))) + (st/emit! (rt/nav :auth-login))) + + :email-already-validated + (let [msg (tr "errors.email-already-validated")] + (ts/schedule 100 #(st/emit! (dm/warn msg))) + (st/emit! (rt/nav :auth-login))) + + (let [msg (tr "errors.generic")] + (ts/schedule 100 #(st/emit! (dm/error msg))) + (st/emit! (rt/nav :auth-login))))))))) + + [:div.verify-token + i/loader-pencil])) diff --git a/frontend/src/app/main/ui/components/color_bullet.cljs b/frontend/src/app/main/ui/components/color_bullet.cljs new file mode 100644 index 0000000000..d4f774674a --- /dev/null +++ b/frontend/src/app/main/ui/components/color_bullet.cljs @@ -0,0 +1,42 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.components.color-bullet + (:require + [rumext.alpha :as mf] + [app.util.i18n :as i18n :refer [tr]] + [app.util.color :as uc])) + +(mf/defc color-bullet [{:keys [color on-click]}] + (if (uc/multiple? color) + [:div.color-bullet.multiple {:on-click #(when on-click (on-click %))}] + + ;; No multiple selection + (let [color (if (string? color) {:color color :opacity 1} color)] + [:div.color-bullet {:class (when (:id color) "is-library-color") + :on-click #(when on-click (on-click %))} + (when (not (:gradient color)) + [:div.color-bullet-left {:style {:background (uc/color->background (assoc color :opacity 1))}}]) + + [:div.color-bullet-right {:style {:background (uc/color->background color)}}]]))) + +(defn gradient-type->string [type] + (case type + :linear (tr "workspace.gradients.linear") + :radial (tr "workspace.gradients.radial") + (str "???" type))) + +(mf/defc color-name [{:keys [color size on-click on-double-click]}] + (let [color (if (string? color) {:color color :opacity 1} color) + {:keys [name color opacity gradient]} color + color-str (or name color (gradient-type->string (:type gradient)))] + (when (or (not size) (= size :big)) + [:span.color-text {:on-click #(when on-click (on-click %)) + :on-double-click #(when on-double-click (on-double-click %)) + :title name } color-str]))) diff --git a/frontend/src/app/main/ui/components/dropdown.cljs b/frontend/src/app/main/ui/components/dropdown.cljs index d6479d61ff..209224bff9 100644 --- a/frontend/src/app/main/ui/components/dropdown.cljs +++ b/frontend/src/app/main/ui/components/dropdown.cljs @@ -31,8 +31,8 @@ on-mount (fn [] - (let [lkey1 (events/listen js/document EventType.CLICK on-click) - lkey2 (events/listen js/document EventType.KEYUP on-keyup)] + (let [lkey1 (events/listen (dom/get-root) EventType.CLICK on-click) + lkey2 (events/listen (dom/get-root) EventType.KEYUP on-keyup)] #(do (events/unlistenByKey lkey1) (events/unlistenByKey lkey2))))] diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs index a6793674fa..3af9230766 100644 --- a/frontend/src/app/main/ui/components/forms.cljs +++ b/frontend/src/app/main/ui/components/forms.cljs @@ -20,21 +20,22 @@ [app.util.dom :as dom])) (def form-ctx (mf/create-context nil)) +(def use-form fm/use-form) (mf/defc input [{:keys [type label help-icon disabled name form hint trim] :as props}] - (let [form (mf/use-ctx form-ctx) + (let [form (or form (mf/use-ctx form-ctx)) type' (mf/use-state type) focus? (mf/use-state false) locale (mf/deref i18n/locale) - touched? (get-in form [:touched name]) - error (get-in form [:errors name]) + touched? (get-in @form [:touched name]) + error (get-in @form [:errors name]) - value (get-in form [:data name] "") + value (get-in @form [:data name] "") - help-icon' (cond + help-icon' (cond (and (= type "password") (= @type' "password")) i/eye @@ -67,7 +68,7 @@ on-blur (fn [event] (reset! focus? false) - (when-not (get-in form [:touched name]) + (when-not (get-in @form [:touched name]) (swap! form assoc-in [:touched name] true))) props (-> props @@ -80,33 +81,33 @@ :type @type') (obj/clj->props))] - [:div.field.custom-input - {:class klass} - [:* - [:label label] - [:> :input props] - (when help-icon' - [:div.help-icon - {:style {:cursor "pointer"} - :on-click (when (= "password" type) - swap-text-password)} - help-icon']) - (cond - (and touched? (:message error)) - [:span.error (t locale (:message error))] + [:div.custom-input + {:class klass} + [:* + [:label label] + [:> :input props] + (when help-icon' + [:div.help-icon + {:style {:cursor "pointer"} + :on-click (when (= "password" type) + swap-text-password)} + help-icon']) + (cond + (and touched? (:message error)) + [:span.error (t locale (:message error))] - (string? hint) - [:span.hint hint])]])) + (string? hint) + [:span.hint hint])]])) (mf/defc select [{:keys [options label name form default] :or {default ""}}] - (let [form (mf/use-ctx form-ctx) - value (get-in form [:data name] default) + (let [form (or form (mf/use-ctx form-ctx)) + value (get-in @form [:data name] default) cvalue (d/seek #(= value (:value %)) options) on-change (fm/on-input-change form name)] - [:div.field.custom-select + [:div.custom-select [:select {:value value :on-change on-change} (for [item options] @@ -122,34 +123,21 @@ (mf/defc submit-button [{:keys [label form on-click] :as props}] - (let [form (mf/use-ctx form-ctx)] + (let [form (or form (mf/use-ctx form-ctx))] [:input.btn-primary.btn-large {:name "submit" - :class (when-not (:valid form) "btn-disabled") - :disabled (not (:valid form)) + :class (when-not (:valid @form) "btn-disabled") + :disabled (not (:valid @form)) :on-click on-click :value label :type "submit"}])) (mf/defc form - [{:keys [on-submit spec validators initial children class] :as props}] - (let [frm (fm/use-form :spec spec - :validators validators - :initial initial)] - - (mf/use-effect - (mf/deps initial) - (fn [] - (if (fn? initial) - (swap! frm update :data merge (initial)) - (swap! frm update :data merge initial)))) - - [:& (mf/provider form-ctx) {:value frm} + [{:keys [on-submit form children class] :as props}] + (let [on-submit (or on-submit (constantly nil))] + [:& (mf/provider form-ctx) {:value form} [:form {:class class :on-submit (fn [event] (dom/prevent-default event) - (on-submit frm event))} + (on-submit form event))} children]])) - - - diff --git a/frontend/src/app/main/ui/confirm.cljs b/frontend/src/app/main/ui/confirm.cljs index 11c2891979..d791de0f4f 100644 --- a/frontend/src/app/main/ui/confirm.cljs +++ b/frontend/src/app/main/ui/confirm.cljs @@ -2,53 +2,96 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) 2016 Andrey Antukh -;; Copyright (c) 2016 Juan de la Cruz +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL (ns app.main.ui.confirm + (:import goog.events.EventType) (:require - [app.main.ui.icons :as i] [rumext.alpha :as mf] - [app.main.ui.modal :as modal] - [app.util.i18n :refer (tr)] - [app.util.data :refer [classnames]] - [app.util.dom :as dom])) + [goog.events :as events] + [app.main.data.modal :as modal] + [app.main.store :as st] + [app.main.ui.icons :as i] + [app.main.ui.keyboard :as k] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr t]] + [app.util.data :refer [classnames]])) (mf/defc confirm-dialog {::mf/register modal/components - ::mf/register-as :confirm-dialog} - [{:keys [message on-accept on-cancel hint cancel-text accept-text not-danger?] :as ctx}] - (let [message (or message (tr "ds.confirm-title")) - cancel-text (or cancel-text (tr "ds.confirm-cancel")) - accept-text (or accept-text (tr "ds.confirm-ok")) + ::mf/register-as :confirm} + [{:keys [message + title + on-accept + on-cancel + hint + cancel-label + accept-label + accept-style] :as props}] + (let [locale (mf/deref i18n/locale) - accept - (fn [event] - (dom/prevent-default event) - (modal/hide!) - (on-accept (dissoc ctx :on-accept :on-cancel))) + on-accept (or on-accept identity) + on-cancel (or on-cancel identity) + message (or message (t locale "ds.confirm-title")) + cancel-label (or cancel-label (tr "ds.confirm-cancel")) + accept-label (or accept-label (tr "ds.confirm-ok")) + accept-style (or accept-style :danger) + title (or title (t locale "ds.confirm-title")) + + accept-fn + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (st/emit! (modal/hide)) + (on-accept props))) + + cancel-fn + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (st/emit! (modal/hide)) + (on-cancel props)))] + + (mf/use-effect + (fn [] + (let [on-keydown + (fn [event] + (when (k/enter? event) + (do (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (modal/hide)) + (on-accept props)))) + key (events/listen (dom/get-root) EventType.KEYDOWN on-keydown)] + #(events/unlistenByKey key)))) - cancel - (fn [event] - (dom/prevent-default event) - (modal/hide!) - (when on-cancel - (on-cancel (dissoc ctx :on-accept :on-cancel))))] [:div.modal-overlay - [:div.modal.confirm-dialog - [:a.close {:on-click cancel} i/close] + [:div.modal-container.confirm-dialog + [:div.modal-header + [:div.modal-header-title + [:h2 title]] + [:div.modal-close-button + {:on-click cancel-fn} i/close]] + [:div.modal-content - [:h3.dialog-title message] - (if hint [:span hint]) - [:div.dialog-buttons - [:input.dialog-cancel-button - {:type "button" - :value cancel-text - :on-click cancel}] + [:h3 message] + (when (string? hint) + [:p hint])] - [:input.dialog-accept-button - {:type "button" - :class (classnames :not-danger not-danger?) - :value accept-text - :on-click accept}]]]]])) + [:div.modal-footer + [:div.action-buttons + (when-not (= cancel-label :omit) + [:input.cancel-button + {:type "button" + :value cancel-label + :on-click cancel-fn}]) + + [:input.accept-button + {:class (classnames :danger (= accept-style :danger) + :primary (= accept-style :primary)) + :type "button" + :value accept-label + :on-click accept-fn}]]]]])) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 9b56bc217e..11abc5fdb3 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -12,3 +12,9 @@ [rumext.alpha :as mf])) (def embed-ctx (mf/create-context false)) +(def render-ctx (mf/create-context nil)) + +(def current-team-id (mf/create-context nil)) +(def current-project-id (mf/create-context nil)) +(def current-page-id (mf/create-context nil)) +(def current-file-id (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index e0c7ce10e5..d1abccf4e7 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -9,22 +9,24 @@ (ns app.main.ui.dashboard (:require - [cuerdas.core :as str] - [rumext.alpha :as mf] - [app.main.ui.icons :as i] [app.common.exceptions :as ex] - [app.common.uuid :as uuid] [app.common.spec :as us] - [app.main.store :as st] + [app.common.uuid :as uuid] + [app.main.data.dashboard :as dd] [app.main.refs :as refs] - [app.main.ui.dashboard.sidebar :refer [sidebar]] - [app.main.ui.dashboard.search :refer [search-page]] - [app.main.ui.dashboard.project :refer [project-page]] - [app.main.ui.dashboard.recent-files :refer [recent-files-page]] + [app.main.store :as st] + [app.main.ui.dashboard.files :refer [files-section]] [app.main.ui.dashboard.libraries :refer [libraries-page]] - [app.main.ui.dashboard.profile :refer [profile-section]] + [app.main.ui.dashboard.projects :refer [projects-section]] + [app.main.ui.dashboard.search :refer [search-page]] + [app.main.ui.dashboard.sidebar :refer [sidebar]] + [app.main.ui.dashboard.team :refer [team-settings-page team-members-page]] + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [t]] [app.util.router :as rt] - [app.util.i18n :as i18n :refer [t]])) + [cuerdas.core :as str] + [okulary.core :as l] + [rumext.alpha :as mf])) (defn ^boolean uuid-str? [s] @@ -44,40 +46,77 @@ (assoc :team-id (uuid team-id)) (uuid-str? project-id) - (assoc :project-id (uuid project-id)) + (assoc :project-id (uuid project-id))))) - ;; TODO: delete the usage of "drafts" +(defn- team-ref + [id] + (l/derived (l/in [:teams id]) st/state)) - (= "drafts" project-id) - (assoc :project-id (:default-project-id profile))))) +(defn- projects-ref + [team-id] + (l/derived (l/in [:projects team-id]) st/state)) + +(mf/defc dashboard-content + [{:keys [team projects project section search-term profile] :as props}] + [:div.dashboard-content + (case section + :dashboard-projects + [:& projects-section {:team team + :projects projects}] + + :dashboard-files + (when project + [:& files-section {:team team :project project}]) + + + :dashboard-search + [:& search-page {:team team + :search-term search-term}] + + :dashboard-libraries + [:& libraries-page {:team team}] + + :dashboard-team-members + [:& team-members-page {:team team :profile profile}] + + :dashboard-team-settings + [:& team-settings-page {:team team :profile profile}] + + nil)]) (mf/defc dashboard [{:keys [route] :as props}] - (let [profile (mf/deref refs/profile) - page (get-in route [:data :name]) - {:keys [search-term team-id project-id] :as params} (parse-params route profile)] + (let [profile (mf/deref refs/profile) + section (get-in route [:data :name]) + params (parse-params route profile) + + project-id (:project-id params) + team-id (:team-id params) + search-term (:search-term params) + + projects-ref (mf/use-memo (mf/deps team-id) #(projects-ref team-id)) + team-ref (mf/use-memo (mf/deps team-id) #(team-ref team-id)) + + team (mf/deref team-ref) + projects (mf/deref projects-ref) + project (get projects project-id)] + + (mf/use-effect + (mf/deps team-id) + (st/emitf (dd/fetch-bundle {:id team-id}))) + [:section.dashboard-layout - [:div.main-logo - [:a {:on-click #(st/emit! (rt/nav :dashboard-team {:team-id team-id}))} - i/logo-icon]] - [:& profile-section {:profile profile}] - [:& sidebar {:team-id team-id - :project-id project-id - :section page + [:& sidebar {:team team + :projects projects + :project project + :profile profile + :section section :search-term search-term}] - [:div.dashboard-content - (case page - :dashboard-search - [:& search-page {:team-id team-id :search-term search-term}] - - :dashboard-team - [:& recent-files-page {:team-id team-id}] - - :dashboard-libraries - [:& libraries-page {:team-id team-id}] - - :dashboard-project - [:& project-page {:team-id team-id - :project-id project-id}])]])) - + (when team + [:& dashboard-content {:projects projects + :profile profile + :project project + :section section + :search-term search-term + :team team}])])) diff --git a/frontend/src/app/main/ui/dashboard/common.cljs b/frontend/src/app/main/ui/dashboard/common.cljs deleted file mode 100644 index 0acbd96be2..0000000000 --- a/frontend/src/app/main/ui/dashboard/common.cljs +++ /dev/null @@ -1,58 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns app.main.ui.dashboard.common - (:require - [cuerdas.core :as str] - [rumext.alpha :as mf] - [app.main.ui.icons :as i] - [app.main.ui.keyboard :as k] - [app.util.dom :as dom] - [app.util.i18n :as t :refer [tr]])) - -;; --- Page Title - -(mf/defc grid-header - [{:keys [on-change on-delete value read-only?] :as props}] - (let [edit? (mf/use-state false) - input (mf/use-ref nil)] - (letfn [(save [] - (let [new-value (-> (mf/ref-val input) - (dom/get-inner-text) - (str/trim))] - (on-change new-value) - (reset! edit? false))) - (cancel [] - (reset! edit? false)) - (edit [] - (reset! edit? true)) - (on-input-keydown [e] - (cond - (k/esc? e) (cancel) - (k/enter? e) - (do - (dom/prevent-default e) - (dom/stop-propagation e) - (save))))] - [:div.dashboard-title - [:h2 - (if @edit? - [:div.dashboard-title-field - [:span.edit {:content-editable true - :ref input - :on-key-down on-input-keydown - :dangerouslySetInnerHTML {"__html" value}}] - [:span.close {:on-click cancel} i/close]] - (if-not read-only? - [:span.dashboard-title-field {:on-double-click edit} value] - [:span.dashboard-title-field value]))] - (when-not read-only? - [:div.edition - (if @edit? - [:span {:on-click save} i/save] - [:span {:on-click edit} i/pencil]) - [:span {:on-click on-delete} i/trash]])]))) - diff --git a/frontend/src/app/main/ui/dashboard/files.cljs b/frontend/src/app/main/ui/dashboard/files.cljs new file mode 100644 index 0000000000..d87aa390ae --- /dev/null +++ b/frontend/src/app/main/ui/dashboard/files.cljs @@ -0,0 +1,111 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.dashboard.files + (:require + [app.main.data.dashboard :as dd] + [app.main.data.modal :as modal] + [app.main.store :as st] + [app.main.ui.components.context-menu :refer [context-menu]] + [app.main.ui.dashboard.grid :refer [grid]] + [app.main.ui.dashboard.inline-edition :refer [inline-edition]] + [app.main.ui.icons :as i] + [app.main.ui.keyboard :as kbd] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [t]] + [app.util.router :as rt] + [okulary.core :as l] + [rumext.alpha :as mf])) + +(mf/defc header + [{:keys [team project] :as props}] + (let [local (mf/use-state {:menu-open false + :edition false}) + locale (mf/deref i18n/locale) + project-id (:id project) + team-id (:id team) + + on-menu-click + (mf/use-callback #(swap! local assoc :menu-open true)) + + on-menu-close + (mf/use-callback #(swap! local assoc :menu-open false)) + + on-edit + (mf/use-callback #(swap! local assoc :edition true :menu-open false)) + + + delete-fn + (mf/use-callback + (mf/deps project) + (fn [event] + (st/emit! (dd/delete-project project) + (rt/nav :dashboard-projects {:team-id (:id team)})))) + + on-delete + (mf/use-callback + (mf/deps project) + (st/emitf (modal/show + {:type :confirm + :title (t locale "modals.delete-project-confirm.title") + :message (t locale "modals.delete-project-confirm.message") + :accept-label (t locale "modals.delete-project-confirm.accept") + :on-accept delete-fn}))) + + on-create-clicked + (mf/use-callback + (mf/deps project) + (fn [event] + (dom/prevent-default event) + (st/emit! (dd/create-file {:project-id (:id project)}))))] + + + [:header.dashboard-header + (if (:is-default project) + [:div.dashboard-title + [:h1 (t locale "dashboard.draft-title")]] + + (if (:edition @local) + [:& inline-edition {:content (:name project) + :on-end (fn [name] + (st/emit! (dd/rename-project (assoc project :name name))) + (swap! local assoc :edition false))}] + [:div.dashboard-title + [:h1 (:name project)] + [:div.icon {:on-click on-menu-click} i/actions] + [:& context-menu {:on-close on-menu-close + :show (:menu-open @local) + :options [[(t locale "labels.rename") on-edit] + [(t locale "labels.delete") on-delete]]}]])) + [:a.btn-secondary.btn-small {:on-click on-create-clicked} + (t locale "dashboard.new-file")]])) + +(defn files-ref + [project-id] + (l/derived (l/in [:files project-id]) st/state)) + +(mf/defc files-section + [{:keys [project team] :as props}] + (let [files-ref (mf/use-memo (mf/deps (:id project)) #(files-ref (:id project))) + files-map (mf/deref files-ref) + files (->> (vals files-map) + (sort-by :modified-at) + (reverse))] + + (mf/use-effect + (mf/deps (:id project)) + (fn [] + (st/emit! (dd/fetch-files {:project-id (:id project)})))) + + [:* + [:& header {:team team :project project}] + [:section.dashboard-container + [:& grid {:id (:id project) + :files files}]]])) + diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index 348b6db64e..fdd75d1fa7 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -10,14 +10,16 @@ (ns app.main.ui.dashboard.grid (:require [app.common.uuid :as uuid] + [app.common.math :as mth] [app.config :as cfg] - [app.main.data.dashboard :as dsh] + [app.main.data.dashboard :as dd] [app.main.fonts :as fonts] [app.main.store :as st] [app.main.ui.components.context-menu :refer [context-menu]] + [app.main.ui.dashboard.inline-edition :refer [inline-edition]] [app.main.ui.icons :as i] [app.main.ui.keyboard :as kbd] - [app.main.ui.modal :as modal] + [app.main.data.modal :as modal] [app.main.worker :as wrk] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [t tr]] @@ -59,20 +61,26 @@ (mf/defc grid-item {:wrap [mf/memo]} [{:keys [id file] :as props}] - (let [local (mf/use-state {:menu-open false :edition false}) - locale (mf/deref i18n/locale) + (let [local (mf/use-state {:menu-open false :edition false}) + locale (mf/deref i18n/locale) + on-close (mf/use-callback #(swap! local assoc :menu-open false)) - delete (mf/use-callback (mf/deps id) #(st/emit! nil (dsh/delete-file id))) - add-shared (mf/use-callback (mf/deps id) #(st/emit! (dsh/set-file-shared id true))) - del-shared (mf/use-callback (mf/deps id) #(st/emit! (dsh/set-file-shared id false))) - on-close (mf/use-callback #(swap! local assoc :menu-open false)) + delete-fn + (mf/use-callback + (mf/deps file) + (st/emitf (dd/delete-file file))) on-delete (mf/use-callback - (mf/deps id) + (mf/deps file) (fn [event] (dom/stop-propagation event) - (modal/show! :confirm-dialog {:on-accept delete}))) + (st/emit! (modal/show + {:type :confirm + :title (t locale "modals.delete-file-confirm.title") + :message (t locale "modals.delete-file-confirm.message") + :accept-label (t locale "modals.delete-file-confirm.accept") + :on-accept delete-fn})))) on-navigate (mf/use-callback @@ -83,72 +91,79 @@ qparams {:page-id (first (get-in file [:data :pages]))}] (st/emit! (rt/nav :workspace pparams qparams))))) + + add-shared + (mf/use-callback + (mf/deps file) + (st/emitf (dd/set-file-shared (assoc file :is-shared true)))) + + del-shared + (mf/use-callback + (mf/deps file) + (st/emitf (dd/set-file-shared (assoc file :is-shared false)))) + on-add-shared (mf/use-callback - (mf/deps id) + (mf/deps file) (fn [event] (dom/stop-propagation event) - (modal/show! :confirm-dialog - {:message (t locale "dashboard.grid.add-shared-message" (:name file)) - :hint (t locale "dashboard.grid.add-shared-hint") - :accept-text (t locale "dashboard.grid.add-shared-accept") - :not-danger? true - :on-accept add-shared}))) - - on-edit - (mf/use-callback - (mf/deps id) - (fn [event] - (dom/stop-propagation event) - (swap! local assoc :edition true))) + (st/emit! (modal/show + {:type :confirm + :message "" + :title (t locale "modals.add-shared-confirm.message" (:name file)) + :hint (t locale "modals.add-shared-confirm.hint") + :cancel-label :omit + :accept-label (t locale "modals.add-shared-confirm.accept") + :accept-style :primary + :on-accept add-shared})))) on-del-shared (mf/use-callback - (mf/deps id) + (mf/deps file) (fn [event] + (dom/prevent-default event) (dom/stop-propagation event) - (modal/show! :confirm-dialog - {:message (t locale "dashboard.grid.remove-shared-message" (:name file)) - :hint (t locale "dashboard.grid.remove-shared-hint") - :accept-text (t locale "dashboard.grid.remove-shared-accept") - :not-danger? false - :on-accept del-shared}))) + (st/emit! (modal/show + {:type :confirm + :message "" + :title (t locale "modals.remove-shared-confirm.message" (:name file)) + :hint (t locale "modals.remove-shared-confirm.hint") + :cancel-label :omit + :accept-label (t locale "modals.remove-shared-confirm.accept") + :on-accept del-shared})))) on-menu-click (mf/use-callback - (mf/deps id) + (mf/deps file) (fn [event] + (dom/prevent-default event) (dom/stop-propagation event) (swap! local assoc :menu-open true))) - on-blur + edit (mf/use-callback - (mf/deps id) - (fn [event] - (let [name (-> event dom/get-target dom/get-value)] - (st/emit! (dsh/rename-file id name)) - (swap! local assoc :edition false)))) + (mf/deps file) + (fn [name] + (st/emit! (dd/rename-file (assoc file :name name))) + (swap! local assoc :edition false))) - on-key-down + on-edit (mf/use-callback - #(cond - (kbd/enter? %) (on-blur %) - (kbd/esc? %) (swap! local assoc :edition false))) + (mf/deps file) + (fn [event] + (dom/stop-propagation event) + (swap! local assoc :edition true))) ] [:div.grid-item.project-th {:on-click on-navigate} [:div.overlay] [:& grid-item-thumbnail {:file file}] (when (:is-shared file) - [:div.item-badge - i/library]) + [:div.item-badge i/library]) [:div.item-info (if (:edition @local) - [:input.element-name {:type "text" - :auto-focus true - :on-key-down on-key-down - :on-blur on-blur - :default-value (:name file)}] + [:& inline-edition {:content (:name file) + :on-end edit}] [:h3 (:name file)]) [:& grid-item-metadata {:modified-at (:modified-at file)}]] [:div.project-th-actions {:class (dom/classnames @@ -158,34 +173,89 @@ i/actions] [:& context-menu {:on-close on-close :show (:menu-open @local) - :options [[(t locale "dashboard.grid.rename") on-edit] - [(t locale "dashboard.grid.delete") on-delete] + :options [[(t locale "labels.rename") on-edit] + [(t locale "labels.delete") on-delete] (if (:is-shared file) - [(t locale "dashboard.grid.remove-shared") on-del-shared] - [(t locale "dashboard.grid.add-shared") on-add-shared])]}]]])) + [(t locale "dashboard.remove-shared") on-del-shared] + [(t locale "dashboard.add-shared") on-add-shared])]}]]])) -;; --- Grid +(mf/defc empty-placeholder + [] + (let [locale (mf/deref i18n/locale)] + [:div.grid-empty-placeholder + [:div.icon i/file-html] + [:div.text (t locale "dashboard.empty-files")]])) (mf/defc grid - [{:keys [id opts files hide-new?] :as props}] - (let [locale (mf/deref i18n/locale) - click #(st/emit! (dsh/create-file id))] + [{:keys [id opts files] :as props}] + (let [locale (mf/deref i18n/locale)] [:section.dashboard-grid - (cond - (pos? (count files)) - [:div.dashboard-grid-row - (when (not hide-new?) - [:div.grid-item.add-file {:on-click click} - [:span (t locale "ds.new-file")]]) - + (if (pos? (count files)) + [:div.grid-row (for [item files] [:& grid-item {:id (:id item) :file item :key (:id item)}])] - (zero? (count files)) - [:div.grid-files-empty - [:div.grid-files-desc (t locale "dashboard.grid.empty-files")] - [:div.grid-files-link - [:a.btn-secondary.btn-small {:on-click click} (t locale "ds.new-file")]]])])) + [:& empty-placeholder])])) + +(mf/defc line-grid-row + [{:keys [locale files on-load-more] :as props}] + (let [rowref (mf/use-ref) + + width (mf/use-state 900) + limit (mf/use-state 1) + + itemsize 290] + + (mf/use-layout-effect + (mf/deps width) + (fn [] + (let [node (mf/ref-val rowref) + obs (new js/ResizeObserver + (fn [entries x] + (ts/raf (fn [] + (let [data (first entries) + rect (.-contentRect ^js data)] + (reset! width (.-width ^js rect))))))) + + nitems (/ @width itemsize) + num (mth/floor nitems)] + + (.observe ^js obs node) + + (cond + (< (* itemsize (count files)) @width) + (reset! limit num) + + (< nitems (+ num 0.51)) + (reset! limit (dec num)) + + :else + (reset! limit num)) + (fn [] + (.disconnect ^js obs))))) + + [:div.grid-row.no-wrap {:ref rowref} + (for [item (take @limit files)] + [:& grid-item + {:id (:id item) + :file item + :key (:id item)}]) + (when (> (count files) @limit) + [:div.grid-item.placeholder {:on-click on-load-more} + [:div.placeholder-icon i/arrow-down] + [:div.placeholder-label + (t locale "dashboard.show-all-files")]])])) + +(mf/defc line-grid + [{:keys [project-id opts files on-load-more] :as props}] + (let [locale (mf/deref i18n/locale)] + [:section.dashboard-grid + (if (pos? (count files)) + [:& line-grid-row {:files files + :on-load-more on-load-more + :locale locale}] + [:& empty-placeholder])])) + diff --git a/frontend/src/app/main/ui/dashboard/inline_edition.cljs b/frontend/src/app/main/ui/dashboard/inline_edition.cljs new file mode 100644 index 0000000000..6439414a8b --- /dev/null +++ b/frontend/src/app/main/ui/dashboard/inline_edition.cljs @@ -0,0 +1,73 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.dashboard.inline-edition + (:require + [app.main.ui.icons :as i] + [app.main.ui.keyboard :as kbd] + [app.util.dom :as dom] + [rumext.alpha :as mf])) + +(mf/defc inline-edition + [{:keys [content on-end] :as props}] + (let [name (mf/use-state content) + input-ref (mf/use-ref) + + on-input + (mf/use-callback + (fn [event] + (->> (dom/get-target-val event) + (reset! name)))) + + on-cancel + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (on-end @name))) + + on-click + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event))) + + on-blur + (mf/use-callback + (fn [event] + (let [name (dom/get-target-val event)] + (on-end name)))) + + on-keyup + (mf/use-callback + (fn [event] + (cond + (kbd/esc? event) + (on-cancel) + + (kbd/enter? event) + (let [name (dom/get-target-val event)] + (on-end name)))))] + + (mf/use-effect + (fn [] + (let [node (mf/ref-val input-ref)] + (dom/focus! node) + (dom/select-text! node)))) + + [:div.edit-wrapper + [:input.element-title {:value @name + :ref input-ref + :on-click on-click + :on-change on-input + :on-key-down on-keyup + :on-blur on-blur}] + [:span.close {:on-click on-cancel} i/close]])) + + diff --git a/frontend/src/app/main/ui/dashboard/libraries.cljs b/frontend/src/app/main/ui/dashboard/libraries.cljs index 56b00b35c2..8f7ed8c3d9 100644 --- a/frontend/src/app/main/ui/dashboard/libraries.cljs +++ b/frontend/src/app/main/ui/dashboard/libraries.cljs @@ -9,35 +9,35 @@ (ns app.main.ui.dashboard.libraries (:require - [okulary.core :as l] - [rumext.alpha :as mf] - [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [tr]] - [app.util.dom :as dom] - [app.util.router :as rt] - [app.main.data.dashboard :as dsh] + [app.main.data.dashboard :as dd] [app.main.store :as st] - [app.main.ui.modal :as modal] - [app.main.ui.keyboard :as kbd] - [app.main.ui.components.context-menu :refer [context-menu]] - [app.main.ui.dashboard.grid :refer [grid]])) + [app.main.ui.dashboard.grid :refer [grid]] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [app.util.router :as rt] + [okulary.core :as l] + [rumext.alpha :as mf])) -(def files-ref - (-> (comp vals :files) - (l/derived st/state))) +(defn files-ref + [team-id] + (l/derived (l/in [:shared-files team-id]) st/state)) (mf/defc libraries-page - [{:keys [section team-id] :as props}] - (let [files (->> (mf/deref files-ref) - (sort-by :modified-at) - (reverse))] + [{:keys [team] :as props}] + (let [files-ref (mf/use-memo (mf/deps (:id team)) #(files-ref (:id team))) + files-map (mf/deref files-ref) + files (->> (vals files-map) + (sort-by :modified-at) + (reverse))] (mf/use-effect - (mf/deps section team-id) - #(st/emit! (dsh/initialize-libraries team-id))) + (mf/deps team) + #(st/emit! (dd/fetch-shared-files {:team-id (:id team)}))) [:* - [:header.main-bar - [:h1.dashboard-title (tr "dashboard.header.libraries")]] - [:section.libraries-page - [:& grid {:files files :hide-new? true}]]])) + [:header.dashboard-header + [:div.dashboard-title + [:h1 (tr "dashboard.libraries-title")]]] + [:section.dashboard-container + [:& grid {:files files}]]])) diff --git a/frontend/src/app/main/ui/dashboard/profile.cljs b/frontend/src/app/main/ui/dashboard/profile.cljs deleted file mode 100644 index 5d92a86beb..0000000000 --- a/frontend/src/app/main/ui/dashboard/profile.cljs +++ /dev/null @@ -1,57 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2015-2020 Andrey Antukh -;; Copyright (c) 2015-2020 Juan de la Cruz - -(ns app.main.ui.dashboard.profile - (:require - [cuerdas.core :as str] - [rumext.alpha :as mf] - [app.main.data.auth :as da] - [app.main.refs :as refs] - [app.main.store :as st] - [app.main.ui.components.dropdown :refer [dropdown]] - [app.main.ui.icons :as i] - [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t]] - [app.util.router :as rt])) - -;; --- Component: Profile - -(mf/defc profile-section - [{:keys [profile] :as props}] - (let [show (mf/use-state false) - photo (:photo-uri profile "") - photo (if (str/empty? photo) - "/images/avatar.jpg" - photo) - - locale (i18n/use-locale) - on-click - (fn [event section] - (dom/stop-propagation event) - (if (keyword? section) - (st/emit! (rt/nav section)) - (st/emit! section)))] - - [:div.user-zone {:on-click #(reset! show true)} - [:img {:src photo}] - [:span (:fullname profile)] - - [:& dropdown {:on-close #(reset! show false) - :show @show} - [:ul.profile-menu - [:li {:on-click #(on-click % :settings-profile)} - i/user - [:span (t locale "dashboard.header.profile-menu.profile")]] - [:li {:on-click #(on-click % :settings-password)} - i/lock - [:span (t locale "dashboard.header.profile-menu.password")]] - [:li {:on-click #(on-click % da/logout)} - i/exit - [:span (t locale "dashboard.header.profile-menu.logout")]]]]])) diff --git a/frontend/src/app/main/ui/dashboard/project.cljs b/frontend/src/app/main/ui/dashboard/project.cljs deleted file mode 100644 index 464490ded0..0000000000 --- a/frontend/src/app/main/ui/dashboard/project.cljs +++ /dev/null @@ -1,85 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL - -(ns app.main.ui.dashboard.project - (:require - [okulary.core :as l] - [rumext.alpha :as mf] - [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [t]] - [app.util.dom :as dom] - [app.util.router :as rt] - [app.main.data.dashboard :as dsh] - [app.main.store :as st] - [app.main.ui.modal :as modal] - [app.main.ui.keyboard :as kbd] - [app.main.ui.components.context-menu :refer [context-menu]] - [app.main.ui.dashboard.grid :refer [grid]])) - -(def projects-ref - (l/derived :projects st/state)) - -(def files-ref - (-> (comp vals :files) - (l/derived st/state))) - -(mf/defc project-header - [{:keys [team-id project-id] :as props}] - (let [local (mf/use-state {:menu-open false - :edition false}) - projects (mf/deref projects-ref) - project (get projects project-id) - locale (i18n/use-locale) - on-menu-click #(swap! local assoc :menu-open true) - on-menu-close #(swap! local assoc :menu-open false) - on-edit #(swap! local assoc :edition true :menu-open false) - on-blur #(let [name (-> % dom/get-target dom/get-value)] - (st/emit! (dsh/rename-project project-id name)) - (swap! local assoc :edition false)) - on-key-down #(cond - (kbd/enter? %) (on-blur %) - (kbd/esc? %) (swap! local assoc :edition false)) - delete-fn #(do - (st/emit! (dsh/delete-project project-id)) - (st/emit! (rt/nav :dashboard-team {:team-id team-id}))) - on-delete #(modal/show! :confirm-dialog {:on-accept delete-fn})] - [:header.main-bar - (if (:is-default project) - [:h1.dashboard-title (t locale "dashboard.header.draft")] - [:* - [:h1.dashboard-title (t locale "dashboard.header.project" (:name project))] - [:div.main-bar-icon {:on-click on-menu-click} i/arrow-down] - [:& context-menu {:on-close on-menu-close - :show (:menu-open @local) - :options [[(t locale "dashboard.grid.rename") on-edit] - [(t locale "dashboard.grid.delete") on-delete]]}] - (if (:edition @local) - [:input.element-name {:type "text" - :auto-focus true - :on-key-down on-key-down - :on-blur on-blur - :default-value (:name project)}])]) - [:a.btn-secondary.btn-small {:on-click #(do - (dom/prevent-default %) - (st/emit! (dsh/create-file project-id)))} - (t locale "dashboard.header.new-file")]])) - -(mf/defc project-page - [{:keys [section team-id project-id] :as props}] - (let [files (->> (mf/deref files-ref) - (sort-by :modified-at) - (reverse))] - (mf/use-effect - (mf/deps section team-id project-id) - #(st/emit! (dsh/initialize-project team-id project-id))) - - [:* - [:& project-header {:team-id team-id :project-id project-id}] - [:section.projects-page - [:& grid { :id project-id :files files :hide-new? true}]]])) diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs new file mode 100644 index 0000000000..b2fee3984a --- /dev/null +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -0,0 +1,136 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.dashboard.projects + (:require + [app.common.exceptions :as ex] + [app.main.constants :as c] + [app.main.data.dashboard :as dd] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.dashboard.grid :refer [line-grid]] + [app.main.ui.icons :as i] + [app.main.ui.keyboard :as kbd] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [t tr]] + [app.util.router :as rt] + [app.util.time :as dt] + [okulary.core :as l] + [rumext.alpha :as mf])) + +(mf/defc header + {::mf/wrap [mf/memo]} + [{:keys [locale team] :as props}] + (let [create #(st/emit! (dd/create-project {:team-id (:id team)}))] + [:header.dashboard-header + [:div.dashboard-title + [:h1 (t locale "dashboard.projects-title")]] + [:a.btn-secondary.btn-small {:on-click create} + (t locale "dashboard.new-project")]])) + +(defn files-ref + [project-id] + (l/derived (l/in [:files project-id]) st/state)) + +(defn recent-ref + [project-id] + (l/derived (l/in [:recent-files project-id]) st/state)) + +(mf/defc project-item + [{:keys [project first? locale] :as props}] + (let [files-ref (mf/use-memo (mf/deps project) #(files-ref (:id project))) + recent-ref (mf/use-memo (mf/deps project) #(recent-ref (:id project))) + + files-map (mf/deref files-ref) + recent-ids (mf/deref recent-ref) + + files (->> recent-ids + (map #(get files-map %)) + (sort-by :modified-at) + (reverse)) + + project-id (:id project) + team-id (:team-id project) + file-count (or (:count project) 0) + + on-nav + (mf/use-callback + (mf/deps project) + (st/emitf (rt/nav :dashboard-files {:team-id (:team-id project) + :project-id (:id project)}))) + toggle-pin + (mf/use-callback + (mf/deps project) + (st/emitf (dd/toggle-project-pin project))) + + on-file-created + (mf/use-callback + (mf/deps project) + (fn [data] + (let [pparams {:project-id (:project-id data) + :file-id (:id data)} + qparams {:page-id (get-in data [:data :pages 0])}] + (st/emit! (rt/nav :workspace pparams qparams))))) + + + create-file + (mf/use-callback + (mf/deps project) + (fn [] + (let [mdata {:on-success on-file-created} + params {:project-id (:id project)}] + (st/emit! (dd/create-file (with-meta params mdata))))))] + + + [:div.dashboard-project-row {:class (when first? "first")} + [:div.project + (when-not (:is-default project) + [:span.pin-icon + {:class (when (:is-pinned project) "active") + :on-click toggle-pin} + i/pin]) + [:h2 {:on-click on-nav} (:name project)] + [:span.info (str file-count " files")] + (when (> file-count 0) + (let [time (-> (:modified-at project) + (dt/timeago {:locale locale}))] + [:span.recent-files-row-title-info (str ", " time)])) + + [:a.btn-secondary.btn-small + {:on-click create-file} + (t locale "dashboard.new-file")]] + + [:& line-grid + {:project-id (:id project) + :on-load-more on-nav + :files files}]])) + +(mf/defc projects-section + [{:keys [team projects] :as props}] + (let [projects (->> (vals projects) + (sort-by :modified-at) + (reverse)) + locale (mf/deref i18n/locale)] + + (mf/use-effect + (mf/deps team) + (fn [] + (st/emit! (dd/fetch-recent-files {:team-id (:id team)})))) + + (when (seq projects) + [:* + [:& header {:locale locale + :team team}] + [:section.dashboard-container + (for [project projects] + [:& project-item {:project project + :locale locale + :first? (= project (first projects)) + :key (:id project)}])]]))) + diff --git a/frontend/src/app/main/ui/dashboard/recent_files.cljs b/frontend/src/app/main/ui/dashboard/recent_files.cljs deleted file mode 100644 index 473e3a912a..0000000000 --- a/frontend/src/app/main/ui/dashboard/recent_files.cljs +++ /dev/null @@ -1,95 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL - -(ns app.main.ui.dashboard.recent-files - (:require - [okulary.core :as l] - [rumext.alpha :as mf] - [app.common.exceptions :as ex] - [app.main.constants :as c] - [app.main.data.dashboard :as dsh] - [app.main.refs :as refs] - [app.main.store :as st] - [app.main.ui.dashboard.grid :refer [grid]] - [app.main.ui.icons :as i] - [app.main.ui.keyboard :as kbd] - [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t tr]] - [app.util.router :as rt] - [app.util.time :as dt])) - -;; --- Component: Content - -(def projects-ref - (l/derived :projects st/state)) - -(def recent-file-ids-ref - (l/derived :recent-file-ids st/state)) - -(def files-ref - (l/derived :files st/state)) - -;; --- Component: Recent files - -(mf/defc recent-files-header - [{:keys [profile] :as props}] - (let [locale (i18n/use-locale)] - [:header#main-bar.main-bar - [:h1.dashboard-title "Recent"] - [:a.btn-secondary.btn-small {:on-click #(st/emit! dsh/create-project)} - (t locale "dashboard.header.new-project")]])) - -(mf/defc recent-project - [{:keys [project files first? locale] :as props}] - (let [project-id (:id project) - team-id (:team-id project) - file-count (or (:file-count project) 0)] - [:div.recent-files-row - {:class-name (when first? "first")} - [:div.recent-files-row-title - [:h2.recent-files-row-title-name {:on-click #(st/emit! (rt/nav :dashboard-project {:team-id team-id - :project-id project-id})) - :style {:cursor "pointer"}} (:name project)] - [:span.recent-files-row-title-info (str file-count " files")] - (when (> file-count 0) - (let [time (-> (:modified-at project) - (dt/timeago {:locale locale}))] - [:span.recent-files-row-title-info (str ", " time)]))] - [:& grid {:id (:id project) - :files files - :hide-new? true}]])) - - -(mf/defc recent-files-page - [{:keys [team-id] :as props}] - (let [projects (->> (mf/deref projects-ref) - (vals) - (sort-by :modified-at) - (reverse)) - files (mf/deref files-ref) - recent-file-ids (mf/deref recent-file-ids-ref) - locale (i18n/use-locale) - setup #(st/emit! (dsh/initialize-recent team-id))] - - (-> (mf/deps team-id) - (mf/use-effect #(st/emit! (dsh/initialize-recent team-id)))) - - (when (and projects recent-file-ids) - [:* - [:& recent-files-header] - [:section.recent-files-page - (for [project projects] - [:& recent-project {:project project - :locale locale - :key (:id project) - :files (->> (get recent-file-ids (:id project)) - (map #(get files %)) - (filter identity)) ;; avoid failure if a "project only" files list is in global state - :first? (= project (first projects))}])]]))) - diff --git a/frontend/src/app/main/ui/dashboard/search.cljs b/frontend/src/app/main/ui/dashboard/search.cljs index 6a3eb914c6..eeb4aff635 100644 --- a/frontend/src/app/main/ui/dashboard/search.cljs +++ b/frontend/src/app/main/ui/dashboard/search.cljs @@ -5,47 +5,48 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2015-2017 Juan de la Cruz -;; Copyright (c) 2015-2020 Andrey Antukh +;; Copyright (c) 2020 UXBOX Labs SL (ns app.main.ui.dashboard.search (:require - [okulary.core :as l] - [rumext.alpha :as mf] + [app.main.data.dashboard :as dd] [app.main.store :as st] - [app.main.data.dashboard :as dsh] + [app.main.ui.dashboard.grid :refer [grid]] + [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [t]] - [app.main.ui.dashboard.grid :refer [grid]])) + [okulary.core :as l] + [rumext.alpha :as mf])) -;; --- Component: Search - -(def search-result-ref - (-> #(get-in % [:dashboard-local :search-result]) - (l/derived st/state))) +(def result-ref + (l/derived (l/in [:dashboard-local :search-result]) st/state)) (mf/defc search-page - [{:keys [team-id search-term] :as props}] - (let [search-result (mf/deref search-result-ref) - locale (i18n/use-locale)] + [{:keys [team search-term] :as props}] + (let [result (mf/deref result-ref) + locale (mf/deref i18n/locale)] + (mf/use-effect - (mf/deps search-term) - #(st/emit! (dsh/initialize-search team-id search-term))) + (mf/deps team search-term) + (st/emitf (dd/search-files {:team-id (:id team) + :search-term search-term}))) - [:section.search-page - [:section.dashboard-grid - (cond - (empty? search-term) - [:div.grid-files-empty - [:div.grid-files-desc (t locale "dashboard.search.type-something")]] + [:section.dashboard-container.search + (cond + (empty? search-term) + [:div.grid-empty-placeholder + [:div.icon i/search] + [:div.text (t locale "dashboard.type-something")]] - (nil? search-result) - [:div.grid-files-empty - [:div.grid-files-desc (t locale "dashboard.search.searching-for" search-term)]] + (nil? result) + [:div.grid-empty-placeholder + [:div.icon i/search] + [:div.text (t locale "dashboard.searching-for" search-term)]] - (empty? search-result) - [:div.grid-files-empty - [:div.grid-files-desc (t locale "dashboard.search.no-matches-for" search-term)]] - - :else - [:& grid { :files search-result :hide-new? true}])]])) + (empty? result) + [:div.grid-empty-placeholder + [:div.icon i/search] + [:div.text (t locale "dashboard.no-matches-for" search-term)]] + :else + [:& grid {:files result + :hide-new? true}])])) diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index 10d6b2e61c..333ab72d73 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -5,195 +5,466 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 Andrey Antukh -;; Copyright (c) 2020 Juan de la Cruz +;; Copyright (c) 2020 UXBOX Labs SL (ns app.main.ui.dashboard.sidebar (:require + [app.common.data :as d] + [app.common.spec :as us] + [app.config :as cfg] + [app.main.data.auth :as da] + [app.main.data.dashboard :as dd] + [app.main.data.messages :as dm] + [app.main.refs :as refs] + [app.main.repo :as rp] + [app.main.store :as st] + [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.dashboard.inline-edition :refer [inline-edition]] + [app.main.ui.components.forms :as fm] + [app.main.ui.dashboard.team-form] + [app.main.ui.icons :as i] + [app.main.ui.keyboard :as kbd] + [app.main.data.modal :as modal] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [t tr]] + [app.util.object :as obj] + [app.util.router :as rt] + [app.util.time :as dt] + [beicon.core :as rx] + [cljs.spec.alpha :as s] [cuerdas.core :as str] [goog.functions :as f] [okulary.core :as l] - [rumext.alpha :as mf] - [app.main.constants :as c] - [app.main.data.dashboard :as dsh] - [app.main.refs :as refs] - [app.main.store :as st] - [app.main.ui.dashboard.common :as common] - [app.main.ui.icons :as i] - [app.main.ui.keyboard :as kbd] - [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t tr]] - [app.util.router :as rt] - [app.util.time :as dt])) - -;; --- Component: Sidebar + [rumext.alpha :as mf])) (mf/defc sidebar-project - [{:keys [id name selected? team-id] :as props}] - (let [dashboard-local @refs/dashboard-local - project-for-edit (:project-for-edit dashboard-local) - local (mf/use-state {:name name - :editing (= id project-for-edit)}) - editable? (not (nil? id)) - edit-input-ref (mf/use-ref) + [{:keys [item selected?] :as props}] + (let [dstate (mf/deref refs/dashboard-local) + edit-id (:project-for-edit dstate) - on-click #(st/emit! (rt/nav :dashboard-project {:team-id team-id :project-id id})) - on-dbl-click #(when editable? (swap! local assoc :editing true)) - on-input #(as-> % $ - (dom/get-target $) - (dom/get-value $) - (swap! local assoc :name $)) - on-cancel #(do - (st/emit! dsh/clear-project-for-edit) - (swap! local assoc :editing false :name name)) - on-keyup #(cond - (kbd/esc? %) - (on-cancel) + edition? (mf/use-state (= (:id item) edit-id)) - (kbd/enter? %) - (let [name (-> % dom/get-target dom/get-value)] - (st/emit! dsh/clear-project-for-edit) - (st/emit! (dsh/rename-project id name)) - (swap! local assoc :editing false)))] + on-click + (mf/use-callback + (mf/deps item) + (fn [] + (st/emit! (rt/nav :dashboard-files {:team-id (:team-id item) + :project-id (:id item)})))) - (mf/use-effect - (mf/deps (:editing @local)) - #(when (:editing @local) - (let [edit-input (mf/ref-val edit-input-ref)] - (dom/focus! edit-input) - (dom/select-text! edit-input)) - nil)) + on-dbl-click + (mf/use-callback #(reset! edition? true)) + + on-edit + (mf/use-callback + (mf/deps item) + (fn [name] + (st/emit! (dd/rename-project (assoc item :name name))) + (reset! edition? false)))] [:li {:on-click on-click :on-double-click on-dbl-click - :class-name (when selected? "current")} - (if (:editing @local) - [:div.edit-wrapper - [:input.element-title {:value (:name @local) - :ref edit-input-ref - :on-change on-input - :on-key-down on-keyup}] - [:span.close {:on-click on-cancel} i/close]] - [:* - i/folder - [:span.element-title name]])])) + :class (when selected? "current")} + (if @edition? + [:& inline-edition {:content (:name item) + :on-end on-edit}] + [:span.element-title (:name item)])])) -(def projects-iref - (l/derived :projects st/state)) - -(mf/defc sidebar-projects - [{:keys [team-id selected-project-id] :as props}] - (let [projects (->> (mf/deref projects-iref) - (vals) - (remove #(:is-default %)) - (sort-by :created-at))] - (for [item projects] - [:& sidebar-project - {:id (:id item) - :key (:id item) - :name (:name item) - :selected? (= (:id item) selected-project-id) - :team-id team-id - }]))) - -(mf/defc sidebar-team - [{:keys [profile - team-id - selected-section - selected-project-id - selected-team-id] :as props}] - (let [home? (and (= selected-section :dashboard-team) - (= selected-team-id (:default-team-id profile))) - drafts? (and (= selected-section :dashboard-project) - (= selected-team-id (:default-team-id profile)) - (= selected-project-id (:default-project-id profile))) - libraries? (= selected-section :dashboard-libraries) - ;; library? (and (str/starts-with? (name selected-section) "dashboard-library") - ;; (= selected-team-id (:default-team-id profile))) - locale (i18n/use-locale)] - [:div.sidebar-team - [:ul.dashboard-elements.dashboard-common - [:li.recent-projects - {:on-click #(st/emit! (rt/nav :dashboard-team {:team-id team-id})) - :class-name (when home? "current")} - i/recent - [:span.element-title (t locale "dashboard.sidebar.recent")]] - - [:li - {:on-click #(st/emit! (rt/nav :dashboard-project {:team-id team-id - :project-id "drafts"})) - :class-name (when drafts? "current")} - i/file-html - [:span.element-title (t locale "dashboard.sidebar.drafts")]] - - [:li - {:on-click #(st/emit! (rt/nav :dashboard-libraries {:team-id team-id})) - :class-name (when libraries? "current")} - i/library - [:span.element-title (t locale "dashboard.sidebar.libraries")]]] - - [:div.projects-row - [:span "PROJECTS"] - [:a.btn-icon-light.btn-small {:on-click #(st/emit! dsh/create-project)} - i/close]] - - [:ul.dashboard-elements - [:& sidebar-projects - {:selected-team-id selected-team-id - :selected-project-id selected-project-id - :team-id team-id}]]] - - )) - - -(def debounced-emit! (f/debounce st/emit! 500)) - -(mf/defc sidebar - [{:keys [section team-id project-id search-term] :as props}] - (let [locale (i18n/use-locale) - profile (mf/deref refs/profile) - search-term-not-nil (or search-term "") +(mf/defc sidebar-search + [{:keys [search-term team-id locale] :as props}] + (let [search-term (or search-term "") + emit! (mf/use-memo #(f/debounce st/emit! 500)) on-search-focus - (fn [event] - (let [target (dom/get-target event) - value (dom/get-value target)] - (dom/select-text! target) - (if (empty? value) - (debounced-emit! (rt/nav :dashboard-search {:team-id team-id} {})) - (debounced-emit! (rt/nav :dashboard-search {:team-id team-id} {:search-term value}))))) + (mf/use-callback + (mf/deps team-id) + (fn [event] + (let [target (dom/get-target event) + value (dom/get-value target)] + (dom/select-text! target) + (if (empty? value) + (emit! (rt/nav :dashboard-search {:team-id team-id} {})) + (emit! (rt/nav :dashboard-search {:team-id team-id} {:search-term value})))))) on-search-change - (fn [event] - (let [value (-> (dom/get-target event) - (dom/get-value))] - (debounced-emit! (rt/nav :dashboard-search {:team-id team-id} {:search-term value})))) + (mf/use-callback + (mf/deps team-id) + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value))] + (emit! (rt/nav :dashboard-search {:team-id team-id} {:search-term value}))))) on-clear-click - (fn [event] - (let [search-input (dom/get-element "search-input")] - (dom/clean-value! search-input) - (dom/focus! search-input) - (debounced-emit! (rt/nav :dashboard-search {:team-id team-id} {}))))] + (mf/use-callback + (mf/deps team-id) + (fn [event] + (let [search-input (dom/get-element "search-input")] + (dom/clean-value! search-input) + (dom/focus! search-input) + (emit! (rt/nav :dashboard-search {:team-id team-id} {})))))] + [:form.sidebar-search + [:input.input-text + {:key :images-search-box + :id "search-input" + :type "text" + :placeholder (t locale "dashboard.search-placeholder") + :default-value search-term + :auto-complete "off" + :on-focus on-search-focus + :on-change on-search-change + :ref #(when % (set! (.-value %) search-term))}] + [:div.clear-search + {:on-click on-clear-click} + i/close]])) + +(mf/defc teams-selector-dropdown + [{:keys [team profile locale] :as props}] + (let [show-dropdown? (mf/use-state false) + teams (mf/use-state []) + + on-create-clicked + (mf/use-callback + (st/emitf (modal/show :team-form {}))) + + go-projects + (mf/use-callback #(st/emit! (rt/nav :dashboard-projects {:team-id %})))] + + (mf/use-layout-effect + (mf/deps (:id team)) + (fn [] + (->> (rp/query! :teams) + (rx/map #(mapv dd/assoc-team-avatar %)) + (rx/subs #(reset! teams %))))) + + [:ul.dropdown.teams-dropdown + [:li.title (t locale "dashboard.switch-team")] + [:hr] + [:li.team-name {:on-click (partial go-projects (:default-team-id profile))} + [:span.team-icon i/logo-icon] + [:span.team-text (t locale "dashboard.your-penpot")]] + + (for [team (remove :is-default @teams)] + [:* {:key (:id team)} + [:li.team-name {:on-click (partial go-projects (:id team))} + [:span.team-icon + [:img {:src (cfg/resolve-media-path (:photo team))}]] + [:span.team-text {:title (:name team)} (:name team)]]]) + + [:hr] + [:li.action {:on-click on-create-clicked} + (t locale "dashboard.create-new-team")]])) + +(s/def ::member-id ::us/uuid) +(s/def ::leave-modal-form + (s/keys :req-un [::member-id])) + +(mf/defc leave-and-reassign-modal + {::mf/register modal/components + ::mf/register-as ::leave-and-reassign} + [{:keys [members profile team accept]}] + (let [form (fm/use-form :spec ::leave-modal-form :initial {}) + options (into [{:value "" :label (tr "modals.leave-and-reassign.select-memeber-to-promote")}] + (map #(hash-map :name (:name %) :value (str (:id %))) members)) + + on-cancel + (mf/use-callback (st/emitf (modal/hide))) + + on-accept + (mf/use-callback + (mf/deps form) + (fn [event] + (let [member-id (get-in @form [:clean-data :member-id])] + (accept member-id))))] + + [:div.modal-overlay + [:div.modal-container.confirm-dialog + [:div.modal-header + [:div.modal-header-title + [:h2 (tr "modals.leave-and-reassign.title")]] + [:div.modal-close-button + {:on-click on-cancel} i/close]] + + [:div.modal-content.generic-form + [:p (tr "modals.leave-and-reassign.hint1" (:name team))] + [:p (tr "modals.leave-and-reassign.hint2")] + + [:& fm/form {:form form} + [:& fm/select {:name :member-id + :options options}]]] + + [:div.modal-footer + [:div.action-buttons + [:input.cancel-button + {:type "button" + :value (tr "labels.cancel") + :on-click on-cancel}] + + [:input.accept-button + {:type "button" + :class (when-not (:valid @form) "btn-disabled") + :disabled (not (:valid @form)) + :value (tr "modals.leave-and-reassign.promote-and-leave") + :on-click on-accept}]]]]])) + + +(mf/defc team-options-dropdown + [{:keys [team locale profile] :as props}] + (let [members (mf/use-state []) + + go-members + (mf/use-callback + (mf/deps team) + (st/emitf (rt/nav :dashboard-team-members {:team-id (:id team)}))) + + go-settings + (mf/use-callback + (mf/deps team) + (st/emitf (rt/nav :dashboard-team-settings {:team-id (:id team)}))) + + go-projects + (mf/use-callback #(st/emit! (rt/nav :dashboard-projects {:team-id %}))) + + on-create-clicked + (mf/use-callback + (st/emitf (modal/show :team-form {}))) + + on-rename-clicked + (mf/use-callback + (mf/deps team) + (st/emitf (modal/show :team-form {:team team}))) + + on-leaved-success + (mf/use-callback + (mf/deps team profile) + (st/emitf (rt/nav :dashboard-projects {:team-id (:default-team-id profile)}))) + + leave-fn + (mf/use-callback + (mf/deps team) + (st/emitf (dd/leave-team (with-meta team {:on-success on-leaved-success})))) + + leave-and-reassign-fn + (mf/use-callback + (mf/deps team) + (fn [member-id] + (let [team (assoc team :reassign-to member-id)] + (st/emit! (dd/leave-team (with-meta team {:on-success on-leaved-success})))))) + + on-leave-clicked + (mf/use-callback + (mf/deps team) + (st/emitf (modal/show + {:type :confirm + :title (t locale "modals.leave-confirm.title") + :message (t locale "modals.leave-confirm.message") + :accept-label (t locale "modals.leave-confirm.accept") + :on-accept leave-fn}))) + + on-leave-as-owner-clicked + (mf/use-callback + (mf/deps team @members) + (st/emitf (modal/show + {:type ::leave-and-reassign + :profile profile + :team team + :accept leave-and-reassign-fn + :members @members}))) + + delete-fn + (mf/use-callback + (mf/deps team) + (st/emitf (dd/delete-team (with-meta team {:on-success on-leaved-success})))) + + on-delete-clicked + (mf/use-callback + (mf/deps team) + (st/emitf (modal/show + {:type :confirm + :title (t locale "modals.delete-team-confirm.title") + :message (t locale "modals.delete-team-confirm.message") + :accept-label (t locale "modals.delete-team-confirm.accept") + :on-accept delete-fn})))] + + (mf/use-layout-effect + (mf/deps (:id team)) + (fn [] + (->> (rp/query! :team-members {:team-id (:id team)}) + (rx/subs #(reset! members %))))) + + [:ul.dropdown.options-dropdown + [:li {:on-click go-members} (t locale "labels.members")] + [:li {:on-click go-settings} (t locale "labels.settings")] + [:hr] + [:li {:on-click on-rename-clicked} (t locale "labels.rename")] + + (cond + (:is-owner team) + [:li {:on-click on-leave-as-owner-clicked} (t locale "dashboard.leave-team")] + + (> (count @members) 1) + [:li {:on-click on-leave-clicked} (t locale "dashboard.leave-team")]) + + + (when (:is-owner team) + [:li {:on-click on-delete-clicked} (t locale "dashboard.delete-team")])])) + + +(mf/defc sidebar-team-switch + [{:keys [team profile locale] :as props}] + (let [show-dropdown? (mf/use-state false) + + show-team-opts-ddwn? (mf/use-state false) + show-teams-ddwn? (mf/use-state false)] + + [:div.sidebar-team-switch + [:div.switch-content + [:div.current-team + (if (:is-default team) + [:div.team-name + [:span.team-icon i/logo-icon] + [:span.team-text (t locale "dashboard.default-team-name")]] + [:div.team-name + [:span.team-icon + [:img {:src (cfg/resolve-media-path (:photo team))}]] + [:span.team-text {:title (:name team)} (:name team)]]) + + [:span.switch-icon {:on-click #(reset! show-teams-ddwn? true)} + i/arrow-down]] + + (when-not (:is-default team) + [:div.switch-options {:on-click #(reset! show-team-opts-ddwn? true)} + i/actions])] + + ;; Teams Dropdown + [:& dropdown {:show @show-teams-ddwn? + :on-close #(reset! show-teams-ddwn? false)} + [:& teams-selector-dropdown {:team team + :profile profile + :locale locale}]] + + [:& dropdown {:show @show-team-opts-ddwn? + :on-close #(reset! show-team-opts-ddwn? false)} + [:& team-options-dropdown {:team team + :profile profile + :locale locale}]]])) + +(mf/defc sidebar-content + [{:keys [locale projects profile section team project search-term] :as props}] + (let [default-project-id + (->> (vals projects) + (d/seek :is-default) + (:id)) + + projects? (= section :dashboard-projects) + libs? (= section :dashboard-libraries) + drafts? (and (= section :dashboard-files) + (= (:id project) default-project-id)) + + go-projects + (mf/use-callback + (mf/deps team) + (st/emitf (rt/nav :dashboard-projects {:team-id (:id team)}))) + + go-drafts + (mf/use-callback + (mf/deps team default-project-id) + (fn [] + (st/emit! (rt/nav :dashboard-files + {:team-id (:id team) + :project-id default-project-id})))) + go-libs + (mf/use-callback + (mf/deps team) + (st/emitf (rt/nav :dashboard-libraries {:team-id (:id team)}))) + + pinned-projects + (->> (vals projects) + (remove :is-default) + (filter :is-pinned))] + + [:div.sidebar-content + [:& sidebar-team-switch {:team team :profile profile :locale locale}] + [:hr] + [:& sidebar-search {:search-term search-term + :team-id (:id team) + :locale locale}] + [:div.sidebar-content-section + [:ul.sidebar-nav.no-overflow + [:li.recent-projects + {:on-click go-projects + :class-name (when projects? "current")} + i/recent + [:span.element-title (t locale "labels.projects")]] + + [:li {:on-click go-drafts + :class-name (when drafts? "current")} + i/file-html + [:span.element-title (t locale "labels.drafts")]] + + + [:li {:on-click go-libs + :class-name (when libs? "current")} + i/library + [:span.element-title (t locale "labels.shared-libraries")]]]] + + [:hr] + + [:div.sidebar-content-section + (if (seq pinned-projects) + [:ul.sidebar-nav + (for [item pinned-projects] + [:& sidebar-project + {:item item + :key (:id item) + :id (:id item) + :selected? (= (:id item) (:id project))}])] + [:div.sidebar-empty-placeholder + [:span.icon i/pin] + [:span.text (t locale "dashboard.no-projects-placeholder")]])]])) + + +(mf/defc profile-section + [{:keys [profile locale] :as props}] + (let [show (mf/use-state false) + photo (:photo-uri profile "") + photo (if (str/empty? photo) + "/images/avatar.jpg" + photo) + + on-click + (mf/use-callback + (fn [section event] + (dom/stop-propagation event) + (if (keyword? section) + (st/emit! (rt/nav section)) + (st/emit! section))))] + + [:div.profile-section {:on-click #(reset! show true)} + [:img {:src photo}] + [:span (:fullname profile)] + + [:& dropdown {:on-close #(reset! show false) + :show @show} + [:ul.dropdown + [:li {:on-click (partial on-click :settings-profile)} + [:span.icon i/user] + [:span.text (t locale "labels.profile")]] + [:li {:on-click (partial on-click :settings-password)} + [:span.icon i/lock] + [:span.text (t locale "labels.password")]] + [:li {:on-click (partial on-click da/logout)} + [:span.icon i/exit] + [:span.text (t locale "labels.logout")]]]]])) + +(mf/defc sidebar + {::mf/wrap-props false + ::mf/wrap [mf/memo]} + [props] + (let [locale (mf/deref i18n/locale) + profile (obj/get props "profile") + props (-> (obj/clone props) + (obj/set! "locale" locale))] [:div.dashboard-sidebar - [:div.dashboard-sidebar-inside - [:form.dashboard-search - [:input.input-text - {:key :images-search-box - :id "search-input" - :type "text" - :placeholder (t locale "ds.search.placeholder") - :default-value search-term-not-nil - :auto-complete "off" - :on-focus on-search-focus - :on-change on-search-change - :ref #(when % (set! (.-value %) search-term-not-nil))}] - [:div.clear-search - {:on-click on-clear-click} - i/close]] - [:& sidebar-team {:selected-team-id team-id - :selected-project-id project-id - :selected-section section - :profile profile - :team-id (:default-team-id profile)}]]])) + [:div.sidebar-inside + [:> sidebar-content props] + [:& profile-section {:profile profile :locale locale}]]])) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs new file mode 100644 index 0000000000..02cee28008 --- /dev/null +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -0,0 +1,303 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.dashboard.team + (:require + [app.common.data :as d] + [app.common.exceptions :as ex] + [app.common.spec :as us] + [app.config :as cfg] + [app.main.constants :as c] + [app.main.data.dashboard :as dd] + [app.main.data.messages :as dm] + [app.main.data.modal :as modal] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.components.file-uploader :refer [file-uploader]] + [app.main.ui.components.forms :as fm] + [app.main.ui.dashboard.team-form] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [t tr]] + [app.util.router :as rt] + [app.util.time :as dt] + [cljs.spec.alpha :as s] + [okulary.core :as l] + [rumext.alpha :as mf])) + +(mf/defc header + {::mf/wrap [mf/memo]} + [{:keys [section locale team] :as props}] + (let [go-members + (mf/use-callback + (mf/deps team) + (st/emitf (rt/nav :dashboard-team-members {:team-id (:id team)}))) + + go-settings + (mf/use-callback + (mf/deps team) + (st/emitf (rt/nav :dashboard-team-settings {:team-id (:id team)}))) + + invite-member + (mf/use-callback + (mf/deps team) + (st/emitf (modal/show {:type ::invite-member + :team team}))) + + members-section? (= section :dashboard-team-members) + settings-section? (= section :dashboard-team-settings)] + + [:header.dashboard-header + [:div.dashboard-title + [:h1 (cond + members-section? (t locale "labels.members") + settings-section? (t locale "labels.settings") + nil)]] + [:nav + [:ul + [:li {:class (when members-section? "active")} + [:a {:on-click go-members} (t locale "labels.members")]] + [:li {:class (when settings-section? "active")} + [:a {:on-click go-settings} (t locale "labels.settings")]]]] + + (if members-section? + [:a.btn-secondary.btn-small {:on-click invite-member} + (t locale "dashboard.invite-profile")] + [:div])])) + +(s/def ::email ::us/email) +(s/def ::role ::us/keyword) +(s/def ::invite-member-form + (s/keys :req-un [::role ::email])) + +(mf/defc invite-member-modal + {::mf/register modal/components + ::mf/register-as ::invite-member} + [{:keys [team] :as props}] + (let [roles [{:value "" :label (tr "labels.role")} + {:value "admin" :label (tr "labels.admin")} + {:value "editor" :label (tr "labels.editor")} + {:value "viewer" :label (tr "labels.viewer")}] + + initial (mf/use-memo (mf/deps team) (constantly {:team-id (:id team)})) + form (fm/use-form :spec ::invite-member-form + :initial initial) + on-success + (mf/use-callback + (mf/deps team) + (st/emitf (dm/success "Invitation sent successfully"))) + + on-submit + (mf/use-callback + (mf/deps team) + (fn [form] + (let [params (:clean-data @form) + mdata {:on-success (partial on-success form)}] + (st/emit! (dd/invite-team-member (with-meta params mdata))))))] + + [:div.modal.dashboard-invite-modal.form-container + [:& fm/form {:on-submit on-submit :form form} + [:div.title + [:span.text (tr "modals.invite-member.title")]] + + [:div.form-row + [:& fm/input {:name :email + :label (tr "labels.email")}] + [:& fm/select {:name :role + :options roles}]] + + [:div.action-buttons + [:& fm/submit-button {:label "Send invitation"}]]]])) + + +(mf/defc team-member + [{:keys [team member profile] :as props}] + (let [show? (mf/use-state false) + + set-role + #(st/emit! (dd/update-team-member-role {:team-id (:id team) + :member-id (:id member) + :role %})) + set-owner-fn + (partial set-role :owner) + + set-admin + (mf/use-callback (mf/deps team member) (partial set-role :admin)) + + set-editor + (mf/use-callback (mf/deps team member) (partial set-role :editor)) + + set-viewer + (mf/use-callback (mf/deps team member) (partial set-role :viewer)) + + set-owner + (mf/use-callback + (mf/deps team member) + (st/emitf (modal/show + {:type :confirm + :title (tr "modals.promote-owner-confirm.title") + :message (tr "modals.promote-owner-confirm.message") + :accept-label (tr "modals.promote-owner-confirm.accept") + :on-accept set-owner-fn}))) + + delete-fn + (st/emitf (dd/delete-team-member {:team-id (:id team) :member-id (:id member)})) + + delete + (mf/use-callback + (mf/deps team member) + (st/emitf (modal/show + {:type :confirm + :title (tr "modals.delete-team-member-confirm.title") + :message (tr "modals.delete-team-member-confirm.message") + :accept-label (tr "modals.delete-team-member-confirm.accept") + :on-accept delete-fn})))] + + + [:div.table-row + [:div.table-field.name (:name member)] + [:div.table-field.email (:email member)] + [:div.table-field.permissions + [:* + (cond + (:is-owner member) + [:span.label (tr "labels.owner")] + + (:is-admin member) + [:span.label (tr "labels.admin")] + + (:can-edit member) + [:span.label (tr "labels.editor")] + + :else + [:span.label (tr "labels.viewer")]) + + (when (and (not (:is-owner member)) + (or (:is-admin team) + (:is-owner team))) + [:span.icon {:on-click #(reset! show? true)} i/arrow-down])] + + [:& dropdown {:show @show? + :on-close #(reset! show? false)} + [:ul.dropdown.options-dropdown + [:li {:on-click set-admin} (tr "labels.admin")] + [:li {:on-click set-editor} (tr "labels.editor")] + [:li {:on-click set-viewer} (tr "labels.viewer")] + (when (:is-owner team) + [:* + [:hr] + [:li {:on-click set-owner} (tr "dashboard.promote-to-owner")]]) + [:hr] + (when (and (or (:is-owner team) + (:is-admin team)) + (not= (:id profile) + (:id member))) + [:li {:on-click delete} (tr "labels.remove")])]]]])) + + +(mf/defc team-members + [{:keys [members-map team profile] :as props}] + (let [members (->> (vals members-map) + (sort-by :created-at) + (remove :is-owner)) + owner (->> (vals members-map) + (d/seek :is-owner))] + [:div.dashboard-table + [:div.table-header + [:div.table-field.name (tr "labels.name")] + [:div.table-field.email (tr "labels.email")] + [:div.table-field.permissions (tr "labels.permissions")]] + [:div.table-rows + [:& team-member {:member owner :team team :profile profile}] + (for [item members] + [:& team-member {:member item :team team :profile profile :key (:id item)}])]])) + +(defn- members-ref + [team-id] + (l/derived (l/in [:team-members team-id]) st/state)) + +(mf/defc team-members-page + [{:keys [team profile] :as props}] + (let [locale (mf/deref i18n/locale) + members-ref (mf/use-memo (mf/deps team) #(members-ref (:id team))) + members-map (mf/deref members-ref)] + + (mf/use-effect + (mf/deps team) + (st/emitf (dd/fetch-team-members team))) + + [:* + [:& header {:locale locale + :section :dashboard-team-members + :team team}] + [:section.dashboard-container.dashboard-team-members + [:& team-members {:locale locale + :profile profile + :team team + :members-map members-map}]]])) + + +(mf/defc team-settings-page + [{:keys [team profile] :as props}] + (let [locale (mf/deref i18n/locale) + finput (mf/use-ref) + + members-ref (mf/use-memo (mf/deps team) #(members-ref (:id team))) + members-map (mf/deref members-ref) + + on-image-click + (mf/use-callback #(dom/click (mf/ref-val finput))) + + on-file-selected + (mf/use-callback + (mf/deps team) + (fn [file] + (st/emit! (dd/update-team-photo {:file file + :team-id (:id team)}))))] + + (mf/use-effect + (mf/deps team) + (st/emitf (dd/fetch-team-members team))) + + [:* + [:& header {:locale locale + :section :dashboard-team-settings + :team team}] + [:section.dashboard-container.dashboard-team-settings + [:div.team-settings + [:div.horizontal-blocks + [:div.block.info-block + [:div.label (t locale "dashboard.team-info")] + [:div.name (:name team)] + [:div.icon + [:span.update-overlay {:on-click on-image-click} i/exit] + [:img {:src (cfg/resolve-media-path (:photo team))}] + [:& file-uploader {:accept "image/jpeg,image/png" + :multi false + :input-ref finput + :on-selected on-file-selected}]]] + + [:div.block.owner-block + [:div.label (t locale "dashboard.team-members")] + [:div.owner + [:span.icon [:img {:src (cfg/resolve-media-path (:photo-uri profile))}]] + [:span.text (str (:fullname profile) " (" (t locale "labels.owner") ")") ]] + [:div.summary + [:span.icon i/user] + [:span.text (t locale "dashboard.num-of-members" (count members-map))]]] + + [:div.block.stats-block + [:div.label (t locale "dashboard.team-projects")] + [:div.projects + [:span.icon i/folder] + [:span.text "4 projects"]] + [:div.files + [:span.icon i/file-html] + [:span.text "4 files"]]]]]]])) diff --git a/frontend/src/app/main/ui/dashboard/team_form.cljs b/frontend/src/app/main/ui/dashboard/team_form.cljs new file mode 100644 index 0000000000..0e263e5516 --- /dev/null +++ b/frontend/src/app/main/ui/dashboard/team_form.cljs @@ -0,0 +1,117 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.dashboard.team-form + (:require + [app.common.data :as d] + [app.common.spec :as us] + [app.config :as cfg] + [app.main.data.auth :as da] + [app.main.data.dashboard :as dd] + [app.main.data.messages :as dm] + [app.main.data.modal :as modal] + [app.main.repo :as rp] + [app.main.store :as st] + [app.main.ui.components.forms :refer [input submit-button form]] + [app.main.ui.icons :as i] + [app.main.ui.keyboard :as kbd] + [app.util.dom :as dom] + [app.util.forms :as fm] + [app.util.i18n :as i18n :refer [t tr]] + [app.util.object :as obj] + [app.util.router :as rt] + [app.util.time :as dt] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [rumext.alpha :as mf])) + +(s/def ::name ::us/not-empty-string) +(s/def ::team-form + (s/keys :req-un [::name])) + +(defn- on-create-success + [form response] + (let [msg "Team created successfuly"] + (st/emit! (dm/success msg) + (modal/hide) + (rt/nav :dashboard-projects {:team-id (:id response)})))) + +(defn- on-update-success + [form response] + (let [msg "Team created successfuly"] + (st/emit! (dm/success msg) + (modal/hide)))) + +(defn- on-error + [form response] + (let [id (get-in @form [:clean-data :id])] + (if id + (st/emit! (dm/error "Error on updating team.")) + (st/emit! (dm/error "Error on creating team."))))) + +;; TODO: check global error handler + +(defn- on-create-submit + [form] + (let [mdata {:on-success (partial on-create-success form) + :on-error (partial on-error form)} + params {:name (get-in @form [:clean-data :name])}] + (st/emit! (dd/create-team (with-meta params mdata))))) + +(defn- on-update-submit + [form] + (let [mdata {:on-success (partial on-update-success form) + :on-error (partial on-error form)} + team (get @form :clean-data)] + (st/emit! (dd/update-team (with-meta team mdata)) + (modal/hide)))) + +(mf/defc team-form-modal + {::mf/register modal/components + ::mf/register-as :team-form} + [{:keys [team] :as props}] + (let [locale (mf/deref i18n/locale) + form (fm/use-form :spec ::team-form + :initial (or team {})) + + on-submit + (mf/use-callback + (mf/deps team) + (if team + (partial on-update-submit form) + (partial on-create-submit form)))] + + [:div.modal-overlay + [:div.modal-container.team-form-modal + [:div.modal-header + [:div.modal-header-title + (if team + [:h2 "Rename team"] + [:h2 "Create new team"])] + [:div.modal-close-button + {:on-click (st/emitf (modal/hide))} i/close]] + + [:div.modal-content.generic-form + [:form + [:& input {:type "text" + :form form + :name :name + :label "Enter new team name:"}]]] + + [:div.modal-footer + [:div.action-buttons + [:& submit-button + {:form form + :on-click on-submit + :label (if team + "Update team" + "Create team")}]]]]])) + + diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 157bcddb2f..08ac0679d7 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -17,7 +17,6 @@ (def align-middle (icon-xref :align-middle)) (def align-top (icon-xref :align-top)) (def alignment (icon-xref :alignment)) -(def arrow (icon-xref :arrow)) (def arrow-down (icon-xref :arrow-down)) (def arrow-end (icon-xref :arrow-end)) (def arrow-slide (icon-xref :arrow-slide)) @@ -28,6 +27,7 @@ (def auto-width (icon-xref :auto-width)) (def box (icon-xref :box)) (def chain (icon-xref :chain)) +(def unchain (icon-xref :unchain)) (def chat (icon-xref :chat)) (def circle (icon-xref :circle)) (def close (icon-xref :close)) @@ -68,6 +68,7 @@ (def logo-icon (icon-xref :uxbox-logo-icon)) (def lowercase (icon-xref :lowercase)) (def mail (icon-xref :mail)) +(def mask (icon-xref :mask)) (def minus (icon-xref :minus)) (def move (icon-xref :move)) (def msg-error (icon-xref :msg-error)) @@ -89,7 +90,6 @@ (def rotate (icon-xref :rotate)) (def ruler (icon-xref :ruler)) (def ruler-tool (icon-xref :ruler-tool)) -(def save (icon-xref :save)) (def search (icon-xref :search)) (def shape-halign-center (icon-xref :shape-halign-center)) (def shape-halign-left (icon-xref :shape-halign-left)) @@ -121,6 +121,12 @@ (def uppercase (icon-xref :uppercase)) (def user (icon-xref :user)) (def tick (icon-xref :tick)) +(def picker-harmony (icon-xref :picker-harmony)) +(def picker-hsv (icon-xref :picker-hsv)) +(def picker-ramp (icon-xref :picker-ramp)) +(def checkbox-checked (icon-xref :checkbox-checked)) +(def checkbox-unchecked (icon-xref :checkbox-unchecked)) +(def code (icon-xref :code)) (def loader-pencil (mf/html diff --git a/frontend/src/app/main/ui/messages.cljs b/frontend/src/app/main/ui/messages.cljs index e7ed09daec..0b41989bd9 100644 --- a/frontend/src/app/main/ui/messages.cljs +++ b/frontend/src/app/main/ui/messages.cljs @@ -65,7 +65,7 @@ on-close #(st/emit! dm/hide)] (when message [:& banner (assoc message - :position (or (:position message) :floating) + :position (or (:position message) :fixed) :controls (if (some? (:controls message)) (:controls message) (if (some? (:timeout message)) diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs index f8288d25f3..908ebced87 100644 --- a/frontend/src/app/main/ui/modal.cljs +++ b/frontend/src/app/main/ui/modal.cljs @@ -14,63 +14,46 @@ [rumext.alpha :as mf] [app.main.store :as st] [app.main.ui.keyboard :as k] + [app.main.data.modal :as dm] [app.util.dom :as dom] - [app.main.refs :as refs] - [potok.core :as ptk] - [app.main.data.modal :as mdm]) + [app.main.refs :as refs]) (:import goog.events.EventType)) -(defonce components (atom {})) - -(defn show! - [type props] - (let [id (random-uuid)] - (st/emit! (mdm/show-modal id type props)))) - -(defn allow-click-outside! [] - (st/emit! (mdm/update-modal {:allow-click-outside true}))) - -(defn disallow-click-outside! [] - (st/emit! (mdm/update-modal {:allow-click-outside false}))) - -(defn hide! - [] - (st/emit! (mdm/hide-modal))) - -(def hide (mdm/hide-modal)) - (defn- on-esc-clicked - [event] - (when (k/esc? event) - (hide!) - (dom/stop-propagation event))) + [event allow-click-outside] + (when (and (k/esc? event) (not allow-click-outside)) + (do (dom/stop-propagation event) + (st/emit! (dm/hide))))) (defn- on-pop-state [event] (dom/prevent-default event) (dom/stop-propagation event) - (hide!) + (st/emit! (dm/hide)) (.forward js/history)) (defn- on-parent-clicked [event parent-ref] - (let [parent (mf/ref-val parent-ref) + (let [parent (mf/ref-val parent-ref) current (dom/get-target event)] (when (and (dom/equals? (.-firstElementChild ^js parent) current) (= (.-className ^js current) "modal-overlay")) (dom/stop-propagation event) (dom/prevent-default event) - (hide!)))) + (st/emit! (dm/hide))))) (defn- on-click-outside - [event wrapper-ref allow-click-outside] + [event wrapper-ref type allow-click-outside] (let [wrapper (mf/ref-val wrapper-ref) current (dom/get-target event)] - (when (and wrapper (not allow-click-outside) (not (.contains wrapper current))) + (when (and wrapper + (not allow-click-outside) + (not (.contains wrapper current)) + (not (= type (keyword (.getAttribute current "data-allow-click-modal"))))) (dom/stop-propagation event) (dom/prevent-default event) - (hide!)))) + (st/emit! (dm/hide))))) (mf/defc modal-wrapper {::mf/wrap-props false @@ -78,25 +61,34 @@ [props] (let [data (unchecked-get props "data") wrapper-ref (mf/use-ref nil) + + allow-click-outside (:allow-click-outside data) + handle-click-outside (fn [event] - (on-click-outside event wrapper-ref (:allow-click-outside data)))] + (on-click-outside event wrapper-ref (:type data) allow-click-outside)) + + handle-keydown + (fn [event] + (on-esc-clicked event allow-click-outside))] (mf/use-layout-effect + (mf/deps allow-click-outside) (fn [] - (let [keys [(events/listen js/document EventType.KEYDOWN on-esc-clicked) - (events/listen js/window EventType.POPSTATE on-pop-state) - (events/listen js/document EventType.CLICK handle-click-outside)]] - #(for [key keys] + (let [keys [(events/listen js/window EventType.POPSTATE on-pop-state) + (events/listen (dom/get-root) EventType.KEYDOWN handle-keydown) + (events/listen (dom/get-root) EventType.CLICK handle-click-outside)]] + #(doseq [key keys] (events/unlistenByKey key))))) + [:div.modal-wrapper {:ref wrapper-ref} (mf/element - (get @components (:type data)) + (get @dm/components (:type data)) (:props data))])) (def modal-ref - (l/derived ::mdm/modal st/state)) + (l/derived ::dm/modal st/state)) (mf/defc modal [] diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index eb3ba01eed..6994f243a8 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -9,31 +9,47 @@ (ns app.main.ui.settings (:require - [cuerdas.core :as str] - [potok.core :as ptk] - [rumext.alpha :as mf] - [app.main.ui.icons :as i] [app.main.refs :as refs] [app.main.store :as st] - [app.util.router :as rt] - [app.main.ui.dashboard.profile :refer [profile-section]] - [app.main.ui.settings.header :refer [header]] - [app.main.ui.settings.password :refer [password-page]] [app.main.ui.settings.options :refer [options-page]] - [app.main.ui.settings.profile :refer [profile-page]])) + [app.main.ui.settings.password :refer [password-page]] + [app.main.ui.settings.profile :refer [profile-page]] + [app.main.ui.settings.sidebar :refer [sidebar]] + [app.main.ui.settings.change-email] + [app.main.ui.settings.delete-account] + [app.util.i18n :as i18n :refer [t]] + [rumext.alpha :as mf])) + +(mf/defc header + {::mf/wrap [mf/memo]} + [{:keys [locale] :as props}] + (let [logout (constantly nil)] + [:header.dashboard-header + [:div.dashboard-title + [:h1 (t locale "dashboard.your-account-title")]] + [:a.btn-secondary.btn-small {:on-click logout} + (t locale "labels.logout")]])) (mf/defc settings [{:keys [route] :as props}] (let [section (get-in route [:data :name]) - profile (mf/deref refs/profile)] - [:main.settings-main - [:div.settings-content - [:& header {:section section :profile profile}] - (case section - :settings-profile (mf/element profile-page) - :settings-password (mf/element password-page) - :settings-options (mf/element options-page))]])) + profile (mf/deref refs/profile) + locale (mf/deref i18n/locale)] + [:section.dashboard-layout + [:& sidebar {:profile profile + :locale locale + :section section}] + [:div.dashboard-content + [:& header {:locale locale}] + [:section.dashboard-container + (case section + :settings-profile + [:& profile-page {:locale locale}] + :settings-password + [:& password-page {:locale locale}] + :settings-options + [:& options-page {:locale locale}])]]])) diff --git a/frontend/src/app/main/ui/settings/change_email.cljs b/frontend/src/app/main/ui/settings/change_email.cljs index d1258f77ed..cdad1a33e1 100644 --- a/frontend/src/app/main/ui/settings/change_email.cljs +++ b/frontend/src/app/main/ui/settings/change_email.cljs @@ -12,14 +12,15 @@ [app.common.spec :as us] [app.main.data.auth :as da] [app.main.data.messages :as dm] + [app.main.data.modal :as modal] [app.main.data.users :as du] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.components.forms :refer [input submit-button form]] + [app.main.ui.components.forms :as fm] [app.main.ui.icons :as i] [app.main.ui.messages :as msgs] - [app.main.ui.modal :as modal] [app.util.i18n :as i18n :refer [tr t]] + [beicon.core :as rx] [cljs.spec.alpha :as s] [cuerdas.core :as str] [rumext.alpha :as mf])) @@ -47,55 +48,65 @@ (assoc-in data [:errors :email-1] error)))) :else - (let [msg (tr "errors.unexpected-error")] - (st/emit! (dm/error msg))))) + (rx/throw error))) (defn- on-success - [profile data] - (let [msg (tr "auth.notifications.validation-email-sent" (:email profile))] - (st/emit! (dm/info msg) modal/hide))) + [form data] + (let [email (get-in @form [:clean-data :email-1]) + message (tr "notifications.validation-email-sent" email)] + (st/emit! (dm/info message) + (modal/hide)))) (defn- on-submit - [profile form event] - (let [data (with-meta {:email (get-in form [:clean-data :email-1])} - {:on-error (partial on-error form) - :on-success (partial on-success profile)})] - (st/emit! (du/request-email-change data)))) - -(mf/defc change-email-form - [{:keys [locale profile] :as props}] - [:section.modal-content.generic-form - [:h2 (t locale "settings.change-email-title")] - - [:& msgs/inline-banner - {:type :info - :content (t locale "settings.change-email-info" (:email profile))}] - - [:& form {:on-submit (partial on-submit profile) - :spec ::email-change-form - :validators [email-equality] - :initial {}} - [:& input {:type "text" - :name :email-1 - :label (t locale "settings.new-email-label") - :trim true}] - - [:& input {:type "text" - :name :email-2 - :label (t locale "settings.confirm-email-label") - :trim true}] - - [:& submit-button - {:label (t locale "settings.change-email-submit-label")}]]]) + [form event] + (let [params {:email (get-in @form [:clean-data :email-1])} + mdata {:on-error (partial on-error form) + :on-success (partial on-success form)}] + (st/emit! (du/request-email-change (with-meta params mdata))))) (mf/defc change-email-modal {::mf/register modal/components ::mf/register-as :change-email} - [props] + [] (let [locale (mf/deref i18n/locale) - profile (mf/deref refs/profile)] - [:div.modal-overlay - [:div.generic-modal.change-email-modal - [:span.close {:on-click #(modal/hide!)} i/close] - [:& change-email-form {:locale locale :profile profile}]]])) + profile (mf/deref refs/profile) + form (fm/use-form :spec ::email-change-form + :validators [email-equality] + :initial profile) + on-close + (mf/use-callback (st/emitf (modal/hide)))] + + [:div.modal-overlay + [:div.modal-container.change-email-modal.form-container + [:& fm/form {:form form + :on-submit on-submit} + + [:div.modal-header + [:div.modal-header-title + [:h2 (t locale "modals.change-email.title")]] + [:div.modal-close-button + {:on-click on-close} i/close]] + + [:div.modal-content + [:& msgs/inline-banner + {:type :info + :content (t locale "modals.change-email.info" (:email profile))}] + + [:div.fields-row + [:& fm/input {:type "text" + :name :email-1 + :label (t locale "modals.change-email.new-email") + :trim true}]] + [:div.fields-row + [:& fm/input {:type "text" + :name :email-2 + :label (t locale "modals.change-email.confirm-email") + :trim true}]]] + + [:div.modal-footer + [:div.action-buttons + [:& fm/submit-button + {:label (t locale "modals.change-email.submit")}]]]]]])) + + diff --git a/frontend/src/app/main/ui/settings/delete_account.cljs b/frontend/src/app/main/ui/settings/delete_account.cljs index 7c79a8c0f2..570d2f38cb 100644 --- a/frontend/src/app/main/ui/settings/delete_account.cljs +++ b/frontend/src/app/main/ui/settings/delete_account.cljs @@ -9,38 +9,62 @@ (ns app.main.ui.settings.delete-account (:require - [cljs.spec.alpha :as s] - [rumext.alpha :as mf] [app.main.data.auth :as da] + [app.main.data.messages :as dm] + [app.main.data.modal :as modal] [app.main.data.users :as du] [app.main.store :as st] [app.main.ui.icons :as i] [app.main.ui.messages :as msgs] - [app.main.ui.modal :as modal] - [app.util.i18n :as i18n :refer [tr t]])) + [app.util.i18n :as i18n :refer [tr t]] + [app.util.router :as rt] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [rumext.alpha :as mf])) + +(defn on-error + [{:keys [code] :as error}] + (if (= :owner-teams-with-people code) + (let [msg (tr "notifications.profile-deletion-not-allowed")] + (rx/of (dm/error msg))) + (rx/throw error))) + +(defn on-success + [x] + (st/emit! (rt/nav :auth-goodbye))) (mf/defc delete-account-modal {::mf/register modal/components ::mf/register-as :delete-account} [props] - (let [locale (mf/deref i18n/locale)] + (let [locale (mf/deref i18n/locale) + on-close + (mf/use-callback (st/emitf (modal/hide))) + + on-accept + (mf/use-callback + (st/emitf (modal/hide) + (da/request-account-deletion + (with-meta {} {:on-error on-error + :on-success on-success}))))] + [:div.modal-overlay - [:section.generic-modal.change-email-modal - [:span.close {:on-click #(modal/hide!)} i/close] - - [:section.modal-content.generic-form - [:h2 (t locale "settings.delete-account-title")] + [:div.modal-container.change-email-modal + [:div.modal-header + [:div.modal-header-title + [:h2 (t locale "modals.delete-account.title")]] + [:div.modal-close-button + {:on-click on-close} i/close]] + [:div.modal-content [:& msgs/inline-banner {:type :warning - :content (t locale "settings.delete-account-info")}] + :content (t locale "modals.delete-account.info")}]] + + [:div.modal-footer + [:div.action-buttons + [:button.btn-warning.btn-large {:on-click on-accept} + (t locale "modals.delete-account.confirm")] + [:button.btn-secondary.btn-large {:on-click on-close} + (t locale "modals.delete-account.cancel")]]]]])) - [:div.button-row - [:button.btn-warning.btn-large - {:on-click #(do - (modal/hide!) - (st/emit! da/request-account-deletion))} - (t locale "settings.yes-delete-my-account")] - [:button.btn-secondary.btn-large - {:on-click #(modal/hide!)} - (t locale "settings.cancel-and-keep-my-account")]]]]])) diff --git a/frontend/src/app/main/ui/settings/header.cljs b/frontend/src/app/main/ui/settings/header.cljs deleted file mode 100644 index 9f35e59985..0000000000 --- a/frontend/src/app/main/ui/settings/header.cljs +++ /dev/null @@ -1,59 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2020 UXBOX Labs SL - -(ns app.main.ui.settings.header - (:require - [rumext.alpha :as mf] - [app.main.ui.icons :as i] - [app.main.data.auth :as da] - [app.main.store :as st] - [app.util.i18n :as i18n :refer [tr t]] - [app.util.router :as rt])) - -(mf/defc header - [{:keys [section profile] :as props}] - (let [profile? (= section :settings-profile) - password? (= section :settings-password) - options? (= section :settings-options) - - team-id (:default-team-id profile) - go-back #(st/emit! (rt/nav :dashboard-team {:team-id team-id})) - logout #(st/emit! da/logout) - - locale (mf/deref i18n/locale) - team-id (:default-team-id profile)] - [:header - [:section.secondary-menu - [:div.left {:on-click go-back} - [:span.icon i/arrow-slide] - [:span.label "Dashboard"]] - [:div.right {:on-click logout} - [:span.label "Log out"] - [:span.icon i/logout]]] - [:h1 "Your account"] - [:nav - [:a.nav-item - {:class (when profile? "current") - :on-click #(st/emit! (rt/nav :settings-profile))} - (t locale "settings.profile")] - - [:a.nav-item - {:class (when password? "current") - :on-click #(st/emit! (rt/nav :settings-password))} - (t locale "settings.password")] - - [:a.nav-item - {:class (when options? "current") - :on-click #(st/emit! (rt/nav :settings-options))} - (t locale "settings.options")]]])) - - ;; [:a.nav-item - ;; {:class "foobar" - ;; :on-click #(st/emit! (rt/nav :settings-profile))} - ;; (t locale "settings.teams")]]])) diff --git a/frontend/src/app/main/ui/settings/options.cljs b/frontend/src/app/main/ui/settings/options.cljs index abc36957b8..0ae1a3d3c1 100644 --- a/frontend/src/app/main/ui/settings/options.cljs +++ b/frontend/src/app/main/ui/settings/options.cljs @@ -9,69 +9,72 @@ (ns app.main.ui.settings.options (:require - [rumext.alpha :as mf] - [cljs.spec.alpha :as s] - [app.main.ui.icons :as i] - [app.main.data.users :as udu] + [app.common.spec :as us] [app.main.data.messages :as dm] - [app.main.ui.components.forms :refer [select submit-button form]] + [app.main.data.users :as du] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.components.forms :as fm] + [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.forms :as fm] - [app.util.i18n :as i18n :refer [t tr]])) + [app.util.i18n :as i18n :refer [t tr]] + [cljs.spec.alpha :as s] + [rumext.alpha :as mf])) -(s/def ::lang (s/nilable ::fm/not-empty-string)) -(s/def ::theme (s/nilable ::fm/not-empty-string)) +(s/def ::lang (s/nilable ::us/not-empty-string)) +(s/def ::theme (s/nilable ::us/not-empty-string)) (s/def ::options-form (s/keys :opt-un [::lang ::theme])) (defn- on-error - [form error]) + [form error] + (st/emit! (dm/error (tr "errors.generic")))) + +(defn- on-success + [form] + (st/emit! (dm/success (tr "notifications.profile-saved")))) (defn- on-submit [form event] - (dom/prevent-default event) - (let [data (:clean-data form) - on-success #(st/emit! (dm/success (tr "settings.notifications.profile-saved"))) - on-error #(on-error % form)] - (st/emit! (udu/update-profile (with-meta data - {:on-success on-success - :on-error on-error}))))) + (let [data (:clean-data @form) + mdata {:on-success (partial on-success form) + :on-error (partial on-error form)}] + (st/emit! (du/update-profile (with-meta data mdata))))) (mf/defc options-form - [{:keys [locale profile] :as props}] - [:& form {:class "options-form" - :on-submit on-submit - :spec ::options-form - :initial profile} + [{:keys [locale] :as props}] + (let [profile (mf/deref refs/profile) + form (fm/use-form :spec ::options-form + :initial profile)] + [:& fm/form {:class "options-form" + :on-submit on-submit + :form form} - [:h2 (t locale "settings.language-change-title")] + [:h2 (t locale "labels.language")] - [:& select {:options [{:label "English" :value "en"} - {:label "Français" :value "fr"} - {:label "Español" :value "es"} - {:label "Русский" :value "ru"}] - :label (t locale "settings.language-label") - :default "en" - :name :lang}] + [:div.fields-row + [:& fm/select {:options [{:label "English" :value "en"} + {:label "Français" :value "fr"} + {:label "Español" :value "es"} + {:label "Русский" :value "ru"}] + :label (t locale "dashboard.select-ui-language") + :default "en" + :name :lang}]] - [:h2 (t locale "settings.theme-change-title")] - [:& select {:label (t locale "settings.theme-label") - :name :theme - :default "default" - :options [{:label "Default" :value "default"}]}] - - [:& submit-button - {:label (t locale "settings.profile-submit-label")}]]) + [:h2 (t locale "dashboard.theme-change")] + [:div.fields-row + [:& fm/select {:label (t locale "dashboard.select-ui-theme") + :name :theme + :default "default" + :options [{:label "Default" :value "default"}]}]] + [:& fm/submit-button + {:label (t locale "dashboard.update-settings")}]])) ;; --- Password Page (mf/defc options-page - [props] - (let [locale (mf/deref i18n/locale) - profile (mf/deref refs/profile)] - [:section.settings-options.generic-form - [:div.forms-container - [:& options-form {:locale locale :profile profile}]]])) + [{:keys [locale]}] + [:div.dashboard-settings + [:div.form-container + [:& options-form {:locale locale}]]]) diff --git a/frontend/src/app/main/ui/settings/password.cljs b/frontend/src/app/main/ui/settings/password.cljs index 77b0305888..5663a522a0 100644 --- a/frontend/src/app/main/ui/settings/password.cljs +++ b/frontend/src/app/main/ui/settings/password.cljs @@ -9,16 +9,16 @@ (ns app.main.ui.settings.password (:require - [rumext.alpha :as mf] - [cljs.spec.alpha :as s] - [app.main.ui.icons :as i] - [app.main.data.users :as udu] + [app.common.spec :as us] [app.main.data.messages :as dm] - [app.main.ui.components.forms :refer [input submit-button form]] + [app.main.data.users :as udu] [app.main.store :as st] + [app.main.ui.components.forms :as fm] + [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.forms :as fm] - [app.util.i18n :as i18n :refer [t tr]])) + [app.util.i18n :as i18n :refer [t tr]] + [cljs.spec.alpha :as s] + [rumext.alpha :as mf])) (defn- on-error [form error] @@ -33,20 +33,20 @@ (defn- on-success [form] - (let [msg (tr "settings.notifications.password-saved")] + (let [msg (tr "dashboard.notifications.password-saved")] (st/emit! (dm/success msg)))) (defn- on-submit [form event] (dom/prevent-default event) - (let [params (with-meta (:clean-data form) + (let [params (with-meta (:clean-data @form) {:on-success (partial on-success form) :on-error (partial on-error form)})] (st/emit! (udu/update-password params)))) -(s/def ::password-1 ::fm/not-empty-string) -(s/def ::password-2 ::fm/not-empty-string) -(s/def ::password-old ::fm/not-empty-string) +(s/def ::password-1 ::us/not-empty-string) +(s/def ::password-2 ::us/not-empty-string) +(s/def ::password-old ::us/not-empty-string) (defn- password-equality [data] @@ -67,36 +67,38 @@ (mf/defc password-form [{:keys [locale] :as props}] - [:& form {:class "password-form" - :on-submit on-submit - :spec ::password-form - :validators [password-equality] - :initial {}} - [:h2 (t locale "settings.password-change-title")] + (let [form (fm/use-form :spec ::password-form + :validators [password-equality] + :initial {})] + [:& fm/form {:class "password-form" + :on-submit on-submit + :form form} + [:h2 (t locale "dashboard.password-change")] + [:div.fields-row + [:& fm/input + {:type "password" + :name :password-old + :label (t locale "labels.old-password")}]] - [:& input - {:type "password" - :name :password-old - :label (t locale "settings.old-password-label")}] + [:div.fields-row + [:& fm/input + {:type "password" + :name :password-1 + :label (t locale "labels.new-password")}]] - [:& input - {:type "password" - :name :password-1 - :label (t locale "settings.new-password-label")}] + [:div.fields-row + [:& fm/input + {:type "password" + :name :password-2 + :label (t locale "labels.confirm-password")}]] - [:& input - {:type "password" - :name :password-2 - :label (t locale "settings.confirm-password-label")}] - - [:& submit-button - {:label (t locale "settings.profile-submit-label")}]]) + [:& fm/submit-button + {:label (t locale "dashboard.update-settings")}]])) ;; --- Password Page (mf/defc password-page - [props] - (let [locale (mf/deref i18n/locale)] - [:section.settings-password.generic-form - [:div.forms-container - [:& password-form {:locale locale}]]])) + [{:keys [locale]}] + [:section.dashboard-settings.form-container + [:div.form-container + [:& password-form {:locale locale}]]]) diff --git a/frontend/src/app/main/ui/settings/profile.cljs b/frontend/src/app/main/ui/settings/profile.cljs index b391e15ee5..e07c536548 100644 --- a/frontend/src/app/main/ui/settings/profile.cljs +++ b/frontend/src/app/main/ui/settings/profile.cljs @@ -9,91 +9,80 @@ (ns app.main.ui.settings.profile (:require - [cljs.spec.alpha :as s] - [cuerdas.core :as str] - [rumext.alpha :as mf] + [app.common.spec :as us] [app.main.data.messages :as dm] - [app.main.data.users :as udu] + [app.main.data.modal :as modal] + [app.main.data.users :as du] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.components.forms :refer [input submit-button form]] [app.main.ui.components.file-uploader :refer [file-uploader]] + [app.main.ui.components.forms :as fm] [app.main.ui.icons :as i] [app.main.ui.messages :as msgs] - [app.main.ui.modal :as modal] [app.util.dom :as dom] - [app.util.forms :as fm] - [app.util.i18n :as i18n :refer [tr t]])) + [app.util.i18n :as i18n :refer [tr t]] + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [rumext.alpha :as mf])) -(s/def ::fullname ::fm/not-empty-string) -(s/def ::email ::fm/email) +(s/def ::fullname ::us/not-empty-string) +(s/def ::email ::us/email) (s/def ::profile-form (s/keys :req-un [::fullname ::lang ::theme ::email])) +(defn- on-success + [form] + (st/emit! (dm/success (tr "notifications.profile-saved")))) + (defn- on-error - [error form] + [form error] (st/emit! (dm/error (tr "errors.generic")))) (defn- on-submit [form event] - (let [data (:clean-data form) - on-success #(st/emit! (dm/success (tr "settings.notifications.profile-saved"))) - on-error #(on-error % form)] - (st/emit! (udu/update-profile (with-meta data - {:on-success on-success - :on-error on-error}))))) + (let [data (:clean-data @form) + mdata {:on-success (partial on-success form) + :on-error (partial on-error form)}] + (st/emit! (du/update-profile (with-meta data mdata))))) + ;; --- Profile Form (mf/defc profile-form [{:keys [locale] :as props}] - (let [prof (mf/deref refs/profile)] - [:& form {:on-submit on-submit - :class "profile-form" - :spec ::profile-form - :initial prof} - [:& input - {:type "text" - :name :fullname - :label (t locale "settings.fullname-label") - :trim true}] + (let [profile (mf/deref refs/profile) + form (fm/use-form :spec ::profile-form + :initial profile)] + [:& fm/form {:on-submit on-submit + :form form + :class "profile-form"} + [:div.fields-row + [:& fm/input + {:type "text" + :name :fullname + :label (t locale "dashboard.your-name")}]] - [:& input - {:type "email" - :name :email - :disabled true - :help-icon i/at - :label (t locale "settings.email-label")}] + [:div.fields-row + [:& fm/input + {:type "email" + :name :email + :disabled true + :help-icon i/at + :label (t locale "dashboard.your-email")}] - (cond - (nil? (:pending-email prof)) + [:div.options [:div.change-email [:a {:on-click #(modal/show! :change-email {})} - (t locale "settings.change-email-label")]] + (t locale "dashboard.change-email")]]]] - (not= (:pending-email prof) (:email prof)) - [:& msgs/inline-banner - {:type :info - :content (t locale "settings.change-email-info3" (:pending-email prof)) - :actions [{:label (t locale "settings.cancel-email-change") - :callback #(st/emit! udu/cancel-email-change)}]}] - ;; [:div.btn-secondary.btn-small - ;; {:on-click #(st/emit! udu/cancel-email-change)} - ;; (t locale "settings.cancel-email-change")]] - - :else - [:& msgs/inline-banner - {:type :info - :content (t locale "settings.email-verification-pending")}]) - - [:& submit-button - {:label (t locale "settings.profile-submit-label")}] + [:& fm/submit-button + {:label (t locale "dashboard.update-settings")}] [:div.links [:div.link-item [:a {:on-click #(modal/show! :delete-account {})} - (t locale "settings.remove-account-label")]]]])) + (t locale "dashboard.remove-account")]]]])) ;; --- Profile Photo Form @@ -110,11 +99,11 @@ on-file-selected (fn [file] - (st/emit! (udu/update-photo file)))] + (st/emit! (du/update-photo file)))] [:form.avatar-form [:div.image-change-field - [:span.update-overlay {:on-click on-image-click} (t locale "settings.update-photo-label")] + [:span.update-overlay {:on-click on-image-click} (t locale "labels.update")] [:img {:src photo}] [:& file-uploader {:accept "image/jpeg,image/png" :multi false @@ -124,10 +113,9 @@ ;; --- Profile Page (mf/defc profile-page - {::mf/wrap-props false} - [props] - (let [locale (i18n/use-locale)] - [:section.settings-profile.generic-form - [:div.forms-container - [:& profile-photo-form {:locale locale}] - [:& profile-form {:locale locale}]]])) + [{:keys [locale]}] + [:div.dashboard-settings + [:div.form-container.two-columns + [:& profile-photo-form {:locale locale}] + [:& profile-form {:locale locale}]]]) + diff --git a/frontend/src/app/main/ui/settings/sidebar.cljs b/frontend/src/app/main/ui/settings/sidebar.cljs new file mode 100644 index 0000000000..7447783881 --- /dev/null +++ b/frontend/src/app/main/ui/settings/sidebar.cljs @@ -0,0 +1,91 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.settings.sidebar + (:require + [app.common.spec :as us] + [app.main.data.auth :as da] + [app.main.data.messages :as dm] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.components.forms :as fm] + [app.main.ui.dashboard.sidebar :refer [profile-section]] + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [t tr]] + [app.util.object :as obj] + [app.util.router :as rt] + [app.util.time :as dt] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [goog.functions :as f] + [okulary.core :as l] + [rumext.alpha :as mf])) + +(mf/defc sidebar-content + [{:keys [locale profile section] :as props}] + (let [profile? (= section :settings-profile) + password? (= section :settings-password) + options? (= section :settings-options) + + go-dashboard + (mf/use-callback + (mf/deps profile) + (st/emitf (rt/nav :dashboard-projects {:team-id (:default-team-id profile)}))) + + go-settings-profile + (mf/use-callback + (mf/deps profile) + (st/emitf (rt/nav :settings-profile))) + + go-settings-password + (mf/use-callback + (mf/deps profile) + (st/emitf (rt/nav :settings-password))) + + go-settings-options + (mf/use-callback + (mf/deps profile) + (st/emitf (rt/nav :settings-options)))] + + [:div.sidebar-content + [:div.sidebar-content-section + [:div.back-to-dashboard {:on-click go-dashboard} + [:span.icon i/arrow-down] + [:span.text (t locale "labels.dashboard")]]] + [:hr] + + [:div.sidebar-content-section + [:ul.sidebar-nav.no-overflow + [:li {:class (when profile? "current") + :on-click go-settings-profile} + i/user + [:span.element-title (t locale "labels.profile")]] + + [:li {:class (when password? "current") + :on-click go-settings-password} + i/lock + [:span.element-title (t locale "labels.password")]] + + [:li {:class (when options? "current") + :on-click go-settings-options} + i/tree + [:span.element-title (t locale "labels.settings")]]]]])) + +(mf/defc sidebar + {::mf/wrap [mf/memo]} + [{:keys [profile locale section]}] + [:div.dashboard-sidebar.settings + [:div.sidebar-inside + [:& sidebar-content {:locale locale + :profile profile + :section section}] + [:& profile-section {:profile profile + :locale locale}]]]) diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index 52c0281703..27083b0ab3 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -8,7 +8,11 @@ ;; Copyright (c) 2016-2020 UXBOX Labs SL (ns app.main.ui.shapes.attrs - (:require [app.util.object :as obj])) + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [app.util.object :as obj] + [app.main.ui.context :as muc])) (defn- stroke-type->dasharray [style] @@ -18,17 +22,37 @@ :dashed "10,10" nil)) -(defn extract-style-attrs - [shape] +(defn add-border-radius [attrs shape] + (obj/merge! attrs #js {:rx (:rx shape) + :ry (:ry shape)})) + +(defn add-fill [attrs shape render-id] + (let [fill-color-gradient-id (str "fill-color-gradient_" render-id)] + (if (:fill-color-gradient shape) + (obj/merge! attrs #js {:fill (str/format "url(#%s)" fill-color-gradient-id)}) + (obj/merge! attrs #js {:fill (or (:fill-color shape) "transparent") + :fillOpacity (:fill-opacity shape nil)})))) + +(defn add-stroke [attrs shape render-id] (let [stroke-style (:stroke-style shape :none) - attrs #js {:fill (or (:fill-color shape) "transparent") - :fillOpacity (:fill-opacity shape nil) - :rx (:rx shape nil) - :ry (:ry shape nil)}] - (when (not= stroke-style :none) - (obj/merge! attrs - #js {:stroke (:stroke-color shape nil) - :strokeWidth (:stroke-width shape 1) - :strokeOpacity (:stroke-opacity shape nil) - :strokeDasharray (stroke-type->dasharray stroke-style)})) - attrs)) + stroke-color-gradient-id (str "stroke-color-gradient_" render-id)] + (if (not= stroke-style :none) + (if (:stroke-color-gradient shape) + (obj/merge! attrs + #js {:stroke (str/format "url(#%s)" stroke-color-gradient-id) + :strokeWidth (:stroke-width shape 1) + :strokeDasharray (stroke-type->dasharray stroke-style)}) + (obj/merge! attrs + #js {:stroke (:stroke-color shape nil) + :strokeWidth (:stroke-width shape 1) + :strokeOpacity (:stroke-opacity shape nil) + :strokeDasharray (stroke-type->dasharray stroke-style)})))) + attrs) + +(defn extract-style-attrs + ([shape] + (let [render-id (mf/use-ctx muc/render-ctx)] + (-> (obj/new) + (add-border-radius shape) + (add-fill shape render-id) + (add-stroke shape render-id))))) diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index 8d0784f8f6..e509150dd4 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 @@ [rumext.alpha :as mf] [app.common.uuid :as uuid] [app.common.geom.shapes :as geom] + [app.main.ui.shapes.group :refer [mask-id-ctx]] [app.util.object :as obj])) ; The SVG standard does not implement yet the 'stroke-alignment' @@ -23,13 +24,16 @@ base-props (unchecked-get props "base-props") elem-name (unchecked-get props "elem-name") {:keys [x y width height]} (geom/shape->rect-shape shape) + mask-id (mf/use-ctx mask-id-ctx) stroke-id (mf/use-var (uuid/next)) stroke-style (:stroke-style shape :none) stroke-position (:stroke-alignment shape :center)] (cond ;; Center alignment (or no stroke): the default in SVG (or (= stroke-style :none) (= stroke-position :center)) - [:> elem-name base-props] + [:> elem-name (cond-> (obj/merge! #js {} base-props) + (some? mask-id) + (obj/merge! #js {:mask mask-id}))] ;; Inner alignment: display the shape with double width stroke, ;; and clip the result with the original shape without stroke. @@ -49,10 +53,15 @@ shape-props (-> (obj/merge! #js {} base-props) (obj/merge! #js {:strokeWidth (* stroke-width 2) :clipPath (str "url('#" clip-id "')")}))] - [:* - [:> "clipPath" #js {:id clip-id} - [:> elem-name clip-props]] - [:> elem-name shape-props]]) + (if (nil? mask-id) + [:* + [:> "clipPath" #js {:id clip-id} + [:> elem-name clip-props]] + [:> elem-name shape-props]] + [:g {:mask mask-id} + [:> "clipPath" #js {:id clip-id} + [:> elem-name clip-props]] + [:> elem-name shape-props]])) ;; Outer alingmnent: display the shape in two layers. One ;; without stroke (only fill), and another one only with stroke @@ -61,7 +70,7 @@ ;; without stroke (= stroke-position :outer) - (let [mask-id (str "mask-" @stroke-id) + (let [stroke-mask-id (str "mask-" @stroke-id) stroke-width (.-strokeWidth ^js base-props) mask-props1 (-> (obj/merge! #js {} base-props) (obj/merge! #js {:stroke "white" @@ -89,11 +98,18 @@ (obj/merge! #js {:strokeWidth (* stroke-width 2) :fill "none" :fillOpacity 0 - :mask (str "url('#" mask-id "')")}))] - [:* - [:mask {:id mask-id} - [:> elem-name mask-props1] - [:> elem-name mask-props2]] - [:> elem-name shape-props1] - [:> elem-name shape-props2]])))) + :mask (str "url('#" stroke-mask-id "')")}))] + (if (nil? mask-id) + [:* + [:mask {:id mask-id} + [:> elem-name mask-props1] + [:> elem-name mask-props2]] + [:> elem-name shape-props1] + [:> elem-name shape-props2]] + [:g {:mask mask-id} + [:mask {:id stroke-mask-id} + [:> elem-name mask-props1] + [:> elem-name mask-props2]] + [:> elem-name shape-props1] + [:> elem-name shape-props2]]))))) diff --git a/frontend/src/app/main/ui/shapes/filters.cljs b/frontend/src/app/main/ui/shapes/filters.cljs index 5f583b2dce..89a7374f3d 100644 --- a/frontend/src/app/main/ui/shapes/filters.cljs +++ b/frontend/src/app/main/ui/shapes/filters.cljs @@ -12,6 +12,7 @@ [rumext.alpha :as mf] [cuerdas.core :as str] [app.util.color :as color] + [app.common.data :as d] [app.common.math :as mth] [app.common.uuid :as uuid])) @@ -21,22 +22,23 @@ (defn filter-str [filter-id shape] - (when (seq (:shadow shape)) + (when (or (seq (->> (:shadow shape) (remove :hidden))) + (and (:blur shape) (-> shape :blur :hidden not))) (str/fmt "url(#$0)" [filter-id]))) (mf/defc color-matrix - [{:keys [color opacity]}] - (let [[r g b a] (color/hex->rgba color opacity) + [{:keys [color]}] + (let [{:keys [color opacity]} color + [r g b a] (color/hex->rgba color opacity) [r g b] [(/ r 255) (/ g 255) (/ b 255)]] [:feColorMatrix {:type "matrix" :values (str/fmt "0 0 0 0 $0 0 0 0 0 $1 0 0 0 0 $2 0 0 0 $3 0" [r g b a])}])) (mf/defc drop-shadow-filter - [{:keys [filter-id filter shape]}] + [{:keys [filter-in filter-id params]}] - (let [{:keys [x y width height]} (:selrect shape) - {:keys [id in-filter color opacity offset-x offset-y blur spread]} filter] + (let [{:keys [color offset-x offset-y blur spread]} params] [:* [:feColorMatrix {:in "SourceAlpha" :type "matrix" :values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"}] @@ -44,21 +46,20 @@ [:feMorphology {:radius spread :operator "dilate" :in "SourceAlpha" - :result (str "filter" id)}]) + :result filter-id}]) [:feOffset {:dx offset-x :dy offset-y}] [:feGaussianBlur {:stdDeviation (/ blur 2)}] - [:& color-matrix {:color color :opacity opacity}] + [:& color-matrix {:color color}] [:feBlend {:mode "normal" - :in2 in-filter - :result (str "filter" id)}]])) + :in2 filter-in + :result filter-id}]])) (mf/defc inner-shadow-filter - [{:keys [filter-id filter shape]}] + [{:keys [filter-in filter-id params]}] - (let [{:keys [x y width height]} (:selrect shape) - {:keys [id in-filter color opacity offset-x offset-y blur spread]} filter] + (let [{:keys [color offset-x offset-y blur spread]} params] [:* [:feColorMatrix {:in "SourceAlpha" :type "matrix" :values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" @@ -68,7 +69,7 @@ [:feMorphology {:radius spread :operator "erode" :in "SourceAlpha" - :result (str "filter" id)}]) + :result filter-id}]) [:feOffset {:dx offset-x :dy offset-y}] [:feGaussianBlur {:stdDeviation (/ blur 2)}] @@ -78,15 +79,39 @@ :k2 "-1" :k3 "1"}] - [:& color-matrix {:color color :opacity opacity}] + [:& color-matrix {:color color}] [:feBlend {:mode "normal" - :in2 in-filter - :result (str "filter" id)}]])) + :in2 filter-in + :result filter-id}]])) -(defn filter-bounds [shape filter] +(mf/defc background-blur-filter + [{:keys [filter-id filter-in params]}] + [:* + [:feGaussianBlur {:in "BackgroundImage" + :stdDeviation (/ (:value params) 2)}] + [:feComposite {:in2 "SourceAlpha" + :operator "in" + :result filter-id}]]) + +(mf/defc layer-blur-filter + [{:keys [filter-id params]}] + + [:feGaussianBlur {:stdDeviation (/ (:value params) 2) + :result filter-id}]) + +(mf/defc image-fix-filter [{:keys [filter-id]}] + [:feFlood {:flood-opacity 0 :result filter-id}]) + +(mf/defc blend-filters [{:keys [filter-id filter-in]}] + [:feBlend {:mode "normal" + :in "SourceGraphic" + :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}} filter + {: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) @@ -97,70 +122,83 @@ :y2 (+ filter-y filter-height)})) (defn get-filters-bounds - [shape filters] + [shape filters blur-value] - (let [filter-bounds (->> - filters - (filter #(= :drop-shadow (:style %))) - (map (partial filter-bounds shape) )) + (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 (:selrect shape)) 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))] + 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))] [x1 y1 (- x2 x1) (- y2 y1)])) +(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) + :params (:params entry)}] + (case (:type entry) + :drop-shadow [:> drop-shadow-filter props] + :inner-shadow [:> inner-shadow-filter props] + :background-blur [:> background-blur-filter props] + :layer-blur [:> layer-blur-filter props] + :image-fix [:> image-fix-filter props] + :blend-filters [:> blend-filters props]))) + (mf/defc filters [{:keys [filter-id shape]}] - (let [add-in-filter - (fn [filter in-filter] - (assoc filter :in-filter in-filter)) + (let [filters (d/concat + [] + [{:id "BackgroundImageFix" :type :image-fix}] - filters (->> shape :shadow (filter (comp not :hidden))) + ;; Background blur won't work in current SVG specification + ;; We can revisit this in the future + #_(->> shape :blur (blur-filters :background-blur)) - [filter-x filter-y filter-width filter-height] (get-filters-bounds shape filters)] - (when (seq filters) - [:defs + (->> shape :shadow (shadow-filters :drop-shadow)) + [{:id "shape" :type :blend-filters}] + (->> shape :shadow (shadow-filters :inner-shadow)) + (->> shape :blur (blur-filters :layer-blur))) + + ;; Adds the previous filter as `filter-in` parameter + filters (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters))) + + [filter-x filter-y filter-width filter-height] (get-filters-bounds shape filters (or (-> shape :blur :value) 0))] + + [:* + (when (> (count filters) 2) [:filter {:id filter-id - :x filter-x :y filter-y - :width filter-width :height filter-height + :x filter-x + :y filter-y + :width filter-width + :height filter-height :filterUnits "userSpaceOnUse" :color-interpolation-filters "sRGB"} - (let [;; Add as a paramter the input filter - drop-shadow-filters (->> filters (filter #(= :drop-shadow (:style %)))) - drop-shadow-filters (->> drop-shadow-filters - (map #(str "filter" (:id %))) - (cons "BackgroundImageFix") - (map add-in-filter drop-shadow-filters)) + (for [entry filters] + [:& filter-entry {:entry entry}])])])) - inner-shadow-filters (->> filters (filter #(= :inner-shadow (:style %)))) - inner-shadow-filters (->> inner-shadow-filters - (map #(str "filter" (:id %))) - (cons "shape") - (map add-in-filter inner-shadow-filters))] - - [:* - [:feFlood {:flood-opacity 0 :result "BackgroundImageFix"}] - (for [{:keys [id type] :as filter} drop-shadow-filters] - [:& drop-shadow-filter {:key id - :filter-id filter-id - :filter filter - :shape shape}]) - - [:feBlend {:mode "normal" - :in "SourceGraphic" - :in2 (if (seq drop-shadow-filters) - (str "filter" (:id (last drop-shadow-filters))) - "BackgroundImageFix") - :result "shape"}] - - (for [{:keys [id type] :as filter} inner-shadow-filters] - [:& inner-shadow-filter {:key id - :filter-id filter-id - :filter filter - :shape shape}]) - ]) - ]]))) diff --git a/frontend/src/app/main/ui/shapes/gradients.cljs b/frontend/src/app/main/ui/shapes/gradients.cljs new file mode 100644 index 0000000000..120e623dd7 --- /dev/null +++ b/frontend/src/app/main/ui/shapes/gradients.cljs @@ -0,0 +1,86 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.shapes.gradients + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [app.util.object :as obj] + [app.common.uuid :as uuid] + [app.main.ui.context :as muc] + [app.common.geom.point :as gpt])) + +(mf/defc linear-gradient [{:keys [id gradient shape]}] + (let [{:keys [x y width height]} shape] + [:defs + [:linearGradient {:id id + :x1 (:start-x gradient) + :y1 (:start-y gradient) + :x2 (:end-x gradient) + :y2 (:end-y gradient)} + (for [{:keys [offset color opacity]} (:stops gradient)] + [:stop {:key (str id "-stop-" offset) + :offset (or offset 0) + :stop-color color + :stop-opacity opacity}])]])) + +(mf/defc radial-gradient [{:keys [id gradient shape]}] + (let [{:keys [x y width height]} shape] + [:defs + (let [[x y] (if (= (:type shape) :frame) [0 0] [x y]) + translate-vec (gpt/point (+ x (* width (:start-x gradient))) + (+ y (* height (:start-y gradient)))) + + gradient-vec (gpt/to-vec (gpt/point (* width (:start-x gradient)) + (* height (:start-y gradient))) + (gpt/point (* width (:end-x gradient)) + (* height (:end-y gradient)))) + + angle (gpt/angle gradient-vec + (gpt/point 1 0)) + + shape-height-vec (gpt/point 0 (/ height 2)) + + scale-factor-y (/ (gpt/length gradient-vec) (/ height 2)) + scale-factor-x (* scale-factor-y (:width gradient)) + + scale-vec (gpt/point (* scale-factor-y (/ height 2)) + (* scale-factor-x (/ width 2))) + + tr-translate (str/fmt "translate(%s, %s)" (:x translate-vec) (:y translate-vec)) + tr-rotate (str/fmt "rotate(%s)" angle) + tr-scale (str/fmt "scale(%s, %s)" (:x scale-vec) (:y scale-vec)) + transform (str/fmt "%s %s %s" tr-translate tr-rotate tr-scale)] + [:radialGradient {:id id + :cx 0 + :cy 0 + :r 1 + :gradientUnits "userSpaceOnUse" + :gradientTransform transform} + (for [{:keys [offset color opacity]} (:stops gradient)] + [:stop {:key (str id "-stop-" offset) + :offset (or offset 0) + :stop-color color + :stop-opacity opacity}])])])) + +(mf/defc gradient + {::mf/wrap-props false} + [props] + (let [attr (obj/get props "attr") + shape (obj/get props "shape") + render-id (mf/use-ctx muc/render-ctx) + id (str (name attr) "_" render-id) + gradient (get shape attr) + gradient-props #js {:id id + :gradient gradient + :shape shape}] + (when gradient + (case (:type gradient) + :linear [:> linear-gradient gradient-props] + :radial [:> radial-gradient gradient-props])))) diff --git a/frontend/src/app/main/ui/shapes/group.cljs b/frontend/src/app/main/ui/shapes/group.cljs index 5835b89d6a..6b4a5bf5c5 100644 --- a/frontend/src/app/main/ui/shapes/group.cljs +++ b/frontend/src/app/main/ui/shapes/group.cljs @@ -10,26 +10,44 @@ (ns app.main.ui.shapes.group (:require [rumext.alpha :as mf] + [cuerdas.core :as str] [app.main.ui.shapes.attrs :as attrs] [app.util.debug :refer [debug?]] [app.common.geom.shapes :as geom])) +(def mask-id-ctx (mf/create-context nil)) + (defn group-shape [shape-wrapper] (mf/fnc group-shape {::mf/wrap-props false} [props] - (let [frame (unchecked-get props "frame") - shape (unchecked-get props "shape") - childs (unchecked-get props "childs") + (let [frame (unchecked-get props "frame") + shape (unchecked-get props "shape") + childs (unchecked-get props "childs") + expand-mask (unchecked-get props "expand-mask") + mask (if (and (:masked-group? shape) (not expand-mask)) + (first childs) + nil) + childs (if (and (:masked-group? shape) (not expand-mask)) + (rest childs) + childs) is-child-selected? (unchecked-get props "is-child-selected?") {:keys [id x y width height]} shape transform (geom/transform-matrix shape)] [:g - (for [item childs] - [:& shape-wrapper {:frame frame - :shape item - :key (:id item)}]) + (when mask + [:defs + [:mask {:id (:id mask) + :width width + :height height} + [:& shape-wrapper {:frame frame + :shape mask}]]]) + [:& (mf/provider mask-id-ctx) {:value (str/fmt "url(#%s)" (:id mask))} + (for [item childs] + [:& shape-wrapper {:frame frame + :shape item + :key (:id item)}])] (when (not is-child-selected?) [:rect {:transform transform :x x diff --git a/frontend/src/app/main/ui/shapes/icon.cljs b/frontend/src/app/main/ui/shapes/icon.cljs index dbf759393b..bba2c92518 100644 --- a/frontend/src/app/main/ui/shapes/icon.cljs +++ b/frontend/src/app/main/ui/shapes/icon.cljs @@ -12,6 +12,7 @@ [rumext.alpha :as mf] [app.common.geom.shapes :as geom] [app.main.ui.shapes.attrs :as attrs] + [app.main.ui.shapes.group :refer [mask-id-ctx]] [app.util.object :as obj])) (mf/defc icon-shape @@ -20,6 +21,7 @@ (let [shape (unchecked-get props "shape") {:keys [id x y width height metadata rotation content]} shape + mask-id (mf/use-ctx mask-id-ctx) transform (geom/transform-matrix shape) vbox (apply str (interpose " " (:view-box metadata))) @@ -33,6 +35,7 @@ :height height :viewBox vbox :preserveAspectRatio "none" + :mask mask-id :dangerouslySetInnerHTML #js {:__html content}}))] [:g {:transform transform} [:> "svg" props]])) @@ -41,7 +44,9 @@ [{:keys [shape] :as props}] (let [{:keys [content id metadata]} shape view-box (apply str (interpose " " (:view-box metadata))) + mask-id (mf/use-ctx mask-id-ctx) props {:viewBox view-box :id (str "shape-" id) + :mask mask-id :dangerouslySetInnerHTML #js {:__html content}}] [:& "svg" props])) diff --git a/frontend/src/app/main/ui/shapes/image.cljs b/frontend/src/app/main/ui/shapes/image.cljs index 4fbe7d6ed3..0380ba3431 100644 --- a/frontend/src/app/main/ui/shapes/image.cljs +++ b/frontend/src/app/main/ui/shapes/image.cljs @@ -13,6 +13,7 @@ [app.config :as cfg] [app.common.geom.shapes :as geom] [app.main.ui.shapes.attrs :as attrs] + [app.main.ui.shapes.group :refer [mask-id-ctx]] [app.util.object :as obj] [app.main.ui.context :as muc] [app.main.data.fetch :as df] @@ -26,6 +27,7 @@ {:keys [id x y width height rotation metadata]} shape uri (cfg/resolve-media-path (:path metadata)) embed-resources? (mf/use-ctx muc/embed-ctx) + mask-id (mf/use-ctx mask-id-ctx) data-uri (mf/use-state (when (not embed-resources?) uri))] (mf/use-effect @@ -44,7 +46,8 @@ :id (str "shape-" id) :width width :height height - :preserveAspectRatio "none"}))] + :preserveAspectRatio "none" + :mask mask-id}))] (if (nil? @data-uri) [:> "rect" (obj/merge! props diff --git a/frontend/src/app/main/ui/shapes/path.cljs b/frontend/src/app/main/ui/shapes/path.cljs index bead937a4d..a591df4871 100644 --- a/frontend/src/app/main/ui/shapes/path.cljs +++ b/frontend/src/app/main/ui/shapes/path.cljs @@ -13,6 +13,7 @@ [rumext.alpha :as mf] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]] + [app.main.ui.shapes.group :refer [mask-id-ctx]] [app.common.geom.shapes :as geom] [app.util.object :as obj])) @@ -45,6 +46,7 @@ (let [shape (unchecked-get props "shape") background? (unchecked-get props "background?") {:keys [id x y width height]} (geom/shape->rect-shape shape) + mask-id (mf/use-ctx mask-id-ctx) transform (geom/transform-matrix shape) pdata (render-path shape) props (-> (attrs/extract-style-attrs shape) @@ -53,7 +55,7 @@ :id (str "shape-" id) :d pdata}))] (if background? - [:g + [:g {:mask mask-id} [:path {:stroke "transparent" :fill "transparent" :stroke-width "20px" @@ -63,5 +65,6 @@ :elem-name "path"}]] [:& shape-custom-stroke {:shape shape :base-props props + :mask mask-id :elem-name "path"}]))) diff --git a/frontend/src/app/main/ui/shapes/rect.cljs b/frontend/src/app/main/ui/shapes/rect.cljs index a1a7c8457f..f2c8e98e97 100644 --- a/frontend/src/app/main/ui/shapes/rect.cljs +++ b/frontend/src/app/main/ui/shapes/rect.cljs @@ -13,7 +13,12 @@ [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]] [app.common.geom.shapes :as geom] - [app.util.object :as obj])) + [app.util.object :as obj] + [app.main.ui.shapes.gradients :refer [gradient]] + + [cuerdas.core :as str] + [app.common.uuid :as uuid] + [app.common.geom.point :as gpt])) (mf/defc rect-shape {::mf/wrap-props false} @@ -21,6 +26,7 @@ (let [shape (unchecked-get props "shape") {:keys [id x y width height]} shape transform (geom/transform-matrix shape) + props (-> (attrs/extract-style-attrs shape) (obj/merge! #js {:x x @@ -33,4 +39,3 @@ [:& shape-custom-stroke {:shape shape :base-props props :elem-name "rect"}])) - diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index a4202b8449..086f37bcb3 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -7,5 +7,67 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL -(ns app.main.ui.shapes.shape) +(ns app.main.ui.shapes.shape + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [app.util.object :as obj] + [app.common.uuid :as uuid] + [app.common.geom.shapes :as geom] + [app.main.ui.shapes.filters :as filters] + [app.main.ui.shapes.gradients :as grad] + [app.main.ui.context :as muc])) +(mf/defc background-blur [{:keys [shape]}] + (when-let [background-blur-filters (->> shape :blur (remove #(= (:type %) :layer-blur)) (remove :hidden))] + (for [filter background-blur-filters] + [:* + + + [:foreignObject {:key (str "blur_" (:id filter)) + :pointerEvents "none" + :x (:x shape) + :y (:y shape) + :width (:width shape) + :height (:height shape) + :transform (geom/transform-matrix shape)} + [:style ""] + [:div.backround-blur + {:style {:width "100%" + :height "100%" + ;; :backdrop-filter (str/format "blur(%spx)" (:value filter)) + :filter (str/format "blur(4px") + }}]]]))) + +(mf/defc shape-container + {::mf/wrap-props false} + [props] + + (let [shape (unchecked-get props "shape") + children (unchecked-get props "children") + render-id (mf/use-memo #(str (uuid/next))) + filter-id (str "filter_" render-id) + group-props (-> props + (obj/clone) + (obj/without ["shape" "children"]) + (obj/set! "className" "shape") + (obj/set! "data-type" (:type shape)) + (obj/set! "filter" (filters/filter-str filter-id shape))) + + ;;group-props (if (seq (:blur shape)) + ;; (obj/set! group-props "clip-path" (str/fmt "url(#%s)" (str "blur_" render-id))) + ;; group-props) + ] + [:& (mf/provider muc/render-ctx) {:value render-id} + [:> :g group-props + [:defs + [:& filters/filters {:shape shape :filter-id filter-id}] + [:& grad/gradient {:shape shape :attr :fill-color-gradient}] + [:& grad/gradient {:shape shape :attr :stroke-color-gradient}] + + #_(when (:blur shape) + [:clipPath {:id (str "blur_" render-id)} + children])] + + [:& background-blur {:shape shape}] + children]])) diff --git a/frontend/src/app/main/ui/shapes/text.cljs b/frontend/src/app/main/ui/shapes/text.cljs index de1a87f4f9..240c0d0adb 100644 --- a/frontend/src/app/main/ui/shapes/text.cljs +++ b/frontend/src/app/main/ui/shapes/text.cljs @@ -13,10 +13,13 @@ [app.main.data.fetch :as df] [app.main.fonts :as fonts] [app.main.ui.context :as muc] + [app.main.ui.shapes.group :refer [mask-id-ctx]] [app.common.data :as d] [app.common.geom.shapes :as geom] [app.common.geom.matrix :as gmt] - [app.util.object :as obj])) + [app.util.object :as obj] + [app.util.color :as uc] + [app.util.text :as ut])) ;; --- Text Editor Rendering @@ -54,20 +57,37 @@ text-transform (obj/get data "text-transform") line-height (obj/get data "line-height") - font-id (obj/get data "font-id" fonts/default-font) + font-id (obj/get data "font-id" (:font-id ut/default-text-attrs)) font-variant-id (obj/get data "font-variant-id") font-family (obj/get data "font-family") font-size (obj/get data "font-size") + + ;; Old properties for backwards compatibility fill (obj/get data "fill") - opacity (obj/get data "opacity") + opacity (obj/get data "opacity" 1) + + fill-color (obj/get data "fill-color" fill) + fill-opacity (obj/get data "fill-opacity" opacity) + fill-color-gradient (obj/get data "fill-color-gradient" nil) + fill-color-gradient (when fill-color-gradient + (-> (js->clj fill-color-gradient :keywordize-keys true) + (update :type keyword))) + + fill-color-ref-id (obj/get data "fill-color-ref-id") + fill-color-ref-file (obj/get data "fill-color-ref-file") + + [r g b a] (uc/hex->rgba fill-color fill-opacity) + background (if fill-color-gradient + (uc/gradient->css (js->clj fill-color-gradient)) + (str/format "rgba(%s, %s, %s, %s)" r g b a)) + fontsdb (deref fonts/fontsdb) base #js {:textDecoration text-decoration - :color fill - :opacity opacity :textTransform text-transform - :lineHeight (or line-height "inherit")}] + :lineHeight (or line-height "inherit") + "--text-color" background}] (when (and (string? letter-spacing) (pos? (alength letter-spacing))) @@ -149,14 +169,14 @@ (fn [] (when (and embed-resources? (= type "root")) (let [font-to-embed (get-all-fonts node) - font-to-embed (if (empty? font-to-embed) #{{:font-id fonts/default-font}} font-to-embed) + font-to-embed (if (empty? font-to-embed) #{ut/default-text-attrs} font-to-embed) embeded (map embed-font font-to-embed)] (-> (p/all embeded) (p/then (fn [result] (reset! embeded-fonts (str/join "\n" result))))))))) (if (string? text) (let [style (generate-text-styles (clj->js node))] - [:span {:style style :key index} (if (= text "") "\u00A0" text)]) + [:span.text-node {:style style} (if (= text "") "\u00A0" text)]) (let [children (map-indexed (fn [index node] (mf/element text-node {:index index :node node :key index})) children)] @@ -168,13 +188,15 @@ {:key index :style style :xmlns "http://www.w3.org/1999/xhtml"} - (when (not (nil? @embeded-fonts)) - [:style @embeded-fonts]) + [:* + [:style ".text-node { background: var(--text-color); -webkit-text-fill-color: transparent; -webkit-background-clip: text;"] + (when (not (nil? @embeded-fonts)) + [:style @embeded-fonts])] children]) "paragraph-set" (let [style #js {:display "inline-block"}] - [:div.paragraphs {:key index :style style} children]) + [:div.paragraphs {:key index :style style} children]) "paragraph" (let [style (generate-paragraph-styles (clj->js node))] @@ -203,6 +225,7 @@ [props] (let [shape (unchecked-get props "shape") selected? (unchecked-get props "selected?") + mask-id (mf/use-ctx mask-id-ctx) {:keys [id x y width height rotation content]} shape] [:foreignObject {:x x :y y @@ -210,6 +233,7 @@ :transform (geom/transform-matrix shape) :id (str id) :width width - :height height} + :height height + :mask mask-id} [:& text-content {:content (:content shape)}]])) diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index aec7182210..21774edd95 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -92,10 +92,12 @@ :toggle-fullscreen toggle-fullscreen :fullscreen? fullscreen? :local local - :index index}] + :index index + :screen :viewer}] [:div.viewer-content {:on-click on-click} (when (:show-thumbnails local) - [:& thumbnails-panel {:index index + [:& thumbnails-panel {:screen :viewer + :index index :data data}]) [:& main-panel {:data data :local local diff --git a/frontend/src/app/main/ui/viewer/handoff.cljs b/frontend/src/app/main/ui/viewer/handoff.cljs new file mode 100644 index 0000000000..cd1aa34cdd --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff.cljs @@ -0,0 +1,127 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff + (:require + [rumext.alpha :as mf] + [beicon.core :as rx] + [goog.events :as events] + [okulary.core :as l] + [app.common.exceptions :as ex] + [app.util.data :refer [classnames]] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [t tr]] + [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 hooks] + [app.main.ui.icons :as i] + [app.main.ui.keyboard :as kbd] + [app.main.ui.viewer.header :refer [header]] + [app.main.ui.viewer.thumbnails :refer [thumbnails-panel]] + [app.main.ui.viewer.handoff.render :refer [render-frame-svg]] + [app.main.ui.viewer.handoff.left-sidebar :refer [left-sidebar]] + [app.main.ui.viewer.handoff.right-sidebar :refer [right-sidebar]]) + (:import goog.events.EventType)) + +(defn handle-select-frame [frame] + #(do (dom/prevent-default %) + (dom/stop-propagation %) + (st/emit! (dv/select-shape (:id frame))))) + +(mf/defc render-panel + [{:keys [data local index]}] + (let [locale (mf/deref i18n/locale) + frames (:frames data []) + objects (:objects data) + frame (get frames index)] + + (mf/use-effect + (mf/deps index) + (fn [] + (st/emit! (dv/set-current-frame (:id frame)) + (dv/select-shape (:id frame))))) + + [:section.viewer-preview + (cond + (empty? frames) + [:section.empty-state + [:span (t locale "viewer.empty-state")]] + + (nil? frame) + [:section.empty-state + [:span (t locale "viewer.frame-not-found")]] + + :else + [:* + [:& left-sidebar {:frame frame}] + [:div.handoff-svg-wrapper {:on-click (handle-select-frame frame)} + [:& render-frame-svg {:frame-id (:id frame) + :zoom (:zoom local) + :objects objects}]] + [:& right-sidebar {:frame frame}]])])) + +(mf/defc handoff-content + [{:keys [data local index] :as props}] + (let [container (mf/use-ref) + + [toggle-fullscreen fullscreen?] (hooks/use-fullscreen container) + + on-mouse-wheel + (fn [event] + (when (kbd/ctrl? event) + (dom/prevent-default event) + (let [event (.getBrowserEvent ^js event)] + (if (pos? (.-deltaY ^js event)) + (st/emit! dv/decrease-zoom) + (st/emit! dv/increase-zoom))))) + + on-mount + (fn [] + ;; bind with passive=false to allow the event to be cancelled + ;; https://stackoverflow.com/a/57582286/3219895 + (let [key1 (events/listen goog/global EventType.WHEEL + on-mouse-wheel #js {"passive" false})] + (fn [] + (events/unlistenByKey key1))))] + + (mf/use-effect on-mount) + (hooks/use-shortcuts dv/shortcuts) + + [:div.handoff-layout {:class (classnames :fullscreen fullscreen?) + :ref container} + [:& header {:data data + :toggle-fullscreen toggle-fullscreen + :fullscreen? fullscreen? + :local local + :index index + :screen :handoff}] + [:div.viewer-content + (when (:show-thumbnails local) + [:& thumbnails-panel {:index index + :data data + :screen :handoff}]) + [:& render-panel {:data data + :local local + :index index}]]])) + +(mf/defc handoff + [{:keys [file-id page-id index] :as props}] + (mf/use-effect + (mf/deps file-id page-id) + (fn [] + (st/emit! (dv/initialize props)))) + + (let [data (mf/deref refs/viewer-data) + local (mf/deref refs/viewer-local)] + (when data + [:& handoff-content {:index index + :local local + :data data}]))) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes.cljs new file mode 100644 index 0000000000..2863ced879 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/attributes.cljs @@ -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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.attributes + (:require + [rumext.alpha :as mf] + [app.util.i18n :as i18n] + [app.common.geom.shapes :as gsh] + [app.main.ui.viewer.handoff.attributes.layout :refer [layout-panel]] + [app.main.ui.viewer.handoff.attributes.fill :refer [fill-panel]] + [app.main.ui.viewer.handoff.attributes.stroke :refer [stroke-panel]] + [app.main.ui.viewer.handoff.attributes.shadow :refer [shadow-panel]] + [app.main.ui.viewer.handoff.attributes.blur :refer [blur-panel]] + [app.main.ui.viewer.handoff.attributes.image :refer [image-panel]] + [app.main.ui.viewer.handoff.attributes.text :refer [text-panel]])) + +(mf/defc attributes + [{:keys [shapes frame options]}] + (let [locale (mf/deref i18n/locale) + shapes (->> shapes + (map #(gsh/translate-to-frame % frame))) + + shape (first shapes)] + [:div.element-options + (for [option options] + [:> (case option + :layout layout-panel + :fill fill-panel + :stroke stroke-panel + :shadow shadow-panel + :blur blur-panel + :image image-panel + :text text-panel) + {:shapes shapes + :frame frame + :locale locale}])])) + diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/blur.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/blur.cljs new file mode 100644 index 0000000000..a84a91b9cb --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/blur.cljs @@ -0,0 +1,41 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.attributes.blur + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [app.util.i18n :refer [t]] + [app.main.ui.icons :as i] + [app.main.ui.viewer.handoff.attributes.common :refer [copy-cb]])) + +(defn has-blur? [shape] + (:blur shape)) + +(defn copy-blur [shape] + (copy-cb shape + :blur + :to-prop "filter" + :format #(str/fmt "blur(%spx)" (:value %)))) + +(mf/defc blur-panel [{:keys [shapes locale]}] + (let [shapes (->> shapes (filter has-blur?)) + handle-copy (when (= (count shapes) 1) (copy-blur (first shapes)))] + (when (seq shapes) + [:div.attributes-block + [:div.attributes-block-title + [:div.attributes-block-title-text (t locale "handoff.attributes.blur")] + (when handle-copy + [:button.attributes-copy-button {:on-click handle-copy} i/copy])] + + (for [shape shapes] + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.blur.value")] + [:div.attributes-value (-> shape :blur :value) "px"] + [:button.attributes-copy-button {:on-click (copy-blur shape)} i/copy]])]))) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs new file mode 100644 index 0000000000..b7ccd21fbf --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs @@ -0,0 +1,81 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.attributes.common + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [app.util.dom :as dom] + [app.util.i18n :refer [t] :as i18n] + [app.util.color :as uc] + [app.common.math :as mth] + [app.main.ui.icons :as i] + [app.util.webapi :as wapi] + [app.main.ui.components.color-bullet :refer [color-bullet color-name]])) + +(defn copy-cb [values properties & {:keys [to-prop format] :or {to-prop {}}}] + (fn [event] + (let [ + ;; We allow the :format and :to-prop to be a map for different properties + ;; or just a value for a single property. This code transform a single + ;; property to a uniform one + properties (if-not (coll? properties) [properties] properties) + + format (if (not (map? format)) + (into {} (map #(vector % format) properties)) + format) + + to-prop (if (not (map? to-prop)) + (into {} (map #(vector % to-prop) properties)) + to-prop) + + default-format (fn [value] (str (mth/precision value 2) "px")) + format-property (fn [prop] + (let [css-prop (or (prop to-prop) (name prop))] + (str/fmt " %s: %s;" css-prop ((or (prop format) default-format) (prop values) values)))) + + text-props (->> properties + (remove #(let [value (get values %)] + (or (nil? value) (= value 0)))) + (map format-property) + (str/join "\n")) + + result (str/fmt "{\n%s\n}" text-props)] + + (wapi/write-to-clipboard result)))) + +(mf/defc color-row [{:keys [color format on-copy on-change-format]}] + (let [locale (mf/deref i18n/locale)] + [:div.attributes-color-row + [:& color-bullet {:color color}] + + (if (:gradient color) + [:& color-name {:color color}] + (case format + :rgba (let [[r g b a] (->> (uc/hex->rgba (:color color) (:opacity color)) (map #(mth/precision % 2)))] + [:div (str/fmt "%s, %s, %s, %s" r g b a)]) + :hsla (let [[h s l a] (->> (uc/hex->hsla (:color color) (:opacity color)) (map #(mth/precision % 2)))] + [:div (str/fmt "%s, %s, %s, %s" h s l a)]) + [:* + [:& color-name {:color color}] + (when-not (:gradient color) [:div (str (* 100 (:opacity color)) "%")])])) + + (when-not (and on-change-format (:gradient color)) + [:select {:on-change #(-> (dom/get-target-val %) keyword on-change-format)} + [:option {:value "hex"} + (t locale "handoff.attributes.color.hex")] + + [:option {:value "rgba"} + (t locale "handoff.attributes.color.rgba")] + + [:option {:value "hsla"} + (t locale "handoff.attributes.color.hsla")]]) + + (when on-copy + [:button.attributes-copy-button {:on-click on-copy} i/copy])])) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/fill.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/fill.cljs new file mode 100644 index 0000000000..c4c9d6e273 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/fill.cljs @@ -0,0 +1,67 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.attributes.fill + (:require + [rumext.alpha :as mf] + [app.util.i18n :refer [t]] + [app.util.color :as uc] + [app.main.ui.icons :as i] + [app.main.ui.viewer.handoff.attributes.common :refer [copy-cb color-row]])) + +(def fill-attributes [:fill-color :fill-color-gradient]) + +(defn shape->color [shape] + {:color (:fill-color shape) + :opacity (:fill-opacity shape) + :gradient (:fill-color-gradient shape) + :id (:fill-ref-id shape) + :file-id (:fill-ref-file-id shape)}) + +(defn has-color? [shape] + (and + (not (contains? #{:image :text :group} (:type shape))) + (or (:fill-color shape) + (:fill-color-gradient shape)))) + +(mf/defc fill-block [{:keys [shape locale]}] + (let [color-format (mf/use-state :hex) + color (shape->color shape) + handle-copy (copy-cb shape + fill-attributes + :to-prop "background" + :format #(uc/color->background color))] + + [:& color-row {:color color + :format @color-format + :on-change-format #(reset! color-format %) + :on-copy handle-copy}])) + +(mf/defc fill-panel + [{:keys [shapes locale]}] + (let [shapes (->> shapes (filter has-color?)) + handle-copy (when (= (count shapes) 1) + (copy-cb (first shapes) + fill-attributes + :to-prop "background" + :format #(-> shapes first shape->color uc/color->background)))] + + (when (seq shapes) + [:div.attributes-block + [:div.attributes-block-title + [:div.attributes-block-title-text (t locale "handoff.attributes.fill")] + (when handle-copy + [:button.attributes-copy-button + {:on-click handle-copy} + i/copy])] + + (for [shape shapes] + [:& fill-block {:key (str "fill-block-" (:id shape)) + :shape shape + :locale locale}])]))) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/image.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/image.cljs new file mode 100644 index 0000000000..057c02369b --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/image.cljs @@ -0,0 +1,44 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.attributes.image + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [app.config :as cfg] + [app.util.i18n :refer [t]] + [app.main.ui.icons :as i] + [app.main.ui.viewer.handoff.attributes.common :refer [copy-cb]])) + +(defn has-image? [shape] + (and (= (:type shape) :image))) + +(mf/defc image-panel [{:keys [shapes locale]}] + (let [shapes (->> shapes (filter has-image?))] + (for [shape shapes] + [:div.attributes-block {:key (str "image-" (:id shape))} + [:div.attributes-image-row + [:div.attributes-image + [:img {:src (cfg/resolve-media-path (-> shape :metadata :path))}]]] + + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.image.width")] + [:div.attributes-value (-> shape :metadata :width) "px"] + [:button.attributes-copy-button {:on-click (copy-cb shape :width)} i/copy]] + + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.image.height")] + [:div.attributes-value (-> shape :metadata :height) "px"] + [:button.attributes-copy-button {:on-click (copy-cb shape :height)} i/copy]] + + (let [filename (last (str/split (-> shape :metadata :path) "/"))] + [:a.download-button {:target "_blank" + :download filename + :href (cfg/resolve-media-path (-> shape :metadata :path))} + (t locale "handoff.attributes.image.download")])]))) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs new file mode 100644 index 0000000000..748905449f --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs @@ -0,0 +1,84 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.attributes.layout + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [app.util.i18n :refer [t]] + [app.common.math :as mth] + [app.main.ui.icons :as i] + [app.main.ui.viewer.handoff.attributes.common :refer [copy-cb]])) + +(defn copy-layout [shape] + (copy-cb shape + [:width :height :x :y :rotation] + :to-prop {:x "left" :y "top" :rotation "transform"} + :format {:rotation #(str/fmt "rotate(%sdeg)" %)})) + +(mf/defc layout-block + [{:keys [shape locale]}] + [:* + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.layout.width")] + [:div.attributes-value (mth/precision (:width shape) 2) "px"] + [:button.attributes-copy-button + {:on-click (copy-cb shape :width)} + i/copy]] + + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.layout.height")] + [:div.attributes-value (mth/precision (:height shape) 2) "px"] + [:button.attributes-copy-button + {:on-click (copy-cb shape :height)} + i/copy]] + + (when (not= (:x shape) 0) + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.layout.left")] + [:div.attributes-value (mth/precision (:x shape) 2) "px"] + [:button.attributes-copy-button + {:on-click (copy-cb shape :x :to-prop "left")} + i/copy]]) + + (when (not= (:y shape) 0) + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.layout.top")] + [:div.attributes-value (mth/precision (:y shape) 2) "px"] + [:button.attributes-copy-button + {:on-click (copy-cb shape :y :to-prop "top")} + i/copy]]) + + (when (not= (:rotation shape 0) 0) + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.layout.rotation")] + [:div.attributes-value (mth/precision (:rotation shape) 2) "deg"] + [:button.attributes-copy-button + {:on-click (copy-cb shape + :rotation + :to-prop "transform" + :format #(str/fmt "rotate(%sdeg)" %))} + i/copy]])]) + + +(mf/defc layout-panel + [{:keys [shapes locale]}] + (let [handle-copy (when (= (count shapes) 1) + (copy-layout (first shapes)))] + [:div.attributes-block + [:div.attributes-block-title + [:div.attributes-block-title-text (t locale "handoff.attributes.layout")] + (when handle-copy + [:button.attributes-copy-button + {:on-click handle-copy} + i/copy])] + + (for [shape shapes] + [:& layout-block {:shape shape + :locale locale}])])) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/shadow.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/shadow.cljs new file mode 100644 index 0000000000..05ab8d2dca --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/shadow.cljs @@ -0,0 +1,79 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.attributes.shadow + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [app.util.i18n :refer [t]] + [app.util.color :as uc] + [app.main.ui.icons :as i] + [app.main.ui.viewer.handoff.attributes.common :refer [copy-cb color-row]])) + +(defn has-shadow? [shape] + (:shadow shape)) + +(defn shadow->css [shadow] + (let [{:keys [style offset-x offset-y blur spread]} shadow + css-color (uc/color->background (:color shadow))] + (str + (if (= style :inner-shadow) "inset " "") + (str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color)))) + + +(mf/defc shadow-block [{:keys [shape locale shadow]}] + (let [color-format (mf/use-state :hex)] + [:div.attributes-shadow-block + [:div.attributes-shadow-row + [:div.attributes-label (->> shadow :style name (str "handoff.attributes.shadow.style.") (t locale))] + [:div.attributes-shadow + [:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.offset-x")] + [:div.attributes-value (str (:offset-x shadow))]] + + [:div.attributes-shadow + [:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.offset-y")] + [:div.attributes-value (str (:offset-y shadow))]] + + [:div.attributes-shadow + [:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.blur")] + [:div.attributes-value (str (:blur shadow))]] + + [:div.attributes-shadow + [:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.spread")] + [:div.attributes-value (str (:spread shadow))]] + + [:button.attributes-copy-button + {:on-click (copy-cb shadow + :style + :to-prop "box-shadow" + :format #(shadow->css shadow))} + i/copy]] + [:& color-row {:color (:color shadow) + :format @color-format + :on-change-format #(reset! color-format %)}]])) + +(mf/defc shadow-panel [{:keys [shapes locale]}] + (let [shapes (->> shapes (filter has-shadow?)) + handle-copy-shadow (when (= (count shapes) 1) + (copy-cb (first shapes) + :shadow + :to-prop "box-shadow" + :format #(str/join ", " (map shadow->css (:shadow (first shapes))))))] + (when (seq shapes) + [:div.attributes-block + [:div.attributes-block-title + [:div.attributes-block-title-text (t locale "handoff.attributes.shadow")] + (when handle-copy-shadow + [:button.attributes-copy-button {:on-click handle-copy-shadow} i/copy])] + + (for [shape shapes] + (for [shadow (:shadow shape)] + [:& shadow-block {:shape shape + :locale locale + :shadow shadow}]))]))) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs new file mode 100644 index 0000000000..6dc73f410d --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs @@ -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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.attributes.stroke + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [app.util.i18n :refer [t]] + [app.util.color :as uc] + [app.main.ui.icons :as i] + [app.main.ui.viewer.handoff.attributes.common :refer [copy-cb color-row]])) + +(defn shape->color [shape] + {:color (:stroke-color shape) + :opacity (:stroke-opacity shape) + :gradient (:stroke-color-gradient shape) + :id (:stroke-ref-id shape) + :file-id (:stroke-ref-file-id shape)}) + +(defn format-stroke [shape] + (let [width (:stroke-width shape) + style (name (:stroke-style shape)) + color (-> shape shape->color uc/color->background)] + (str/format "%spx %s %s" width style color))) + +(defn has-stroke? [shape] + (and (:stroke-style shape) + (not= (:stroke-style shape) :none))) + +(mf/defc stroke-block + [{:keys [shape locale]}] + (let [color-format (mf/use-state :hex) + color (shape->color shape) + handle-copy-stroke (copy-cb shape + :stroke-style + :to-prop "border" + :format #(format-stroke shape)) + + handle-copy-color (copy-cb shape + :stroke-color + :to-prop "border-color" + :format #(uc/color->background color))] + + [:* + [:& color-row {:color color + :format @color-format + :on-change-format #(reset! color-format %) + :on-copy handle-copy-color}] + + [:div.attributes-stroke-row + [:div.attributes-label (t locale "handoff.attributes.stroke.width")] + [:div.attributes-value (:stroke-width shape) "px"] + [:div.attributes-value (->> shape :stroke-style name (str "handoff.attributes.stroke.style.") (t locale))] + [:div.attributes-label (->> shape :stroke-alignment name (str "handoff.attributes.stroke.alignment.") (t locale))] + [:button.attributes-copy-button {:on-click handle-copy-stroke} i/copy]]])) + +(mf/defc stroke-panel + [{:keys [shapes locale]}] + (let [shapes (->> shapes (filter has-stroke?)) + handle-copy (when (= (count shapes) 1) + (copy-cb (first shapes) + :stroke-style + :to-prop "border" + :format #(format-stroke (first shapes))))] + + (when (seq shapes) + [:div.attributes-block + [:div.attributes-block-title + [:div.attributes-block-title-text (t locale "handoff.attributes.stroke")] + (when handle-copy + [:button.attributes-copy-button + {:on-click handle-copy} i/copy])] + + (for [shape shapes] + [:& stroke-block {:key (str "stroke-color-" (:id shape)) + :shape shape + :locale locale}])]))) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs new file mode 100644 index 0000000000..b5847cc14d --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs @@ -0,0 +1,164 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.attributes.text + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [app.util.data :as d] + [app.util.i18n :refer [t]] + [app.util.color :as uc] + [app.util.text :as ut] + [app.main.fonts :as fonts] + [app.main.ui.icons :as i] + [app.util.webapi :as wapi] + [app.main.ui.viewer.handoff.attributes.common :refer [copy-cb color-row]])) + +(defn has-text? [shape] + (:content shape)) + +(def properties [:fill-color + :fill-color-gradient + :font-family + :font-style + :font-size + :line-height + :letter-spacing + :text-decoration + :text-transform]) + +(defn shape->color [shape] + {:color (:fill-color shape) + :opacity (:fill-opacity shape) + :gradient (:fill-color-gradient shape) + :id (:fill-ref-id shape) + :file-id (:fill-ref-file-id shape)}) + +(defn format-style [color] + {:font-family #(str "'" % "'") + :font-style #(str "'" % "'") + :font-size #(str % "px") + :line-height #(str % "px") + :letter-spacing #(str % "px") + :text-decoration name + :text-transform name + :fill-color #(uc/color->background color) + :fill-color-gradient #(uc/color->background color)}) + +(mf/defc typography-block [{:keys [shape locale text style full-style]}] + (let [color-format (mf/use-state :hex) + color (shape->color style) + to-prop {:fill-color "color" + :fill-color-gradient "color"}] + [:div.attributes-text-block + [:div.attributes-typography-row + [:div.typography-sample + {:style {:font-family (:font-family full-style) + :font-weight (:font-weight full-style) + :font-style (:font-style full-style)}} + (t locale "workspace.assets.typography.sample")] + [:button.attributes-copy-button + {:on-click (copy-cb style properties + :to-prop to-prop + :format (format-style color))} + i/copy]] + + [:div.attributes-content-row + [:pre.attributes-content (str/trim text)] + [:button.attributes-copy-button + {:on-click #(wapi/write-to-clipboard (str/trim text))} + i/copy]] + + (when (or (:fill-color style) (:fill-color-gradient style)) + [:& color-row {:format @color-format + :on-change-format #(reset! color-format %) + :color (shape->color style) + :on-copy (copy-cb style + [:fill-color :fill-color-gradient] + :to-prop to-prop + :format (format-style color))}]) + + (when (:font-id style) + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.typography.font-family")] + [:div.attributes-value (-> style :font-id fonts/get-font-data :name)] + [:button.attributes-copy-button {:on-click (copy-cb style :font-family :format identity)} i/copy]]) + + (when (:font-style style) + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.typography.font-style")] + [:div.attributes-value (str (:font-style style))] + [:button.attributes-copy-button {:on-click (copy-cb style :font-style :format identity)} i/copy]]) + + (when (:font-size style) + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.typography.font-size")] + [:div.attributes-value (str (:font-size style)) "px"] + [:button.attributes-copy-button {:on-click (copy-cb style :font-size :format #(str % "px"))} i/copy]]) + + (when (:line-height style) + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.typography.line-height")] + [:div.attributes-value (str (:line-height style)) "px"] + [:button.attributes-copy-button {:on-click (copy-cb style :line-height :format #(str % "px"))} i/copy]]) + + (when (:letter-spacing style) + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.typography.letter-spacing")] + [:div.attributes-value (str (:letter-spacing style)) "px"] + [:button.attributes-copy-button {:on-click (copy-cb style :letter-spacing :format #(str % "px"))} i/copy]]) + + (when (:text-decoration style) + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.typography.text-decoration")] + [:div.attributes-value (->> style :text-decoration (str "handoff.attributes.typography.text-decoration.") (t locale))] + [:button.attributes-copy-button {:on-click (copy-cb style :text-decoration :format name)} i/copy]]) + + (when (:text-transform style) + [:div.attributes-unit-row + [:div.attributes-label (t locale "handoff.attributes.typography.text-transform")] + [:div.attributes-value (->> style :text-transform (str "handoff.attributes.typography.text-transform.") (t locale))] + [:button.attributes-copy-button {:on-click (copy-cb style :text-transform :format name)} i/copy]])])) + + +(mf/defc text-block [{:keys [shape locale]}] + (let [font (ut/search-text-attrs (:content shape) + (keys ut/default-text-attrs)) + + style-text-blocks (->> (keys ut/default-text-attrs) + (ut/parse-style-text-blocks (:content shape)) + (remove (fn [[style text]] (str/empty? (str/trim text)))) + (mapv (fn [[style text]] (vector (merge ut/default-text-attrs style) text)))) + + font (merge ut/default-text-attrs font)] + (for [[idx [full-style text]] (map-indexed vector style-text-blocks)] + (let [previus-style (first (nth style-text-blocks (dec idx) nil)) + style (d/remove-equal-values full-style previus-style) + + ;; If the color is set we need to add opacity otherwise the display will not work + style (cond-> style + (:fill-color style) + (assoc :fill-opacity (:fill-opacity full-style)))] + [:& typography-block {:shape shape + :locale locale + :full-style full-style + :style style + :text text}])))) + +(mf/defc text-panel [{:keys [shapes locale]}] + (let [shapes (->> shapes (filter has-text?))] + (when (seq shapes) + [:div.attributes-block + [:div.attributes-block-title + [:div.attributes-block-title-text (t locale "handoff.attributes.typography")]] + + (for [shape shapes] + [:& text-block {:shape shape + :locale locale}])]))) + diff --git a/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs new file mode 100644 index 0000000000..0c2a4ef829 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs @@ -0,0 +1,114 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.left-sidebar + (:require + [rumext.alpha :as mf] + [okulary.core :as l] + [app.common.data :as d] + [app.common.uuid :as uuid] + [app.main.store :as st] + [app.util.dom :as dom] + [app.main.data.viewer :as dv] + [app.main.ui.icons :as i] + [app.main.ui.keyboard :as kbd] + [app.main.ui.workspace.sidebar.layers :refer [element-icon layer-name frame-wrapper]])) + +(def selected-shapes + (l/derived (comp :selected :viewer-local) st/state)) + +(def page-ref + (l/derived (comp :page :viewer-data) st/state)) + +(defn- make-collapsed-iref + [id] + #(-> (l/in [:viewer-local :collapsed id]) + (l/derived st/state) )) + +(mf/defc layer-item + [{:keys [index item selected objects disable-collapse?] :as props}] + (let [id (:id item) + selected? (contains? selected id) + item-ref (mf/use-ref nil) + collapsed-iref (mf/use-memo + (mf/deps id) + (make-collapsed-iref id)) + + expanded? (not (mf/deref collapsed-iref)) + + toggle-collapse + (fn [event] + (dom/stop-propagation event) + (st/emit! (dv/toggle-collapse id))) + + select-shape + (fn [event] + (dom/prevent-default event) + (let [id (:id item)] + (cond + (.-ctrlKey event) + (st/emit! (dv/toggle-selection id)) + + (.-shiftKey event) + (st/emit! (dv/shift-select-to id)) + + :else + (st/emit! (dv/select-shape id))) + )) + ] + + (mf/use-effect + (mf/deps selected) + (fn [] + (when (and (= (count selected) 1) selected?) + (.scrollIntoView (mf/ref-val item-ref) false)))) + + [:li {:ref item-ref + :class (dom/classnames + :component (not (nil? (:component-id item))) + :masked (:masked-group? item) + :selected selected?)} + + [:div.element-list-body {:class (dom/classnames :selected selected? + :icon-layer (= (:type item) :icon)) + :on-click select-shape} + [:& element-icon {:shape item}] + [:& layer-name {:shape item}] + + (when (and (not disable-collapse?) (:shapes item)) + [:span.toggle-content + {:on-click toggle-collapse + :class (when expanded? "inverse")} + i/arrow-slide])] + + (when (and (:shapes item) expanded?) + [:ul.element-children + (for [[index id] (reverse (d/enumerate (:shapes item)))] + (when-let [item (get objects id)] + [:& layer-item + {:item item + :selected selected + :index index + :objects objects + :key (:id item)}]))])])) + +(mf/defc left-sidebar [{:keys [frame]}] + (let [page (mf/deref page-ref) + selected (mf/deref selected-shapes) + objects (:objects page)] + + [:aside.settings-bar.settings-bar-left + [:div.settings-bar-inside + [:ul.element-list + [:& layer-item + {:item frame + :selected selected + :index 0 + :objects objects + :disable-collapse? true}]]]])) diff --git a/frontend/src/app/main/ui/viewer/handoff/render.cljs b/frontend/src/app/main/ui/viewer/handoff/render.cljs new file mode 100644 index 0000000000..ab760080f0 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/render.cljs @@ -0,0 +1,177 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.render + "The main container for a frame in handoff mode" + (:require + [rumext.alpha :as mf] + [app.util.object :as obj] + [app.util.dom :as dom] + [app.common.data :as d] + [app.common.pages :as cp] + [app.common.pages-helpers :as cph] + [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as geom] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.data.viewer :as dv] + [app.main.ui.shapes.filters :as filters] + [app.main.ui.shapes.circle :as circle] + [app.main.ui.shapes.frame :as frame] + [app.main.ui.shapes.group :as group] + [app.main.ui.shapes.icon :as icon] + [app.main.ui.shapes.image :as image] + [app.main.ui.shapes.path :as path] + [app.main.ui.shapes.rect :as rect] + [app.main.ui.shapes.text :as text] + [app.main.ui.viewer.handoff.selection-feedback :refer [selection-feedback]] + [app.main.ui.shapes.shape :refer [shape-container]])) + +(declare shape-container-factory) + +(defn handle-hover-shape [{:keys [type id]} hover?] + #(when-not (#{:group :frame} type) + (do + (dom/prevent-default %) + (dom/stop-propagation %) + (st/emit! (dv/hover-shape id hover?))))) + +(defn select-shape [{:keys [type id]}] + (fn [event] + (when-not (#{:group :frame} type) + (do + (dom/stop-propagation event) + (dom/prevent-default event) + (cond + (.-shiftKey event) + (st/emit! (dv/toggle-selection id)) + + :else + (st/emit! (dv/select-shape id))))))) + +(defn shape-wrapper-factory + [component] + (mf/fnc shape-wrapper + {::mf/wrap-props false} + [props] + (let [shape (unchecked-get props "shape") + childs (unchecked-get props "childs") + frame (unchecked-get props "frame")] + + [:> shape-container {:shape shape + :on-mouse-enter (handle-hover-shape shape true) + :on-mouse-leave (handle-hover-shape shape false) + :on-click (select-shape shape)} + [:& component {:shape shape + :frame frame + :childs childs + :is-child-selected? true}]]))) + +(defn frame-container-factory + [objects] + (let [shape-container (shape-container-factory objects) + frame-shape (frame/frame-shape shape-container) + frame-wrapper (shape-wrapper-factory frame-shape)] + (mf/fnc frame-container + {::mf/wrap-props false} + [props] + (let [shape (unchecked-get props "shape") + childs (mapv #(get objects %) (:shapes shape)) + shape (geom/transform-shape shape) + + props (-> (obj/new) + (obj/merge! props) + (obj/merge! #js {:shape shape + :childs childs}))] + [:> frame-wrapper props])))) + +(defn group-container-factory + [objects] + (let [shape-container (shape-container-factory objects) + group-shape (group/group-shape shape-container) + group-wrapper (shape-wrapper-factory group-shape)] + (mf/fnc group-container + {::mf/wrap-props false} + [props] + (let [shape (unchecked-get props "shape") + childs (mapv #(get objects %) (:shapes shape)) + props (-> (obj/new) + (obj/merge! props) + (obj/merge! #js {:childs childs}))] + [:> group-wrapper props])))) + +(defn shape-container-factory + [objects show-interactions?] + (let [path-wrapper (shape-wrapper-factory path/path-shape) + text-wrapper (shape-wrapper-factory text/text-shape) + icon-wrapper (shape-wrapper-factory icon/icon-shape) + rect-wrapper (shape-wrapper-factory rect/rect-shape) + image-wrapper (shape-wrapper-factory image/image-shape) + circle-wrapper (shape-wrapper-factory circle/circle-shape)] + (mf/fnc shape-container + {::mf/wrap-props false} + [props] + (let [shape (unchecked-get props "shape") + frame (unchecked-get props "frame") + group-container (mf/use-memo + (mf/deps objects) + #(group-container-factory objects))] + (when (and shape (not (:hidden shape))) + (let [shape (geom/transform-shape frame shape) + opts #js {:shape shape + :frame frame}] + (case (:type shape) + :curve [:> path-wrapper opts] + :text [:> text-wrapper opts] + :icon [:> icon-wrapper opts] + :rect [:> rect-wrapper opts] + :path [:> path-wrapper opts] + :image [:> image-wrapper opts] + :circle [:> circle-wrapper opts] + :group [:> group-container opts]))))))) + +(defn adjust-frame-position [frame-id objects] + (let [frame (get objects frame-id) + modifier (-> (gpt/point (:x frame) (:y frame)) + (gpt/negate) + (gmt/translate-matrix)) + + update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) + modifier-ids (d/concat [frame-id] (cph/get-children frame-id objects))] + (reduce update-fn objects modifier-ids))) + +(defn make-vbox [frame] + (str "0 0 " (:width frame 0) " " (:height frame 0))) + +(mf/defc render-frame-svg + {::mf/wrap [mf/memo]} + [{:keys [objects frame-id zoom] :or {zoom 1} :as props}] + + (let [objects (adjust-frame-position frame-id objects) + frame (get objects frame-id) + width (* (:width frame) zoom) + height (* (:height frame) zoom) + vbox (make-vbox frame) + render-frame (mf/use-memo + (mf/deps objects) + #(frame-container-factory objects))] + + [:svg {:view-box vbox + :width width + :height height + :version "1.1" + :xmlnsXlink "http://www.w3.org/1999/xlink" + :xmlns "http://www.w3.org/2000/svg"} + + [:& render-frame {:shape frame + :view-box vbox}] + + [:& selection-feedback {:frame frame}]])) + diff --git a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs new file mode 100644 index 0000000000..3a97ff6b2f --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs @@ -0,0 +1,79 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.right-sidebar + (:require + [rumext.alpha :as mf] + [okulary.core :as l] + [app.util.i18n :refer [t] :as i18n] + [app.main.store :as st] + [app.main.ui.icons :as i] + [app.main.ui.components.tab-container :refer [tab-container tab-element]] + [app.main.ui.viewer.handoff.attributes :refer [attributes]] + [app.main.ui.workspace.sidebar.layers :refer [element-icon]])) + +(defn make-selected-shapes-iref + [] + (let [selected->shapes + (fn [state] + (let [selected (get-in state [:viewer-local :selected]) + objects (get-in state [:viewer-data :page :objects]) + resolve-shape #(get objects %)] + (mapv resolve-shape selected)))] + #(l/derived selected->shapes st/state))) + +(mf/defc attributes-panel [{:keys [frame shapes]}] + (let [type (if (= (count shapes) 1) + (-> shapes first :type) + :multiple)] + (let [options (case type + :multiple [:fill :stroke :image :text :shadow :blur] + :frame [:layout :fill] + :group [:layout] + :rect [:layout :fill :stroke :shadow :blur] + :circle [:layout :fill :stroke :shadow :blur] + :path [:layout :fill :stroke :shadow :blur] + :curve [:layout :fill :stroke :shadow :blur] + :image [:image :layout :shadow :blur] + :text [:layout :text :shadow :blur])] + [:& attributes {:frame frame + :shapes shapes + :options options}]))) + +(mf/defc code-panel [] + [:div.element-options]) + +(mf/defc right-sidebar [{:keys [frame]}] + (let [locale (mf/deref i18n/locale) + section (mf/use-state :info #_:code) + selected-ref (mf/use-memo (make-selected-shapes-iref)) + shapes (mf/deref selected-ref)] + [:aside.settings-bar.settings-bar-right + [:div.settings-bar-inside + (when (seq shapes) + [:div.tool-window + [:div.tool-window-bar.big + (if (> (count shapes) 1) + [:* + [:span.tool-window-bar-icon i/layers] + [:span.tool-window-bar-title (t locale "handoff.tabs.code.selected.multiple" (count shapes))]] + [:* + [:span.tool-window-bar-icon + [:& element-icon {:shape (-> shapes first)}]] + [:span.tool-window-bar-title (->> shapes first :type name (str "handoff.tabs.code.selected.") (t locale))]]) + ] + [:div.tool-window-content + [:& tab-container {:on-change-tab #(reset! section %) + :selected @section} + [:& tab-element {:id :info :title (t locale "handoff.tabs.info")} + [:& attributes-panel {:frame frame + :shapes shapes}]] + + [:& tab-element {:id :code :title (t locale "handoff.tabs.code")} + [:& code-panel]]]]])]])) diff --git a/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs new file mode 100644 index 0000000000..9b362a1f34 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs @@ -0,0 +1,66 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.viewer.handoff.selection-feedback + (:require + [rumext.alpha :as mf] + [okulary.core :as l] + [app.common.geom.shapes :as gsh] + [app.main.store :as st])) + +(def selection-rect-color-normal "#1FDEA7") +(def selection-rect-color-component "#00E0FF") +(def selection-rect-width 1) + +(defn make-selected-shapes-iref + [] + (let [selected->shapes + (fn [state] + (let [selected (get-in state [:viewer-local :selected]) + objects (get-in state [:viewer-data :page :objects]) + resolve-shape #(get objects %)] + (mapv resolve-shape selected)))] + #(l/derived selected->shapes st/state))) + +(defn make-hover-shapes-iref + [] + (let [hover->shapes + (fn [state] + (let [hover (get-in state [:viewer-local :hover]) + objects (get-in state [:viewer-data :page :objects]) + resolve-shape #(get objects %)] + (mapv resolve-shape hover)))] + #(l/derived hover->shapes st/state))) + +(mf/defc selection-rect [{:keys [shape]}] + (let [{:keys [x y width height]} (:selrect shape)] + [:rect {:x x + :y y + :width width + :height height + :fill "transparent" + :stroke selection-rect-color-normal + :stroke-width selection-rect-width + :pointer-events "none"}])) + +(mf/defc selection-feedback [{:keys [frame]}] + (let [hover-shapes-ref (mf/use-memo (make-hover-shapes-iref)) + hover-shapes (->> (mf/deref hover-shapes-ref) + (map #(gsh/translate-to-frame % frame))) + + selected-shapes-ref (mf/use-memo (make-selected-shapes-iref)) + selected-shapes (->> (mf/deref selected-shapes-ref) + (map #(gsh/translate-to-frame % frame)))] + + [:* + (for [shape hover-shapes] + [:& selection-rect {:shape shape}]) + + (for [shape selected-shapes] + [:& selection-rect {:shape shape}])])) diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 44fddb5289..4992e4b958 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -122,7 +122,7 @@ (t locale "viewer.header.share.create-link")])]]]])) (mf/defc header - [{:keys [data index local fullscreen? toggle-fullscreen] :as props}] + [{:keys [data index local fullscreen? toggle-fullscreen screen] :as props}] (let [{:keys [project file page frames]} data total (count frames) on-click #(st/emit! dv/toggle-thumbnails-panel) @@ -141,7 +141,14 @@ on-edit #(st/emit! (rt/nav :workspace {:project-id project-id :file-id file-id} - {:page-id page-id}))] + {:page-id page-id})) + + change-screen + (fn [screen] + (st/emit! + (rt/nav screen + {:file-id file-id :page-id page-id} + {:index index})))] [:header.viewer-header [:div.main-icon [:a {:on-click on-edit} i/logo-icon]] @@ -156,6 +163,14 @@ [:span.dropdown-button i/arrow-down] [:span.counters (str (inc index) " / " total)]] + [:div.mode-zone + [:button.mode-zone-button {:on-click #(when (not= screen :viewer) + (change-screen :viewer)) + :class (when (= screen :viewer) "active")} i/play] + [:button.mode-zone-button {:on-click #(when (not= screen :handoff) + (change-screen :handoff)) + :class (when (= screen :handoff) "active")} i/code]] + [:div.options-zone [:& interactions-menu {:interactions-mode interactions-mode}] diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index 2ed021856c..7ceb6166c8 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -29,7 +29,8 @@ [app.util.object :as obj] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] - [app.common.geom.shapes :as geom])) + [app.common.geom.shapes :as geom] + [app.main.ui.shapes.shape :refer [shape-container]])) (defn on-mouse-down [event {:keys [interactions] :as shape}] @@ -54,14 +55,11 @@ on-mouse-down (mf/use-callback (mf/deps shape) - #(on-mouse-down % shape)) + #(on-mouse-down % shape))] - filter-id (filters/get-filter-id)] - - [:g.shape {:on-mouse-down on-mouse-down - :cursor (when (:interactions shape) "pointer") - :filter (filters/filter-str filter-id shape)} - [:& filters/filters {:filter-id filter-id :shape shape}] + [:> shape-container {:shape shape + :on-mouse-down on-mouse-down + :cursor (when (:interactions shape) "pointer")} [:& component {:shape shape :frame frame :childs childs diff --git a/frontend/src/app/main/ui/viewer/thumbnails.cljs b/frontend/src/app/main/ui/viewer/thumbnails.cljs index dc41b197d1..57fb011522 100644 --- a/frontend/src/app/main/ui/viewer/thumbnails.cljs +++ b/frontend/src/app/main/ui/viewer/thumbnails.cljs @@ -94,7 +94,7 @@ [:span.name (:name frame)]]]) (mf/defc thumbnails-panel - [{:keys [data index] :as props}] + [{:keys [data index screen] :as props}] (let [expanded? (mf/use-state false) container (mf/use-ref) page-id (get-in data [:page :id]) @@ -111,7 +111,7 @@ on-item-click (fn [event index] (compare-and-set! selected false true) - (st/emit! (rt/nav :viewer {:file-id file-id + (st/emit! (rt/nav screen {:file-id file-id :page-id page-id} {:index index})) (when @expanded? (on-close)))] diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index e7ff4857f4..aa3cb8023e 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -19,93 +19,79 @@ [app.main.ui.hooks :as hooks] [app.main.ui.icons :as i] [app.main.ui.keyboard :as kbd] + [app.main.ui.context :as ctx] [app.main.ui.workspace.colorpalette :refer [colorpalette]] + [app.main.ui.workspace.colorpicker] [app.main.ui.workspace.context-menu :refer [context-menu]] [app.main.ui.workspace.header :refer [header]] [app.main.ui.workspace.left-toolbar :refer [left-toolbar]] + [app.main.ui.workspace.libraries] [app.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]] [app.main.ui.workspace.scroll :as scroll] [app.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]] [app.main.ui.workspace.viewport :refer [viewport coordinates]] [app.util.dom :as dom] [beicon.core :as rx] + [cuerdas.core :as str] [okulary.core :as l] [rumext.alpha :as mf])) ;; --- Workspace -(mf/defc workspace-content - [{:keys [page-id file layout project] :as params}] - (let [local (mf/deref refs/workspace-local) - left-sidebar? (:left-sidebar? local) - right-sidebar? (:right-sidebar? local) - classes (dom/classnames - :no-tool-bar-right (not right-sidebar?) - :no-tool-bar-left (not left-sidebar?))] +(mf/defc workspace-rules + {::mf/wrap-props false} + [props] + (let [local (unchecked-get props "local")] [:* - (when (:colorpalette layout) - [:& colorpalette {:left-sidebar? left-sidebar? - :team-id (:team-id project)}]) + [:div.empty-rule-square] + [:& horizontal-rule {:zoom (:zoom local) + :vbox (:vbox local) + :vport (:vport local)}] + [:& vertical-rule {:zoom (:zoom local 1) + :vbox (:vbox local) + :vport (:vport local)}] + [:& coordinates]])) - [:section.workspace-content {:class classes} - [:section.workspace-viewport - (when (contains? layout :rules) - [:* - [:div.empty-rule-square] - [:& horizontal-rule {:zoom (:zoom local) - :vbox (:vbox local) - :vport (:vport local)}] - [:& vertical-rule {:zoom (:zoom local 1) - :vbox (:vbox local) - :vport (:vport local)}] - [:& coordinates]]) +(mf/defc workspace-content + [{:keys [file layout local] :as params}] + [:* + ;; TODO: left-sidebar option is obsolete because left-sidebar now + ;; is always visible. + (when (:colorpalette layout) + [:& colorpalette {:left-sidebar? true}]) - [:& viewport {:page-id page-id - :key (str page-id) - :file file - :local local - :layout layout}]]] + [:section.workspace-content + [:section.workspace-viewport + (when (contains? layout :rules) + [:& workspace-rules {:local local}]) - [:& left-toolbar {:layout layout}] + [:& viewport {:file file + :local local + :layout layout}]]] - ;; Aside - (when left-sidebar? - [:& left-sidebar - {:file file - :page-id page-id - :project project - :layout layout}]) - (when right-sidebar? - [:& right-sidebar - {:page-id page-id - :file-id (:id file) - :local local - :layout layout}])])) + [:& left-toolbar {:layout layout}] -(defn trimmed-page-ref - [id] - (l/derived (fn [state] - (let [page-id (:current-page-id state) - data (:workspace-data state)] - (select-keys (get-in data [:pages-index page-id]) [:id :name]))) - st/state =)) + ;; Aside + [:& left-sidebar {:layout layout}] + [:& right-sidebar {:local local}]]) + +(def trimmed-page-ref (l/derived :trimmed-page st/state =)) (mf/defc workspace-page - [{:keys [project file layout page-id] :as props}] - (mf/use-effect - (mf/deps page-id) - (fn [] - (st/emit! (dw/initialize-page page-id)) - #(st/emit! (dw/finalize-page page-id)))) + [{:keys [file layout page-id] :as props}] + (let [local (mf/deref refs/workspace-local) + page (mf/deref trimmed-page-ref)] + (mf/use-layout-effect + (mf/deps page-id) + (fn [] + (st/emit! (dw/initialize-page page-id)) + (st/emitf (dw/finalize-page page-id)))) - (let [page-ref (mf/use-memo (mf/deps page-id) #(trimmed-page-ref page-id)) - page (mf/deref page-ref)] (when page - [:& workspace-content {:page page - :page-id (:id page) - :project project + [:& workspace-content {:key page-id :file file - :layout layout}]))) + :layout layout + :local local}]))) (mf/defc workspace-loader [] @@ -120,7 +106,7 @@ (mf/deps project-id file-id) (fn [] (st/emit! (dw/initialize-file project-id file-id)) - #(st/emit! (dw/finalize-file project-id file-id)))) + (st/emitf (dw/finalize-file project-id file-id)))) (hooks/use-shortcuts dw/shortcuts) @@ -128,19 +114,21 @@ project (mf/deref refs/workspace-project) layout (mf/deref refs/workspace-layout)] - [:section#workspace - [:& header {:file file - :page-id page-id - :project project - :layout layout}] + [:& (mf/provider ctx/current-file-id) {:value (:id file)} + [:& (mf/provider ctx/current-team-id) {:value (:team-id project)} + [:& (mf/provider ctx/current-project-id) {:value (:id project)} + [:& (mf/provider ctx/current-page-id) {:value page-id} - [:& context-menu] + [:section#workspace + [:& header {:file file + :page-id page-id + :project project + :layout layout}] - (if (and (and file project) - (:initialized file)) + [:& context-menu] + + (if (and (and file project) + (:initialized file)) + [:& workspace-page {:page-id page-id :file file :layout layout}] + [:& workspace-loader])]]]]])) - [:& workspace-page {:page-id page-id - :project project - :file file - :layout layout}] - [:& workspace-loader])])) diff --git a/frontend/src/app/main/ui/workspace/colorpalette.cljs b/frontend/src/app/main/ui/workspace/colorpalette.cljs index bfda8cd1b3..7d3f37862d 100644 --- a/frontend/src/app/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/app/main/ui/workspace/colorpalette.cljs @@ -9,23 +9,25 @@ (ns app.main.ui.workspace.colorpalette (:require - [beicon.core :as rx] - [goog.events :as events] - [okulary.core :as l] - [rumext.alpha :as mf] - [cuerdas.core :as str] [app.common.math :as mth] [app.main.data.colors :as mdc] [app.main.data.workspace :as udw] + [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.context :as ctx] [app.main.ui.icons :as i] [app.main.ui.keyboard :as kbd] [app.util.color :refer [hex->rgb]] [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [t]] [app.util.object :as obj] - [app.main.refs :as refs] - [app.util.i18n :as i18n :refer [t]])) + [beicon.core :as rx] + [cuerdas.core :as str] + [goog.events :as events] + [okulary.core :as l] + [rumext.alpha :as mf])) ;; --- Refs @@ -49,20 +51,17 @@ ;; --- Components (mf/defc palette-item [{:keys [color size local?]}] - (let [id (:id color) - file-id (:file-id color) - select-color + (let [select-color (fn [event] (let [ids (get-in @st/state [:workspace-local :selected])] (if (kbd/shift? event) - (st/emit! (mdc/change-stroke ids (:value color) id file-id)) - (st/emit! (mdc/change-fill ids (:value color) id file-id)))))] + (st/emit! (mdc/change-stroke ids color)) + (st/emit! (mdc/change-fill ids color)))))] [:div.color-cell {:class (str "cell-"(name size)) - :key (or (str (:id color)) (:value color)) :on-click select-color} - [:span.color {:style {:background (:value color)}}] - (when (= size :big) [:span.color-text {:title (:name color) } (or (:name color) (:value color))])])) + [:& cb/color-bullet {:color color}] + [:& cb/color-name {:color color :size size}]])) (mf/defc palette [{:keys [left-sidebar? current-colors recent-colors file-colors shared-libs selected size]}] @@ -138,9 +137,9 @@ (when (= selected (:id cur-library)) i/tick) [:div.library-name (str (:name cur-library) " " (str/format "(%s)" (count colors)))] [:div.color-sample - (for [[idx {:keys [id value]}] (map-indexed vector (take 7 colors))] - [:div.color-bullet {:key (str "color-" idx) - :style {:background-color value}}])]])) + (for [[idx {:keys [id color]}] (map-indexed vector (take 7 colors))] + [:& cb/color-bullet {:key (str "color-" idx) + :color color}])]])) [:li.palette-library @@ -149,9 +148,9 @@ [:div.library-name (str (t locale "workspace.libraries.colors.file-library") (str/format " (%s)" (count file-colors)))] [:div.color-sample - (for [[idx {:keys [value]}] (map-indexed vector (take 7 (vals file-colors))) ] - [:div.color-bullet {:key (str "color-" idx) - :style {:background-color value}}])]] + (for [[idx color] (map-indexed vector (take 7 (vals file-colors))) ] + [:& cb/color-bullet {:key (str "color-" idx) + :color color}])]] [:li.palette-library {:on-click #(st/emit! (mdc/change-palette-selected :recent))} @@ -159,9 +158,9 @@ [:div.library-name (str (t locale "workspace.libraries.colors.recent-colors") (str/format " (%s)" (count recent-colors)))] [:div.color-sample - (for [[idx value] (map-indexed vector (take 7 (reverse recent-colors))) ] - [:div.color-bullet {:key (str "color-" idx) - :style {:background-color value}}])]] + (for [[idx color] (map-indexed vector (take 7 (reverse recent-colors))) ] + [:& cb/color-bullet {:key (str "color-" idx) + :color color}])]] [:hr.dropdown-separator] @@ -191,19 +190,16 @@ [:span.right-arrow {:on-click on-right-arrow-click} i/arrow-slide]])) -(defn recent->colors [recent-colors] - (map #(hash-map :value %) (reverse (or recent-colors [])))) - -(defn file->colors [file-colors] - (map #(select-keys % [:id :value :name]) (vals file-colors))) - (defn library->colors [shared-libs selected] - (map #(merge {:file-id selected} (select-keys % [:id :value :name])) - (vals (get-in shared-libs [selected :data :colors])))) + (map #(merge % {:file-id selected}) + (-> shared-libs + (get-in [selected :data :colors]) + (vals)))) (mf/defc colorpalette - [{:keys [left-sidebar? team-id]}] - (let [recent-colors (mf/deref refs/workspace-recent-colors) + [{:keys [left-sidebar?]}] + (let [team-id (mf/use-ctx ctx/current-team-id) + 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) @@ -217,21 +213,21 @@ (reset! current-library-colors (into [] (cond - (= selected :recent) (recent->colors recent-colors) - (= selected :file) (file->colors file-colors) + (= selected :recent) (reverse recent-colors) + (= selected :file) (vals file-colors) :else (library->colors shared-libs selected)))))) (mf/use-effect (mf/deps recent-colors) (fn [] (when (= selected :recent) - (reset! current-library-colors (into [] (recent->colors recent-colors)))))) + (reset! current-library-colors (reverse recent-colors))))) (mf/use-effect (mf/deps file-colors) (fn [] (when (= selected :file) - (reset! current-library-colors (into [] (file->colors file-colors)))))) + (reset! current-library-colors (into [] (vals file-colors)))))) [:& palette {:left-sidebar? left-sidebar? :current-colors @current-library-colors diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index a92ed60639..c0ff40544f 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -10,19 +10,27 @@ (ns app.main.ui.workspace.colorpicker (:require [rumext.alpha :as mf] - [app.main.store :as st] + [okulary.core :as l] [cuerdas.core :as str] - [app.util.dom :as dom] - [app.util.color :as uc] - [app.main.ui.icons :as i] + [app.common.geom.point :as gpt] [app.common.math :as math] [app.common.uuid :refer [uuid]] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.colors :as dwc] - [app.main.ui.modal :as modal] - [okulary.core :as l] + [app.util.dom :as dom] + [app.util.color :as uc] + [app.util.object :as obj] + [app.main.store :as st] [app.main.refs :as refs] - [app.util.i18n :as i18n :refer [t]])) + [app.main.data.workspace.libraries :as dwl] + [app.main.data.colors :as dc] + [app.main.data.modal :as modal] + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [t]] + [app.main.ui.workspace.colorpicker.gradients :refer [gradients]] + [app.main.ui.workspace.colorpicker.harmony :refer [harmony-selector]] + [app.main.ui.workspace.colorpicker.hsva :refer [hsva-selector]] + [app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]] + [app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]] + [app.main.ui.workspace.colorpicker.libraries :refer [libraries]])) ;; --- Refs @@ -41,66 +49,15 @@ (def viewport (l/derived (l/in [:workspace-local :vport]) st/state)) +(def editing-spot-state-ref + (l/derived (l/in [:workspace-local :editing-stop]) st/state)) + +(def current-gradient-ref + (l/derived (l/in [:workspace-local :current-gradient]) st/state)) ;; --- Color Picker Modal -(mf/defc value-selector [{:keys [hue saturation value on-change]}] - (let [dragging? (mf/use-state false) - calculate-pos - (fn [ev] - (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) - {:keys [x y]} (-> ev dom/get-client-position) - px (math/clamp (/ (- x left) (- right left)) 0 1) - py (* 255 (- 1 (math/clamp (/ (- y top) (- bottom top)) 0 1)))] - (on-change px py)))] - [:div.value-selector - {:on-mouse-down #(reset! dragging? true) - :on-mouse-up #(reset! dragging? false) - :on-pointer-down (partial dom/capture-pointer) - :on-pointer-up (partial dom/release-pointer) - :on-click calculate-pos - :on-mouse-move #(when @dragging? (calculate-pos %))} - [:div.handler {:style {:pointer-events "none" - :left (str (* 100 saturation) "%") - :top (str (* 100 (- 1 (/ value 255))) "%")}}]])) - -(mf/defc hue-selector [{:keys [hue on-change]}] - (let [dragging? (mf/use-state false) - calculate-pos - (fn [ev] - (let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect) - {:keys [x]} (-> ev dom/get-client-position) - px (math/clamp (/ (- x left) (- right left)) 0 1)] - (on-change (* px 360))))] - [:div.hue-selector - {:on-mouse-down #(reset! dragging? true) - :on-mouse-up #(reset! dragging? false) - :on-pointer-down (partial dom/capture-pointer) - :on-pointer-up (partial dom/release-pointer) - :on-click calculate-pos - :on-mouse-move #(when @dragging? (calculate-pos %))} - [:div.handler {:style {:pointer-events "none" - :left (str (* (/ hue 360) 100) "%")}}]])) - -(mf/defc opacity-selector [{:keys [opacity on-change]}] - (let [dragging? (mf/use-state false) - calculate-pos - (fn [ev] - (let [{:keys [left right]} (-> ev dom/get-target dom/get-bounding-rect) - {:keys [x]} (-> ev dom/get-client-position) - px (math/clamp (/ (- x left) (- right left)) 0 1)] - (on-change px)))] - [:div.opacity-selector - {:on-mouse-down #(reset! dragging? true) - :on-mouse-up #(reset! dragging? false) - :on-pointer-down (partial dom/capture-pointer) - :on-pointer-up (partial dom/release-pointer) - :on-click calculate-pos - :on-mouse-move #(when @dragging? (calculate-pos %))} - [:div.handler {:style {:pointer-events "none" - :left (str (* opacity 100) "%")}}]])) - -(defn as-color-components [value opacity] +(defn color->components [value opacity] (let [value (if (uc/hex? value) value "#000000") [r g b] (uc/hex->rgb value) [h s v] (uc/hex->hsv value)] @@ -108,270 +65,278 @@ {:hex (or value "000000") :alpha (or opacity 1) :r r :g g :b b - :h h :s s :v v} - )) + :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 [value opacity on-change on-accept]}] - (let [current-color (mf/use-state (as-color-components value opacity)) - selected-library (mf/use-state "recent") - current-library-colors (mf/use-state []) + [{: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) + locale (mf/deref i18n/locale) + ref-picker (mf/use-ref) - file-colors (mf/deref refs/workspace-file-colors) - shared-libs (mf/deref refs/workspace-libraries) - recent-colors (mf/deref refs/workspace-recent-colors) + dirty? (mf/use-var false) + last-color (mf/use-var data) picking-color? (mf/deref picking-color?) picked-color (mf/deref picked-color) picked-color-select (mf/deref picked-color-select) picked-shift? (mf/deref picked-shift?) - locale (mf/deref i18n/locale) + editing-spot-state (mf/deref editing-spot-state-ref) + current-gradient (mf/deref current-gradient-ref) - value-ref (mf/use-var value) + current-color (:current-color @state) - on-change (or on-change identity) + change-tab + (fn [tab] + #(reset! active-tab tab)) - parse-selected (fn [selected] - (if (#{"recent" "file"} selected) - (keyword selected) - (uuid selected)) )] + handle-change-color + (fn [changes] + (let [editing-stop (:editing-stop @state)] + (swap! state #(cond-> % + true (update :current-color merge changes) + editing-stop (update-in [:stops editing-stop] merge changes))) + (reset! dirty? true))) - ;; Update state when there is a change in the props upstream - (mf/use-effect - (mf/deps value opacity) - (fn [] - (reset! current-color (as-color-components value opacity)))) + 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))))) + + handle-change-stop + (fn [offset] + (when-let [offset-color (get-in @state [:stops offset])] + (do (swap! state assoc + :current-color offset-color + :editing-stop offset) + + (st/emit! (dc/select-gradient-stop offset))))) + + on-select-library-color + (fn [color] + (reset! state (data->state color)) + (on-change color)) + + on-add-library-color + (fn [color] + (st/emit! (dwl/add-color (state->data @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))}))))))] ;; Updates the CSS color variable when there is a change in the color (mf/use-effect - (mf/deps @current-color) + (mf/deps current-color) (fn [] (let [node (mf/ref-val ref-picker) - rgb [(:r @current-color) (:g @current-color) (:b @current-color)] - hue-rgb (uc/hsv->rgb [(:h @current-color) 1.0 255])] + {:keys [r g b h s 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" (str/join ", " hue-rgb))))) - - ;; Load library colors when the select is changed - (mf/use-effect - (mf/deps @selected-library) - (fn [] - (let [mapped-colors - (cond - (= @selected-library "recent") - (map #(hash-map :value %) (reverse (or recent-colors []))) - - (= @selected-library "file") - (map #(select-keys % [:id :value]) (vals file-colors)) - - :else ;; Library UUID - (map #(merge {:file-id (uuid @selected-library)} (select-keys % [:id :value])) - (vals (get-in shared-libs [(uuid @selected-library) :data :colors]))))] - (reset! current-library-colors (into [] mapped-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 (map #(select-keys % [:id :value]) (vals file-colors))] - (reset! current-library-colors (into [] colors)))))) + (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 [] (fn [] - (st/emit! (dwc/stop-picker)) - (when @value-ref - (st/emit! (dwl/add-recent-color @value-ref)))))) - - (mf/use-effect - (mf/deps picking-color? picked-color) - (fn [] (when picking-color? - (let [[r g b] (or picked-color [0 0 0]) - hex (uc/rgb->hex [r g b]) - [h s v] (uc/hex->hsv hex)] - (swap! current-color assoc - :r r :g g :b b - :h h :s s :v v - :hex hex) - (reset! value-ref hex) - (when picked-color-select - (on-change hex (:alpha @current-color) nil nil picked-shift?)))))) + #(fn [] + (st/emit! (dc/stop-picker)) + (when @last-color + (st/emit! (dwl/add-recent-color @last-color))))) + ;; Updates color when used el pixel picker (mf/use-effect (mf/deps picking-color? picked-color-select) - (fn [] (when (and picking-color? picked-color-select) - (on-change (:hex @current-color) (:alpha @current-color) nil nil picked-shift?)))) + (fn [] + (when (and picking-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) + (do + (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 [] + (if @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.top-actions - [:button.picker-btn - {:class (when picking-color? "active") - :on-click (fn [] - (modal/allow-click-outside!) - (st/emit! (dwc/start-picker)))} - i/picker]] + [:div.colorpicker-content + [:div.top-actions + [:button.picker-btn + {:class (when picking-color? "active") + :on-click handle-click-picker} + i/picker] - (if picking-color? - [:div.picker-detail-wrapper - [:div.center-circle] - [:canvas#picker-detail {:width 200 - :height 160}]] - [:& value-selector {:hue (:h @current-color) - :saturation (:s @current-color) - :value (:v @current-color) - :on-change (fn [s v] - (let [hex (uc/hsv->hex [(:h @current-color) s v]) - [r g b] (uc/hex->rgb hex)] - (swap! current-color assoc - :hex hex - :r r :g g :b b - :s s :v v) - (reset! value-ref hex) - (on-change hex (:alpha @current-color))))}]) - (when (not picking-color?) - [:div.shade-selector - [:div.color-bullet] - [:& hue-selector {:hue (:h @current-color) - :on-change (fn [h] - (let [hex (uc/hsv->hex [h (:s @current-color) (:v @current-color)]) - [r g b] (uc/hex->rgb hex)] - (swap! current-color assoc - :hex hex - :r r :g g :b b - :h h ) - (reset! value-ref hex) - (on-change hex (:alpha @current-color))))}] - [:& opacity-selector {:opacity (:alpha @current-color) - :on-change (fn [alpha] - (swap! current-color assoc :alpha alpha) - (on-change (:hex @current-color) alpha))}]]) + (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")}] - [:div.color-values - [:input.hex-value {:id "hex-value" - :value (:hex @current-color) - :on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value) - val (if (= (first val) \#) val (str \# val))] - (swap! current-color assoc :hex val) - (when (uc/hex? val) - (reset! value-ref val) - (let [[r g b] (uc/hex->rgb val) - [h s v] (uc/hex->hsv val)] - (swap! current-color assoc - :r r :g g :b b - :h h :s s :v v) - (on-change val (:alpha @current-color))))))}] - [:input.red-value {:id "red-value" - :type "number" - :min 0 - :max 255 - :value (:r @current-color) - :on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))] - (swap! current-color assoc :r val) - (when (not (nil? val)) - (let [{:keys [g b]} @current-color - hex (uc/rgb->hex [val g b]) - [h s v] (uc/hex->hsv hex)] - (reset! value-ref hex) - (swap! current-color assoc - :hex hex - :h h :s s :v v) - (on-change hex (:alpha @current-color))))))}] - [:input.green-value {:id "green-value" - :type "number" - :min 0 - :max 255 - :value (:g @current-color) - :on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))] - (swap! current-color assoc :g val) - (when (not (nil? val)) - (let [{:keys [r b]} @current-color - hex (uc/rgb->hex [r val b]) - [h s v] (uc/hex->hsv hex)] - (reset! value-ref hex) - (swap! current-color assoc - :hex hex - :h h :s s :v v) - (on-change hex (:alpha @current-color))))))}] - [:input.blue-value {:id "blue-value" - :type "number" - :min 0 - :max 255 - :value (:b @current-color) - :on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value (math/clamp 0 255))] - (swap! current-color assoc :b val) - (when (not (nil? val)) - (let [{:keys [r g]} @current-color - hex (uc/rgb->hex [r g val]) - [h s v] (uc/hex->hsv hex)] - (reset! value-ref hex) - (swap! current-color assoc - :hex hex - :h h :s s :v v) - (on-change hex (:alpha @current-color))))))}] - [:input.alpha-value {:id "alpha-value" - :type "number" - :min 0 - :step 0.1 - :max 1 - :value (math/precision (:alpha @current-color) 2) - :on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value (math/clamp 0 1))] - (swap! current-color assoc :alpha val) - (on-change (:hex @current-color) val)))}] - [:label.hex-label {:for "hex-value"} "HEX"] - [:label.red-label {:for "red-value"} "R"] - [:label.green-label {:for "green-value"} "G"] - [:label.blue-label {:for "blue-value"} "B"] - [:label.alpha-label {:for "alpha-value"} "A"]] + [:button.gradient.radial-gradient + {:on-click (on-activate-gradient :radial-gradient) + :class (when (= :radial-gradient (:type @state)) "active")}]])] - [:div.libraries - [:select {:on-change (fn [e] - (let [val (-> e dom/get-target dom/get-value)] - (reset! selected-library val))) - :value @selected-library} - [:option {:value "recent"} (t locale "workspace.libraries.colors.recent-colors")] - [:option {:value "file"} (t locale "workspace.libraries.colors.file-library")] - (for [[_ {:keys [name id]}] shared-libs] - [:option {:key id - :value id} name])] + [:& gradients {:type (:type @state) + :stops (:stops @state) + :editing-stop (:editing-stop @state) + :on-select-stop handle-change-stop}] - [:div.selected-colors - (when (= "file" @selected-library) - [:div.color-bullet.button.plus-button {:style {:background-color "white"} - :on-click #(st/emit! (dwl/add-color (:hex @current-color)))} - i/plus]) + [:div.colorpicker-tabs + [:div.colorpicker-tab {:class (when (= @active-tab :ramp) "active") + :on-click (change-tab :ramp)} i/picker-ramp] + [:div.colorpicker-tab {:class (when (= @active-tab :harmony) "active") + :on-click (change-tab :harmony)} i/picker-harmony] + [:div.colorpicker-tab {:class (when (= @active-tab :hsva) "active") + :on-click (change-tab :hsva)} i/picker-hsv]] - [:div.color-bullet.button {:style {:background-color "white"} - :on-click #(st/emit! (dwc/show-palette (parse-selected @selected-library)))} - i/palette] + (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}] + :harmony [:& harmony-selector {:color current-color + :disable-opacity disable-opacity + :on-change handle-change-color}] + :hsva [:& hsva-selector {:color current-color + :disable-opacity disable-opacity + :on-change handle-change-color}] + nil)) - (for [[idx {:keys [id file-id value]}] (map-indexed vector @current-library-colors)] - [:div.color-bullet {:key (str "color-" idx) - :on-click (fn [] - (swap! current-color assoc :hex value) - (reset! value-ref value) - (let [[r g b] (uc/hex->rgb value) - [h s v] (uc/hex->hsv value)] - (swap! current-color assoc - :r r :g g :b b - :h h :s s :v v) - (on-change value (:alpha @current-color) id file-id))) - :style {:background-color value}}])] + [:& color-inputs {:type (if (= @active-tab :hsva) :hsv :rgb) + :disable-opacity disable-opacity + :color current-color + :on-change handle-change-color}] - ] - (when on-accept - [:div.actions - [:button.btn-primary.btn-large - {:on-click (fn [] - (on-accept @value-ref) - (modal/hide!))} - (t locale "workspace.libraries.colors.save-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)) + (modal/hide!))} + (t locale "workspace.libraries.colors.save-color")]])]])) (defn calculate-position "Calculates the style properties for the given coordinates and position" @@ -382,40 +347,41 @@ overflow-fix (max 0 (+ y (- 50) h (- vh)))] (cond (or (nil? x) (nil? y)) {:left "auto" :right "16rem" :top "4rem"} - (= position :left) {:left (str (- x 270) "px") + (= position :left) {:left (str (- x 250) "px") :top (str (- y 50 overflow-fix) "px")} - :else {:left (str (+ x 24) "px") - :top (str (- y 50 overflow-fix) "px")}))) + :else {:left (str (+ x 80) "px") + :top (str (- y 70 overflow-fix) "px")}))) (mf/defc colorpicker-modal {::mf/register modal/components ::mf/register-as :colorpicker} - [{:keys [x y default value opacity page on-change on-close disable-opacity position on-accept] :as props}] + [{:keys [x y default data page position + disable-gradient + disable-opacity + on-change on-close on-accept] :as props}] (let [vport (mf/deref viewport) dirty? (mf/use-var false) last-change (mf/use-var nil) position (or position :left) style (calculate-position vport position x y) - handle-change (fn [new-value new-opacity id file-id shift-clicked?] - (when (or (not= new-value value) (not= new-opacity opacity)) - (reset! dirty? true)) - (reset! last-change [new-value new-opacity id file-id]) + handle-change (fn [new-data shift-clicked?] + (reset! dirty? (not= data new-data)) + (reset! last-change new-data) (when on-change - (on-change new-value new-opacity id file-id shift-clicked?)))] + (on-change new-data)))] (mf/use-effect (fn [] - #(when (and @dirty? on-close) - (when-let [[value opacity id file-id] @last-change] - (on-close value opacity id file-id))))) + #(when (and @dirty? @last-change on-close) + (on-close @last-change)))) [:div.colorpicker-tooltip {:style (clj->js style)} - [:& colorpicker {:value (or value default) - :opacity (or opacity 1) + [:& colorpicker {:data data + :disable-gradient disable-gradient + :disable-opacity disable-opacity :on-change handle-change - :on-accept on-accept - :disable-opacity disable-opacity}]])) + :on-accept on-accept}]])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs new file mode 100644 index 0000000000..72a6341bb7 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs @@ -0,0 +1,175 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.colorpicker.color-inputs + (:require + [rumext.alpha :as mf] + [okulary.core :as l] + [cuerdas.core :as str] + [app.common.geom.point :as gpt] + [app.common.math :as math] + [app.common.uuid :refer [uuid]] + [app.util.dom :as dom] + [app.util.color :as uc] + [app.util.object :as obj] + [app.main.store :as st] + [app.main.refs :as refs] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.colors :as dc] + [app.main.data.modal :as modal] + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [t]])) + +(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) + :b (mf/use-ref nil) + :h (mf/use-ref nil) + :s (mf/use-ref nil) + :v (mf/use-ref nil) + :alpha (mf/use-ref nil)} + + on-change-hex + (fn [e] + (let [val (-> e dom/get-target-val parse-hex)] + (when (uc/hex? val) + (let [[r g b] (uc/hex->rgb val) + [h s v] (uc/hex->hsv hex)] + (on-change {:hex val + :h h :s s :v v + :r r :g g :b b}))))) + + on-change-property + (fn [property max-value] + (fn [e] + (let [val (-> e dom/get-target-val (math/clamp 0 max-value)) + val (if (#{:s} property) (/ val 100) val)] + (when (not (nil? val)) + (if (#{:r :g :b} property) + (let [{:keys [r g b]} (merge color (hash-map property val)) + hex (uc/rgb->hex [r g b]) + [h s v] (uc/hex->hsv hex)] + (on-change {:hex hex + :h h :s s :v v + :r r :g g :b b})) + + (let [{:keys [h s v]} (merge color (hash-map property val)) + hex (uc/hsv->hex [h s v]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :h h :s s :v v + :r r :g g :b b}))))))) + + on-change-opacity + (fn [e] + (when-let [new-alpha (-> e dom/get-target-val (math/clamp 0 100) (/ 100))] + (on-change {:alpha new-alpha})))] + + + ;; Updates the inputs values when a property is changed in the parent + (mf/use-effect + (mf/deps color type) + (fn [] + (doseq [ref-key (keys refs)] + (let [property-val (get color ref-key) + property-ref (get refs ref-key)] + (when (and property-val property-ref) + (when-let [node (mf/ref-val property-ref)] + (case ref-key + (:s :alpha) (dom/set-value! node (math/round (* property-val 100))) + :hex (dom/set-value! node property-val) + (dom/set-value! node (math/round property-val))))))))) + + [:div.color-values + {:class (when disable-opacity "disable-opacity")} + [:input {:id "hex-value" + :ref (:hex refs) + :default-value hex + :on-change on-change-hex}] + + (if (= type :rgb) + [:* + [:input {:id "red-value" + :ref (:r refs) + :type "number" + :min 0 + :max 255 + :default-value red + :on-change (on-change-property :r 255)}] + + [:input {:id "green-value" + :ref (:g refs) + :type "number" + :min 0 + :max 255 + :default-value green + :on-change (on-change-property :g 255)}] + + [:input {:id "blue-value" + :ref (:b refs) + :type "number" + :min 0 + :max 255 + :default-value blue + :on-change (on-change-property :b 255)}]] + [:* + [:input {:id "hue-value" + :ref (:h refs) + :type "number" + :min 0 + :max 360 + :default-value hue + :on-change (on-change-property :h 360)}] + + [:input {:id "saturation-value" + :ref (:s refs) + :type "number" + :min 0 + :max 100 + :step 1 + :default-value saturation + :on-change (on-change-property :s 100)}] + + [:input {:id "value-value" + :ref (:v refs) + :type "number" + :min 0 + :max 255 + :default-value value + :on-change (on-change-property :v 255)}]]) + + (when (not disable-opacity) + [:input.alpha-value {:id "alpha-value" + :ref (:alpha refs) + :type "number" + :min 0 + :step 1 + :max 100 + :default-value (if (= alpha :multiple) "" (math/precision alpha 2)) + :on-change on-change-opacity}]) + + [:label.hex-label {:for "hex-value"} "HEX"] + (if (= type :rgb) + [:* + [:label.red-label {:for "red-value"} "R"] + [:label.green-label {:for "green-value"} "G"] + [:label.blue-label {:for "blue-value"} "B"]] + [:* + [:label.red-label {:for "hue-value"} "H"] + [:label.green-label {:for "saturation-value"} "S"] + [:label.blue-label {:for "value-value"} "V"]]) + (when (not disable-opacity) + [:label.alpha-label {:for "alpha-value"} "A"])])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs new file mode 100644 index 0000000000..459274b99d --- /dev/null +++ b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs @@ -0,0 +1,54 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.colorpicker.gradients + (:require + [rumext.alpha :as mf] + [okulary.core :as l] + [cuerdas.core :as str] + [app.common.geom.point :as gpt] + [app.common.math :as math] + [app.common.uuid :refer [uuid]] + [app.util.dom :as dom] + [app.util.color :as uc] + [app.util.object :as obj] + [app.main.store :as st] + [app.main.refs :as refs] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.colors :as dc] + [app.main.data.modal :as modal] + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [t]])) + +(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) "%"))) + + gradient-css (str/join "," (map format-stop stops))] + (str/fmt "linear-gradient(90deg, %s)" 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)}}]] + + [: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) "%")}} + + (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)}}]])])]])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs new file mode 100644 index 0000000000..a17a510061 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs @@ -0,0 +1,155 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.colorpicker.harmony + (:require + [rumext.alpha :as mf] + [okulary.core :as l] + [cuerdas.core :as str] + [app.common.geom.point :as gpt] + [app.common.math :as math] + [app.common.uuid :refer [uuid]] + [app.util.dom :as dom] + [app.util.color :as uc] + [app.util.object :as obj] + [app.main.store :as st] + [app.main.refs :as refs] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.colors :as dc] + [app.main.data.modal :as modal] + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [t]] + [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]])) + + +(defn create-color-wheel + [canvas-node] + (let [ctx (.getContext canvas-node "2d") + width (obj/get canvas-node "width") + height (obj/get canvas-node "height") + radius (/ width 2) + cx (/ width 2) + cy (/ width 2) + step 0.2] + + (.clearRect ctx 0 0 width height) + + (doseq [degrees (range 0 360 step)] + (let [degrees-rad (math/radians degrees) + x (* radius (math/cos (- degrees-rad))) + y (* radius (math/sin (- degrees-rad)))] + (obj/set! ctx "strokeStyle" (str/format "hsl(%s, 100%, 50%)" degrees)) + (.beginPath ctx) + (.moveTo ctx cx cy) + (.lineTo ctx (+ cx x) (+ cy y)) + (.stroke ctx))) + + (let [grd (.createRadialGradient ctx cx cy 0 cx cx radius)] + (.addColorStop grd 0 "white") + (.addColorStop grd 1 "rgba(255, 255, 255, 0") + (obj/set! ctx "fillStyle" grd) + + (.beginPath ctx) + (.arc ctx cx cy radius 0 (* 2 math/PI) true) + (.closePath ctx) + (.fill ctx)))) + +(defn color->point + [canvas-side hue saturation] + (let [hue-rad (math/radians (- hue)) + comp-x (* saturation (math/cos hue-rad)) + comp-y (* saturation (math/sin hue-rad)) + x (+ (/ canvas-side 2) (* comp-x (/ canvas-side 2))) + y (+ (/ canvas-side 2) (* comp-y (/ canvas-side 2)))] + (gpt/point x y))) + +(mf/defc harmony-selector [{:keys [color disable-opacity on-change]}] + (let [canvas-ref (mf/use-ref nil) + {hue :h saturation :s value :v alpha :alpha} color + + canvas-side 152 + pos-current (color->point canvas-side hue saturation) + pos-complement (color->point canvas-side (mod (+ hue 180) 360) saturation) + dragging? (mf/use-state false) + + calculate-pos (fn [ev] + (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) + {:keys [x y]} (-> ev dom/get-client-position) + px (math/clamp (/ (- x left) (- right left)) 0 1) + py (math/clamp (/ (- y top) (- bottom top)) 0 1) + + px (- (* 2 px) 1) + py (- (* 2 py) 1) + + angle (math/degrees (math/atan2 px py)) + new-hue (math/precision (mod (- angle 90 ) 360) 2) + new-saturation (math/clamp (math/distance [px py] [0 0]) 0 1) + hex (uc/hsv->hex [new-hue new-saturation value]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :r r :g g :b b + :h new-hue + :s new-saturation}))) + + on-change-value (fn [new-value] + (let [hex (uc/hsv->hex [hue saturation new-value]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :r r :g g :b b + :v new-value}))) + on-complement-click (fn [ev] + (let [new-hue (mod (+ hue 180) 360) + hex (uc/hsv->hex [new-hue saturation value]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :r r :g g :b b + :h new-hue + :s saturation}))) + + on-change-opacity (fn [new-alpha] (on-change {:alpha new-alpha}))] + + (mf/use-effect + (mf/deps canvas-ref) + (fn [] (when canvas-ref + (create-color-wheel (mf/ref-val canvas-ref))))) + + [:div.harmony-selector + [:div.hue-wheel-wrapper + [:canvas.hue-wheel + {:ref canvas-ref + :width canvas-side + :height canvas-side + :on-mouse-down #(reset! dragging? true) + :on-mouse-up #(reset! dragging? false) + :on-pointer-down (partial dom/capture-pointer) + :on-pointer-up (partial dom/release-pointer) + :on-click calculate-pos + :on-mouse-move #(when @dragging? (calculate-pos %))}] + [:div.handler {:style {:pointer-events "none" + :left (:x pos-current) + :top (:y pos-current)}}] + [:div.handler.complement {:style {:left (:x pos-complement) + :top (:y pos-complement) + :cursor "pointer"} + :on-click on-complement-click}]] + [:div.handlers-wrapper + [:& slider-selector {:class "value" + :vertical? true + :reverse? true + :value value + :max-value 255 + :vertical true + :on-change on-change-value}] + (when (not disable-opacity) + [:& slider-selector {:class "opacity" + :vertical? true + :value alpha + :max-value 1 + :vertical true + :on-change on-change-opacity}])]])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs b/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs new file mode 100644 index 0000000000..5112f74732 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs @@ -0,0 +1,59 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.colorpicker.hsva + (:require + [rumext.alpha :as mf] + [okulary.core :as l] + [cuerdas.core :as str] + [app.common.geom.point :as gpt] + [app.common.math :as math] + [app.common.uuid :refer [uuid]] + [app.util.dom :as dom] + [app.util.color :as uc] + [app.util.object :as obj] + [app.main.store :as st] + [app.main.refs :as refs] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.colors :as dc] + [app.main.data.modal :as modal] + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [t]] + [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]])) + +(mf/defc hsva-selector [{:keys [color disable-opacity on-change]}] + (let [{hue :h saturation :s value :v alpha :alpha} color + handle-change-slider (fn [key] + (fn [new-value] + (let [change (hash-map key new-value) + {:keys [h s v]} (merge color change) + hex (uc/hsv->hex [h s v]) + [r g b] (uc/hex->rgb hex)] + (on-change (merge change + {:hex hex + :r r :g g :b b}))))) + on-change-opacity (fn [new-alpha] (on-change {:alpha new-alpha}))] + [:div.hsva-selector + [:span.hsva-selector-label "H"] + [:& slider-selector + {:class "hue" :max-value 360 :value hue :on-change (handle-change-slider :h)}] + + [:span.hsva-selector-label "S"] + [:& slider-selector + {:class "saturation" :max-value 1 :value saturation :on-change (handle-change-slider :s)}] + + [:span.hsva-selector-label "V"] + [:& slider-selector + {:class "value" :reverse? true :max-value 255 :value value :on-change (handle-change-slider :v)}] + + (when (not disable-opacity) + [:* + [:span.hsva-selector-label "A"] + [:& slider-selector + {:class "opacity" :max-value 1 :value alpha :on-change on-change-opacity}]])])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs new file mode 100644 index 0000000000..2912a88fea --- /dev/null +++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.colorpicker.libraries + (:require + [rumext.alpha :as mf] + [okulary.core :as l] + [cuerdas.core :as str] + [app.common.geom.point :as gpt] + [app.common.math :as math] + [app.common.uuid :refer [uuid]] + [app.util.dom :as dom] + [app.util.color :as uc] + [app.util.object :as obj] + [app.main.store :as st] + [app.main.refs :as refs] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.colors :as dc] + [app.main.data.modal :as modal] + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [t]] + [app.main.ui.components.color-bullet :refer [color-bullet]] + [app.main.ui.workspace.colorpicker.gradients :refer [gradients]] + [app.main.ui.workspace.colorpicker.harmony :refer [harmony-selector]] + [app.main.ui.workspace.colorpicker.hsva :refer [hsva-selector]] + [app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]] + [app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]])) + +(mf/defc libraries [{:keys [current-color on-select-color on-add-library-color + disable-gradient disable-opacity]}] + (let [selected-library (mf/use-state "recent") + current-library-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) + locale (mf/deref i18n/locale) + + parse-selected + (fn [selected] + (if (#{"recent" "file"} selected) + (keyword selected) + (uuid selected)) ) + + 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 []))) + + (= @selected-library "file") + (vals file-colors) + + :else ;; Library UUID + (->> (get-in shared-libs [(uuid @selected-library) :data :colors]) + (vals) + (map #(merge % {:file-id (uuid @selected-library)}))))] + + (reset! current-library-colors (into [] (filter check-valid-color?) mapped-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)))))) + + + [:div.libraries + [:select {:on-change (fn [e] + (when-let [val (dom/get-target-val e)] + (reset! selected-library val))) + :value @selected-library} + [:option {:value "recent"} (t locale "workspace.libraries.colors.recent-colors")] + [:option {:value "file"} (t locale "workspace.libraries.colors.file-library")] + + (for [[_ {:keys [name id]}] shared-libs] + [:option {:key id + :value id} name])] + + [:div.selected-colors + (when (= "file" @selected-library) + [:div.color-bullet.button.plus-button {:style {:background-color "white"} + :on-click on-add-library-color} + i/plus]) + + [:div.color-bullet.button {:style {:background-color "white"} + :on-click #(st/emit! (dc/show-palette (parse-selected @selected-library)))} + 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)}])]])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/pixel_overlay.cljs b/frontend/src/app/main/ui/workspace/colorpicker/pixel_overlay.cljs new file mode 100644 index 0000000000..3599660a05 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/colorpicker/pixel_overlay.cljs @@ -0,0 +1,187 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.colorpicker.pixel-overlay + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [okulary.core :as l] + [promesa.core :as p] + [beicon.core :as rx] + [goog.events :as events] + [app.common.uuid :as uuid] + [app.util.timers :as timers] + [app.util.dom :as dom] + [app.util.object :as obj] + [app.main.data.colors :as dwc] + [app.main.data.fetch :as mdf] + [app.main.data.modal :as modal] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.context :as muc] + [app.main.ui.cursors :as cur] + [app.main.ui.keyboard :as kbd] + [app.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]]) + (:import goog.events.EventType)) + +(defn format-viewbox [vbox] + (str/join " " [(+ (:x vbox 0) (:left-offset vbox 0)) + (:y vbox 0) + (:width vbox 0) + (:height vbox 0)])) + +(mf/defc overlay-frames + {::mf/wrap [mf/memo] + ::mf/wrap-props false} + [] + (let [data (mf/deref refs/workspace-page) + objects (:objects data) + root (get objects uuid/zero) + shapes (->> (:shapes root) (map #(get objects %)))] + [:* + [:g.shapes + (for [item shapes] + (if (= (:type item) :frame) + [:& frame-wrapper {:shape item + :key (:id item) + :objects objects}] + [:& shape-wrapper {:shape item + :key (:id item)}]))]])) + +(defn draw-picker-canvas [svg-node canvas-node] + (let [canvas-context (.getContext canvas-node "2d") + xml (.serializeToString (js/XMLSerializer.) svg-node) + img-src (str "data:image/svg+xml;base64," + (-> xml js/encodeURIComponent js/unescape js/btoa)) + img (js/Image.) + + on-error (fn [err] (.error js/console "ERROR" err)) + on-load (fn [] (.drawImage canvas-context img 0 0))] + (.addEventListener img "error" on-error) + (.addEventListener img "load" on-load) + (obj/set! img "src" img-src))) + +(mf/defc pixel-overlay + {::mf/wrap-props false} + [props] + (let [vport (unchecked-get props "vport") + vbox (unchecked-get props "vbox") + viewport-ref (unchecked-get props "viewport-ref") + options (unchecked-get props "options") + svg-ref (mf/use-ref nil) + canvas-ref (mf/use-ref nil) + fetch-pending (mf/deref (mdf/pending-ref)) + + update-canvas-stream (rx/subject) + + handle-keydown + (fn [event] + (when (and (kbd/esc? event)) + (do (dom/stop-propagation event) + (dom/prevent-default event) + (st/emit! (dwc/stop-picker)) + (modal/disallow-click-outside!)))) + + on-mouse-move-picker + (fn [event] + (when-let [zoom-view-node (.getElementById js/document "picker-detail")] + (let [{brx :left bry :top} (dom/get-bounding-rect (mf/ref-val viewport-ref)) + x (- (.-clientX event) brx) + y (- (.-clientY event) bry) + + zoom-context (.getContext zoom-view-node "2d") + canvas-node (mf/ref-val canvas-ref) + canvas-context (.getContext canvas-node "2d") + pixel-data (.getImageData canvas-context x y 1 1) + rgba (.-data pixel-data) + r (obj/get rgba 0) + g (obj/get rgba 1) + b (obj/get rgba 2) + a (obj/get rgba 3) + + area-data (.getImageData canvas-context (- x 25) (- y 20) 50 40)] + + (-> (js/createImageBitmap area-data) + (p/then (fn [image] + ;; Draw area + (obj/set! zoom-context "imageSmoothingEnabled" false) + (.drawImage zoom-context image 0 0 200 160)))) + (st/emit! (dwc/pick-color [r g b a]))))) + + on-mouse-down-picker + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (dwc/pick-color-select true (kbd/shift? event)))) + + on-mouse-up-picker + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (dwc/stop-picker)) + (modal/disallow-click-outside!))] + + (mf/use-effect + (fn [] + (let [listener (events/listen (dom/get-root) EventType.KEYDOWN handle-keydown)] + #(events/unlistenByKey listener)))) + + (mf/use-effect + (fn [] + (let [sub (->> update-canvas-stream + (rx/debounce 10) + (rx/subs #(draw-picker-canvas (mf/ref-val svg-ref) + (mf/ref-val canvas-ref))))] + + #(rx/dispose! sub)))) + + (mf/use-effect + (mf/deps svg-ref canvas-ref) + (fn [] + (when (and svg-ref canvas-ref) + + (let [config (clj->js {:attributes true + :childList true + :subtree true + :characterData true}) + on-svg-change (fn [mutation-list] (rx/push! update-canvas-stream :update)) + observer (js/MutationObserver. on-svg-change)] + + (.observe observer (mf/ref-val svg-ref) config) + + ;; Disconnect on unmount + #(.disconnect observer))))) + + [:* + [:div.overlay + {:tab-index 0 + :style {:position "absolute" + :top 0 + :left 0 + :width "100%" + :height "100%" + :cursor cur/picker} + :on-mouse-down on-mouse-down-picker + :on-mouse-up on-mouse-up-picker + :on-mouse-move on-mouse-move-picker}] + [:canvas {:ref canvas-ref + :width (:width vport 0) + :height (:height vport 0) + :style {:display "none"}}] + + [:& (mf/provider muc/embed-ctx) {:value true} + [:svg.viewport + {:ref svg-ref + :preserveAspectRatio "xMidYMid meet" + :width (:width vport 0) + :height (:height vport 0) + :view-box (format-viewbox vbox) + :style {:display "none" + :background-color (get options :background "#E8E9EA")}} + [:& overlay-frames]]]])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/pixel_picker.cljs b/frontend/src/app/main/ui/workspace/colorpicker/pixel_picker.cljs new file mode 100644 index 0000000000..21a35a0fc5 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/colorpicker/pixel_picker.cljs @@ -0,0 +1,29 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.colorpicker.pixel-picker + (:require + [rumext.alpha :as mf] + [okulary.core :as l] + [cuerdas.core :as str] + [app.common.geom.point :as gpt] + [app.common.math :as math] + [app.common.uuid :refer [uuid]] + [app.util.dom :as dom] + [app.util.color :as uc] + [app.util.object :as obj] + [app.main.store :as st] + [app.main.refs :as refs] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.colors :as dc] + [app.main.data.modal :as modal] + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [t]])) + + diff --git a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs new file mode 100644 index 0000000000..f88794bab5 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs @@ -0,0 +1,95 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.colorpicker.ramp + (:require + [rumext.alpha :as mf] + [okulary.core :as l] + [cuerdas.core :as str] + [app.common.geom.point :as gpt] + [app.common.math :as math] + [app.common.uuid :refer [uuid]] + [app.util.dom :as dom] + [app.util.color :as uc] + [app.util.object :as obj] + [app.main.store :as st] + [app.main.refs :as refs] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.colors :as dc] + [app.main.data.modal :as modal] + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [t]] + [app.main.ui.components.color-bullet :refer [color-bullet]] + [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]])) + +(mf/defc value-saturation-selector [{:keys [hue saturation value on-change]}] + (let [dragging? (mf/use-state false) + calculate-pos + (fn [ev] + (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) + {:keys [x y]} (-> ev dom/get-client-position) + px (math/clamp (/ (- x left) (- right left)) 0 1) + py (* 255 (- 1 (math/clamp (/ (- y top) (- bottom top)) 0 1)))] + (on-change px py)))] + [:div.value-saturation-selector + {:on-mouse-down #(reset! dragging? true) + :on-mouse-up #(reset! dragging? false) + :on-pointer-down (partial dom/capture-pointer) + :on-pointer-up (partial dom/release-pointer) + :on-click calculate-pos + :on-mouse-move #(when @dragging? (calculate-pos %))} + [:div.handler {:style {:pointer-events "none" + :left (str (* 100 saturation) "%") + :top (str (* 100 (- 1 (/ value 255))) "%")}}]])) + + +(mf/defc ramp-selector [{:keys [color disable-opacity on-change]}] + (let [{hex :hex + hue :h saturation :s value :v alpha :alpha} color + + on-change-value-saturation + (fn [new-saturation new-value] + (let [hex (uc/hsv->hex [hue new-saturation new-value]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :r r :g g :b b + :s new-saturation + :v new-value}))) + + on-change-hue + (fn [new-hue] + (let [hex (uc/hsv->hex [new-hue saturation value]) + [r g b] (uc/hex->rgb hex)] + (on-change {:hex hex + :r r :g g :b b + :h new-hue} ))) + + on-change-opacity + (fn [new-opacity] + (on-change {:alpha new-opacity} ))] + [:* + [:& value-saturation-selector + {:hue hue + :saturation saturation + :value value + :on-change on-change-value-saturation}] + + [:div.shade-selector + [:& color-bullet {:color {:color hex + :opacity alpha}}] + [:& slider-selector {:class "hue" + :max-value 360 + :value hue + :on-change on-change-hue}] + + (when (not disable-opacity) + [:& slider-selector {:class "opacity" + :max-value 1 + :value alpha + :on-change on-change-opacity}])]])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs new file mode 100644 index 0000000000..e6b4e0c4fa --- /dev/null +++ b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs @@ -0,0 +1,68 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.colorpicker.slider-selector + (:require + [rumext.alpha :as mf] + [okulary.core :as l] + [cuerdas.core :as str] + [app.common.geom.point :as gpt] + [app.common.math :as math] + [app.common.uuid :refer [uuid]] + [app.util.dom :as dom] + [app.util.color :as uc] + [app.util.object :as obj] + [app.main.store :as st] + [app.main.refs :as refs] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.colors :as dc] + [app.main.data.modal :as modal] + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [t]])) + +(mf/defc slider-selector + [{:keys [value class min-value max-value vertical? reverse? on-change]}] + (let [min-value (or min-value 0) + max-value (or max-value 1) + dragging? (mf/use-state false) + calculate-pos + (fn [ev] + (when on-change + (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) + {:keys [x y]} (-> ev dom/get-client-position) + unit-value (if vertical? + (math/clamp (/ (- bottom y) (- bottom top)) 0 1) + (math/clamp (/ (- x left) (- right left)) 0 1)) + unit-value (if reverse? + (math/abs (- unit-value 1.0)) + unit-value) + value (+ min-value (* unit-value (- max-value min-value)))] + (on-change (math/precision value 2)))))] + + [:div.slider-selector + {:class (str (if vertical? "vertical " "") class) + :on-mouse-down #(reset! dragging? true) + :on-mouse-up #(reset! dragging? false) + :on-pointer-down (partial dom/capture-pointer) + :on-pointer-up (partial dom/release-pointer) + :on-click calculate-pos + :on-mouse-move #(when @dragging? (calculate-pos %))} + + (let [value-percent (* (/ (- value min-value) + (- max-value min-value)) 100) + + value-percent (if reverse? + (math/abs (- value-percent 100)) + value-percent) + value-percent-str (str value-percent "%") + + style-common #js {:pointerEvents "none"} + style-horizontal (obj/merge! #js {:left value-percent-str} style-common) + style-vertical (obj/merge! #js {:bottom value-percent-str} style-common)] + [:div.handler {:style (if vertical? style-vertical style-horizontal)}])])) diff --git a/frontend/src/app/main/ui/workspace/comments.cljs b/frontend/src/app/main/ui/workspace/comments.cljs new file mode 100644 index 0000000000..dd98602982 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/comments.cljs @@ -0,0 +1,549 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.comments + (:require + [app.config :as cfg] + [app.main.data.workspace :as dw] + [app.main.data.workspace.comments :as dwcm] + [app.main.data.workspace.common :as dwc] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.streams :as ms] + [app.main.ui.context :as ctx] + [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.data.modal :as modal] + [app.main.ui.hooks :as hooks] + [app.main.ui.icons :as i] + [app.main.ui.keyboard :as kbd] + [app.main.ui.workspace.colorpicker] + [app.main.ui.workspace.context-menu :refer [context-menu]] + [app.util.time :as dt] + [app.util.timers :as tm] + [app.util.dom :as dom] + [app.util.object :as obj] + [beicon.core :as rx] + [app.util.i18n :as i18n :refer [t tr]] + [cuerdas.core :as str] + [okulary.core :as l] + [rumext.alpha :as mf])) + +(declare group-threads-by-page) +(declare apply-filters) + +(mf/defc resizing-textarea + {::mf/wrap-props false} + [props] + (let [value (obj/get props "value" "") + on-focus (obj/get props "on-focus") + on-blur (obj/get props "on-blur") + placeholder (obj/get props "placeholder") + on-change (obj/get props "on-change") + + on-esc (obj/get props "on-esc") + + ref (mf/use-ref) + ;; state (mf/use-state value) + + on-key-down + (mf/use-callback + (fn [event] + (when (and (kbd/esc? event) + (fn? on-esc)) + (on-esc event)))) + + on-change* + (mf/use-callback + (mf/deps on-change) + (fn [event] + (let [content (dom/get-target-val event)] + (on-change content))))] + + + (mf/use-layout-effect + nil + (fn [] + (let [node (mf/ref-val ref)] + (set! (.-height (.-style node)) "0") + (set! (.-height (.-style node)) (str (+ 2 (.-scrollHeight node)) "px"))))) + + [:textarea + {:ref ref + :on-key-down on-key-down + :on-focus on-focus + :on-blur on-blur + :value value + :placeholder placeholder + :on-change on-change*}])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Workspace +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(mf/defc reply-form + [{:keys [thread] :as props}] + (let [show-buttons? (mf/use-state false) + content (mf/use-state "") + + on-focus + (mf/use-callback + #(reset! show-buttons? true)) + + on-blur + (mf/use-callback + #(reset! show-buttons? false)) + + on-change + (mf/use-callback + #(reset! content %)) + + on-cancel + (mf/use-callback + #(do (reset! content "") + (reset! show-buttons? false))) + + on-submit + (mf/use-callback + (mf/deps thread @content) + (fn [] + (st/emit! (dwcm/add-comment thread @content)) + (on-cancel)))] + + [:div.reply-form + [:& resizing-textarea {:value @content + :placeholder "Reply" + :on-blur on-blur + :on-focus on-focus + :on-change on-change}] + (when (or @show-buttons? + (not (empty? @content))) + [:div.buttons + [:input.btn-primary {:type "button" :value "Post" :on-click on-submit}] + [:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]])])) + +(mf/defc draft-thread + [{:keys [draft zoom] :as props}] + (let [position (:position draft) + content (:content draft) + pos-x (* (:x position) zoom) + pos-y (* (:y position) zoom) + + on-esc + (mf/use-callback + (mf/deps draft) + (st/emitf :interrupt)) + + on-change + (mf/use-callback + (mf/deps draft) + (fn [content] + (st/emit! (dwcm/update-draft-thread (assoc draft :content content))))) + + on-submit + (mf/use-callback + (mf/deps draft) + (st/emitf (dwcm/create-thread draft)))] + + [:* + [:div.thread-bubble + {:style {:top (str pos-y "px") + :left (str pos-x "px")}} + [:span "?"]] + [:div.thread-content + {:style {:top (str (- pos-y 14) "px") + :left (str (+ pos-x 14) "px")}} + [:div.reply-form + [:& resizing-textarea {:placeholder "Write new comment" + :value content + :on-esc on-esc + :on-change on-change}] + [:div.buttons + [:input.btn-primary + {:on-click on-submit + :type "button" + :value "Post"}] + [:input.btn-secondary + {:on-click on-esc + :type "button" + :value "Cancel"}]]]]])) + + +(mf/defc edit-form + [{:keys [content on-submit on-cancel] :as props}] + (let [content (mf/use-state content) + + on-change + (mf/use-callback + #(reset! content %)) + + on-submit* + (mf/use-callback + (mf/deps @content) + (fn [] (on-submit @content)))] + + [:div.reply-form.edit-form + [:& resizing-textarea {:value @content + :on-change on-change}] + [:div.buttons + [:input.btn-primary {:type "button" :value "Post" :on-click on-submit*}] + [:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]]])) + + +(mf/defc comment-item + [{:keys [comment thread] :as props}] + (let [profile (get @refs/workspace-users (:owner-id comment)) + + options (mf/use-state false) + edition? (mf/use-state false) + + on-show-options + (mf/use-callback #(reset! options true)) + + on-hide-options + (mf/use-callback #(reset! options false)) + + on-edit-clicked + (mf/use-callback + (fn [] + (reset! options false) + (reset! edition? true))) + + on-delete-comment + (mf/use-callback + (mf/deps comment) + (st/emitf (dwcm/delete-comment comment))) + + delete-thread + (mf/use-callback + (mf/deps thread) + (st/emitf (dwcm/close-thread) + (dwcm/delete-comment-thread thread))) + + + on-delete-thread + (mf/use-callback + (mf/deps thread) + (st/emitf (modal/show + {:type :confirm + :title (tr "modals.delete-comment-thread.title") + :message (tr "modals.delete-comment-thread.message") + :accept-label (tr "modals.delete-comment-thread.accept") + :on-accept delete-thread}))) + + on-submit + (mf/use-callback + (mf/deps comment thread) + (fn [content] + (reset! edition? false) + (st/emit! (dwcm/update-comment (assoc comment :content content))))) + + on-cancel + (mf/use-callback #(reset! edition? false)) + + toggle-resolved + (mf/use-callback + (mf/deps thread) + (st/emitf (dwcm/update-comment-thread (update thread :is-resolved not))))] + + [:div.comment-container + [:div.comment + [:div.author + [:div.avatar + [:img {:src (cfg/resolve-media-path (:photo profile))}]] + [:div.name + [:div.fullname (:fullname profile)] + [:div.timeago (dt/timeago (:modified-at comment))]] + + (when (some? thread) + [:div.options-resolve {:on-click toggle-resolved} + (if (:is-resolved thread) + [:span i/checkbox-checked] + [:span i/checkbox-unchecked])]) + + [:div.options + [:div.options-icon {:on-click on-show-options} i/actions]]] + + [:div.content + (if @edition? + [:& edit-form {:content (:content comment) + :on-submit on-submit + :on-cancel on-cancel}] + [:span.text (:content comment)])]] + + [:& dropdown {:show @options + :on-close on-hide-options} + [:ul.dropdown.comment-options-dropdown + [:li {:on-click on-edit-clicked} "Edit"] + (if thread + [:li {:on-click on-delete-thread} "Delete thread"] + [:li {:on-click on-delete-comment} "Delete comment"])]]])) + +(defn comments-ref + [{:keys [id] :as thread}] + (l/derived (l/in [:comments id]) st/state)) + +(mf/defc thread-comments + [{:keys [thread zoom]}] + (let [ref (mf/use-ref) + pos (:position thread) + pos-x (+ (* (:x pos) zoom) 14) + pos-y (- (* (:y pos) zoom) 14) + + comments-ref (mf/use-memo (mf/deps thread) #(comments-ref thread)) + comments-map (mf/deref comments-ref) + comments (->> (vals comments-map) + (sort-by :created-at)) + comment (first comments)] + + (mf/use-effect + (st/emitf (dwcm/update-comment-thread-status thread))) + + (mf/use-effect + (mf/deps thread) + (st/emitf (dwcm/retrieve-comments (:id thread)))) + + (mf/use-layout-effect + (mf/deps thread comments-map) + (fn [] + (when-let [node (mf/ref-val ref)] + (.scrollIntoView ^js node)))) + + [:div.thread-content + {:style {:top (str pos-y "px") + :left (str pos-x "px")}} + + [:div.comments + [:& comment-item {:comment comment + :thread thread}] + (for [item (rest comments)] + [:* + [:hr] + [:& comment-item {:comment item}]]) + [:div {:ref ref}]] + [:& reply-form {:thread thread}]])) + +(mf/defc thread-bubble + {::mf/wrap [mf/memo]} + [{:keys [thread zoom open?] :as params}] + (let [pos (:position thread) + pos-x (* (:x pos) zoom) + pos-y (* (:y pos) zoom) + + on-open-toggle + (mf/use-callback + (mf/deps thread open?) + (fn [] + (if open? + (st/emit! (dwcm/close-thread)) + (st/emit! (dwcm/open-thread thread)))))] + + [:div.thread-bubble + {:style {:top (str pos-y "px") + :left (str pos-x "px")} + :class (dom/classnames + :resolved (:is-resolved thread) + :unread (pos? (:count-unread-comments thread))) + :on-click on-open-toggle} + [:span (:seqn thread)]])) + +(def threads-ref + (l/derived :comment-threads st/state)) + +(def workspace-comments-ref + (l/derived :workspace-comments st/state)) + +(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) + profile (mf/deref refs/profile) + local (mf/deref workspace-comments-ref) + threads-map (mf/deref threads-ref) + threads (->> (vals threads-map) + (filter #(= (:page-id %) page-id)) + (apply-filters local profile))] + + (mf/use-effect + (mf/deps file-id) + (fn [] + (st/emit! (dwcm/initialize-comments file-id)) + (fn [] + (st/emit! ::dwcm/finalize)))) + + [:div.workspace-comments + {:style {:width (str (:width vport) "px") + :height (str (:height vport) "px")}} + [:div.threads {:style {:transform (str/format "translate(%spx, %spx)" pos-x pos-y)}} + (for [item threads] + [:& thread-bubble {:thread item + :zoom zoom + :open? (= (:id item) (:open local)) + :key (:seqn item)}]) + + (when-let [id (:open local)] + (when-let [thread (get threads-map id)] + [:& thread-comments {:thread thread + :zoom zoom}])) + + (when-let [draft (:comment drawing)] + [:& draft-thread {:draft draft :zoom zoom}])]])) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Sidebar +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(mf/defc sidebar-group-item + [{:keys [item] :as props}] + (let [profile (get @refs/workspace-users (:owner-id item)) + page-id (mf/use-ctx ctx/current-page-id) + file-id (mf/use-ctx ctx/current-file-id) + + on-click + (mf/use-callback + (mf/deps item page-id) + (fn [] + (when (not= page-id (:page-id item)) + (st/emit! (dw/go-to-page (:page-id item)))) + (tm/schedule + (st/emitf (dwcm/center-to-comment-thread item) + (dwcm/open-thread item)))))] + + [:div.comment {:on-click on-click} + [:div.author + [:div.thread-bubble + {:class (dom/classnames + :resolved (:is-resolved item) + :unread (pos? (:count-unread-comments item)))} + (:seqn item)] + [:div.avatar + [:img {:src (cfg/resolve-media-path (:photo profile))}]] + [:div.name + [:div.fullname (:fullname profile) ", "] + [:div.timeago (dt/timeago (:modified-at item))]]] + [:div.content + [:span.text (:content item)]] + [:div.content.replies + (let [unread (:count-unread-comments item ::none) + total (:count-comments item 1)] + [:* + (when (> total 1) + (if (= total 2) + [:span.total-replies "1 reply"] + [:span.total-replies (str (dec total) " replies")])) + + (when (and (> total 1) (> unread 0)) + (if (= unread 1) + [:span.new-replies "1 new reply"] + [:span.new-replies (str unread " new replies")]))])]])) + +(defn page-name-ref + [id] + (l/derived (l/in [:workspace-data :pages-index id :name]) st/state)) + +(mf/defc sidebar-item + [{:keys [group]}] + (let [page-name-ref (mf/use-memo (mf/deps (:page-id group)) #(page-name-ref (:page-id group))) + page-name (mf/deref page-name-ref)] + [:div.page-section + [:div.section-title + [:span.icon i/file-html] + [:span.label page-name]] + [:div.comments-container + (for [item (:items group)] + [:& sidebar-group-item {:item item :key (:id item)}])]])) + +(mf/defc sidebar-options + [{:keys [local] :as props}] + (let [filter-yours + (mf/use-callback + (mf/deps local) + (st/emitf (dwcm/update-filters {:main :yours}))) + + filter-all + (mf/use-callback + (mf/deps local) + (st/emitf (dwcm/update-filters {:main :all}))) + + toggle-resolved + (mf/use-callback + (mf/deps local) + (st/emitf (dwcm/update-filters {:resolved (not (:filter-resolved local))})))] + + [:ul.dropdown.sidebar-options-dropdown + [:li {:on-click filter-all} "All"] + [:li {:on-click filter-yours} "Only yours"] + [:hr] + (if (:filter-resolved local) + [:li {:on-click toggle-resolved} "Show resolved comments"] + [:li {:on-click toggle-resolved} "Hide resolved comments"])])) + +(mf/defc comments-sidebar + [] + (let [threads-map (mf/deref threads-ref) + profile (mf/deref refs/profile) + local (mf/deref workspace-comments-ref) + options? (mf/use-state false) + + tgroups (->> (vals threads-map) + (sort-by :modified-at) + (reverse) + (apply-filters local profile) + (group-threads-by-page))] + + [:div.workspace-comments.workspace-comments-sidebar + [:div.sidebar-title + [:div.label "Comments"] + [:div.options {:on-click #(reset! options? true)} + [:div.label (case (:filter local) + (nil :all) "All" + :yours "Only yours")] + [:div.icon i/arrow-down]]] + + [:& dropdown {:show @options? + :on-close #(reset! options? false)} + [:& sidebar-options {:local local}]] + + (when (seq tgroups) + [:div.threads + [:& sidebar-item {:group (first tgroups)}] + (for [tgroup (rest tgroups)] + [:* + [:hr] + [:& sidebar-item {:group tgroup + :key (:page-id tgroup)}]])])])) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Helpers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- group-threads-by-page + [threads] + (letfn [(group-by-page [result thread] + (let [current (first result)] + (if (= (:page-id current) (:page-id thread)) + (cons (update current :items conj thread) + (rest result)) + (cons {:page-id (:page-id thread) :items [thread]} + result))))] + (reverse + (reduce group-by-page nil threads)))) + +(defn- apply-filters + [local profile threads] + (cond->> threads + (true? (:filter-resolved local)) + (filter (fn [item] + (or (not (:is-resolved item)) + (= (:id item) (:open local))))) + + (= :yours (:filter local)) + (filter #(contains? (:participants %) (:id profile))))) + diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index a7330c3cb4..00043faea3 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -19,7 +19,9 @@ [app.main.streams :as ms] [app.main.ui.icons :as i] [app.util.dom :as dom] + [app.util.i18n :refer [t] :as i18n] [app.main.data.workspace :as dw] + [app.main.data.workspace.common :as dwc] [app.main.data.workspace.libraries :as dwl] [app.main.ui.hooks :refer [use-rxsub]] [app.main.ui.components.dropdown :refer [dropdown]])) @@ -44,13 +46,14 @@ (mf/defc shape-context-menu [{:keys [mdata] :as props}] - (let [{:keys [id] :as shape} (:shape mdata) + (let [locale (mf/deref i18n/locale) + {:keys [id] :as shape} (:shape mdata) selected (:selected mdata) - root-shape (:root-shape mdata) do-duplicate #(st/emit! dw/duplicate-selected) do-delete #(st/emit! dw/delete-selected) do-copy #(st/emit! dw/copy-selected) + do-cut #(st/emit! dw/copy-selected dw/delete-selected) do-paste #(st/emit! dw/paste) do-bring-forward #(st/emit! (dw/vertical-order-selected :up)) do-bring-to-front #(st/emit! (dw/vertical-order-selected :top)) @@ -62,89 +65,111 @@ do-unlock-shape #(st/emit! (dw/update-shape-flags id {:blocked false})) do-create-group #(st/emit! dw/group-selected) do-remove-group #(st/emit! dw/ungroup-selected) + do-mask-group #(st/emit! dw/mask-group) + do-unmask-group #(st/emit! dw/unmask-group) do-add-component #(st/emit! dwl/add-component) do-detach-component #(st/emit! (dwl/detach-component id)) do-reset-component #(st/emit! (dwl/reset-component id)) do-update-component #(do + (st/emit! dwc/start-undo-transaction) (st/emit! (dwl/update-component id)) - (st/emit! (dwl/sync-file nil))) + (st/emit! (dwl/sync-file nil)) + (st/emit! dwc/commit-undo-transaction)) do-navigate-component-file #(st/emit! (dwl/nav-to-component-file - (:component-file root-shape)))] + (:component-file shape)))] [:* - [:& menu-entry {:title "Copy" + [:& menu-entry {:title (t locale "workspace.shape.menu.copy") :shortcut "Ctrl + c" :on-click do-copy}] - [:& menu-entry {:title "Paste" + [:& menu-entry {:title (t locale "workspace.shape.menu.cut") + :shortcut "Ctrl + x" + :on-click do-cut}] + [:& menu-entry {:title (t locale "workspace.shape.menu.paste") :shortcut "Ctrl + v" :on-click do-paste}] - [:& menu-entry {:title "Duplicate" + [:& menu-entry {:title (t locale "workspace.shape.menu.duplicate") :shortcut "Ctrl + d" :on-click do-duplicate}] [:& menu-separator] - [:& menu-entry {:title "Bring forward" + [:& menu-entry {:title (t locale "workspace.shape.menu.forward") :shortcut "Ctrl + ↑" :on-click do-bring-forward}] - [:& menu-entry {:title "Bring to front" + [:& menu-entry {:title (t locale "workspace.shape.menu.front") :shortcut "Ctrl + Shift + ↑" :on-click do-bring-to-front}] - [:& menu-entry {:title "Send backward" + [:& menu-entry {:title (t locale "workspace.shape.menu.backward") :shortcut "Ctrl + ↓" :on-click do-send-backward}] - [:& menu-entry {:title "Send to back" + [:& menu-entry {:title (t locale "workspace.shape.menu.back") :shortcut "Ctrl + Shift + ↓" :on-click do-send-to-back}] [:& menu-separator] (when (> (count selected) 1) - [:& menu-entry {:title "Group" - :shortcut "Ctrl + g" - :on-click do-create-group}]) + [:* + [:& menu-entry {:title (t locale "workspace.shape.menu.group") + :shortcut "Ctrl + g" + :on-click do-create-group}] + [:& menu-entry {:title (t locale "workspace.shape.menu.mask") + :shortcut "Ctrl + M" + :on-click do-mask-group}]]) (when (and (= (count selected) 1) (= (:type shape) :group)) - [:& menu-entry {:title "Ungroup" - :shortcut "Shift + g" - :on-click do-remove-group}]) + [:* + [:& menu-entry {:title (t locale "workspace.shape.menu.ungroup") + :shortcut "Shift + g" + :on-click do-remove-group}] + (if (:masked-group? shape) + [:& menu-entry {:title (t locale "workspace.shape.menu.unmask") + :shortcut "Shift + M" + :on-click do-unmask-group}] + [:& menu-entry {:title "Mask" + :shortcut "Ctrl + M" + :on-click do-mask-group}])]) (if (:hidden shape) - [:& menu-entry {:title "Show" + [:& menu-entry {:title (t locale "workspace.shape.menu.show") :on-click do-show-shape}] - [:& menu-entry {:title "Hide" + [:& menu-entry {:title (t locale "workspace.shape.menu.hide") :on-click do-hide-shape}]) (if (:blocked shape) - [:& menu-entry {:title "Unlock" + [:& menu-entry {:title (t locale "workspace.shape.menu.unlock") :on-click do-unlock-shape}] - [:& menu-entry {:title "Lock" + [:& menu-entry {:title (t locale "workspace.shape.menu.lock") :on-click do-lock-shape}]) - [:& menu-separator] - - (if (nil? (:shape-ref shape)) - [:& menu-entry {:title "Create component" - :shortcut "Ctrl + K" - :on-click do-add-component}] + (when (nil? (:shape-ref shape)) [:* - [:& menu-entry {:title "Detach instance" - :on-click do-detach-component}] - [:& menu-entry {:title "Reset overrides" - :on-click do-reset-component}] - (if (nil? (:component-file root-shape)) - [:& menu-entry {:title "Update master component" - :on-click do-update-component}] - [:& menu-entry {:title "Go to master component file" - :on-click do-navigate-component-file}])]) + [:& menu-separator] + [:& menu-entry {:title (t locale "workspace.shape.menu.create-component") + :shortcut "Ctrl + K" + :on-click do-add-component}]]) + + (when (:component-id shape) + [:* + [:& menu-separator] + [:& menu-entry {:title (t locale "workspace.shape.menu.detach-instance") + :on-click do-detach-component}] + [:& menu-entry {:title (t locale "workspace.shape.menu.reset-overrides") + :on-click do-reset-component}] + (if (nil? (:component-file shape)) + [:& menu-entry {:title (t locale "workspace.shape.menu.update-master") + :on-click do-update-component}] + [:& menu-entry {:title (t locale "workspace.shape.menu.go-master") + :on-click do-navigate-component-file}])]) [:& menu-separator] - [:& menu-entry {:title "Delete" + [:& menu-entry {:title (t locale "workspace.shape.menu.delete") :shortcut "Supr" - :on-click do-delete}] - ])) + :on-click do-delete}]])) (mf/defc viewport-context-menu [{:keys [mdata] :as props}] - (let [do-paste #(st/emit! dw/paste)] + (let [locale (mf/deref i18n/locale) + do-paste #(st/emit! dw/paste)] [:* - [:& menu-entry {:title "Paste" + [:& menu-entry {:title (t locale "workspace.shape.menu.paste") :shortcut "Ctrl + v" :on-click do-paste}]])) diff --git a/frontend/src/app/main/ui/workspace/frame_grid.cljs b/frontend/src/app/main/ui/workspace/frame_grid.cljs index 969a55414d..88b2000963 100644 --- a/frontend/src/app/main/ui/workspace/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/frame_grid.cljs @@ -18,7 +18,9 @@ (mf/defc square-grid [{:keys [frame zoom grid] :as props}] (let [{:keys [color size] :as params} (-> grid :params) - {color-value :value color-opacity :opacity} (-> grid :params :color) + {color-value :color color-opacity :opacity} (-> grid :params :color) + ;; Support for old color format + color-value (or color-value (:value (get-in grid [:params :color :value]))) {frame-width :width frame-height :height :keys [x y]} frame] (when (> size 0) [:g.grid @@ -43,7 +45,9 @@ :stroke-width (str (/ 1 zoom))}}])]]))) (mf/defc layout-grid [{:keys [key frame zoom grid]}] - (let [{color-value :value color-opacity :opacity} (-> grid :params :color) + (let [{color-value :color color-opacity :opacity} (-> grid :params :color) + ;; Support for old color format + color-value (or color-value (:value (get-in grid [:params :color :value]))) gutter (-> grid :params :gutter) gutter? (and (not (nil? gutter)) (not= gutter 0)) diff --git a/frontend/src/app/main/ui/workspace/gradients.cljs b/frontend/src/app/main/ui/workspace/gradients.cljs new file mode 100644 index 0000000000..007b8631d8 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/gradients.cljs @@ -0,0 +1,300 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.gradients + "Gradients handlers and renders" + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [beicon.core :as rx] + [okulary.core :as l] + [app.common.math :as mth] + [app.common.geom.point :as gpt] + [app.common.geom.matrix :as gmt] + [app.util.dom :as dom] + [app.main.store :as st] + [app.main.refs :as refs] + [app.main.streams :as ms] + [app.main.data.modal :as modal] + [app.main.data.workspace.common :as dwc] + [app.main.data.colors :as dc])) + +(def gradient-line-stroke-width 2) +(def gradient-line-stroke-color "white") +(def gradient-square-width 15) +(def gradient-square-radius 2) +(def gradient-square-stroke-width 2) +(def gradient-width-handler-radius 5) +(def gradient-width-handler-color "white") +(def gradient-square-stroke-color "white") +(def gradient-square-stroke-color-selected "#1FDEA7") + +(def editing-spot-ref + (l/derived (l/in [:workspace-local :editing-stop]) st/state)) + +(def current-gradient-ref + (l/derived (l/in [:workspace-local :current-gradient]) st/state)) + +(mf/defc shadow [{:keys [id x y width height offset]}] + [:filter {:id id + :x x + :y y + :width width + :height height + :filterUnits "userSpaceOnUse" + :color-interpolation-filters "sRGB"} + [:feFlood {:flood-opacity "0" :result "BackgroundImageFix"}] + [:feColorMatrix {:in "SourceAlpha" :type "matrix" :values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"}] + [:feOffset {:dy offset}] + [:feGaussianBlur {:stdDeviation "1"}] + [:feColorMatrix {:type "matrix" :values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"}] + [:feBlend {:mode "normal" :in2 "BackgroundImageFix" :result id}] + [:feBlend {:mode "normal" :in "SourceGraphic" :in2 id :result "shape"}]]) + +(mf/defc gradient-line-drop-shadow-filter [{:keys [id zoom from-p to-p]}] + [:& shadow + {:id id + :x (min (- (:x from-p) (/ 2 zoom)) + (- (:x to-p) (/ 2 zoom))) + :y (min (- (:y from-p) (/ 2 zoom)) + (- (:y to-p) (/ 2 zoom))) + :width (+ (mth/abs (- (:x to-p) (:x from-p))) (/ 4 zoom)) + :height (+ (mth/abs (- (:y to-p) (:y from-p))) (/ 4 zoom)) + :offset (/ 2 zoom)}]) + + +(mf/defc gradient-square-drop-shadow-filter [{:keys [id zoom point]}] + [:& shadow + {:id id + :x (- (:x point) (/ gradient-square-width zoom 2) 2) + :y (- (:y point) (/ gradient-square-width zoom 2) 2) + :width (+ (/ gradient-square-width zoom) (/ 2 zoom) 4) + :height (+ (/ gradient-square-width zoom) (/ 2 zoom) 4) + :offset (/ 2 zoom)}]) + +(mf/defc gradient-width-handler-shadow-filter [{:keys [id zoom point]}] + [:& shadow + {:id id + :x (- (:x point) (/ gradient-width-handler-radius zoom) 2) + :y (- (:y point) (/ gradient-width-handler-radius zoom) 2) + :width (+ (/ (* 2 gradient-width-handler-radius) zoom) (/ 2 zoom) 4) + :height (+ (/ (* 2 gradient-width-handler-radius) zoom) (/ 2 zoom) 4) + :offset (/ 2 zoom)}]) + +(def checkboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAACvUlEQVQoFQGyAk39AeLi4gAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB////AAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjScaa0cU7nIAAAAASUVORK5CYII=") + +#_(def checkboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") + +(mf/defc gradient-color-handler + [{:keys [filter-id zoom point color angle selected + on-click on-mouse-down on-mouse-up]}] + [:g {:filter (str/fmt "url(#%s)" filter-id) + :transform (gmt/rotate-matrix angle point)} + + [:image {:href checkboard + :x (- (:x point) (/ gradient-square-width 2 zoom)) + :y (- (:y point) (/ gradient-square-width 2 zoom)) + :width (/ gradient-square-width zoom) + :height (/ gradient-square-width zoom)}] + + [:rect {:x (- (:x point) (/ gradient-square-width 2 zoom)) + :y (- (:y point) (/ gradient-square-width 2 zoom)) + :rx (/ gradient-square-radius zoom) + :width (/ gradient-square-width zoom 2) + :height (/ gradient-square-width zoom) + :fill (:value color) + :on-click (partial on-click :to-p) + :on-mouse-down (partial on-mouse-down :to-p) + :on-mouse-up (partial on-mouse-up :to-p)}] + + [:rect {:data-allow-click-modal "colorpicker" + :x (- (:x point) (/ gradient-square-width 2 zoom)) + :y (- (:y point) (/ gradient-square-width 2 zoom)) + :rx (/ gradient-square-radius zoom) + :width (/ gradient-square-width zoom) + :height (/ gradient-square-width zoom) + :stroke (if selected "#31EFB8" "white") + :stroke-width (/ gradient-square-stroke-width zoom) + :fill (:value color) + :fill-opacity (:opacity color) + :on-click on-click + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up}]]) + +(mf/defc gradient-handler-transformed + [{:keys [from-p to-p width-p from-color to-color zoom editing + on-change-start on-change-finish on-change-width on-change-stop-color]}] + (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-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-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) + (fn [] + (let [subs (->> st/stream + (rx/filter ms/pointer-event?) + (rx/filter #(= :viewport (:source %))) + (rx/map :pt) + (rx/subs #(case @moving-point + :from-p (when on-change-start (on-change-start %)) + :to-p (when on-change-finish (on-change-finish %)) + :width-p (when on-change-width + (let [width-v (gpt/unit (gpt/to-vec from-p width-p)) + distance (gpt/point-line-distance % from-p to-p) + new-width-p (gpt/add + from-p + (gpt/multiply width-v (gpt/point distance)))] + (on-change-width new-width-p))) + nil)))] + (fn [] (rx/dispose! subs))))) + [:g.gradient-handlers + [:defs + [:& gradient-line-drop-shadow-filter {:id "gradient_line_drop_shadow" :from-p from-p :to-p to-p :zoom zoom}] + [:& gradient-line-drop-shadow-filter {:id "gradient_widh_line_drop_shadow" :from-p from-p :to-p width-p :zoom zoom}] + [:& gradient-square-drop-shadow-filter {:id "gradient_square_from_drop_shadow" :point from-p :zoom zoom}] + [:& gradient-square-drop-shadow-filter {:id "gradient_square_to_drop_shadow" :point to-p :zoom zoom}] + [:& gradient-width-handler-shadow-filter {:id "gradient_width_handler_drop_shadow" :point width-p :zoom zoom}]] + + [:g {:filter "url(#gradient_line_drop_shadow)"} + [:line {:x1 (:x from-p) + :y1 (:y from-p) + :x2 (:x to-p) + :y2 (:y to-p) + :stroke gradient-line-stroke-color + :stroke-width (/ gradient-line-stroke-width zoom)}]] + + (when width-p + [:g {:filter "url(#gradient_widh_line_drop_shadow)"} + [:line {:x1 (:x from-p) + :y1 (:y from-p) + :x2 (:x width-p) + :y2 (:y width-p) + :stroke gradient-line-stroke-color + :stroke-width (/ gradient-line-stroke-width zoom)}]]) + + (when width-p + [:g {:filter "url(#gradient_width_handler_drop_shadow)"} + [:circle {:data-allow-click-modal "colorpicker" + :cx (:x width-p) + :cy (:y width-p) + :r (/ gradient-width-handler-radius zoom) + :fill gradient-width-handler-color + :on-mouse-down (partial on-mouse-down :width-p) + :on-mouse-up (partial on-mouse-up :width-p)}]]) + + [:& gradient-color-handler + {:selected (or (not editing) (= editing 0)) + :filter-id "gradient_square_from_drop_shadow" + :zoom zoom + :point from-p + :color from-color + :angle angle + :on-click (partial on-click :from-p) + :on-mouse-down (partial on-mouse-down :from-p) + :on-mouse-up (partial on-mouse-up :from-p)}] + + [:& gradient-color-handler + {:selected (= editing 1) + :filter-id "gradient_square_to_drop_shadow" + :zoom zoom + :point to-p + :color to-color + :angle angle + :on-click (partial on-click :to-p) + :on-mouse-down (partial on-mouse-down :to-p) + :on-mouse-up (partial on-mouse-up :to-p)}]])) + + +(mf/defc gradient-handlers + [{:keys [id zoom]}] + (let [shape (mf/deref (refs/object-by-id id)) + gradient (mf/deref current-gradient-ref) + editing-spot (mf/deref editing-spot-ref) + + {:keys [x y width height] :as sr} (:selrect shape) + + [{start-color :color start-opacity :opacity} + {end-color :color end-opacity :opacity}] (:stops gradient) + + from-p (gpt/point (+ x (* width (:start-x gradient))) + (+ y (* height (:start-y gradient)))) + + to-p (gpt/point (+ x (* width (:end-x gradient))) + (+ y (* height (:end-y gradient)))) + + gradient-vec (gpt/to-vec from-p to-p) + gradient-length (gpt/length gradient-vec) + + width-v (-> gradient-vec + (gpt/normal-left) + (gpt/multiply (gpt/point (* (:width gradient) (/ gradient-length (/ height 2) )))) + (gpt/multiply (gpt/point (/ width 2)))) + + width-p (gpt/add from-p width-v) + + change! (fn [changes] + (st/emit! (dc/update-gradient changes))) + + on-change-start (fn [point] + (let [start-x (/ (- (:x point) x) width) + start-y (/ (- (:y point) y) height) + start-x (mth/precision start-x 2) + start-y (mth/precision start-y 2)] + (change! {:start-x start-x :start-y start-y}))) + + on-change-finish (fn [point] + (let [end-x (/ (- (:x point) x) width) + end-y (/ (- (:y point) y) height) + + end-x (mth/precision end-x 2) + end-y (mth/precision end-y 2)] + (change! {:end-x end-x :end-y end-y}))) + + on-change-width (fn [point] + (let [scale-factor-y (/ gradient-length (/ height 2)) + norm-dist (/ (gpt/distance point from-p) + (* (/ width 2) scale-factor-y))] + + (change! {:width norm-dist})))] + + (when (and 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}]))) diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index ae5673899f..3ceaa5aaf1 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -18,7 +18,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] - [app.main.ui.modal :as modal] + [app.main.data.modal :as modal] [app.main.ui.workspace.presence :as presence] [app.main.ui.keyboard :as kbd] [app.util.i18n :as i18n :refer [t]] @@ -96,23 +96,37 @@ edit-input-ref (mf/use-ref nil) - add-shared-fn #(st/emit! nil (dw/set-file-shared (:id file) true)) - on-add-shared - #(modal/show! :confirm-dialog - {:message (t locale "dashboard.grid.add-shared-message" (:name file)) - :hint (t locale "dashboard.grid.add-shared-hint") - :accept-text (t locale "dashboard.grid.add-shared-accept") - :not-danger? true - :on-accept add-shared-fn}) + add-shared-fn + (st/emitf (dw/set-file-shared (:id file) true)) + + del-shared-fn + (st/emitf (dw/set-file-shared (:id file) false)) + + on-add-shared + (mf/use-fn + (mf/deps file) + (st/emitf (modal/show + {:type :confirm + :message "" + :title (t locale "modals.add-shared-confirm.message" (:name file)) + :hint (t locale "modals.add-shared-confirm.hint") + :cancel-label :omit + :accept-label (t locale "modals.add-shared-confirm.accept") + :accept-style :primary + :on-accept add-shared-fn}))) - remove-shared-fn #(st/emit! nil (dw/set-file-shared (:id file) false)) on-remove-shared - #(modal/show! :confirm-dialog - {:message (t locale "dashboard.grid.remove-shared-message" (:name file)) - :hint (t locale "dashboard.grid.remove-shared-hint") - :accept-text (t locale "dashboard.grid.remove-shared-accept") - :not-danger? false - :on-accept remove-shared-fn}) + (mf/use-fn + (mf/deps file) + (st/emitf (modal/show + {:type :confirm + :message "" + :title (t locale "modals.remove-shared-confirm.message" (:name file)) + :hint (t locale "modals.remove-shared-confirm.hint") + :cancel-label :omit + :accept-label (t locale "modals.remove-shared-confirm.accept") + :on-accept del-shared-fn}))) + handle-blur (fn [event] (let [value (-> edit-input-ref mf/ref-val dom/get-value)] @@ -132,7 +146,7 @@ [:div.menu-section [:div.btn-icon-dark.btn-small {:on-click #(reset! show-menu? true)} i/actions] - [:div.project-tree {:alt (t locale "header.sitemap")} + [:div.project-tree {:alt (t locale "workspace.sitemap")} [:span.project-name {:on-click #(st/emit! (rt/navigate :dashboard-project {:team-id team-id :project-id (:project-id file)}))} @@ -205,9 +219,9 @@ (if (:is-shared file) [:li {:on-click on-remove-shared} - [:span (t locale "dashboard.grid.remove-shared")]] + [:span (t locale "dashboard.remove-shared")]] [:li {:on-click on-add-shared} - [:span (t locale "dashboard.grid.add-shared")]]) + [:span (t locale "dashboard.add-shared")]]) ]]])) ;; --- Header Component @@ -215,7 +229,7 @@ (mf/defc header [{:keys [file layout project page-id] :as props}] (let [team-id (:team-id project) - go-back #(st/emit! (rt/nav :dashboard-team {:team-id team-id})) + go-back #(st/emit! (rt/nav :dashboard-projects {:team-id team-id})) zoom (mf/deref refs/selected-zoom) locale (mf/deref i18n/locale) router (mf/deref refs/router) diff --git a/frontend/src/app/main/ui/workspace/left_toolbar.cljs b/frontend/src/app/main/ui/workspace/left_toolbar.cljs index 21c6efc8eb..8a1cd65c4f 100644 --- a/frontend/src/app/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/left_toolbar.cljs @@ -25,9 +25,9 @@ [{:keys [layout] :as props}] (let [file-input (mf/use-ref nil) selected-drawtool (mf/deref refs/selected-drawing-tool) - select-drawtool #(st/emit! :interrupt - (dw/select-for-drawing %)) - file (mf/deref refs/workspace-file) + select-drawtool #(st/emit! :interrupt (dw/select-for-drawing %)) + + file (mf/deref refs/workspace-file) locale (i18n/use-locale) on-image #(dom/click (mf/ref-val file-input)) @@ -42,7 +42,7 @@ :id (:id image) :path (:path image)}} aspect-ratio (/ (:width image) (:height image))] - (st/emit! (dw/create-and-add-shape :image shape)))) + (st/emit! (dw/create-and-add-shape :image 0 0 shape)))) on-files-selected (fn [js-files] @@ -93,26 +93,32 @@ {:alt (t locale "workspace.toolbar.path") :class (when (= selected-drawtool :path) "selected") :on-click (partial select-drawtool :path)} - i/curve]] + i/curve] + + [:li.tooltip.tooltip-right + {:alt (t locale "workspace.toolbar.comments") + :class (when (= selected-drawtool :comments) "selected") + :on-click (partial select-drawtool :comments)} + i/chat]] [:ul.left-toolbar-options.panels [:li.tooltip.tooltip-right {:alt "Layers" :class (when (contains? layout :layers) "selected") - :on-click #(st/emit! (dw/toggle-layout-flags :sitemap :layers))} + :on-click (st/emitf (dw/ensure-layout :layers))} i/layers] [:li.tooltip.tooltip-right {:alt (t locale "workspace.toolbar.assets") :class (when (contains? layout :assets) "selected") - :on-click #(st/emit! (dw/toggle-layout-flags :assets))} + :on-click (st/emitf (dw/ensure-layout :assets))} i/library] [:li.tooltip.tooltip-right {:alt "History" :class (when (contains? layout :document-history) "selected") - :on-click #(st/emit! (dw/toggle-layout-flags :document-history))} + :on-click (st/emitf (dw/ensure-layout :document-history))} i/undo-history] [:li.tooltip.tooltip-right {:alt (t locale "workspace.toolbar.color-palette") :class (when (contains? layout :colorpalette) "selected") - :on-click #(st/emit! (dw/toggle-layout-flags :colorpalette))} + :on-click (st/emitf (dw/toggle-layout-flags :colorpalette))} i/palette]]]])) diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 10e9a38347..1b22620817 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -18,7 +18,7 @@ [app.main.data.workspace :as dw] [app.main.data.workspace.libraries :as dwl] [app.main.ui.icons :as i] - [app.main.ui.modal :as modal])) + [app.main.data.modal :as modal])) (def workspace-file (l/derived :workspace-file st/state)) @@ -27,7 +27,8 @@ [library] (let [components-count (count (get-in library [:data :components] [])) graphics-count (count (get-in library [:data :media] [])) - colors-count (count (get-in library [:data :colors] []))] + colors-count (count (get-in library [:data :colors] [])) + typography-count (count (get-in library [:data :typographies] []))] ;; Include a   so this block has always some content (str (str/join " · " @@ -39,7 +40,10 @@ (conj (tr "workspace.libraries.graphics" graphics-count)) (< 0 colors-count) - (conj (tr "workspace.libraries.colors" colors-count)))) + (conj (tr "workspace.libraries.colors" colors-count)) + + (< 0 typography-count) + (conj (tr "workspace.libraries.typography" typography-count)))) "\u00A0"))) (mf/defc libraries-tab @@ -84,7 +88,7 @@ [:div.item-name (:name library)] [:div.item-contents (contents-str library)] [:input.item-button {:type "button" - :value (tr "workspace.libraries.remove") + :value (tr "labels.remove") :on-click #(unlink-library (:id library))}]]) ]] [:div.section diff --git a/frontend/src/app/main/ui/workspace/presence.cljs b/frontend/src/app/main/ui/workspace/presence.cljs index ca0108bb9b..6e7f8ded31 100644 --- a/frontend/src/app/main/ui/workspace/presence.cljs +++ b/frontend/src/app/main/ui/workspace/presence.cljs @@ -10,6 +10,7 @@ (ns app.main.ui.workspace.presence (:require [rumext.alpha :as mf] + [cuerdas.core :as str] [beicon.core :as rx] [app.main.refs :as refs] [app.main.store :as st] @@ -24,34 +25,39 @@ (mf/defc session-cursor [{:keys [session] :as props}] - (let [point (:point session) + (let [zoom (mf/deref refs/selected-zoom) + point (:point session) color (:color session "#000000") - transform (str "translate(" (:x point) "," (:y point) ") scale(4)")] + transform (str/fmt "translate(%s, %s) scale(%s)" (:x point) (:y point) (/ 4 zoom))] [:g.multiuser-cursor {:transform transform} [:path {:fill color :d pointer-icon-path :font-family "sans-serif"}] [:g {:transform "translate(0 -291.708)"} - [:rect {:width "21.415" - :height "5.292" - :x "6.849" - :y "291.755" + [:rect {:width 25 + :height 5 + :x 7 + :y 291.5 :fill color - :fill-opacity ".893" + :fill-opacity 0.8 :paint-order "stroke fill markers" - :rx ".794" - :ry ".794"}] - [:text {:x "9.811" - :y "295.216" + :rx 1 + :ry 1}] + [:text {:x 8 + :y 295 + :width 25 + :height 5 + :overflow "hidden" :fill "#fff" - :stroke-width ".265" + :stroke-width 1 :font-family "Open Sans" - :font-size"2.91" - :font-weight "400" - :letter-spacing"0" - :style {:line-height "1.25"} - :word-spacing "0"} - (:fullname session)]]])) + :font-size 3 + :font-weight 400 + :letter-spacing 0 + :style { :line-height 1.25 } + :word-spacing 0} + (str (str/slice (:fullname session) 0 14) + (when (> (count (:fullname session)) 14) "..."))]]])) (mf/defc active-cursors {::mf/wrap [mf/memo]} diff --git a/frontend/src/app/main/ui/workspace/rules.cljs b/frontend/src/app/main/ui/workspace/rules.cljs index cf5644fe81..04db2ae6ea 100644 --- a/frontend/src/app/main/ui/workspace/rules.cljs +++ b/frontend/src/app/main/ui/workspace/rules.cljs @@ -43,7 +43,7 @@ (.translate dctx txfm 0) (.translate dctx 0 txfm)) - (obj/set! dctx "font" "12px sourcesanspro") + (obj/set! dctx "font" "12px worksans") (obj/set! dctx "fillStyle" "#7B7D85") (obj/set! dctx "strokeStyle" "#7B7D85") (obj/set! dctx "textAlign" "center") diff --git a/frontend/src/app/main/ui/workspace/selection.cljs b/frontend/src/app/main/ui/workspace/selection.cljs index 8e54075e58..eda6c18880 100644 --- a/frontend/src/app/main/ui/workspace/selection.cljs +++ b/frontend/src/app/main/ui/workspace/selection.cljs @@ -16,10 +16,12 @@ [rumext.alpha :as mf] [rumext.util :refer [map->obj]] [app.main.data.workspace :as dw] + [app.main.data.workspace.common :as dwc] [app.main.refs :as refs] [app.main.store :as st] [app.main.streams :as ms] [app.main.ui.cursors :as cur] + [app.common.math :as mth] [app.util.dom :as dom] [app.util.object :as obj] [app.common.geom.shapes :as geom] @@ -28,7 +30,6 @@ [app.util.debug :refer [debug?]] [app.main.ui.workspace.shapes.outline :refer [outline]])) - (def rotation-handler-size 25) (def resize-point-radius 4) (def resize-point-circle-radius 10) @@ -168,7 +169,6 @@ :cursor (if (#{:left :right} position) (cur/resize-ew rotation) (cur/resize-ns rotation)) }}])) - (mf/defc controls {::mf/wrap-props false} [props] @@ -180,7 +180,9 @@ current-transform (mf/deref refs/current-transform) selrect (geom/shape->rect-shape shape) - transform (geom/transform-matrix shape)] + transform (geom/transform-matrix shape) + + tr-shape (geom/transform-shape shape)] (when (not (#{:move :rotate} current-transform)) [:g.controls @@ -190,8 +192,7 @@ :transform transform :zoom zoom :color color}] - [:& outline {:shape (geom/transform-shape shape) - :color color}] + [:& outline {:shape tr-shape :color color}] ;; Handlers (for [{:keys [type position props]} (handlers-for-selection selrect)] diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 880691aef1..69e8fc5fee 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -11,6 +11,7 @@ "A workspace specific shapes wrappers." (:require [rumext.alpha :as mf] + [okulary.core :as l] [beicon.core :as rx] [app.main.streams :as ms] [app.main.ui.hooks :as hooks] @@ -21,6 +22,7 @@ [app.main.ui.shapes.image :as image] [app.main.data.workspace.selection :as dws] [app.main.store :as st] + [app.main.refs :as refs] ;; Shapes that has some peculiarities are defined in its own ;; namespace under app.ui.workspace.shapes.* prefix, all the @@ -68,18 +70,30 @@ (fn [] (st/emit! (dws/change-hover-state id false))))) +(defn make-is-moving-ref + [id] + (let [check-moving (fn [local] + (and (= :move (:transform local)) + (contains? (:selected local) id)))] + (l/derived check-moving refs/workspace-local))) + (mf/defc shape-wrapper {::mf/wrap [#(mf/memo' % shape-wrapper-memo-equals?)] ::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") frame (unchecked-get props "frame") + ghost? (unchecked-get props "ghost?") shape (geom/transform-shape frame shape) opts #js {:shape shape :frame frame} alt? (mf/use-state false) on-mouse-enter (use-mouse-enter shape) - on-mouse-leave (use-mouse-leave shape)] + on-mouse-leave (use-mouse-leave shape) + + moving-iref (mf/use-memo (mf/deps (:id shape)) + #(make-is-moving-ref (:id shape))) + moving? (mf/deref moving-iref)] (hooks/use-stream ms/keyboard-alt #(reset! alt? %)) @@ -88,7 +102,9 @@ (fn [] (on-mouse-leave)))) - (when (and shape (not (:hidden shape))) + (when (and shape + (or ghost? (not moving?)) + (not (:hidden shape))) [:g.shape-wrapper {:on-mouse-enter on-mouse-enter :on-mouse-leave on-mouse-leave :style {:cursor (if @alt? cur/duplicate nil)}} diff --git a/frontend/src/app/main/ui/workspace/shapes/common.cljs b/frontend/src/app/main/ui/workspace/shapes/common.cljs index 2df882f9e9..302c0cf482 100644 --- a/frontend/src/app/main/ui/workspace/shapes/common.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/common.cljs @@ -14,11 +14,11 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.keyboard :as kbd] - [app.main.ui.shapes.filters :as filters] [app.util.dom :as dom] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] - [app.common.geom.shapes :as geom])) + [app.common.geom.shapes :as geom] + [app.main.ui.shapes.shape :refer [shape-container]])) (defn- on-mouse-down [event {:keys [id type] :as shape}] @@ -47,7 +47,7 @@ (st/emit! (dw/select-shape id true))) (do (when-not (or (empty? selected) (kbd/shift? event)) - (st/emit! dw/deselect-all)) + (st/emit! (dw/deselect-all))) (st/emit! (dw/select-shape id)))) (st/emit! (dw/start-move-selected))))))) @@ -70,12 +70,11 @@ #(on-mouse-down % shape)) on-context-menu (mf/use-callback (mf/deps shape) - #(on-context-menu % shape)) - filter-id (mf/use-memo filters/get-filter-id)] - [:g.shape {:on-mouse-down on-mouse-down - :on-context-menu on-context-menu - :filter (filters/filter-str filter-id shape)} - [:& filters/filters {:filter-id filter-id :shape shape}] + #(on-context-menu % shape))] + + [:> shape-container {:shape shape + :on-mouse-down on-mouse-down + :on-context-menu on-context-menu} [:& component {:shape shape}]]))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 0fb0a0024c..433b22f3f3 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -19,13 +19,13 @@ [app.main.ui.workspace.shapes.common :as common] [app.main.data.workspace.selection :as dws] [app.main.ui.shapes.frame :as frame] - [app.main.ui.shapes.filters :as filters] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as geom] [app.util.dom :as dom] [app.main.streams :as ms] - [app.util.timers :as ts])) + [app.util.timers :as ts] + [app.main.ui.shapes.shape :refer [shape-container]])) (defn- frame-wrapper-factory-equals? [np op] @@ -45,9 +45,37 @@ (recur (first ids) (rest ids)) false)))))) -(defn make-selected-ref +(mf/defc frame-title + [{:keys [frame on-double-click on-mouse-over on-mouse-out]}] + (let [zoom (mf/deref refs/selected-zoom) + inv-zoom (/ 1 zoom) + {:keys [width x y]} frame + label-pos (gpt/point x (- y (/ 10 zoom)))] + [:text {:x 0 + :y 0 + :width width + :height 20 + :class "workspace-frame-label" + ;; Ensure that the label has always the same font + ;; size, regardless of zoom + ;; https://css-tricks.com/transforms-on-svg-elements/ + :transform (str + "scale(" inv-zoom ", " inv-zoom ") " + "translate(" (* zoom (:x label-pos)) ", " + (* zoom (:y label-pos)) + ")") + ;; User may also select the frame with single click in the label + :on-click on-double-click + :on-mouse-over on-mouse-over + :on-mouse-out on-mouse-out} + (:name frame)])) + +(defn make-is-moving-ref [id] - (l/derived #(contains? % id) refs/selected-shapes)) + (let [check-moving (fn [local] + (and (= :move (:transform local)) + (contains? (:selected local) id)))] + (l/derived check-moving refs/workspace-local))) (defn frame-wrapper-factory [shape-wrapper] @@ -59,11 +87,15 @@ [props] (let [shape (unchecked-get props "shape") objects (unchecked-get props "objects") + ghost? (unchecked-get props "ghost?") + + moving-iref (mf/use-memo (mf/deps (:id shape)) + #(make-is-moving-ref (:id shape))) + moving? (mf/deref moving-iref) selected-iref (mf/use-memo (mf/deps (:id shape)) - #(make-selected-ref (:id shape))) + #(refs/make-selected-ref (:id shape))) selected? (mf/deref selected-iref) - zoom (mf/deref refs/selected-zoom) on-mouse-down (mf/use-callback (mf/deps shape) #(common/on-mouse-down % shape)) @@ -71,20 +103,15 @@ #(common/on-context-menu % shape)) shape (geom/transform-shape shape) - {:keys [x y width height]} shape - - inv-zoom (/ 1 zoom) children (mapv #(get objects %) (:shapes shape)) ds-modifier (get-in shape [:modifiers :displacement]) - label-pos (gpt/point x (- y (/ 10 zoom))) - on-double-click (mf/use-callback (mf/deps (:id shape)) (fn [event] (dom/prevent-default event) - (st/emit! dw/deselect-all + (st/emit! (dw/deselect-all) (dw/select-shape (:id shape))))) on-mouse-over @@ -97,35 +124,22 @@ (mf/use-callback (mf/deps (:id shape)) (fn [] - (st/emit! (dws/change-hover-state (:id shape) false)))) + (st/emit! (dws/change-hover-state (:id shape) false))))] - filter-id (mf/use-memo filters/get-filter-id)] - - (when-not (:hidden shape) + (when (and shape + (or ghost? (not moving?)) + (not (:hidden shape))) [:g {:class (when selected? "selected") :on-context-menu on-context-menu :on-double-click on-double-click :on-mouse-down on-mouse-down} - [:text {:x 0 - :y 0 - :width width - :height 20 - :class "workspace-frame-label" - ;; Ensure that the label has always the same font - ;; size, regardless of zoom - ;; https://css-tricks.com/transforms-on-svg-elements/ - :transform (str - "scale(" inv-zoom ", " inv-zoom ") " - "translate(" (* zoom (:x label-pos)) ", " - (* zoom (:y label-pos)) - ")") - ;; User may also select the frame with single click in the label - :on-click on-double-click - :on-mouse-over on-mouse-over - :on-mouse-out on-mouse-out} - (:name shape)] - [:g.frame {:filter (filters/filter-str filter-id shape)} - [:& filters/filters {:filter-id filter-id :shape shape}] + + [:& frame-title {:frame shape + :on-context-menu on-context-menu + :on-double-click on-double-click + :on-mouse-down on-mouse-down}] + + [:> shape-container {:shape shape} [:& frame-shape {:shape shape :childs children}]]]))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/group.cljs b/frontend/src/app/main/ui/workspace/shapes/group.cljs index 0ef0d63bc0..bb91982dd3 100644 --- a/frontend/src/app/main/ui/workspace/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/group.cljs @@ -16,6 +16,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.workspace.shapes.common :as common] + [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.group :as group] [app.util.dom :as dom] [app.main.streams :as ms] @@ -46,8 +47,8 @@ on-context-menu (mf/use-callback (mf/deps shape) #(common/on-context-menu % shape)) - childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape))) - childs (mf/deref childs-ref) + childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape))) + childs (mf/deref childs-ref) is-child-selected-ref (mf/use-memo (mf/deps (:id shape)) #(refs/is-child-selected? (:id shape))) @@ -55,6 +56,15 @@ is-child-selected? (mf/deref is-child-selected-ref) + mask-id (when (:masked-group? shape) (first (:shapes shape))) + + is-mask-selected-ref + (mf/use-memo (mf/deps mask-id) + #(refs/make-selected-ref mask-id)) + + is-mask-selected? + (mf/deref is-mask-selected-ref) + on-double-click (mf/use-callback (mf/deps (:id shape)) @@ -63,14 +73,14 @@ (dom/prevent-default event) (st/emit! (dw/select-inside-group (:id shape) @ms/mouse-position))))] - [:g.shape - {:on-mouse-down on-mouse-down - :on-context-menu on-context-menu - :on-double-click on-double-click} - + [:> shape-container {:shape shape + :on-mouse-down on-mouse-down + :on-context-menu on-context-menu + :on-double-click on-double-click} [:& group-shape {:frame frame :shape shape :childs childs - :is-child-selected? is-child-selected?}]])))) + :is-child-selected? is-child-selected? + :expand-mask is-mask-selected?}]])))) diff --git a/frontend/src/app/main/ui/workspace/shapes/interactions.cljs b/frontend/src/app/main/ui/workspace/shapes/interactions.cljs index fb36edad84..d4a8684fea 100644 --- a/frontend/src/app/main/ui/workspace/shapes/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/interactions.cljs @@ -32,7 +32,7 @@ (do (dom/stop-propagation event) (when-not (empty? selected) - (st/emit! dw/deselect-all)) + (st/emit! (dw/deselect-all))) (st/emit! (dw/select-shape id)) (st/emit! (dw/start-create-interaction)))) diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs index d128509aab..81fd438163 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs @@ -11,18 +11,19 @@ (:require [rumext.alpha :as mf] [app.common.data :as d] + [app.util.dom :as dom] + [app.util.timers :as ts] + [app.main.streams :as ms] [app.main.constants :as c] - [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] + [app.main.data.workspace :as dw] + [app.main.data.workspace.drawing :as dr] [app.main.ui.keyboard :as kbd] [app.main.ui.shapes.path :as path] [app.main.ui.shapes.filters :as filters] - [app.main.ui.workspace.shapes.common :as common] - [app.main.data.workspace.drawing :as dr] - [app.util.dom :as dom] - [app.main.streams :as ms] - [app.util.timers :as ts])) + [app.main.ui.shapes.shape :refer [shape-container]] + [app.main.ui.workspace.shapes.common :as common])) (mf/defc path-wrapper {::mf/wrap-props false} @@ -42,13 +43,13 @@ (do (dom/stop-propagation event) (dom/prevent-default event) - (st/emit! (dw/start-edition-mode (:id shape))))))) - filter-id (mf/use-memo filters/get-filter-id)] + (st/emit! (dw/start-edition-mode (:id shape)))))))] - [:g.shape {:on-double-click on-double-click - :on-mouse-down on-mouse-down - :on-context-menu on-context-menu - :filter (filters/filter-str filter-id shape)} - [:& filters/filters {:filter-id filter-id :shape shape}] - [:& path/path-shape {:shape shape :background? true}]])) + [:> shape-container {:shape shape + :on-double-click on-double-click + :on-mouse-down on-mouse-down + :on-context-menu on-context-menu} + + [:& path/path-shape {:shape shape + :background? true}]])) diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs index 5df7525083..c389583f50 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs @@ -9,30 +9,34 @@ (ns app.main.ui.workspace.shapes.text (:require - [cuerdas.core :as str] + ["slate" :as slate] + ["slate-react" :as rslate] [goog.events :as events] [goog.object :as gobj] + [cuerdas.core :as str] [rumext.alpha :as mf] + [beicon.core :as rx] + [app.util.color :as color] + [app.util.dom :as dom] + [app.util.text :as ut] + [app.util.object :as obj] + [app.util.color :as uc] + [app.util.timers :as timers] [app.common.data :as d] + [app.common.geom.shapes :as geom] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.fonts :as fonts] [app.main.data.workspace :as dw] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.texts :as dwt] - [app.main.refs :as refs] - [app.main.store :as st] [app.main.ui.cursors :as cur] [app.main.ui.workspace.shapes.common :as common] [app.main.ui.shapes.text :as text] [app.main.ui.keyboard :as kbd] [app.main.ui.context :as muc] [app.main.ui.shapes.filters :as filters] - [app.main.fonts :as fonts] - [app.util.color :as color] - [app.util.dom :as dom] - [app.common.geom.shapes :as geom] - [app.util.object :as obj] - [app.util.timers :as timers] - ["slate" :as slate] - ["slate-react" :as rslate]) + [app.main.ui.shapes.shape :refer [shape-container]]) (:import goog.events.EventType goog.events.KeyCodes)) @@ -56,13 +60,15 @@ {::mf/wrap-props false} [props] (let [{:keys [id x1 y1 content group grow-type width height ] :as shape} (unchecked-get props "shape") - - selected (mf/deref refs/selected-shapes) + selected-iref (mf/use-memo (mf/deps (:id shape)) + #(refs/make-selected-ref (:id shape))) + selected? (mf/deref selected-iref) edition (mf/deref refs/selected-edition) - zoom (mf/deref refs/selected-zoom) + current-transform (mf/deref refs/current-transform) + + render-editor (mf/use-state false) + edition? (= edition id) - selected? (and (contains? selected id) - (= (count selected) 1)) embed-resources? (mf/use-ctx muc/embed-ctx) @@ -74,29 +80,33 @@ (dom/stop-propagation event) (dom/prevent-default event) (when selected? - (st/emit! (dw/start-edition-mode (:id shape))))) + (st/emit! (dw/start-edition-mode (:id shape)))))] - filter-id (mf/use-memo filters/get-filter-id)] + (mf/use-effect + (mf/deps shape edition selected? current-transform) + (fn [] (let [check? (and (#{:auto-width :auto-height} (:grow-type shape)) + selected? + (not edition?) + (not embed-resources?) + (nil? current-transform)) + result (timers/schedule #(reset! render-editor check?))] + #(rx/dispose! result)))) - [:g.shape {:on-double-click on-double-click - :on-mouse-down on-mouse-down - :on-context-menu on-context-menu - :filter (filters/filter-str filter-id shape)} - [:& filters/filters {:filter-id filter-id :shape shape}] - [:* - (when (and (not edition?) (not embed-resources?)) - [:g {:opacity 0 - :style {:pointer-events "none"}} - ;; We only render the component for its side-effect - [:& text-shape-edit {:shape shape - :zoom zoom - :read-only? true}]]) - - (if edition? + [:> shape-container {:shape shape + :on-double-click on-double-click + :on-mouse-down on-mouse-down + :on-context-menu on-context-menu} + (when @render-editor + [:g {:opacity 0 + :style {:pointer-events "none"}} + ;; We only render the component for its side-effect [:& text-shape-edit {:shape shape - :zoom zoom}] - [:& text/text-shape {:shape shape - :selected? selected?}])]])) + :read-only? true}]]) + + (if edition? + [:& text-shape-edit {:shape shape}] + [:& text/text-shape {:shape shape + :selected? selected?}])])) ;; --- Text Editor Rendering @@ -135,20 +145,37 @@ text-transform (obj/get data "text-transform") line-height (obj/get data "line-height") - font-id (obj/get data "font-id" fonts/default-font) + font-id (obj/get data "font-id" (:font-id ut/default-text-attrs)) font-variant-id (obj/get data "font-variant-id") font-family (obj/get data "font-family") font-size (obj/get data "font-size") + + ;; Old properties for backwards compatibility fill (obj/get data "fill") - opacity (obj/get data "opacity") + opacity (obj/get data "opacity" 1) + + fill-color (obj/get data "fill-color" fill) + fill-opacity (obj/get data "fill-opacity" opacity) + fill-color-gradient (obj/get data "fill-color-gradient" nil) + fill-color-gradient (when fill-color-gradient + (-> (js->clj fill-color-gradient :keywordize-keys true) + (update :type keyword))) + + fill-color-ref-id (obj/get data "fill-color-ref-id") + fill-color-ref-file (obj/get data "fill-color-ref-file") + + [r g b a] (uc/hex->rgba fill-color fill-opacity) + background (if fill-color-gradient + (uc/gradient->css (js->clj fill-color-gradient)) + (str/format "rgba(%s, %s, %s, %s)" r g b a)) + fontsdb (deref fonts/fontsdb) base #js {:textDecoration text-decoration - :color fill - :opacity opacity :textTransform text-transform - :lineHeight (or line-height "inherit")}] + :lineHeight (or line-height "inherit") + "--text-color" background}] (when (and (string? letter-spacing) (pos? (alength letter-spacing))) @@ -223,7 +250,9 @@ childs (obj/get props "children") data (obj/get props "leaf") style (generate-text-styles data) - attrs (obj/set! attrs "style" style)] + attrs (-> attrs + (obj/set! "style" style) + (obj/set! "className" "text-node"))] [:> :span attrs childs])) (defn- render-element @@ -264,11 +293,19 @@ children-count (->> node :children (map content-size) (reduce +))] (+ current children-count))) +(defn fix-gradients + "Fix for the gradient types that need to be keywords" + [content] + (let [fix-node + (fn [node] + (d/update-in-when node [:fill-color-gradient :type] keyword))] + (ut/map-node fix-node content))) + (mf/defc text-shape-edit {::mf/wrap [mf/memo]} - [{:keys [shape zoom read-only?] :or {read-only? false} :as props}] + [{:keys [shape read-only?] :or {read-only? false} :as props}] (let [{:keys [id x y width height content grow-type]} shape - + zoom (mf/deref refs/selected-zoom) state (mf/use-state #(parse-content content)) editor (mf/use-memo #(dwt/create-editor)) self-ref (mf/use-ref) @@ -289,13 +326,16 @@ (dom/prevent-default event) (dom/stop-propagation event) + (let [sidebar (dom/get-element "settings-bar") + assets (dom/get-element-by-class "assets-bar") cpicker (dom/get-element-by-class "colorpicker-tooltip") self (mf/ref-val self-ref) target (dom/get-target event) selecting? (mf/ref-val selecting-ref)] - (when-not (or (.contains sidebar target) - (.contains self target) + (when-not (or (and sidebar (.contains sidebar target)) + (and assets (.contains assets target)) + (and self (.contains self target)) (and cpicker (.contains cpicker target))) (if selecting? (mf/set-ref-val! selecting-ref false) @@ -313,13 +353,15 @@ (fn [event] (dom/stop-propagation event) (when (= (.-keyCode event) 27) ; ESC - (on-close))) + (do + (st/emit! :interrupt) + (on-close)))) on-mount (fn [] (when (not read-only?) - (let [lkey1 (events/listen js/document EventType.CLICK on-click-outside) - lkey2 (events/listen js/document EventType.KEYUP on-key-up)] + (let [lkey1 (events/listen (dom/get-root) EventType.CLICK on-click-outside) + lkey2 (events/listen (dom/get-root) EventType.KEYUP on-key-up)] (st/emit! (dwt/assign-editor id editor) dwc/start-undo-transaction) @@ -339,8 +381,10 @@ (fn [val] (when (not read-only?) (let [content (js->clj val :keywordize-keys true) - content (first content)] - (st/emit! (dw/update-shape id {:content content})) + content (first content) + content (fix-gradients content)] + ;; Append timestamp so we can react to cursor change events + (st/emit! (dw/update-shape id {:content (assoc content :ts (js->clj (.now js/Date)))})) (reset! state val) (reset! content-var content)))))] @@ -393,7 +437,8 @@ :x x :y y :width (if (= :auto-width grow-type) 10000 width) :height height} - [:style "span { line-height: inherit; }"] + [:style "span { line-height: inherit; } + .text-node { background: var(--text-color); -webkit-text-fill-color: transparent; -webkit-background-clip: text;"] [:> rslate/Slate {:editor editor :value @state :on-change on-change} diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index b9d7fe2533..df85defc7a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -9,42 +9,42 @@ (ns app.main.ui.workspace.sidebar (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] + [app.main.refs :as refs] + [app.main.ui.workspace.comments :refer [comments-sidebar]] + [app.main.ui.workspace.sidebar.assets :refer [assets-toolbox]] [app.main.ui.workspace.sidebar.history :refer [history-toolbox]] [app.main.ui.workspace.sidebar.layers :refer [layers-toolbox]] [app.main.ui.workspace.sidebar.options :refer [options-toolbox]] [app.main.ui.workspace.sidebar.sitemap :refer [sitemap]] - [app.main.ui.workspace.sidebar.assets :refer [assets-toolbox]])) + [cuerdas.core :as str] + [rumext.alpha :as mf])) ;; --- Left Sidebar (Component) (mf/defc left-sidebar {:wrap [mf/memo]} - [{:keys [layout page-id file project] :as props}] + [{:keys [layout ] :as props}] [:aside.settings-bar.settings-bar-left [:div.settings-bar-inside {:data-layout (str/join "," layout)} - (when (contains? layout :sitemap) - [:& sitemap {:file file - :page-id page-id - :layout layout}]) - (when (contains? layout :document-history) - [:& history-toolbox]) (when (contains? layout :layers) - [:& layers-toolbox]) + [:* + [:& sitemap {:layout layout}] + [:& layers-toolbox]]) + + (when (contains? layout :document-history) + [:& history-toolbox]) + (when (contains? layout :assets) - [:& assets-toolbox {:team-id (:team-id project) - :file file}])]]) + [:& assets-toolbox])]]) ;; --- Right Sidebar (Component) (mf/defc right-sidebar - [{:keys [layout page-id file-id local] :as props}] - [:aside#settings-bar.settings-bar - [:div.settings-bar-inside - (when (contains? layout :element-options) - [:& options-toolbox - {:page-id page-id - :file-id file-id - :local local}])]]) + [{:keys [local] :as props}] + (let [drawing-tool (:tool (mf/deref refs/workspace-drawing))] + [:aside.settings-bar + [:div.settings-bar-inside + (if (= drawing-tool :comments) + [:& comments-sidebar] + [:& options-toolbox {:local local}])]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index ea76d98706..4cf2d11533 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -17,31 +17,36 @@ [app.common.pages-helpers :as cph] [app.common.uuid :as uuid] [app.config :as cfg] + [app.main.data.colors :as dc] + [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.libraries :as dwl] - [app.main.data.colors :as dc] + [app.main.data.workspace.texts :as dwt] + [app.main.exports :as exports] [app.main.refs :as refs] [app.main.store :as st] - [app.main.exports :as exports] + [app.main.ui.components.color-bullet :as bc] [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.tab-container :refer [tab-container tab-element]] + [app.main.ui.context :as ctx] [app.main.ui.icons :as i] [app.main.ui.keyboard :as kbd] - [app.main.ui.modal :as modal] [app.main.ui.shapes.icon :as icon] + [app.main.ui.workspace.sidebar.options.typography :refer [typography-entry]] [app.util.data :refer [matches-search]] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] [app.util.i18n :as i18n :refer [tr t]] [app.util.router :as rt] + [app.util.text :as ut] [app.util.timers :as timers] [cuerdas.core :as str] [okulary.core :as l] [rumext.alpha :as mf])) (mf/defc components-box - [{:keys [file-id local? components] :as props}] + [{:keys [file-id local? components open? on-open on-close] :as props}] (let [state (mf/use-state {:menu-open false :top nil :left nil @@ -69,33 +74,34 @@ on-drag-start (mf/use-callback - (fn [component-id event] + (fn [component event] (dnd/set-data! event "app/component" {:file-id (if local? nil file-id) - :component-id component-id}) + :component component}) (dnd/set-allowed-effect! event "move")))] [:div.asset-group - [:div.group-title - (tr "workspace.assets.components") + [:div.group-title {:class (when (not open?) "closed")} + [:span {:on-click #(if open? (on-close) (on-open))} i/arrow-slide (tr "workspace.assets.components")] [:span (str "\u00A0(") (count components) ")"]] ;; Unicode 00A0 is non-breaking space - [:div.group-grid.big - (for [component components] - [:div.grid-cell {:key (:id component) - :draggable true - :on-context-menu (on-context-menu (:id component)) - :on-drag-start (partial on-drag-start (:id component))} - [:& exports/component-svg {:group (get-in component [:objects (:id component)]) - :objects (:objects component)}] - [:div.cell-name (:name component)]]) + (when open? + [:div.group-grid.big + (for [component components] + [:div.grid-cell {:key (:id component) + :draggable true + :on-context-menu (on-context-menu (:id component)) + :on-drag-start (partial on-drag-start component)} + [:& exports/component-svg {:group (get-in component [:objects (:id component)]) + :objects (:objects component)}] + [:div.cell-name (:name component)]])]) - (when local? - [:& context-menu - {:selectable false - :show (:menu-open @state) - :on-close #(swap! state assoc :menu-open false) - :top (:top @state) - :left (:left @state) - :options [[(tr "workspace.assets.delete") on-delete]]}])]])) + (when local? + [:& context-menu + {:selectable false + :show (:menu-open @state) + :on-close #(swap! state assoc :menu-open false) + :top (:top @state) + :left (:left @state) + :options [[(tr "workspace.assets.delete") on-delete]]}])])) (mf/defc graphics-box [{:keys [file-id local? objects open? on-open on-close] :as props}] @@ -186,7 +192,7 @@ :options [[(tr "workspace.assets.delete") on-delete]]}])])])) (mf/defc color-item - [{:keys [color local? locale file-id] :as props}] + [{:keys [color local? locale] :as props}] (let [rename? (= (:color-for-rename @refs/workspace-local) (:id color)) id (:id color) input-ref (mf/use-ref) @@ -194,20 +200,27 @@ :top nil :left nil :editing rename?}) + + default-name (cond + (:gradient color) (bc/gradient-type->string (get-in color [:gradient :type])) + (:color color) (:color color) + :else (:value color)) + click-color (fn [event] (let [ids (get-in @st/state [:workspace-local :selected])] (if (kbd/shift? event) - (st/emit! (dc/change-stroke ids (:value color) id (if local? nil file-id))) - (st/emit! (dc/change-fill ids (:value color) id (if local? nil file-id)))))) + (st/emit! (dc/change-stroke ids color)) + (st/emit! (dc/change-fill ids color))))) rename-color (fn [name] (st/emit! (dwl/update-color (assoc color :name name)))) edit-color - (fn [value] - (st/emit! (dwl/update-color (assoc color :value value)))) + (fn [new-color] + (let [updated-color (merge new-color (select-keys color [:id :file-id :name]))] + (st/emit! (dwl/update-color updated-color)))) delete-color (fn [] @@ -241,8 +254,7 @@ {:x (.-clientX event) :y (.-clientY event) :on-accept edit-color - :value (:value color) - :disable-opacity true + :data color :position :right})) on-context-menu @@ -265,7 +277,8 @@ nil)) [:div.group-list-item {:on-context-menu on-context-menu} - [:div.color-block {:style {:background-color (:value color)}}] + [:& bc/color-bullet {:color color}] + (if (:editing @state) [:input.element-name {:type "text" @@ -274,12 +287,13 @@ :on-key-down input-key-down :auto-focus true :default-value (:name color "")}] + [:div.name-block {:on-double-click rename-color-clicked :on-click click-color} (:name color) - (when-not (= (:name color) (:value color)) - [:span (:value color)])]) + (when-not (= (:name color) default-name) + [:span default-name])]) (when local? [:& context-menu {:selectable false @@ -308,8 +322,8 @@ {:x (.-clientX event) :y (.-clientY event) :on-accept add-color - :value "#406280" - :disable-opacity true + :data {:color "#406280" + :opacity 1} :position :right})))] [:div.asset-group [:div.group-title {:class (when (not open?) "closed")} @@ -320,11 +334,117 @@ (when open? [:div.group-list (for [color colors] - [:& color-item {:key (:id color) - :color color - :file-id file-id - :local? local? - :locale locale}])])])) + (let [color (cond-> color + (:value color) (assoc :color (:value color) :opacity 1) + (:value color) (dissoc :value) + true (assoc :file-id (when (not local?) file-id)))] + [:& color-item {:key (:id color) + :color color + :local? local? + :locale locale}]))])])) + +(mf/defc typography-box + [{:keys [file file-id local? typographies locale open? on-open on-close] :as props}] + + (let [state (mf/use-state {:detail-open? false + :menu-open? false + :top nil + :left nil}) + + selected (mf/deref refs/selected-shapes) + local (deref refs/workspace-local) + + add-typography + (mf/use-callback + (mf/deps file-id) + (fn [value opacity] + (st/emit! (dwl/add-typography ut/default-typography)))) + + handle-change + (mf/use-callback + (mf/deps file-id) + (fn [typography changes] + (st/emit! (dwl/update-typography (merge typography changes))))) + + handle-typography-selection + (fn [typography] + (let [attrs (merge + {:typography-ref-file (when-not local? file-id) + :typography-ref-id (:id typography)} + (d/without-keys typography [:id :name]))] + (run! #(st/emit! (dwt/update-text-attrs {:id % :editor (get-in local [:editors %]) :attrs attrs})) + selected))) + + on-context-menu + (fn [id event] + + (when local? + (let [pos (dom/get-client-position event) + top (:y pos) + left (- (:x pos) 20)] + (dom/prevent-default event) + (swap! state assoc + :menu-open? true + :top top + :left left + :id id)))) + + closed-typography-edit + (mf/use-callback + (mf/deps file-id) + (fn [event] )) + + handle-rename-typography-clicked + (fn [] + (st/emit! #(assoc-in % [:workspace-local :rename-typography] (:id @state)))) + + handle-edit-typography-clicked + (fn [] + (st/emit! #(assoc-in % [:workspace-local :edit-typography] (:id @state)))) + + handle-delete-typography + (fn [] + (st/emit! (dwl/delete-typography (:id @state)))) + + editting-id (or (:rename-typography local) (:edit-typography local))] + + (mf/use-effect + (mf/deps local) + (fn [] + (when (:rename-typography local) + (st/emit! #(update % :workspace-local dissoc :rename-typography))) + (when (:edit-typography local) + (st/emit! #(update % :workspace-local dissoc :edit-typography))))) + + [:div.asset-group + [:div.group-title {:class (when (not open?) "closed")} + [:span {:on-click #(if open? (on-close) (on-open))} i/arrow-slide (t locale "workspace.assets.typography")] + [:span.num-assets (str "\u00A0(") (count typographies) ")"] ;; Unicode 00A0 is non-breaking space + (when local? + [:div.group-button {:on-click add-typography} i/plus])] + + [:& context-menu + {:selectable false + :show (:menu-open? @state) + :on-close #(swap! state assoc :menu-open? false) + :top (:top @state) + :left (:left @state) + :options [[(t locale "workspace.assets.rename") handle-rename-typography-clicked] + [(t locale "workspace.assets.edit") handle-edit-typography-clicked] + [(t locale "workspace.assets.delete") handle-delete-typography]]}] + (when open? + [:div.group-list + (for [typography (sort-by :ts typographies)] + [:& typography-entry + {:key (:id typography) + :typography typography + :file file + :read-only? (not local?) + :on-context-menu #(on-context-menu (:id typography) %) + :on-change #(handle-change typography %) + :on-select #(handle-typography-selection typography) + :editting? (= editting-id (:id typography)) + :focus-name? (= (:rename-typography local) (:id typography))}])])])) (defn file-colors-ref [id] @@ -354,6 +474,15 @@ (vals (get-in state [:workspace-libraries id :data :components]))))) st/state =)) +(defn file-typography-ref + [id] + (l/derived (fn [state] + (let [wfile (:workspace-file state)] + (if (= (:id wfile) id) + (vals (get-in wfile [:data :typographies])) + (vals (get-in state [:workspace-libraries id :data :typographies]))))) + st/state =)) + (defn apply-filters [coll filters] (->> coll @@ -369,7 +498,10 @@ router (mf/deref refs/router) toggle-open #(swap! open? not) - toggles (mf/use-state #{:graphics :colors}) + toggles (mf/use-state #{:components + :graphics + :colors + :typographies}) url (rt/resolve router :workspace {:project-id (:project-id file) @@ -379,6 +511,9 @@ colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file))) colors (apply-filters (mf/deref colors-ref) filters) + typography-ref (mf/use-memo (mf/deps (:id file)) #(file-typography-ref (:id file))) + typographies (apply-filters (mf/deref typography-ref) filters) + media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file))) media (apply-filters (mf/deref media-ref) filters) @@ -414,12 +549,19 @@ show-colors? (and (or (= (:box filters) :all) (= (:box filters) :colors)) (or (> (count colors) 0) + (str/empty? (:term filters)))) + show-typography? (and (or (= (:box filters) :all) + (= (:box filters) :typographies)) + (or (> (count typographies) 0) (str/empty? (:term filters))))] [:div.tool-window-content (when show-components? [:& components-box {:file-id (:id file) :local? local? - :components components}]) + :components components + :open? (contains? @toggles :components) + :on-open #(swap! toggles conj :components) + :on-close #(swap! toggles disj :components)}]) (when show-graphics? [:& graphics-box {:file-id (:id file) :local? local? @@ -436,23 +578,34 @@ :on-open #(swap! toggles conj :colors) :on-close #(swap! toggles disj :colors)}]) + (when show-typography? + [:& typography-box {:file file + :file-id (:id file) + :local? local? + :locale locale + :typographies typographies + :open? (contains? @toggles :typographies) + :on-open #(swap! toggles conj :typographies) + :on-close #(swap! toggles disj :typographies)}]) + (when (and (not show-components?) (not show-graphics?) (not show-colors?)) [:div.asset-group [:div.group-title (t locale "workspace.assets.not-found")]])]))])) (mf/defc assets-toolbox - [{:keys [team-id file] :as props}] + [] (let [libraries (mf/deref refs/workspace-libraries) + file (mf/deref refs/workspace-file) locale (mf/deref i18n/locale) + team-id (mf/use-ctx ctx/current-team-id) filters (mf/use-state {:term "" :box :all}) on-search-term-change (mf/use-callback (mf/deps team-id) (fn [event] - (let [value (-> (dom/get-target event) - (dom/get-value))] + (let [value (dom/get-target-val event)] (swap! filters assoc :term value)))) on-search-clear-click @@ -495,8 +648,10 @@ [:select.input-select {:value (:box @filters) :on-change on-box-filter-change} [:option {:value ":all"} (t locale "workspace.assets.box-filter-all")] - [:option {:value ":graphics"} (t locale "workspace.assets.box-filter-graphics")] - [:option {:value ":colors"} (t locale "workspace.assets.box-filter-colors")]]]] + [:option {:value ":components"} (t locale "workspace.assets.components")] + [:option {:value ":graphics"} (t locale "workspace.assets.graphics")] + [:option {:value ":colors"} (t locale "workspace.assets.colors")] + [:option {:value ":typographies"} (t locale "workspace.assets.typography")]]]] [:div.libraries-wrapper [:& file-library diff --git a/frontend/src/app/main/ui/workspace/sidebar/history.cljs b/frontend/src/app/main/ui/workspace/sidebar/history.cljs index 369daf8cb5..aebc62bb59 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/history.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/history.cljs @@ -11,6 +11,7 @@ (:require [rumext.alpha :as mf] [cuerdas.core :as str] + [app.common.data :as d] [app.main.ui.icons :as i] [app.main.data.history :as udh] [app.main.data.workspace :as dw] @@ -27,40 +28,275 @@ (def workspace-undo (l/derived :workspace-undo st/state)) -(mf/defc undo-entry [{:keys [index entry objects is-transaction?] :or {is-transaction? false}}] - (let [{:keys [redo-changes]} entry] - [:li.undo-entry {:class (when is-transaction? "transaction")} - (for [[idx-change {:keys [type id operations]}] (map-indexed vector redo-changes)] - [:div.undo-entry-change {:key (str "change-" idx-change)} - [:div.undo-entry-change-data (when type (str type)) " " (when id (str (get-in objects [id :name] (subs (str id) 0 8))))] - (when operations - [:div.undo-entry-change-data (str/join ", " (map (comp name :attr) operations))])])])) +(defn get-object + "Searchs for a shape inside the objects list or inside the undo history" + [id entries objects] + (let [search-deleted-shape + (fn [id entries] + (let [search-obj (fn [obj] (and (= (:type obj) :add-obj) + (= (:id obj) id))) + search-delete-entry (fn [{:keys [undo-changes redo-changes]}] + (or (d/seek search-obj undo-changes) + (d/seek search-obj redo-changes))) + {:keys [obj]} (->> entries (d/seek search-delete-entry) search-delete-entry)] + obj))] + (or (get objects id) + (search-deleted-shape id entries)))) + + +(defn extract-operation + "Generalizes the type of operation for different types of change" + [change] + (case (:type change) + (:add-obj :add-page :add-color :add-media :add-component :add-typography) :new + (:mod-obj :mod-page :mod-color :mod-media :mod-component :mod-typography) :modify + (:del-obj :del-page :del-color :del-media :del-component :del-typography) :delete + :mov-objects :move + nil)) + +(defn parse-change + "Given a single change parses the information into an uniform map" + [change] + (let [r (fn [type id] + {:type type + :operation (extract-operation change) + :detail (:operations change) + :id (cond + (and (coll? id) (= 1 (count id))) (first id) + (coll? id) :multiple + :else id)})] + (case (:type change) + :set-option (r :page (:page-id change)) + (:add-obj + :mod-obj + :del-obj) (r :shape (:id change)) + :reg-objects nil + :mov-objects (r :shape (:shapes change)) + (:add-page + :mod-page :del-page + :mov-page) (r :page (:id change)) + (:add-color + :mod-color) (r :color (get-in change [:color :id])) + :del-color (r :color (:id change)) + :add-recent-color nil + (:add-media + :mod-media) (r :media (get-in change [:object :id])) + :del-media (r :media (:id change)) + (:add-component + :mod-component + :del-component) (r :component (:id change)) + (:add-typography + :mod-typography) (r :typography (get-in change [:typography :id])) + :del-typography (r :typography (:id change)) + nil))) + +(defn resolve-shape-types + "Retrieve the type to be shown to the user" + [entries objects] + (let [resolve-type (fn [{:keys [type id]}] + (if (or (not= type :shape) (= id :multiple)) + type + (:type (get-object id entries objects)))) + + map-fn (fn [entry] + (if (and (= (:type entry) :shape) + (not= (:id entry) :multiple)) + (assoc entry :type (resolve-type entry)) + entry))] + (fn [entries] + (map map-fn entries)))) + +(defn entry-type->message + "Formats the message that will be displayed to the user" + [locale type multiple?] + (let [arity (if multiple? "multiple" "single") + attribute (name (or type :multiple))] + (t locale (str/format "workspace.undo.entry.%s.%s" arity attribute)))) + +(defn entry->message [locale entry] + (let [value (entry-type->message locale (:type entry) (= :multiple (:id entry)))] + (case (:operation entry) + :new (t locale "workspace.undo.entry.new" value) + :modify (t locale "workspace.undo.entry.modify" value) + :delete (t locale "workspace.undo.entry.delete" value) + :move (t locale "workspace.undo.entry.move" value) + (t locale "workspace.undo.entry.unknown" value)))) + +(defn entry->icon [{:keys [type]}] + (case type + :page i/file-html + :shape i/layers + :rect i/box + :circle i/circle + :text i/text + :curve i/curve + :path i/curve + :frame i/artboard + :group i/folder + :color i/palette + :typography i/titlecase + :component i/component + :media i/image + :image i/image + i/layers)) + +(defn is-shape? [type] + #{:shape :rect :circle :text :curve :path :frame :group}) + +(defn parse-entry [{:keys [redo-changes]}] + (->> redo-changes + (map parse-change))) + +(defn safe-name [maybe-keyword] + (if (keyword? maybe-keyword) + (name maybe-keyword) + maybe-keyword)) + +(defn select-entry + "Selects the entry the user will see inside a list of posible entries. + Sometimes the result will be a combination." + [candidates] + (let [;; Group by id and type + entries (->> candidates + (remove nil?) + (group-by #(vector (:type %) (:operation %) (:id %)) )) + + single? (fn [coll] (= (count coll) 1)) + + ;; Retrieve also by-type and by-operation + types (group-by first (keys entries)) + operations (group-by second (keys entries)) + + ;; The cases for the selection of the representative entry are a bit + ;; convoluted. Best to read the comments to clarify. + ;; At this stage we have cleaned the entries but we can have a batch + ;; of operations for a single undo-entry. We want to select the + ;; one that is most interesting for the user. + selected-entry + (cond + ;; If we only have one operation over one shape we return the last change + (single? entries) + (-> entries (get (first (keys entries))) (last)) + + ;; If we're creating an object it will have priority + (single? (:new operations)) + (-> entries (get (first (:new operations))) (last)) + + ;; If there is only a deletion of 1 group we retrieve this operation because + ;; the others will be the children + (single? (filter #(= :group (first %)) (:delete operations))) + (-> entries (get (first (filter #(= :group (first %)) (:delete operations)))) (last)) + + ;; If there is a move of shapes will have priority + (single? (:move operations)) + (-> entries (get (first (:move operations))) (last)) + + ;; Otherwise we could have the same operation between several + ;; types (i.e: delete various shapes). If that happens we return + ;; the operation with `:multiple` id + (single? operations) + {:type (if (every? is-shape? (keys types)) :shape :multiple) + :id :multiple + :operation (first (keys operations))} + + ;; Finally, if we have several operations over several shapes we return + ;; `:multiple` for operation and type and join the last of the operations for + ;; each shape + :else + {:type :multiple + :id :multiple + :operation :multiple}) + + + ;; We add to the detail the information depending on the type of operation + detail + (case (:operation selected-entry) + :new (:id selected-entry) + :modify (->> candidates + (filter #(= :modify (:operation %))) + (group-by :id) + (d/mapm (fn [k v] (->> v + (mapcat :detail) + (map (comp safe-name :attr)) + (remove nil?) + (into #{}))))) + :delete (->> candidates + (filter #(= :delete (:operation %))) + (map :id)) + candidates)] + + (assoc selected-entry :detail detail))) + +(defn parse-entries [entries objects] + (->> entries + (map parse-entry) + (map (resolve-shape-types entries objects)) + (mapv select-entry))) + +(mf/defc history-entry-details [{:keys [entry]}] + (let [{entries :items} (mf/deref workspace-undo) + objects (mf/deref refs/workspace-page-objects)] + + [:div.history-entry-detail + (case (:operation entry) + :new + (:name (get-object (:detail entry) entries objects)) + + :delete + [:ul.history-entry-details-list + (for [id (:detail entry)] + (let [shape-name (:name (get-object id entries objects))] + [:li {:key id} shape-name]))] + + + :modify + [:ul.history-entry-details-list + (for [[id attributes] (:detail entry)] + (let [shape-name (:name (get-object id entries objects))] + [:li {:key id} + [:div shape-name] + [:div (str/join ", " attributes)]]))] + + nil)])) + +(mf/defc history-entry [{:keys [locale entry disabled? current?]}] + (let [hover? (mf/use-state false) + show-detail? (mf/use-state false)] + [:div.history-entry {:class (dom/classnames + :disabled disabled? + :current current? + :hover @hover? + :show-detail @show-detail?) + :on-mouse-enter #(reset! hover? true) + :on-mouse-leave #(reset! hover? false) + :on-click #(when (:detail entry) + (swap! show-detail? not)) + } + [:div.history-entry-summary + [:div.history-entry-summary-icon (entry->icon entry)] + [:div.history-entry-summary-text (entry->message locale entry)] + (when (:detail entry) + [:div.history-entry-summary-button i/arrow-slide])] + + (when show-detail? + [:& history-entry-details {:entry entry}])])) (mf/defc history-toolbox [] (let [locale (mf/deref i18n/locale) + objects (mf/deref refs/workspace-page-objects) {:keys [items index transaction]} (mf/deref workspace-undo) - objects (mf/deref refs/workspace-page-objects)] + entries (parse-entries items objects)] [:div.history-toolbox - [:div.history-toolbox-title "History"] - [:ul.undo-history - [:* - (when (and - (> (count items) 0) - (or (nil? index) - (>= index (count items)))) - [:hr.separator]) - - (when transaction - [:& undo-entry {:key (str "transaction") - :objects objects - :is-transaction? true - :entry transaction}]) - - (for [[idx-entry entry] (->> items (map-indexed vector) reverse)] - [:* - (when (= index idx-entry) [:hr.separator {:data-index index}]) - [:& undo-entry {:key (str "entry-" idx-entry) - :objects objects - :entry entry}]]) - (when (= index -1) [:hr.separator])]]])) + [:div.history-toolbox-title (t locale "workspace.undo.title")] + (if (empty? entries) + [:div.history-entry-empty + [:div.history-entry-empty-icon i/undo-history] + [:div.history-entry-empty-msg (t locale "workspace.undo.empty")]] + [:ul.history-entries + (for [[idx-entry entry] (->> entries (map-indexed vector) reverse)] #_[i (range 0 10)] + [:& history-entry {:key (str "entry-" idx-entry) + :locale locale + :entry entry + :current? (= idx-entry index) + :disabled? (> idx-entry index)}])])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 41c9288524..e488d0d394 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -43,9 +43,11 @@ :rect i/box :curve i/curve :text i/text - :group (if (nil? (:component-id shape)) - i/folder - i/component) + :group (if (some? (:component-id shape)) + i/component + (if (:masked-group? shape) + i/mask + i/folder)) nil)) ;; --- Layer Name @@ -89,7 +91,8 @@ :default-value (:name shape "")}] [:span.element-name {:on-double-click on-click} - (:name shape "")]))) + (:name shape "") + (when (seq (:touched shape)) " *")]))) (defn- make-collapsed-iref [id] @@ -142,10 +145,10 @@ (st/emit! (dw/select-shape id true)) (> (count selected) 1) - (st/emit! dw/deselect-all + (st/emit! (dw/deselect-all) (dw/select-shape id)) :else - (st/emit! dw/deselect-all + (st/emit! (dw/deselect-all) (dw/select-shape id))))) on-context-menu @@ -159,7 +162,7 @@ on-drag (fn [{:keys [id]}] (when (not (contains? selected id)) - (st/emit! dw/deselect-all + (st/emit! (dw/deselect-all) (dw/select-shape id)))) on-drop @@ -185,10 +188,17 @@ :index index :name (:name item)})] + (mf/use-effect + (mf/deps selected) + (fn [] + (when (and (= (count selected) 1) selected?) + (.scrollIntoView (mf/ref-val dref) #js {:block "nearest", :behavior "smooth"})))) + [:li {:on-context-menu on-context-menu :ref dref :class (dom/classnames :component (not (nil? (:component-id item))) + :masked (:masked-group? item) :dnd-over (= (:over dprops) :center) :dnd-over-top (= (:over dprops) :top) :dnd-over-bot (= (:over dprops) :bot) @@ -299,7 +309,9 @@ :component-id :component-file :shape-ref - :metadata])] + :touched + :metadata + :masked-group?])] (persistent! (reduce-kv (fn [res id obj] (assoc! res id (strip-data obj))) @@ -319,8 +331,7 @@ (mf/defc layers-toolbox {:wrap [mf/memo]} [] - (let [locale (mf/deref i18n/locale) - page (mf/deref refs/workspace-page)] + (let [page (mf/deref refs/workspace-page)] [:div#layers.tool-window [:div.tool-window-bar [:div.tool-window-icon i/layers] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 2bfe53b193..0a4020a8d1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -16,6 +16,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.tab-container :refer [tab-container tab-element]] + [app.main.ui.context :as ctx] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.align :refer [align-options]] [app.main.ui.workspace.sidebar.options.circle :as circle] @@ -40,15 +41,15 @@ [{:keys [shape shapes-with-children page-id file-id]}] [:* (case (:type shape) - :frame [:& frame/options {:shape shape}] - :group [:& group/options {:shape shape :shape-with-children shapes-with-children}] - :text [:& text/options {:shape shape}] - :rect [:& rect/options {:shape shape}] - :icon [:& icon/options {:shape shape}] + :frame [:& frame/options {:shape shape}] + :group [:& group/options {:shape shape :shape-with-children shapes-with-children}] + :text [:& text/options {:shape shape}] + :rect [:& rect/options {:shape shape}] + :icon [:& icon/options {:shape shape}] :circle [:& circle/options {:shape shape}] - :path [:& path/options {:shape shape}] - :curve [:& path/options {:shape shape}] - :image [:& image/options {:shape shape}] + :path [:& path/options {:shape shape}] + :curve [:& path/options {:shape shape}] + :image [:& image/options {:shape shape}] nil) [:& exports-menu {:shape shape @@ -88,8 +89,10 @@ (mf/defc options-toolbox {::mf/wrap [mf/memo]} - [{:keys [page-id file-id local] :as props}] + [{:keys [local] :as props}] (let [section (:options-mode local) + page-id (mf/use-ctx ctx/current-page-id) + file-id (mf/use-ctx ctx/current-file-id) shapes (mf/deref refs/selected-objects) shapes-with-children (mf/deref refs/selected-objects-with-children)] [:& options-content {:shapes shapes diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/blur.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/blur.cljs new file mode 100644 index 0000000000..b6999bd0d1 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/options/blur.cljs @@ -0,0 +1,70 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.sidebar.options.blur + (:require + [rumext.alpha :as mf] + [app.common.data :as d] + [app.common.uuid :as uuid] + [app.main.data.workspace.common :as dwc] + [app.main.store :as st] + [app.main.ui.icons :as i] + [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] + [app.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]] + [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [t]])) + +(defn create-blur [] + (let [id (uuid/next)] + {:id id + :type :layer-blur + :value 4 + :hidden false})) + +(mf/defc blur-menu [{:keys [ids values]}] + (let [locale (i18n/use-locale) + blur (:blur values) + has-value? (not (nil? blur)) + + change! (fn [update-fn] (st/emit! (dwc/update-shapes ids update-fn))) + + handle-add + (fn [] + (change! #(assoc % :blur (create-blur)))) + + handle-delete + (fn [] + (change! #(dissoc % :blur))) + + handle-change + (fn [value] + (change! #(assoc-in % [:blur :value] value))) + + handle-toggle-visibility + (fn [] + (change! #(update-in % [:blur :hidden] not)))] + + [:div.element-set + [:div.element-set-title + [:span (t locale "workspace.options.blur-options.title")] + [:div.element-set-title-actions + (if has-value? + [:div.add-page {:on-click handle-toggle-visibility} (if (:hidden blur) i/eye-closed i/eye)]) + (if has-value? + [:div.add-page {:on-click handle-delete} i/minus] + [:div.add-page {:on-click handle-add} i/close])]] + + (when has-value? + [:div.element-set-content + [:& input-row {:label "Value" + :class "pixels" + :min 0 + :value (:value blur) + :on-change handle-change}]])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/circle.cljs index 3986c025c7..b184f5518e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/circle.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/circle.cljs @@ -13,7 +13,8 @@ [app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]] - [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]])) + [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]] + [app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]])) (mf/defc options [{:keys [shape] :as props}] @@ -33,5 +34,6 @@ :type type :values stroke-values}] [:& shadow-menu {:ids ids - :type type - :values (select-keys shape [:shadow])}]])) + :values (select-keys shape [:shadow])}] + [:& blur-menu {:ids ids + :values (select-keys shape [:blur])}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs index 9e3c803df8..a37caa0095 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs @@ -19,6 +19,7 @@ (when visible? [:* [:div.focus-overlay {:on-click handle-click}] - [:div.advanced-options {} - children]]))) + [:div.advanced-options-wrapper + [:div.advanced-options {} + children]]]))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/fill.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/fill.cljs index 511ebcdfa1..d469acee20 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/fill.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/fill.cljs @@ -21,7 +21,12 @@ [app.util.i18n :as i18n :refer [tr t]] [app.util.object :as obj])) -(def fill-attrs [:fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file]) +(def fill-attrs + [:fill-color + :fill-opacity + :fill-color-ref-id + :fill-color-ref-file + :fill-color-gradient]) (defn- fill-menu-props-equals? [np op] @@ -33,45 +38,44 @@ old-values (obj/get op "values")] (and (= new-ids old-ids) (= new-editor old-editor) - (= (:fill-color new-values) - (:fill-color old-values)) - (= (:fill-opacity new-values) - (:fill-opacity old-values))))) - + (every? #(identical? (% new-values) (% old-values)) fill-attrs)))) (mf/defc fill-menu {::mf/wrap [#(mf/memo' % fill-menu-props-equals?)]} [{:keys [ids type values editor] :as props}] (let [locale (mf/deref i18n/locale) - show? (not (nil? (:fill-color values))) + show? (or (not (nil? (:fill-color values))) + (not (nil? (:fill-color-gradient values)))) label (case type :multiple (t locale "workspace.options.selection-fill") :group (t locale "workspace.options.group-fill") (t locale "workspace.options.fill")) - color {:value (:fill-color values) + color {:color (:fill-color values) :opacity (:fill-opacity values) :id (:fill-color-ref-id values) - :file-id (:fill-color-ref-file values)} + :file-id (:fill-color-ref-file values) + :gradient (:fill-color-gradient values)} on-add (mf/use-callback (mf/deps ids) (fn [event] - (st/emit! (dc/change-fill ids cp/default-color nil nil)))) + (st/emit! (dc/change-fill ids {:color cp/default-color + :opacity 1})))) on-delete (mf/use-callback (mf/deps ids) (fn [event] - (st/emit! (dc/change-fill ids nil nil nil)))) + (st/emit! (dc/change-fill ids nil)))) on-change (mf/use-callback (mf/deps ids) - (fn [value opacity id file-id] - (st/emit! (dc/change-fill ids value opacity id file-id)))) + (fn [color] + (st/emit! (dc/change-fill ids color)))) on-open-picker (mf/use-callback diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/frame.cljs index 78642b1b90..5adaf5c971 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/frame.cljs @@ -23,7 +23,8 @@ [app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.frame-grid :refer [frame-grid]] - [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]])) + [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]] + [app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]])) (declare +size-presets+) @@ -204,16 +205,17 @@ (let [ids [(:id shape)] type (:type shape) stroke-values (select-keys shape stroke-attrs)] - [:* - [:& measures-menu {:shape shape}] - [:& fill-menu {:ids ids - :type type - :values (select-keys shape fill-attrs)}] - [:& stroke-menu {:ids ids + [:* + [:& measures-menu {:shape shape}] + [:& fill-menu {:ids ids :type type - :values stroke-values}] - [:& shadow-menu {:ids ids - :type type - :values (select-keys shape [:shadow])}] - [:& frame-grid {:shape shape}]])) + :values (select-keys shape fill-attrs)}] + [:& stroke-menu {:ids ids + :type type + :values stroke-values}] + [:& shadow-menu {:ids ids + :values (select-keys shape [:shadow])}] + [:& blur-menu {:ids ids + :values (select-keys shape [:blur])}] + [:& frame-grid {:shape shape}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/frame_grid.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/frame_grid.cljs index 04e55265d8..d1aba7a4f5 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/frame_grid.cljs @@ -101,14 +101,17 @@ (assoc-in [:params :item-length] item-length))))) handle-change-color - (fn [value opacity] - (emit-changes! #(-> % - (assoc-in [:params :color :value] value) - (assoc-in [:params :color :opacity] opacity)))) + (fn [color] + (emit-changes! #(-> % (assoc-in [:params :color] color)))) handle-use-default (fn [] - (emit-changes! #(hash-map :params ((:type grid) default-grid-params)))) + (let [params ((:type grid) default-grid-params) + color (or (get-in params [:color :value]) (get-in params [:color :color])) + params (-> params + (assoc-in [:color :color] color) + (update :color dissoc :value))] + (emit-changes! #(hash-map :params params)))) handle-set-as-default (fn [] @@ -214,6 +217,7 @@ :on-change (handle-change :params :margin)}]]) [:& color-row {:color (:color params) + :disable-gradient true :on-change handle-change-color}] [:div.row-flex [:button.btn-options {:disabled is-default diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/group.cljs index 8cba6c38c0..0d6254a878 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/group.cljs @@ -18,6 +18,7 @@ [app.main.ui.workspace.sidebar.options.multiple :refer [get-shape-attrs]] [app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]] + [app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.text :refer [text-fill-attrs text-font-attrs @@ -138,6 +139,10 @@ [:& fill-menu {:ids ids-with-children :type type :values fill-values}] + + [:& blur-menu {:ids [id] + :values (select-keys shape [:blur])}] + (when-not (empty? other-ids) [:& stroke-menu {:ids other-ids :type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/image.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/image.cljs index ba9402205d..d8733c7a9e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/image.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/image.cljs @@ -10,7 +10,9 @@ (ns app.main.ui.workspace.sidebar.options.image (:require [rumext.alpha :as mf] - [app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]])) + [app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]] + [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]] + [app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]])) (mf/defc options [{:keys [shape] :as props}] @@ -20,4 +22,9 @@ [:* [:& measures-menu {:ids ids :type type - :values measure-values}]])) + :values measure-values}] + [:& shadow-menu {:ids ids + :values (select-keys shape [:shadow])}] + + [:& blur-menu {:ids ids + :values (select-keys shape [:blur])}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/multiple.cljs index b09b4d2f7f..b13a69acbf 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/multiple.cljs @@ -15,14 +15,7 @@ [app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]] - [app.main.ui.workspace.sidebar.options.text :refer [text-fill-attrs - text-font-attrs - text-align-attrs - text-spacing-attrs - text-valign-attrs - text-decoration-attrs - text-transform-attrs - text-menu]])) + [app.main.ui.workspace.sidebar.options.text :as ot])) (defn get-shape-attrs [shape attrs text-attrs convert-attrs extract-fn] @@ -47,88 +40,33 @@ text-ids (map :id (filter #(= (:type %) :text) shapes)) other-ids (map :id (filter #(not= (:type %) :text) shapes)) - measure-values - (geom/get-attrs-multi shapes measure-attrs) + extract (fn [{:keys [attrs text-attrs convert-attrs extract-fn]}] + (let [mapfn + (fn [shape] + (get-shape-attrs shape + attrs + text-attrs + convert-attrs + extract-fn))] + (geom/get-attrs-multi (map mapfn shapes) (or attrs text-attrs)))) - fill-values - (geom/get-attrs-multi (map #(get-shape-attrs - % - fill-attrs - text-fill-attrs - fill-attrs - dwt/current-text-values) - shapes) - fill-attrs) + measure-values (geom/get-attrs-multi shapes measure-attrs) - stroke-values - (geom/get-attrs-multi (map #(get-shape-attrs - % - stroke-attrs - nil - nil - nil) - shapes) - stroke-attrs) + fill-values (extract {:attrs fill-attrs + :text-attrs ot/text-fill-attrs + :convert-attrs fill-attrs + :extract-fn dwt/current-text-values}) - font-values - (geom/get-attrs-multi (map #(get-shape-attrs - % - nil - text-font-attrs - nil - dwt/current-text-values) - shapes) - text-font-attrs) + stroke-values (extract {:attrs stroke-attrs}) - align-values - (geom/get-attrs-multi (map #(get-shape-attrs - % - nil - text-align-attrs - nil - dwt/current-paragraph-values) - shapes) - text-align-attrs) + root-values (extract {:text-attrs ot/root-attrs + :extract-fn dwt/current-root-values}) - spacing-values - (geom/get-attrs-multi (map #(get-shape-attrs - % - nil - text-spacing-attrs - nil - dwt/current-text-values) - shapes) - text-spacing-attrs) + paragraph-values (extract {:text-attrs ot/paragraph-attrs + :extract-fn dwt/current-paragraph-values}) - valign-values - (geom/get-attrs-multi (map #(get-shape-attrs - % - nil - text-valign-attrs - nil - dwt/current-root-values) - shapes) - text-valign-attrs) - - decoration-values - (geom/get-attrs-multi (map #(get-shape-attrs - % - nil - text-decoration-attrs - nil - dwt/current-text-values) - shapes) - text-decoration-attrs) - - transform-values - (geom/get-attrs-multi (map #(get-shape-attrs - % - nil - text-transform-attrs - nil - dwt/current-text-values) - shapes) - text-transform-attrs)] + text-values (extract {:text-attrs ot/text-attrs + :extract-fn dwt/current-text-values})] [:* [:& measures-menu {:ids ids :type :multiple @@ -141,14 +79,11 @@ :type :multiple :values stroke-values}]) (when-not (empty? text-ids) - [:& text-menu {:ids text-ids - :type :multiple - :editor nil - :font-values font-values - :align-values align-values - :spacing-values spacing-values - :valign-values valign-values - :decoration-values decoration-values - :transform-values transform-values - :shapes shapes}])])) + [:& ot/text-menu {:ids text-ids + :type :multiple + :editor nil + :values (merge root-values + paragraph-values + text-values) + :shapes shapes}])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs index 04e6fe5e9e..31381c3a08 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs @@ -44,8 +44,9 @@ [:div.element-set [:div.element-set-title (t locale "workspace.options.canvas-background")] [:div.element-set-content - [:& color-row {:disable-opacity true - :color {:value (get options :background "#E8E9EA") + [:& color-row {:disable-gradient true + :disable-opacity true + :color {:color (get options :background "#E8E9EA") :opacity 1} :on-change handle-change-color :on-open on-open diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/path.cljs index 9405ff7841..0f01f9522c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/path.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/path.cljs @@ -13,7 +13,8 @@ [app.common.data :as d] [app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]] - [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]])) + [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]] + [app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]])) (mf/defc options [{:keys [shape] :as props}] @@ -28,5 +29,6 @@ :type type :values stroke-values}] [:& shadow-menu {:ids ids - :type type - :values (select-keys shape [:shadow])}]])) + :values (select-keys shape [:shadow])}] + [:& blur-menu {:ids ids + :values (select-keys shape [:blur])}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rect.cljs index 8e43aeac1d..a5c8c6fb18 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rect.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rect.cljs @@ -13,7 +13,8 @@ [app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]] - [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]])) + [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]] + [app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]])) (mf/defc options {::mf/wrap [mf/memo]} @@ -30,10 +31,13 @@ [:& fill-menu {:ids ids :type type :values fill-values}] + [:& stroke-menu {:ids ids :type type :values stroke-values}] - [:& shadow-menu {:ids ids - :type type - :values (select-keys shape [:shadow])}]])) + [:& shadow-menu {:ids ids + :values (select-keys shape [:shadow])}] + + [:& blur-menu {:ids ids + :values (select-keys shape [:blur])}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index f039df99ff..2a6437fb51 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 @@ -10,31 +10,33 @@ (ns app.main.ui.workspace.sidebar.options.rows.color-row (:require [rumext.alpha :as mf] + [cuerdas.core :as str] [app.common.math :as math] + [app.common.pages :as cp] + [app.common.data :as d] [app.util.dom :as dom] [app.util.data :refer [classnames]] [app.util.i18n :as i18n :refer [tr]] - [app.main.ui.modal :as modal] - [app.common.data :as d] - [app.main.refs :as refs])) + [app.util.color :as uc] + [app.main.refs :as refs] + [app.main.data.modal :as modal] + [app.main.ui.components.color-bullet :as cb])) (defn color-picker-callback - [color handle-change-color handle-open handle-close disable-opacity] + [color disable-gradient disable-opacity handle-change-color handle-open handle-close] (fn [event] (let [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 - :value (:value color) - :opacity (:opacity color) - :disable-opacity disable-opacity}] + :data color}] (handle-open) (modal/show! :colorpicker props)))) -(defn value-to-background [value] - (if (= value :multiple) "transparent" value)) (defn remove-hash [value] (if (or (nil? value) (= value :multiple)) "" (subs value 1))) @@ -59,39 +61,28 @@ (if (= v :multiple) nil v)) (mf/defc color-row - [{:keys [color on-change on-open on-close disable-opacity]}] - (let [;; - file-colors (mf/deref refs/workspace-file-colors) + [{:keys [color disable-gradient disable-opacity on-change on-open on-close]}] + (let [file-colors (mf/deref refs/workspace-file-colors) shared-libs (mf/deref refs/workspace-libraries) get-color-name (fn [{:keys [id file-id]}] (let [src-colors (if file-id (get-in shared-libs [file-id :data :colors]) file-colors)] (get-in src-colors [id :name]))) - ;; - - default-color {:value "#000000" :opacity 1} parse-color (fn [color] - (-> (merge default-color color) - (update :value #(or % "#000000")) - (update :opacity #(or % 1)))) - - state (mf/use-state (parse-color color)) - - value (:value @state) - opacity (:opacity @state) + (-> color + (update :color #(or % (:value color))))) change-value (fn [new-value] - (swap! state assoc :value new-value) - (when on-change (on-change new-value (remove-multiple opacity)))) + (when on-change (on-change (-> color + (assoc :color new-value) + (dissoc :gradient))))) change-opacity (fn [new-opacity] - (swap! state assoc :opacity new-opacity) - (when on-change (on-change (remove-multiple value) new-opacity))) + (when on-change (on-change (assoc color :opacity new-opacity)))) - handle-pick-color (fn [new-value new-opacity id file-id] - (reset! state {:value new-value :opacity new-opacity}) - (when on-change (on-change new-value new-opacity id file-id))) + handle-pick-color (fn [color] + (when on-change (on-change color))) handle-open (fn [] (when on-open (on-open))) @@ -115,38 +106,63 @@ change-opacity)))) select-all (fn [event] - (dom/select-text! (dom/get-target event)))] + (dom/select-text! (dom/get-target event))) + + handle-click-color (mf/use-callback + (mf/deps color) + (let [;; If multiple, we change to default color + color (if (uc/multiple? color) + {:color cp/default-color :opacity 1} + color)] + (color-picker-callback color + disable-gradient + disable-opacity + handle-pick-color + handle-open + handle-close)))] (mf/use-effect (mf/deps color) - #(reset! state (parse-color color))) - ;; is this necessary? + (fn [] + (modal/update-props! :colorpicker {:data (parse-color color)}))) [:div.row-flex.color-data - [:span.color-th - {:class (when (:id color) "color-name") - :style {:background-color (-> value value-to-background)} - :on-click (color-picker-callback @state handle-pick-color handle-open handle-close disable-opacity)} - (when (= value :multiple) "?")] + [:& cb/color-bullet {:color color + :on-click handle-click-color}] - (if (:id color) + (cond + ;; Rendering a color with ID + (:id color) [:div.color-info [:div.color-name (str (get-color-name color))]] + + ;; Rendering a gradient + (and (not (uc/multiple? color)) + (:gradient color) (get-in color [:gradient :type])) [:div.color-info - [:input {:value (-> value remove-hash) - :pattern "^[0-9a-fA-F]{0,6}$" - :placeholder (tr "settings.multiple") - :on-click select-all - :on-change handle-value-change}]]) + [:div.color-name (cb/gradient-type->string (get-in color [:gradient :type]))]] - (when (not disable-opacity) - [:div.input-element - {:class (classnames :percentail (not= opacity :multiple))} - [:input.input-text {:type "number" - :value (-> opacity opacity->string) - :placeholder (tr "settings.multiple") - :on-click select-all - :on-change handle-opacity-change - :min "0" - :max "100"}]])])) + ;; Rendering a plain color/opacity + :else + [:* + [:div.color-info + [:input {:value (if (uc/multiple? color) + "" + (-> color :color remove-hash)) + :pattern "^[0-9a-fA-F]{0,6}$" + :placeholder (tr "settings.multiple") + :on-click select-all + :on-change handle-value-change}]] + + (when (and (not disable-opacity) + (not (:gradient color))) + [:div.input-element + {:class (classnames :percentail (not= (:opacity color) :multiple))} + [:input.input-text {:type "number" + :value (-> color :opacity opacity->string) + :placeholder (tr "settings.multiple") + :on-click select-all + :on-change handle-opacity-change + :min "0" + :max "100"}]])])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shadow.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shadow.cljs index 0f13a906b6..54756d4da2 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shadow.cljs @@ -16,7 +16,6 @@ [app.main.store :as st] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] - [app.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [t]])) @@ -25,8 +24,7 @@ (let [id (uuid/next)] {:id id :style :drop-shadow - :color "#000000" - :opacity 0.2 + :color {:color "#000000" :opacity 0.2} :offset-x 4 :offset-y 4 :blur 4 @@ -174,12 +172,16 @@ [:span.after (t locale "workspace.options.shadow-options.spread")]]] [:div.color-row-wrap - [:& color-row {:color {:value (:color value) :opacity (:opacity value)} + [:& color-row {:color (if (string? (:color value)) + ;; Support for old format colors + {:color (:color value) :opacity (:opacity value)} + (:color value)) + :disable-gradient true :on-change (update-color index) :on-open #(st/emit! dwc/start-undo-transaction) :on-close #(st/emit! dwc/commit-undo-transaction)}]]]])) (mf/defc shadow-menu - [{:keys [ids type values] :as props}] + [{:keys [ids values] :as props}] (let [locale (i18n/use-locale) on-add-shadow diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/stroke.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/stroke.cljs index a8927137b5..6419805d25 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/stroke.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/stroke.cljs @@ -14,6 +14,7 @@ [app.common.data :as d] [app.common.math :as math] [app.main.data.workspace.common :as dwc] + [app.main.data.colors :as dc] [app.main.store :as st] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] @@ -29,7 +30,8 @@ :stroke-color :stroke-color-ref-id :stroke-color-ref-file - :stroke-opacity]) + :stroke-opacity + :stroke-color-gradient]) (defn- stroke-menu-props-equals? [np op] @@ -38,16 +40,7 @@ new-values (obj/get np "values") old-values (obj/get op "values")] (and (= new-ids old-ids) - (identical? (:stroke-style new-values) - (:stroke-style old-values)) - (identical? (:stroke-alignment new-values) - (:stroke-alignment old-values)) - (identical? (:stroke-width new-values) - (:stroke-width old-values)) - (identical? (:stroke-color new-values) - (:stroke-color old-values)) - (identical? (:stroke-opacity new-values) - (:stroke-opacity old-values))))) + (every? #(identical? (% new-values) (% old-values)) stroke-attrs)))) (defn- width->string [width] (if (= width :multiple) @@ -72,19 +65,17 @@ show-options (not= (:stroke-style values :none) :none) - current-stroke-color {:value (:stroke-color values) + current-stroke-color {:color (:stroke-color values) :opacity (:stroke-opacity values) :id (:stroke-color-ref-id values) - :file-id (:stroke-color-ref-file values)} + :file-id (:stroke-color-ref-file values) + :gradient (:stroke-color-gradient values)} handle-change-stroke-color - (fn [value opacity id file-id] - (let [change #(cond-> % - value (assoc :stroke-color value - :stroke-color-ref-id id - :stroke-color-ref-file file-id) - opacity (assoc :stroke-opacity opacity))] - (st/emit! (dwc/update-shapes ids change)))) + (mf/use-callback + (mf/deps ids) + (fn [color] + (st/emit! (dc/change-stroke ids color)))) on-stroke-style-change (fn [event] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/text.cljs index a00c7911e9..cde0071edc 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/text.cljs @@ -14,21 +14,26 @@ [okulary.core :as l] [app.main.ui.icons :as i] [app.common.data :as d] + [app.common.uuid :as uuid] [app.main.data.workspace :as dw] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.texts :as dwt] + [app.main.data.workspace.libraries :as dwl] [app.main.store :as st] [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.fill :refer [fill-menu]] [app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]] - [app.main.ui.components.editable-select :refer [editable-select]] + [app.main.ui.workspace.sidebar.options.typography :refer [typography-entry typography-options]] + [app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]] [app.util.dom :as dom] [app.main.fonts :as fonts] [app.util.i18n :as i18n :refer [tr t]] + [app.util.text :as ut] ["slate" :refer [Transforms]])) -(def text-fill-attrs [:fill :opacity]) +(def text-typography-attrs [:typography-ref-id :typography-ref-file]) +(def text-fill-attrs [:fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient :fill :opacity ]) (def text-font-attrs [:font-id :font-family :font-variant-id :font-size :font-weight :font-style]) (def text-align-attrs [:text-align]) (def text-spacing-attrs [:line-height :letter-spacing]) @@ -36,357 +41,241 @@ (def text-decoration-attrs [:text-decoration]) (def text-transform-attrs [:text-transform]) -(defn- attr->string [value] - (if (= value :multiple) - "" - (str value))) - -(def ^:private editor-ref - (l/derived :editor refs/workspace-local)) - -(mf/defc font-select-optgroups - {::mf/wrap [mf/memo]} - [] - [:* - [:optgroup {:label "Local"} - (for [font fonts/local-fonts] - [:option {:value (:id font) - :key (:id font)} - (:name font)])] - [:optgroup {:label "Google"} - (for [font (fonts/resolve-fonts :google)] - [:option {:value (:id font) - :key (:id font)} - (:name font)])]]) - -(mf/defc font-options - [{:keys [editor ids values locale] :as props}] - (let [{:keys [font-id - font-size - font-variant-id]} values - - font-id (or font-id "sourcesanspro") - font-size (or font-size "14") - font-variant-id (or font-variant-id "regular") - - fonts (mf/deref fonts/fontsdb) - font (get fonts font-id) - - change-font - (fn [new-font-id] - (run! #(st/emit! (dwt/update-text-attrs - {:id % - :editor editor - :attrs {:font-id new-font-id - :font-family (:family (get fonts new-font-id)) - :font-variant-id nil - :font-weight nil - :font-style nil}})) - ids)) - - on-font-family-change - (fn [event] - (let [new-font-id (-> (dom/get-target event) - (dom/get-value))] - (when-not (str/empty? new-font-id) - (let [font (get fonts new-font-id)] - (fonts/ensure-loaded! new-font-id (partial change-font new-font-id)))))) - - on-font-size-change - (fn [new-font-size] - (when-not (str/empty? new-font-size) - (run! #(st/emit! (dwt/update-text-attrs - {:id % - :editor editor - :attrs {:font-size (str new-font-size)}})) - ids))) - - on-font-variant-change - (fn [event] - (let [new-variant-id (-> (dom/get-target event) - (dom/get-value)) - variant (d/seek #(= new-variant-id (:id %)) (:variants font))] - - (run! #(st/emit! (dwt/update-text-attrs - {:id % - :editor editor - :attrs {:font-id (:id font) - :font-family (:family font) - :font-variant-id new-variant-id - :font-weight (:weight variant) - :font-style (:style variant)}})) - ids)))] - - [:* - [:div.row-flex - [:select.input-select {:value (attr->string font-id) - :on-change on-font-family-change} - (when (= font-id :multiple) - [:option {:value ""} (t locale "settings.multiple")]) - [:& font-select-optgroups]]] - - [:div.row-flex - (let [size-options [8 9 10 11 12 14 18 24 36 48 72] - size-options (if (= font-size :multiple) (concat [{:value "" :label "--"}] size-options) size-options)] - [:& editable-select - {:value (attr->string font-size) - :class "input-option" - :options size-options - :type "number" - :placeholder "--" - :on-change on-font-size-change}]) - - [:select.input-select {:value (attr->string font-variant-id) - :on-change on-font-variant-change} - (when (= font-size :multiple) - [:option {:value ""} "--"]) - (for [variant (:variants font)] - [:option {:value (:id variant) - :key (pr-str variant)} - (:name variant)])]]])) - +(def root-attrs (d/concat text-valign-attrs + text-align-attrs)) +(def paragraph-attrs text-align-attrs) +(def text-attrs (d/concat text-typography-attrs + text-font-attrs + text-align-attrs + text-spacing-attrs + text-decoration-attrs + text-transform-attrs)) (mf/defc text-align-options - [{:keys [editor ids values locale] :as props}] + [{:keys [editor ids values locale on-change] :as props}] (let [{:keys [text-align]} values text-align (or text-align "left") - on-change + handle-change (fn [event new-align] - (run! #(st/emit! - (dwt/update-root-attrs - {:id % - :editor editor - :attrs {:text-align new-align}}) - (dwt/update-paragraph-attrs - {:id % - :editor editor - :attrs {:text-align new-align}})) - ids))] + (on-change {:text-align new-align}))] ;; --- Align [:div.row-flex.align-icons [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.align-left") :class (dom/classnames :current (= "left" text-align)) - :on-click #(on-change % "left")} + :on-click #(handle-change % "left")} i/text-align-left] [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.align-center") :class (dom/classnames :current (= "center" text-align)) - :on-click #(on-change % "center")} + :on-click #(handle-change % "center")} i/text-align-center] [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.align-right") :class (dom/classnames :current (= "right" text-align)) - :on-click #(on-change % "right")} + :on-click #(handle-change % "right")} i/text-align-right] [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.align-justify") :class (dom/classnames :current (= "justify" text-align)) - :on-click #(on-change % "justify")} + :on-click #(handle-change % "justify")} i/text-align-justify]])) -(mf/defc spacing-options - [{:keys [editor ids values locale] :as props}] - (let [{:keys [line-height - letter-spacing]} values - - line-height (or line-height "1.2") - letter-spacing (or letter-spacing "0") - - on-change - (fn [event attr] - (let [new-spacing (-> (dom/get-target event) - (dom/get-value))] - (run! #(st/emit! (dwt/update-text-attrs - {:id % - :editor editor - :attrs {attr new-spacing}})) - ids)))] - [:div.row-flex - [:div.input-icon - [:span.icon-before.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.line-height")} - i/line-height] - [:input.input-text - {:type "number" - :step "0.1" - :min "0" - :max "200" - :value (attr->string line-height) - :placeholder (t locale "settings.multiple") - :on-change #(on-change % :line-height)}]] - - [:div.input-icon - [:span.icon-before.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.letter-spacing")} - i/letter-spacing] - [:input.input-text - {:type "number" - :step "0.1" - :min "0" - :max "200" - :value (attr->string letter-spacing) - :placeholder (t locale "settings.multiple") - :on-change #(on-change % :letter-spacing)}]]])) - (mf/defc additional-options - [{:keys [shapes editor ids values locale] :as props}] + [{:keys [shapes editor ids values locale on-change] :as props}] (let [{:keys [vertical-align]} values to-single-value (fn [coll] (if (> (count coll) 1) nil (first coll))) - grow-type (->> shapes (map :grow-type) (into #{}) to-single-value) - + grow-type (->> shapes (map :grow-type) (remove nil?) (into #{}) to-single-value) + vertical-align (or vertical-align "top") - on-change-grow + handle-change-grow (fn [event grow-type] (st/emit! (dwc/update-shapes ids #(assoc % :grow-type grow-type)))) - on-change + handle-change (fn [event new-align] - (run! #(st/emit! (dwt/update-root-attrs - {:id % - :editor editor - :attrs {:vertical-align new-align}})) - ids))] + (on-change {:vertical-align new-align}))] [:div.row-flex [:div.align-icons [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.align-top") :class (dom/classnames :current (= "top" vertical-align)) - :on-click #(on-change % "top")} + :on-click #(handle-change % "top")} i/align-top] [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.align-middle") :class (dom/classnames :current (= "center" vertical-align)) - :on-click #(on-change % "center")} + :on-click #(handle-change % "center")} i/align-middle] [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.align-bottom") :class (dom/classnames :current (= "bottom" vertical-align)) - :on-click #(on-change % "bottom")} + :on-click #(handle-change % "bottom")} i/align-bottom]] [:div.align-icons [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.grow-fixed") :class (dom/classnames :current (= :fixed grow-type)) - :on-click #(on-change-grow % :fixed)} + :on-click #(handle-change-grow % :fixed)} i/auto-fix] [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.grow-auto-width") :class (dom/classnames :current (= :auto-width grow-type)) - :on-click #(on-change-grow % :auto-width)} + :on-click #(handle-change-grow % :auto-width)} i/auto-width] [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.grow-auto-height") :class (dom/classnames :current (= :auto-height grow-type)) - :on-click #(on-change-grow % :auto-height)} + :on-click #(handle-change-grow % :auto-height)} i/auto-height]]])) (mf/defc text-decoration-options - [{:keys [editor ids values locale] :as props}] + [{:keys [editor ids values locale on-change] :as props}] (let [{:keys [text-decoration]} values text-decoration (or text-decoration "none") - on-change + handle-change (fn [event type] - (run! #(st/emit! (dwt/update-text-attrs - {:id % - :editor editor - :attrs {:text-decoration type}})) - ids))] + (on-change {:text-decoration type}))] [:div.row-flex [:span.element-set-subtitle (t locale "workspace.options.text-options.decoration")] [:div.align-icons [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.none") :class (dom/classnames :current (= "none" text-decoration)) - :on-click #(on-change % "none")} + :on-click #(handle-change % "none")} i/minus] [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.underline") :class (dom/classnames :current (= "underline" text-decoration)) - :on-click #(on-change % "underline")} + :on-click #(handle-change % "underline")} i/underline] [:span.tooltip.tooltip-bottom {:alt (t locale "workspace.options.text-options.strikethrough") :class (dom/classnames :current (= "line-through" text-decoration)) - :on-click #(on-change % "line-through")} + :on-click #(handle-change % "line-through")} i/strikethrough]]])) -(mf/defc text-transform-options - [{:keys [editor ids values locale] :as props}] - (let [{:keys [text-transform]} values - - text-transform (or text-transform "none") - - on-change - (fn [event type] - (run! #(st/emit! (dwt/update-text-attrs - {:id % - :editor editor - :attrs {:text-transform type}})) - ids))] - [:div.row-flex - [:span.element-set-subtitle (t locale "workspace.options.text-options.text-case")] - [:div.align-icons - [:span.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.none") - :class (dom/classnames :current (= "none" text-transform)) - :on-click #(on-change % "none")} - i/minus] - [:span.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.uppercase") - :class (dom/classnames :current (= "uppercase" text-transform)) - :on-click #(on-change % "uppercase")} - i/uppercase] - [:span.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.lowercase") - :class (dom/classnames :current (= "lowercase" text-transform)) - :on-click #(on-change % "lowercase")} - i/lowercase] - [:span.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.titlecase") - :class (dom/classnames :current (= "capitalize" text-transform)) - :on-click #(on-change % "capitalize")} - i/titlecase]]])) +(defn generate-typography-name [{:keys [font-id font-variant-id] :as typography}] + (let [{:keys [name]} (fonts/get-font-data font-id)] + (-> typography + (assoc :name (str name " " (str/title font-variant-id))))) ) (mf/defc text-menu {::mf/wrap [mf/memo]} [{:keys [ids type editor - font-values - align-values - spacing-values - valign-values - decoration-values - transform-values + values shapes] :as props}] + (let [locale (mf/deref i18n/locale) + typographies (mf/deref refs/workspace-file-typography) + shared-libs (mf/deref refs/workspace-libraries) label (case type :multiple (t locale "workspace.options.text-options.title-selection") :group (t locale "workspace.options.text-options.title-group") - (t locale "workspace.options.text-options.title"))] - [:div.element-set - [:div.element-set-title label] - [:div.element-set-content - [:& font-options {:editor editor :ids ids :values font-values :locale locale}] - [:& text-align-options {:editor editor :ids ids :values align-values :locale locale}] - [:& spacing-options {:editor editor :ids ids :values spacing-values :locale locale}] - [:& additional-options {:shapes shapes :editor editor :ids ids :values valign-values :locale locale}] - [:& text-decoration-options {:editor editor :ids ids :values decoration-values :locale locale}] - [:& text-transform-options {:editor editor :ids ids :values transform-values :locale locale}]]])) + (t locale "workspace.options.text-options.title")) + + emit-update! + (fn [id attrs] + (let [attrs (select-keys attrs root-attrs)] + (when-not (empty? attrs) + (st/emit! (dwt/update-root-attrs {:id id :editor editor :attrs attrs})))) + + (let [attrs (select-keys attrs paragraph-attrs)] + (when-not (empty? attrs) + (st/emit! (dwt/update-paragraph-attrs {:id id :editor editor :attrs attrs})))) + + (let [attrs (select-keys attrs text-attrs)] + (when-not (empty? attrs) + (st/emit! (dwt/update-text-attrs {:id id :editor editor :attrs attrs}))))) + + typography (cond + (and (:typography-ref-id values) + (not= (:typography-ref-id values) :multiple) + (:typography-ref-file values)) + (-> shared-libs + (get-in [(:typography-ref-file values) :data :typographies (:typography-ref-id values)]) + (assoc :file-id (:typography-ref-file values))) + + (and (:typography-ref-id values) + (not= (:typography-ref-id values) :multiple)) + (get typographies (:typography-ref-id values))) + + + on-convert-to-typography + (mf/use-callback + (mf/deps values) + (fn [event] + (let [setted-values (-> (d/without-nils values) + (select-keys + (d/concat text-font-attrs + text-spacing-attrs + text-transform-attrs))) + typography (merge ut/default-typography setted-values) + typography (generate-typography-name typography)] + (let [id (uuid/next)] + (st/emit! (dwl/add-typography (assoc typography :id id) false)) + (run! #(emit-update! % {:typography-ref-id id}) ids))))) + + handle-deattach-typography + (fn [] + (run! #(emit-update! % {:typography-ref-file nil + :typography-ref-id nil}) + ids)) + + handle-change-typography + (fn [changes] + (st/emit! (dwl/update-typography (merge typography changes)))) + + opts #js {:editor editor + :ids ids + :values values + :shapes shapes + :on-change (fn [attrs] + (run! #(emit-update! % attrs) ids)) + :locale locale}] + + [:div.element-set + [:div.element-set-title + [:span label] + (when (not typography) + [:div.add-page {:on-click on-convert-to-typography} i/close])] + + (cond + typography + [:& typography-entry {:typography typography + :read-only? (some? (:typography-ref-file values)) + :file (get shared-libs (:typography-ref-file values)) + :on-deattach handle-deattach-typography + :on-change handle-change-typography}] + + (= (:typography-ref-id values) :multiple) + [:div.multiple-typography + [:div.multiple-typography-text (t locale "workspace.libraries.text.multiple-typography")] + [:div.multiple-typography-button {:on-click handle-deattach-typography + :title (t locale "workspace.libraries.text.multiple-typography-tooltip")} i/unchain]] + + :else + [:> typography-options opts]) + + [:div.element-set-content + [:> text-align-options opts] + [:> additional-options opts] + [:> text-decoration-options opts]]])) (mf/defc options [{:keys [shape] :as props}] @@ -399,60 +288,42 @@ measure-values (select-keys shape measure-attrs) fill-values (dwt/current-text-values - {:editor editor - :shape shape - :attrs text-fill-attrs}) + {:editor editor + :shape shape + :attrs text-fill-attrs}) - converted-fill-values {:fill-color (:fill fill-values) - :fill-opacity (:opacity fill-values)} + fill-values (d/update-in-when fill-values [:fill-color-gradient :type] keyword) - font-values (dwt/current-text-values - {:editor editor - :shape shape - :attrs text-font-attrs}) + fill-values (cond-> fill-values + ;; Keep for backwards compatibility + (:fill fill-values) (assoc :fill-color (:fill fill-values)) + (:opacity fill-values) (assoc :fill-opacity (:fill fill-values))) - align-values (dwt/current-paragraph-values - {:editor editor - :shape shape - :attrs text-align-attrs}) + text-values (merge + (dwt/current-root-values + {:editor editor :shape shape + :attrs root-attrs}) + (dwt/current-text-values + {:editor editor :shape shape + :attrs paragraph-attrs}) + (dwt/current-text-values + {:editor editor :shape shape + :attrs text-attrs}))] - spacing-values (dwt/current-text-values - {:editor editor - :shape shape - :attrs text-spacing-attrs}) - - valign-values (dwt/current-root-values - {:editor editor - :shape shape - :attrs text-valign-attrs}) - - decoration-values (dwt/current-text-values - {:editor editor - :shape shape - :attrs text-decoration-attrs}) - - transform-values (dwt/current-text-values - {:editor editor - :shape shape - :attrs text-transform-attrs})] [:* [:& measures-menu {:ids ids :type type :values measure-values}] [:& fill-menu {:ids ids :type type - :values converted-fill-values + :values fill-values :editor editor}] [:& shadow-menu {:ids ids - :type type :values (select-keys shape [:shadow])}] + [:& blur-menu {:ids ids + :values (select-keys shape [:blur])}] [:& text-menu {:ids ids :type type + :values text-values :editor editor - :font-values font-values - :align-values align-values - :spacing-values spacing-values - :valign-values valign-values - :decoration-values decoration-values - :transform-values transform-values :shapes [shape]}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/typography.cljs new file mode 100644 index 0000000000..08f83a2c9f --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/options/typography.cljs @@ -0,0 +1,313 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.sidebar.options.typography + (:require + [rumext.alpha :as mf] + [cuerdas.core :as str] + [app.main.ui.icons :as i] + [app.main.refs :as refs] + [app.main.store :as st] + [app.common.data :as d] + [app.main.data.workspace.texts :as dwt] + [app.main.ui.components.editable-select :refer [editable-select]] + [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] + [app.main.fonts :as fonts] + [app.util.dom :as dom] + [app.util.text :as ut] + [app.util.timers :as ts] + [app.util.i18n :as i18n :refer [t]] + [app.util.router :as rt])) + +(defn- attr->string [value] + (if (= value :multiple) + "" + (str value))) + +(mf/defc font-select-optgroups + {::mf/wrap [mf/memo]} + [] + [:* + [:optgroup {:label "Local"} + (for [font fonts/local-fonts] + [:option {:value (:id font) + :key (:id font)} + (:name font)])] + [:optgroup {:label "Google"} + (for [font (fonts/resolve-fonts :google)] + [:option {:value (:id font) + :key (:id font)} + (:name font)])]]) + +(mf/defc font-options + [{:keys [editor ids values locale on-change] :as props}] + (let [{:keys [font-id + font-size + font-variant-id]} values + + font-id (or font-id (:font-id ut/default-text-attrs)) + font-size (or font-size (:font-size ut/default-text-attrs)) + font-variant-id (or font-variant-id (:font-variant-id ut/default-text-attrs)) + + fonts (mf/deref fonts/fontsdb) + font (get fonts font-id) + + change-font + (fn [new-font-id] + (let [{:keys [family] :as font} (get fonts new-font-id) + {:keys [id name weight style]} (fonts/get-default-variant font)] + (on-change {:font-id new-font-id + :font-family family + :font-variant-id (or id name) + :font-weight weight + :font-style style}))) + + on-font-family-change + (fn [event] + (let [new-font-id (dom/get-target-val event)] + (when-not (str/empty? new-font-id) + (let [font (get fonts new-font-id)] + (fonts/ensure-loaded! new-font-id (partial change-font new-font-id)))))) + + on-font-size-change + (fn [new-font-size] + (when-not (str/empty? new-font-size) + (on-change {:font-size (str new-font-size)}))) + + on-font-variant-change + (fn [event] + (let [new-variant-id (dom/get-target-val event) + variant (d/seek #(= new-variant-id (:id %)) (:variants font))] + (on-change {:font-id (:id font) + :font-family (:family font) + :font-variant-id new-variant-id + :font-weight (:weight variant) + :font-style (:style variant)})))] + + [:* + [:div.row-flex + [:select.input-select.font-option + {:value (attr->string font-id) + :on-change on-font-family-change} + (when (= font-id :multiple) + [:option {:value ""} (t locale "settings.multiple")]) + [:& font-select-optgroups]]] + + [:div.row-flex + (let [size-options [8 9 10 11 12 14 18 24 36 48 72] + size-options (if (= font-size :multiple) (into [""] size-options) size-options)] + [:& editable-select + {:value (attr->string font-size) + :class "input-option size-option" + :options size-options + :type "number" + :placeholder "--" + :on-change on-font-size-change}]) + + [:select.input-select.variant-option + {:disabled (= font-id :multiple) + :value (attr->string font-variant-id) + :on-change on-font-variant-change} + (when (or (= font-id :multiple) (= font-variant-id :multiple)) + [:option {:value ""} "--"]) + (for [variant (:variants font)] + [:option {:value (:id variant) + :key (pr-str variant)} + (:name variant)])]]])) + + +(mf/defc spacing-options + [{:keys [editor ids values locale on-change] :as props}] + (let [{:keys [line-height + letter-spacing]} values + + line-height (or line-height "1.2") + letter-spacing (or letter-spacing "0") + + handle-change + (fn [event attr] + (let [new-spacing (dom/get-target-val event)] + (on-change {attr new-spacing})))] + + [:div.row-flex + [:div.input-icon + [:span.icon-before.tooltip.tooltip-bottom + {:alt (t locale "workspace.options.text-options.line-height")} + i/line-height] + [:input.input-text + {:type "number" + :step "0.1" + :min "0" + :max "200" + :value (attr->string line-height) + :placeholder (t locale "settings.multiple") + :on-change #(handle-change % :line-height)}]] + + [:div.input-icon + [:span.icon-before.tooltip.tooltip-bottom + {:alt (t locale "workspace.options.text-options.letter-spacing")} + i/letter-spacing] + [:input.input-text + {:type "number" + :step "0.1" + :min "0" + :max "200" + :value (attr->string letter-spacing) + :placeholder (t locale "settings.multiple") + :on-change #(handle-change % :letter-spacing)}]]])) + +(mf/defc text-transform-options + [{:keys [editor ids values locale on-change] :as props}] + (let [{:keys [text-transform]} values + + text-transform (or text-transform "none") + + handle-change + (fn [event type] + (on-change {:text-transform type}))] + [:div.row-flex + [:span.element-set-subtitle (t locale "workspace.options.text-options.text-case")] + [:div.align-icons + [:span.tooltip.tooltip-bottom + {:alt (t locale "workspace.options.text-options.none") + :class (dom/classnames :current (= "none" text-transform)) + :on-click #(handle-change % "none")} + i/minus] + [:span.tooltip.tooltip-bottom + {:alt (t locale "workspace.options.text-options.uppercase") + :class (dom/classnames :current (= "uppercase" text-transform)) + :on-click #(handle-change % "uppercase")} + i/uppercase] + [:span.tooltip.tooltip-bottom + {:alt (t locale "workspace.options.text-options.lowercase") + :class (dom/classnames :current (= "lowercase" text-transform)) + :on-click #(handle-change % "lowercase")} + i/lowercase] + [:span.tooltip.tooltip-bottom + {:alt (t locale "workspace.options.text-options.titlecase") + :class (dom/classnames :current (= "capitalize" text-transform)) + :on-click #(handle-change % "capitalize")} + i/titlecase]]])) + +(mf/defc typography-options + [{:keys [ids editor values on-change]}] + (let [locale (mf/deref i18n/locale) + opts #js {:editor editor + :ids ids + :values values + :locale locale + :on-change on-change}] + + [:div.element-set-content + [:> font-options opts] + [:> spacing-options opts] + [:> text-transform-options opts]])) + + +(mf/defc typography-entry + [{:keys [typography read-only? on-select on-change on-deattach on-context-menu editting? focus-name? file]}] + (let [locale (mf/deref i18n/locale) + open? (mf/use-state editting?) + selected (mf/deref refs/selected-shapes) + hover-deattach (mf/use-state false) + name-input-ref (mf/use-ref nil) + + #_(rt/resolve router :workspace + {:project-id (:project-id file) + :file-id (:id file)} + {:page-id (get-in file [:data :pages 0])}) + handle-go-to-edit + (fn [] (st/emit! (rt/nav :workspace {:project-id (:project-id file) + :file-id (:id file)} + {:page-id (get-in file [:data :pages 0])})))] + + (mf/use-effect + (mf/deps editting?) + (fn [] + (when editting? + (reset! open? editting?)))) + + (mf/use-effect + (mf/deps focus-name?) + (fn [] + (when focus-name? + (ts/schedule 100 + #(when-let [node (mf/ref-val name-input-ref)] + (dom/focus! node) + (dom/select-text! node)))))) + + [:* + [:div.element-set-options-group.typography-entry + [:div.typography-selection-wrapper + {:class (when on-select "is-selectable") + :on-click on-select + :on-context-menu on-context-menu} + [:div.typography-sample + {:style {:font-family (:font-family typography) + :font-weight (:font-weight typography) + :font-style (:font-style typography)}} + (t locale "workspace.assets.typography.sample")] + [:div.typography-name (:name typography)]] + [:div.element-set-actions + (when on-deattach + [:div.element-set-actions-button + {:on-mouse-enter #(reset! hover-deattach true) + :on-mouse-leave #(reset! hover-deattach false) + :on-click on-deattach} + (if @hover-deattach i/unchain i/chain)]) + + [:div.element-set-actions-button + {:on-click #(reset! open? true)} + i/actions]]] + + [:& advanced-options {:visible? @open? + :on-close #(reset! open? false)} + (if read-only? + [:div.element-set-content.typography-read-only-data + [:div.row-flex.typography-name + [:spang (:name typography)]] + + [:div.row-flex + [:span.label (t locale "workspace.assets.typography.font-id")] + [:span (:font-id typography)]] + + [:div.row-flex + [:span.label (t locale "workspace.assets.typography.font-variant-id")] + [:span (:font-variant-id typography)]] + + [:div.row-flex + [:span.label (t locale "workspace.assets.typography.font-size")] + [:span (:font-size typography)]] + + [:div.row-flex + [:span.label (t locale "workspace.assets.typography.line-height")] + [:span (:line-height typography)]] + + [:div.row-flex + [:span.label (t locale "workspace.assets.typography.letter-spacing")] + [:span (:letter-spacing typography)]] + + [:div.row-flex + [:span.label (t locale "workspace.assets.typography.text-transform")] + [:span (:text-transform typography)]] + + [:div.go-to-lib-button + {:on-click handle-go-to-edit} + (t locale "workspace.assets.typography.go-to-edit")]] + + [:* + [:div.element-set-content + [:div.row-flex + [:input.element-name.adv-typography-name + {:type "text" + :ref name-input-ref + :value (:name typography) + :on-change #(on-change {:name (dom/get-target-val %)})}]]] + [:& typography-options {:values typography + :on-change on-change}]])]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs index 9015f4ec3a..dc3ec8b111 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs @@ -10,13 +10,14 @@ (ns app.main.ui.workspace.sidebar.sitemap (:require [app.common.data :as d] + [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [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.icons :as i] [app.main.ui.keyboard :as kbd] - [app.main.ui.modal :as modal] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [t]] [app.util.router :as rt] @@ -33,7 +34,7 @@ id (:id page) delete-fn (mf/use-callback (mf/deps id) #(st/emit! (dw/delete-page id))) - on-delete (mf/use-callback (mf/deps id) #(modal/show! :confirm-dialog {:on-accept delete-fn})) + on-delete (mf/use-callback (mf/deps id) #(modal/show! :confirm {:on-accept delete-fn})) navigate-fn (mf/use-callback (mf/deps id) #(st/emit! (dw/go-to-page id))) on-double-click @@ -131,9 +132,10 @@ ;; --- Pages List (mf/defc pages-list - [{:keys [file current-page-id] :as props}] - (let [pages (:pages file) - deletable? (> (count pages) 1)] + [{:keys [file] :as props}] + (let [pages (:pages file) + deletable? (> (count pages) 1) + current-page-id (mf/use-ctx ctx/current-page-id)] [:ul.element-list [:& hooks/sortable-container {} (for [[index page-id] (d/enumerate pages)] @@ -147,19 +149,22 @@ ;; --- Sitemap Toolbox (mf/defc sitemap - [{:keys [file page-id layout] :as props}] - (let [create (mf/use-callback #(st/emit! dw/create-empty-page)) - collapse (mf/use-callback #(st/emit! (dw/toggle-layout-flags :sitemap-pages))) - locale (mf/deref i18n/locale)] + [{:keys [layout] :as props}] + (let [create (mf/use-callback #(st/emit! dw/create-empty-page)) + locale (mf/deref i18n/locale) + show-pages? (mf/use-state true) + + file (mf/deref refs/workspace-file) + + toggle-pages + (mf/use-callback #(reset! show-pages? not))] + [:div.sitemap.tool-window [:div.tool-window-bar [:span (t locale "workspace.sidebar.sitemap")] [:div.add-page {:on-click create} i/close] - [:div.collapse-pages {:on-click collapse} i/arrow-slide]] + [:div.collapse-pages {:on-click toggle-pages} i/arrow-slide]] - (when (contains? layout :sitemap-pages) + (when @show-pages? [:div.tool-window-content - [:& pages-list - {:file file - :key (:id file) - :current-page-id page-id}]])])) + [:& pages-list {:file file :key (:id file)}]])])) diff --git a/frontend/src/app/main/ui/workspace/snap_distances.cljs b/frontend/src/app/main/ui/workspace/snap_distances.cljs index bc425057a6..fc7f00f3c3 100644 --- a/frontend/src/app/main/ui/workspace/snap_distances.cljs +++ b/frontend/src/app/main/ui/workspace/snap_distances.cljs @@ -172,11 +172,11 @@ (map pair->distance+pair) (filter (comp pred? first)))) - ;; Checks if the value is in a set of numbers with an error margin of 0.1 + ;; Checks if the value is in a set of numbers with an error margin check-in-set (fn [value number-set] (->> number-set - (some #(<= (mth/abs (- value %)) 0.5)))) + (some #(<= (mth/abs (- value %)) 1)))) ;; Left/Top shapes and right/bottom shapes (depends on `coord` parameter [lt-shapes gt-shapes] @to-measure @@ -185,7 +185,6 @@ lt-distances (->> lt-shapes (map distance-to-selrect) (filter pos?) (into #{})) gt-distances (->> gt-shapes (map distance-to-selrect) (filter pos?) (into #{})) - ;; We'll show the distances that match a distance from the selrect show-candidate? #(check-in-set % (set/union lt-distances gt-distances)) @@ -193,11 +192,17 @@ distance-coincidences (concat (get-shapes-match show-candidate? lt-shapes) (get-shapes-match show-candidate? gt-shapes)) + ;; Show the distances that either match one of the distances from the selrect ;; or are from the selrect and go to a shape on the left and to the right - show-distance? #(check-in-set % (into #{} (concat - (map first distance-coincidences) - (set/intersection lt-distances gt-distances)))) + show-distance? + (fn [dist] + (let [distances-to-show + (->> (d/concat #{} + (map first distance-coincidences) + (filter #(check-in-set % lt-distances) gt-distances) + (filter #(check-in-set % gt-distances) lt-distances)))] + (check-in-set dist distances-to-show))) ;; These are the segments whose distance will be displayed @@ -242,12 +247,16 @@ transform (unchecked-get props "transform") selected-shapes (mf/deref (refs/objects-by-id selected)) frame-id (-> selected-shapes first :frame-id) - frame (mf/deref (refs/object-by-id frame-id))] + frame (mf/deref (refs/object-by-id frame-id)) + local (mf/deref refs/workspace-local) + + update-shape (fn [shape] (-> shape + (update :modifiers merge (:modifiers local)) + gsh/transform-shape))] (when (and (contains? layout :dynamic-alignment) (= transform :move) (not (empty? selected))) - (let [shapes (map gsh/transform-shape selected-shapes) - selrect (gsh/selection-rect shapes) + (let [selrect (->> selected-shapes (map update-shape) gsh/selection-rect) key (->> selected (map str) (str/join "-"))] [:g.distance [:& shape-distance diff --git a/frontend/src/app/main/ui/workspace/snap_points.cljs b/frontend/src/app/main/ui/workspace/snap_points.cljs index 429491aa44..b0934c370f 100644 --- a/frontend/src/app/main/ui/workspace/snap_points.cljs +++ b/frontend/src/app/main/ui/workspace/snap_points.cljs @@ -12,6 +12,7 @@ [app.common.math :as mth] [app.common.data :as d] [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] [app.main.refs :as refs] [app.main.snap :as snap] [app.util.geom.snap-points :as sp] @@ -19,6 +20,13 @@ [rumext.alpha :as mf])) (def ^:private line-color "#D383DA") +(def ^:private line-opacity 0.6) +(def ^:private line-width 1) + +;; Configuration for debug +;; (def ^:private line-color "red") +;; (def ^:private line-opacity 1 ) +;; (def ^:private line-width 2) (mf/defc snap-point [{:keys [point zoom]}] @@ -31,12 +39,12 @@ :y1 (- y cross-width) :x2 (+ x cross-width) :y2 (+ y cross-width) - :style {:stroke line-color :stroke-width (str (/ 1 zoom))}}] + :style {:stroke line-color :stroke-width (str (/ line-width zoom))}}] [:line {:x1 (- x cross-width) :y1 (+ y cross-width) :x2 (+ x cross-width) :y2 (- y cross-width) - :style {:stroke line-color :stroke-width (str (/ 1 zoom))}}]])) + :style {:stroke line-color :stroke-width (str (/ line-width zoom))}}]])) (mf/defc snap-line [{:keys [snap point zoom]}] @@ -44,32 +52,72 @@ :y1 (mth/round (:y snap)) :x2 (mth/round (:x point)) :y2 (mth/round (:y point)) - :style {:stroke line-color :stroke-width (str (/ 1 zoom))} - :opacity 0.4}]) + :style {:stroke line-color :stroke-width (str (/ line-width zoom))} + :opacity line-opacity}]) (defn get-snap - [coord {:keys [shapes page-id filter-shapes]}] - (->> (rx/from shapes) - (rx/flat-map (fn [shape] - (->> (sp/shape-snap-points shape) - (map #(vector (:frame-id shape) %))))) - (rx/flat-map (fn [[frame-id point]] - (->> (snap/get-snap-points page-id frame-id filter-shapes point coord) - (rx/map #(vector point % coord))))) - (rx/reduce conj []))) + [coord {:keys [shapes page-id filter-shapes local]}] + (let [shape (if (> (count shapes) 1) + (->> shapes (map gsh/transform-shape) gsh/selection-rect) + (->> shapes (first))) + + shape (if (:modifiers local) + (-> shape (assoc :modifiers (:modifiers local)) gsh/transform-shape) + shape) + + frame-id (snap/snap-frame-id shapes)] + + (->> (rx/of shape) + (rx/flat-map (fn [shape] + (->> (sp/shape-snap-points shape) + (map #(vector frame-id %))))) + (rx/flat-map (fn [[frame-id point]] + (->> (snap/get-snap-points page-id frame-id filter-shapes point coord) + (rx/map #(vector point % coord))))) + (rx/reduce conj [])))) + +(defn- flip + "Function that reverses the x/y coordinates to their counterpart" + [coord] + (if (= coord :x) :y :x)) + +(defn add-point-to-snaps + [[point snaps coord]] + (let [normalize-coord #(assoc % coord (get point coord))] + (cons point (map normalize-coord snaps)))) + + +(defn- process-snap-lines + "Gets the snaps for a coordinate and creates lines with a fixed coordinate" + [snaps coord] + (->> snaps + ;; only snap on the `coord` coordinate + (filter #(= (nth % 2) coord)) + ;; we add the point so the line goes from the point to the snap + (mapcat add-point-to-snaps) + ;; We flatten because it's a list of from-to points + (flatten) + ;; Put together the points of the coordinate + (group-by coord) + ;; Keep only the other coordinate + (d/mapm #(map (flip coord) %2)) + ;; Finally get the max/min and this will define the line to draw + (d/mapm #(vector (apply min %2) (apply max %2))) + ;; Change the structure to retrieve a list of lines from/todo + (map (fn [[fixedv [minv maxv]]] [(hash-map coord fixedv (flip coord) minv) + (hash-map coord fixedv (flip coord) maxv)])))) (mf/defc snap-feedback - [{:keys [shapes page-id filter-shapes zoom] :as props}] + [{:keys [shapes page-id filter-shapes zoom local] :as props}] (let [state (mf/use-state []) subject (mf/use-memo #(rx/subject)) ;; We use sets to store points/lines so there are no points/lines repeated ;; can cause problems with react keys - snap-points (into #{} (mapcat (fn [[point snaps coord]] - (cons point snaps)) - @state)) - snap-lines (into #{} (mapcat (fn [[point snaps coord]] - (when (not-empty snaps) (map #(vector point %) snaps))) @state))] + snap-points (into #{} (mapcat add-point-to-snaps) @state) + + snap-lines (into (process-snap-lines @state :x) + (process-snap-lines @state :y))] (mf/use-effect (fn [] @@ -78,13 +126,14 @@ d/concat (get-snap :y %) (get-snap :x %))) - (rx/subs #(reset! state %)))] + (rx/subs #(let [rs (filter (fn [[_ snaps _]] (> (count snaps) 0)) %)] + (reset! state rs))))] ;; On unmount callback #(rx/dispose! sub)))) (mf/use-effect - (mf/deps shapes) + (mf/deps shapes local) (fn [] (rx/push! subject props))) @@ -105,7 +154,7 @@ (mf/defc snap-points {::mf/wrap [mf/memo]} - [{:keys [layout zoom selected page-id drawing transform] :as props}] + [{:keys [layout zoom selected page-id drawing transform local] :as props}] (let [shapes (mf/deref (refs/objects-by-id selected)) filter-shapes (mf/deref refs/selected-shapes-with-children) filter-shapes (fn [id] @@ -121,5 +170,6 @@ [:& snap-feedback {:shapes shapes :page-id page-id :filter-shapes filter-shapes - :zoom zoom}]))) + :zoom zoom + :local local}]))) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 6316053234..8f9d77ae26 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -9,47 +9,50 @@ (ns app.main.ui.workspace.viewport (:require - [clojure.set :as set] - [cuerdas.core :as str] - [beicon.core :as rx] - [goog.events :as events] - [potok.core :as ptk] - [rumext.alpha :as mf] - [promesa.core :as p] - [app.main.ui.icons :as i] - [app.main.ui.cursors :as cur] - [app.main.ui.modal :as modal] [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.math :as mth] + [app.common.uuid :as uuid] [app.main.constants :as c] - [app.main.data.workspace :as dw] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.drawing :as dd] [app.main.data.colors :as dwc] [app.main.data.fetch :as mdf] + [app.main.data.modal :as modal] + [app.main.data.workspace :as dw] + [app.main.data.workspace.drawing :as dd] + [app.main.data.workspace.libraries :as dwl] [app.main.refs :as refs] [app.main.store :as st] [app.main.streams :as ms] - [app.main.ui.keyboard :as kbd] + [app.main.ui.context :as ctx] + [app.main.ui.cursors :as cur] [app.main.ui.hooks :as hooks] + [app.main.ui.icons :as i] + [app.main.ui.keyboard :as kbd] + [app.main.ui.workspace.colorpicker.pixel-overlay :refer [pixel-overlay]] + [app.main.ui.workspace.comments :refer [comments-layer]] + [app.main.ui.workspace.drawarea :refer [draw-area]] + [app.main.ui.workspace.frame-grid :refer [frame-grid]] + [app.main.ui.workspace.gradients :refer [gradient-handlers]] + [app.main.ui.workspace.presence :as presence] + [app.main.ui.workspace.selection :refer [selection-handlers]] [app.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]] [app.main.ui.workspace.shapes.interactions :refer [interactions]] - [app.main.ui.workspace.drawarea :refer [draw-area]] - [app.main.ui.workspace.selection :refer [selection-handlers]] - [app.main.ui.workspace.presence :as presence] - [app.main.ui.workspace.snap-points :refer [snap-points]] - [app.main.ui.workspace.snap-distances :refer [snap-distances]] - [app.main.ui.workspace.frame-grid :refer [frame-grid]] [app.main.ui.workspace.shapes.outline :refer [outline]] - [app.common.math :as mth] + [app.main.ui.workspace.snap-distances :refer [snap-distances]] + [app.main.ui.workspace.snap-points :refer [snap-points]] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] [app.util.object :as obj] - [app.main.ui.context :as muc] - [app.common.geom.shapes :as gsh] - [app.common.geom.point :as gpt] [app.util.perf :as perf] - [app.common.uuid :as uuid] - [app.util.timers :as timers]) + [app.util.timers :as timers] + [beicon.core :as rx] + [clojure.set :as set] + [cuerdas.core :as str] + [goog.events :as events] + [potok.core :as ptk] + [promesa.core :as p] + [rumext.alpha :as mf]) (:import goog.events.EventType)) ;; --- Coordinates Widget @@ -132,7 +135,10 @@ selected (or (unchecked-get props "selected") #{}) hover (or (unchecked-get props "hover") #{}) outline? (set/union selected hover) - shapes (->> (vals objects) (filter (comp outline? :id))) + show-outline? (fn [shape] (and (not (:hidden shape)) + (not (:blocked shape)) + (outline? (:id shape)))) + shapes (->> (vals objects) (filter show-outline?)) transform (mf/deref refs/current-transform) color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes)))) "#31EFB8" @@ -148,26 +154,35 @@ {::mf/wrap [mf/memo] ::mf/wrap-props false} [props] - (let [data (mf/deref refs/workspace-page) - hover (unchecked-get props "hover") + (let [hover (unchecked-get props "hover") selected (unchecked-get props "selected") + ids (unchecked-get props "ids") + ghost? (unchecked-get props "ghost?") + data (mf/deref refs/workspace-page) objects (:objects data) root (get objects uuid/zero) shapes (->> (:shapes root) - (map #(get objects %)))] + (map #(get objects %))) + + shapes (if ids + (->> ids (map #(get objects %))) + shapes)] [:* [:g.shapes (for [item shapes] (if (= (:type item) :frame) [:& frame-wrapper {:shape item :key (:id item) - :objects objects}] + :objects objects + :ghost? ghost?}] [:& shape-wrapper {:shape item - :key (:id item)}]))] + :key (:id item) + :ghost? ghost?}]))] - [:& shape-outlines {:objects objects - :selected selected - :hover hover}]])) + (when (not ghost?) + [:& shape-outlines {:objects objects + :selected selected + :hover hover}])])) (defn format-viewbox [vbox] (str/join " " [(+ (:x vbox 0) (:left-offset vbox 0)) @@ -175,106 +190,8 @@ (:width vbox 0) (:height vbox 0)])) -(mf/defc pixel-picker-overlay - {::mf/wrap-props false} - [props] - (let [vport (unchecked-get props "vport") - vbox (unchecked-get props "vbox") - viewport-ref (unchecked-get props "viewport-ref") - options (unchecked-get props "options") - svg-ref (mf/use-ref nil) - canvas-ref (mf/use-ref nil) - fetch-pending (mf/deref (mdf/pending-ref)) - - on-mouse-move-picker - (fn [event] - (when-let [zoom-view-node (.getElementById js/document "picker-detail")] - (let [{brx :left bry :top} (dom/get-bounding-rect (mf/ref-val viewport-ref)) - x (- (.-clientX event) brx) - y (- (.-clientY event) bry) - - zoom-context (.getContext zoom-view-node "2d") - canvas-node (mf/ref-val canvas-ref) - canvas-context (.getContext canvas-node "2d") - pixel-data (.getImageData canvas-context x y 1 1) - rgba (.-data pixel-data) - r (obj/get rgba 0) - g (obj/get rgba 1) - b (obj/get rgba 2) - a (obj/get rgba 3) - - area-data (.getImageData canvas-context (- x 25) (- y 20) 50 40)] - - (-> (js/createImageBitmap area-data) - (p/then (fn [image] - ;; Draw area - (obj/set! zoom-context "imageSmoothingEnabled" false) - (.drawImage zoom-context image 0 0 200 160)))) - (st/emit! (dwc/pick-color [r g b a]))))) - - on-mouse-down-picker - (fn [event] - (dom/prevent-default event) - (dom/stop-propagation event) - (st/emit! (dwc/pick-color-select true (kbd/shift? event)))) - - on-mouse-up-picker - (fn [event] - (dom/prevent-default event) - (dom/stop-propagation event) - (st/emit! (dwc/stop-picker)) - (modal/disallow-click-outside!))] - - (mf/use-effect - ;; Everytime we finish retrieving a new URL we redraw the canvas - ;; so even if we're not finished the user can start to pick basic - ;; shapes - (mf/deps fetch-pending) - (fn [] - (try - (let [canvas-node (mf/ref-val canvas-ref) - canvas-context (.getContext canvas-node "2d") - svg-node (mf/ref-val svg-ref)] - (timers/schedule 100 - #(let [xml (.serializeToString (js/XMLSerializer.) svg-node) - img-src (str "data:image/svg+xml;base64," - (-> xml js/encodeURIComponent js/unescape js/btoa)) - img (js/Image.) - on-error (fn [err] (.error js/console "ERROR" err)) - on-load (fn [] (.drawImage canvas-context img 0 0))] - (.addEventListener img "error" on-error) - (.addEventListener img "load" on-load) - (obj/set! img "src" img-src)))) - (catch :default e (.error js/console e))))) - - [:* - [:div.overlay - {:style {:position "absolute" - :top 0 - :left 0 - :width "100%" - :height "100%" - :cursor cur/picker} - :on-mouse-down on-mouse-down-picker - :on-mouse-up on-mouse-up-picker - :on-mouse-move on-mouse-move-picker}] - [:canvas {:ref canvas-ref - :width (:width vport 0) - :height (:height vport 0) - :style {:display "none"}}] - [:& (mf/provider muc/embed-ctx) {:value true} - [:svg.viewport - {:ref svg-ref - :preserveAspectRatio "xMidYMid meet" - :width (:width vport 0) - :height (:height vport 0) - :view-box (format-viewbox vbox) - :style {:display "none" - :background-color (get options :background "#E8E9EA")}} - [:& frames]]]])) - (mf/defc viewport - [{:keys [page-id page local layout] :as props}] + [{:keys [local layout file] :as props}] (let [{:keys [options-mode zoom flags @@ -286,17 +203,21 @@ panning picking-color?]} local - file (mf/deref refs/workspace-file) + page-id (mf/use-ctx ctx/current-page-id) + selrect-orig (->> (mf/deref refs/selected-objects) + (gsh/selection-rect)) + selrect (-> selrect-orig + (assoc :modifiers (:modifiers local)) + (gsh/transform-shape)) + viewport-ref (mf/use-ref nil) zoom-view-ref (mf/use-ref nil) last-position (mf/use-var nil) drawing (mf/deref refs/workspace-drawing) drawing-tool (:tool drawing) drawing-obj (:object drawing) + zoom (or zoom 1) - pick-color (mf/use-state [255 255 255 255]) - - zoom (or zoom 1) on-mouse-down (mf/use-callback @@ -308,11 +229,11 @@ shift? (kbd/shift? event) alt? (kbd/alt? event)] (st/emit! (ms/->MouseEvent :down ctrl? shift? alt?)) - (cond (and (= 1 (.-which event))) (if drawing-tool - (st/emit! (dd/start-drawing drawing-tool)) + (when (not= drawing-tool :comments) + (st/emit! (dd/start-drawing drawing-tool))) (st/emit! dw/handle-selection)) (and (not edition) @@ -388,7 +309,9 @@ (when-not (.-repeat bevent) (st/emit! (ms/->KeyboardEvent :down key ctrl? shift? alt?)) (when (and (kbd/space? event) - (not= "rich-text" (obj/get target "className"))) + (not= "rich-text" (obj/get target "className")) + (not= "INPUT" (obj/get target "tagName")) + (not= "TEXTAREA" (obj/get target "tagName"))) (handle-viewport-positioning viewport-ref)))))) on-key-up @@ -488,7 +411,7 @@ :id (:id image) :path (:path image)}} aspect-ratio (/ (:width image) (:height image))] - (st/emit! (dw/create-and-add-shape :image shape)))) + (st/emit! (dw/create-and-add-shape :image x y shape)))) on-drop (fn [event] @@ -505,8 +428,13 @@ (assoc :y final-y))))) (dnd/has-type? event "app/component") - (let [{:keys [component-id file-id]} (dnd/get-data event "app/component")] - (st/emit! (dwl/instantiate-component file-id component-id))) + (let [{:keys [component file-id]} (dnd/get-data event "app/component") + shape (get-in component [:objects (:id component)]) + final-x (- (:x viewport-coord) (/ (:width shape) 2)) + final-y (- (:y viewport-coord) (/ (:height shape) 2))] + (st/emit! (dwl/instantiate-component file-id + (:id component) + (gpt/point final-x final-y)))) (dnd/has-type? event "text/uri-list") (let [data (dnd/get-data event "text/uri-list") @@ -536,8 +464,10 @@ on-resize (fn [event] (let [node (mf/ref-val viewport-ref) - prnt (dom/get-parent node)] - (st/emit! (dw/update-viewport-size (dom/get-client-size prnt))))) + prnt (dom/get-parent node) + size (dom/get-client-size prnt)] + ;; We schedule the event so it fires after `initialize-page` event + (timers/schedule #(st/emit! (dw/update-viewport-size size))))) options (mf/deref refs/workspace-page-options)] @@ -546,33 +476,50 @@ (let [node (mf/ref-val viewport-ref) prnt (dom/get-parent node) - key1 (events/listen js/document EventType.KEYDOWN on-key-down) - key2 (events/listen js/document EventType.KEYUP on-key-up) - key3 (events/listen node EventType.MOUSEMOVE on-mouse-move) - ;; bind with passive=false to allow the event to be cancelled - ;; https://stackoverflow.com/a/57582286/3219895 - key4 (events/listen js/window EventType.WHEEL on-mouse-wheel #js {:passive false}) - key5 (events/listen js/window EventType.RESIZE on-resize)] - (st/emit! (dw/initialize-viewport (dom/get-client-size prnt))) + keys [(events/listen (dom/get-root) EventType.KEYDOWN on-key-down) + (events/listen (dom/get-root) EventType.KEYUP on-key-up) + (events/listen node EventType.MOUSEMOVE on-mouse-move) + ;; bind with passive=false to allow the event to be cancelled + ;; https://stackoverflow.com/a/57582286/3219895 + (events/listen js/window EventType.WHEEL on-mouse-wheel #js {:passive false}) + (events/listen js/window EventType.RESIZE on-resize)]] + (fn [] - (events/unlistenByKey key1) - (events/unlistenByKey key2) - (events/unlistenByKey key3) - (events/unlistenByKey key4) - (events/unlistenByKey key5))))) + (doseq [key keys] + (events/unlistenByKey key)))))) + + (mf/use-layout-effect + (fn [] + (mf/deps page-id) + (let [node (mf/ref-val viewport-ref) + prnt (dom/get-parent node) + size (dom/get-client-size prnt)] + ;; We schedule the event so it fires after `initialize-page` event + (timers/schedule #(st/emit! (dw/initialize-viewport size)))))) (mf/use-layout-effect (mf/deps layout) on-resize) [:* (when picking-color? - [:& pixel-picker-overlay {:vport vport - :vbox vbox - :viewport-ref viewport-ref - :options options - :layout layout}]) + [:& pixel-overlay {:vport vport + :vbox vbox + :viewport-ref viewport-ref + :options options + :layout layout}]) + + (when (= drawing-tool :comments) + [:& comments-layer {:vbox (:vbox local) + :vport (:vport local) + :zoom (:zoom local) + :drawing drawing + :page-id page-id + :file-id (:id file)} + ]) + [:svg.viewport {:preserveAspectRatio "xMidYMid meet" + :key page-id :width (:width vport 0) :height (:height vport 0) :view-box (format-viewbox vbox) @@ -580,6 +527,7 @@ :class (when drawing-tool "drawing") :style {:cursor (cond panning cur/hand + (= drawing-tool :comments) cur/hand (= drawing-tool :frame) cur/create-artboard (= drawing-tool :rect) cur/create-rectangle (= drawing-tool :circle) cur/create-ellipse @@ -599,16 +547,34 @@ :on-drag-over on-drag-over :on-drop on-drop} - [:g + [:g {:style {:pointer-events (if (contains? layout :comments) + "none" + "auto")}} [:& frames {:key page-id :hover (:hover local) :selected (:selected selected)}] + (when (= :move (:transform local)) + [:svg.ghost + {:x (:x selrect) + :y (:y selrect) + :width (:width selrect) + :height (:height selrect) + :style {:pointer-events "none"}} + + [:g {:transform (str/fmt "translate(%s,%s)" (- (:x selrect-orig)) (- (:y selrect-orig)))} + [:& frames {:ids selected + :ghost? true}]]]) + (when (seq selected) [:& selection-handlers {:selected selected :zoom zoom :edition edition}]) + (when (= (count selected) 1) + [:& gradient-handlers {:id (first selected) + :zoom zoom}]) + (when drawing-obj [:& draw-area {:shape drawing-obj :zoom zoom @@ -622,7 +588,8 @@ :drawing drawing-obj :zoom zoom :page-id page-id - :selected selected}] + :selected selected + :local local}] [:& snap-distances {:layout layout :zoom zoom diff --git a/frontend/src/app/util/avatars.cljs b/frontend/src/app/util/avatars.cljs index 1a67de9d24..39f3908a6d 100644 --- a/frontend/src/app/util/avatars.cljs +++ b/frontend/src/app/util/avatars.cljs @@ -15,7 +15,7 @@ (defn generate [{:keys [name color size] - :or {color "#303236" size 128}}] + :or {color "#000000" size 128}}] (let [parts (str/words (str/upper name)) letters (if (= 1 (count parts)) (ffirst parts) diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index d80f4436c0..f17c9c7551 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -53,6 +53,11 @@ (into [] (gcolor/hexToHsl hex)) (catch :default e [0 0 0]))) +(defn hex->hsla + [^string data ^number opacity] + (-> (hex->hsl data) + (conj opacity))) + (defn hsl->rgb [[h s l]] (gcolor/hslToRgb h s l)) @@ -77,3 +82,35 @@ (defn hsv->hsl [hsv] (hex->hsl (hsv->hex hsv))) + +(defn gradient->css [{:keys [type stops]}] + (let [parse-stop + (fn [{:keys [offset color opacity]}] + (let [[r g b] (hex->rgb color)] + (str/fmt "rgba(%s, %s, %s, %s) %s" r g b opacity (str (* offset 100) "%")))) + + stops-css (str/join "," (map parse-stop stops))] + + (if (= type :linear) + (str/fmt "linear-gradient(to bottom, %s)" stops-css) + (str/fmt "radial-gradient(circle, %s)" stops-css)))) + +;; TODO: REMOVE `VALUE` WHEN COLOR IS INTEGRATED +(defn color->background [{:keys [color opacity gradient value]}] + (let [color (or color value) + opacity (or opacity 1)] + (cond + (and gradient (not= :multiple gradient)) + (gradient->css gradient) + + (not= color :multiple) + (let [[r g b] (hex->rgb (or color value))] + (str/fmt "rgba(%s, %s, %s, %s)" r g b opacity)) + + :else "transparent"))) + +(defn multiple? [{:keys [value color gradient]}] + (or (= value :multiple) + (= color :multiple) + (= gradient :multiple) + (and gradient color))) diff --git a/frontend/src/app/util/data.cljs b/frontend/src/app/util/data.cljs index 3792b7585e..0a6c2889cb 100644 --- a/frontend/src/app/util/data.cljs +++ b/frontend/src/app/util/data.cljs @@ -25,7 +25,7 @@ (def index-by-id #(index-by :id %)) -(defn remove-nil-vals +(defn without-nils "Given a map, return a map removing key-value pairs when value is `nil`." [data] @@ -111,6 +111,14 @@ not-found)) not-found coll))) +(defn remove-equal-values [m1 m2] + (if (and (map? m1) (map? m2) (not (nil? m1)) (not (nil? m2))) + (->> m1 + (remove (fn [[k v]] (= (k m2) v))) + (into {})) + m1)) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Numbers Parsing ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 57ac2d37cf..92edd65357 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -76,6 +76,8 @@ [node] (.-value node)) +(def get-target-val (comp get-value get-target)) + (defn click "Click a node" [node] @@ -217,3 +219,6 @@ (defn release-pointer [event] (-> event get-target (.releasePointerCapture (.-pointerId event)))) + +(defn get-root [] + (query js/document "#app")) diff --git a/frontend/src/app/util/forms.cljs b/frontend/src/app/util/forms.cljs index e9a6382fc7..17e80068d1 100644 --- a/frontend/src/app/util/forms.cljs +++ b/frontend/src/app/util/forms.cljs @@ -10,34 +10,20 @@ (ns app.util.forms (:refer-clojure :exclude [uuid]) (:require + [app.common.spec :as us] + [app.util.dom :as dom] + [app.util.i18n :refer [tr]] + [app.util.timers :as tm] [beicon.core :as rx] [cljs.spec.alpha :as s] [cuerdas.core :as str] [potok.core :as ptk] - [rumext.alpha :as mf] - [app.common.spec :as us] - [app.util.dom :as dom] - [app.util.i18n :refer [tr]])) + [rumext.alpha :as mf])) ;; --- Handlers Helpers -(defn- impl-mutator - [v update-fn] - (specify v - IReset - (-reset! [_ new-value] - (update-fn new-value)) - - ISwap - (-swap! - ([self f] (update-fn f)) - ([self f x] (update-fn #(f % x))) - ([self f x y] (update-fn #(f % x y))) - ([self f x y more] (update-fn #(apply f % x y more)))))) - (defn- interpret-problem [acc {:keys [path pred val via in] :as problem}] - ;; (prn "interpret-problem" problem) (cond (and (empty? path) (list? pred) @@ -51,45 +37,100 @@ :else acc)) +(declare create-form-mutator) + (defn use-form - [& {:keys [spec validators initial]}] - (let [[state update-state] (mf/useState {:data (if (fn? initial) (initial) initial) - :errors {} - :touched {}}) + [& {:keys [spec validators initial] :as opts}] + (let [state (mf/useState 0) + render (aget state 1) + state-ref (mf/use-ref {:data (if (fn? initial) (initial) initial) + :errors {} + :touched {}}) + form (mf/use-memo #(create-form-mutator state-ref render opts))] - cleaned (s/conform spec (:data state)) - problems (when (= ::s/invalid cleaned) - (::s/problems (s/explain-data spec (:data state)))) + (mf/use-effect + (mf/deps initial) + (fn [] + (if (fn? initial) + (swap! form update :data merge (initial)) + (swap! form update :data merge initial)))) - errors (merge (reduce interpret-problem {} problems) - (reduce (fn [errors vf] - (merge errors (vf (:data state)))) - {} validators) - (:errors state))] - (-> (assoc state - :errors errors - :clean-data (when (not= cleaned ::s/invalid) cleaned) - :valid (and (empty? errors) - (not= cleaned ::s/invalid))) - (impl-mutator update-state)))) + form)) + + +(defn- wrap-update-fn + [f {:keys [spec validators]}] + (fn [& args] + (let [state (apply f args) + cleaned (s/conform spec (:data state)) + problems (when (= ::s/invalid cleaned) + (::s/problems (s/explain-data spec (:data state)))) + + errors (merge (reduce interpret-problem {} problems) + (reduce (fn [errors vf] + (merge errors (vf (:data state)))) + {} validators) + (:errors state))] + + (assoc state + :errors errors + :clean-data (when (not= cleaned ::s/invalid) cleaned) + :valid (and (empty? errors) + (not= cleaned ::s/invalid)))))) + +(defn- create-form-mutator + [state-ref render opts] + (reify + IDeref + (-deref [_] + (mf/ref-val state-ref)) + + IReset + (-reset! [it new-value] + (mf/set-ref-val! state-ref new-value) + (render inc)) + + + ISwap + (-swap! [self f] + (let [f (wrap-update-fn f opts)] + (mf/set-ref-val! state-ref (f (mf/ref-val state-ref))) + (render inc))) + + + (-swap! [self f x] + (let [f (wrap-update-fn f opts)] + (mf/set-ref-val! state-ref (f (mf/ref-val state-ref) x)) + (render inc))) + + + (-swap! [self f x y] + (let [f (wrap-update-fn f opts)] + (mf/set-ref-val! state-ref (f (mf/ref-val state-ref) x y)) + (render inc))) + + (-swap! [self f x y more] + (let [f (wrap-update-fn f opts)] + (mf/set-ref-val! state-ref (apply f (mf/ref-val state-ref) x y more)) + (render inc))))) (defn on-input-change - ([{:keys [data] :as form} field] + ([form field] (on-input-change form field false)) - - ([{:keys [data] :as form} field trim?] + ([form field trim?] (fn [event] (let [target (dom/get-target event) - value (dom/get-value target)] + value (dom/get-value target)] (swap! form (fn [state] (-> state (assoc-in [:data field] (if trim? (str/trim value) value)) (update :errors dissoc field)))))))) (defn on-input-blur - [{:keys [touched] :as form} field] + [form field] (fn [event] - (let [target (dom/get-target event)] + (let [target (dom/get-target event) + touched (get @form :touched)] (when-not (get touched field) (swap! form assoc-in [:touched field] true))))) diff --git a/frontend/src/app/util/http_api.cljs b/frontend/src/app/util/http_api.cljs index 99b1787d28..73cb69a50b 100644 --- a/frontend/src/app/util/http_api.cljs +++ b/frontend/src/app/util/http_api.cljs @@ -16,9 +16,10 @@ [app.util.transit :as t])) (defn- conditional-decode - [{:keys [body headers] :as response}] + [{:keys [body headers status] :as response}] (let [contentype (get headers "content-type")] - (if (str/starts-with? contentype "application/transit+json") + (if (and (str/starts-with? contentype "application/transit+json") + (pos? (count body))) (assoc response :body (t/decode body)) response))) diff --git a/frontend/src/app/util/logging.clj b/frontend/src/app/util/logging.clj new file mode 100644 index 0000000000..50ef0eb8f7 --- /dev/null +++ b/frontend/src/app/util/logging.clj @@ -0,0 +1,39 @@ +(ns app.util.logging) + +(defn- log-expr [form level keyvals] + (let [keyvals-map (apply array-map keyvals) + formatter (::formatter keyvals-map 'identity)] + `(log ~(::logger keyvals-map (str *ns*)) + ~level + ~(-> keyvals-map + (dissoc ::logger) + #_(assoc :line (:line (meta form)))) + ~(:err keyvals-map)))) + +(defmacro set-level! + ([level] + `(set-level* ~(str *ns*) ~level)) + ([n level] + `(set-level* ~n ~level))) + +(defmacro error [& keyvals] + (log-expr &form :error keyvals)) + +(defmacro warn [& keyvals] + (log-expr &form :warn keyvals)) + +(defmacro info [& keyvals] + (log-expr &form :info keyvals)) + +(defmacro debug [& keyvals] + (log-expr &form :debug keyvals)) + +(defmacro trace [& keyvals] + (log-expr &form :trace keyvals)) + +(defmacro spy [form] + (let [res (gensym)] + `(let [~res ~form] + ~(log-expr &form :debug [:spy `'~form + :=> res]) + ~res))) diff --git a/frontend/src/app/util/logging.cljs b/frontend/src/app/util/logging.cljs new file mode 100644 index 0000000000..6ea7392a3a --- /dev/null +++ b/frontend/src/app/util/logging.cljs @@ -0,0 +1,206 @@ +;; 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) 2020 UXBOX Labs SL +;; +;; This code is highly inspired on the lambdaisland/glogi library but +;; adapted and simplified to our needs. The adapted code shares the +;; same license. You can found the origianl source code here: +;; https://github.com/lambdaisland/glogi + +(ns app.util.logging + (:require + [goog.log :as glog] + [goog.debug.Logger :as Logger] + [goog.debug.Logger.Level :as Level] + [goog.debug.Console :as Console] + [cuerdas.core :as str] + [goog.object :as gobj]) + (:import + [goog.debug Logger Console LogRecord] + [goog.debug.Logger Level]) + (:require-macros [app.util.logging])) + +(defn- logger-name + [s] + (cond + (string? s) s + (= s :root) "" + (simple-ident? s) (name s) + (qualified-ident? s) (str (namespace s) "." (name s)) + :else (str s))) + +(defn get-logger + [n] + (if (instance? Logger n) + n + (glog/getLogger (logger-name n)))) + +(def root (get-logger :root)) + + +(def levels + {:off Level/OFF + :shout Level/SHOUT + :error Level/SEVERE + :severe Level/SEVERE + :warning Level/WARNING + :warn Level/WARNING + :info Level/INFO + :config Level/CONFIG + :debug Level/FINE + :fine Level/FINE + :finer Level/FINER + :trace Level/FINER + :finest Level/FINEST + :all Level/ALL}) + +(def colors + {:gray3 "#8e908c" + :gray4 "#969896" + :gray5 "#4d4d4c" + :gray6 "#282a2e" + :black "#1d1f21" + :red "#c82829" + :blue "#4271ae" + :orange "#f5871f"}) + +(defn- get-level-value + [level] + (if (instance? Level level) + (.-value ^Level level) + (.-value ^Level (get levels level)))) + +(defn- level->color + [level] + (condp <= (get-level-value level) + (get-level-value :error) (get colors :red) + (get-level-value :warn) (get colors :orange) + (get-level-value :info) (get colors :blue) + (get-level-value :debug) (get colors :gray4) + (get-level-value :trace) (get colors :gray3) + (get colors :gray2))) + +(defn- level->short-name + [l] + (case l + :fine "DBG" + :debug "DBG" + :finer "TRC" + :trace "TRC" + :info "INF" + :warn "WRN" + :warning "WRN" + :error "ERR" + (subs (.-name ^Level (get levels l)) 0 3))) + +(defn- make-log-record + [level message name exception] + (let [record (LogRecord. level message name)] + (when exception + (.setException record exception)) + record)) + +(defn log + "Output a log message to the given logger, optionally with an exception to be + logged." + ([name lvl message] + (log name lvl message nil)) + ([name lvl message exception] + (when glog/ENABLED + (when-let [l (get-logger name)] + (.logRecord ^Logger l (make-log-record (get levels lvl) message name exception)))))) + +(defn set-level* + "Set the level (a keyword) of the given logger, identified by name." + [name lvl] + (assert (contains? levels lvl)) + (when-let [l (get-logger name)] + (.setLevel ^Logger l (get levels lvl)))) + +(defn set-levels! + [lvls] + (doseq [[logger level] lvls + :let [level (if (string? level) (keyword level) level)]] + (set-level* logger level))) + +(defn add-handler! + ([handler-fn] + (add-handler! root handler-fn)) + ([logger-or-name handler-fn] + (when-let [l (get-logger logger-or-name)] + (letfn [(handler [^LogRecord record] + (handler-fn {:seqn (.-sequenceNumber_ record) + :time (.-time_ record) + :level (keyword (str/lower (.-name (.-level_ record)))) + :message (.-msg_ record) + :logger-name (.-loggerName_ record) + :exception (.-exception_ record)}))] + (unchecked-set handler "handler-fn" handler-fn) + (.addHandler ^Logger l handler))))) + +(defn add-handler-once! + ([handler-fn] + (add-handler-once! root handler-fn)) + ([logger-or-name handler-fn] + (when-let [l (get-logger logger-or-name)] + (when-not (some (comp #{handler-fn} #(gobj/get % "handler-fn")) + (.-handlers_ l)) + (add-handler! l handler-fn))))) + +(defn- prepare-message + [message] + (loop [kvpairs (seq message) + message (array-map) + specials []] + (if (nil? kvpairs) + [message specials] + (let [[k v] (first kvpairs)] + (cond + (= k :err) + (recur (next kvpairs) + message + (conj specials [:error nil v])) + + (and (qualified-ident? k) + (= "js" (namespace k))) + (recur (next kvpairs) + message + (conj specials [:js (name k) (if (object? v) v (clj->js v))])) + + :else + (recur (next kvpairs) + (assoc message k v) + specials)))))) + +(defonce default-console-handler + (fn [{:keys [message exception level logger-name]}] + (let [header-styles (str "font-weight: 600; color: " (level->color level)) + normal-styles (str "font-weight: 300; color: " (get colors :gray6)) + level-name (level->short-name level) + header (str "%c" level-name " [" logger-name "] ")] + + (if (string? message) + (let [message (str header "%c" message)] + (js/console.log message header-styles normal-styles)) + (let [[message specials] (prepare-message message)] + (if (seq specials) + (let [message (str header "%c" (pr-str message))] + (js/console.group message header-styles normal-styles) + (doseq [[type n v] specials] + (case type + :js (js/console.log n v) + :error (if (instance? cljs.core.ExceptionInfo v) + (js/console.error (pr-str v)) + (js/console.error v)))) + (js/console.groupEnd message)) + (let [message (str header "%c" (pr-str message))] + (js/console.log message header-styles normal-styles)))))))) + +(defn initialize! + [] + (when-let [instance Console/instance] + (.setCapturing ^Console instance false)) + + (add-handler-once! default-console-handler)) diff --git a/frontend/src/app/util/object.cljs b/frontend/src/app/util/object.cljs index e591eea5cb..a867cc6463 100644 --- a/frontend/src/app/util/object.cljs +++ b/frontend/src/app/util/object.cljs @@ -9,12 +9,14 @@ (ns app.util.object "A collection of helpers for work with javascript objects." - (:refer-clojure :exclude [set! get get-in assoc!]) + (:refer-clojure :exclude [set! get get-in merge clone]) (:require [cuerdas.core :as str] [goog.object :as gobj] ["lodash/omit" :as omit])) +(defn new [] #js {}) + (defn get ([obj k] (when-not (nil? obj) @@ -44,12 +46,22 @@ :else (throw (js/Error. "unexpected input")))] (omit obj keys))) +(defn clone + [a] + (js/Object.assign #js {} a)) + (defn merge! ([a b] (js/Object.assign a b)) ([a b & more] (reduce merge! (merge! a b) more))) +(defn merge + ([a b] + (js/Object.assign #js {} a b)) + ([a b & more] + (reduce merge! (merge a b) more))) + (defn set! [obj key value] (unchecked-set obj key value) diff --git a/frontend/src/app/util/router.cljs b/frontend/src/app/util/router.cljs index 067c44e5fc..a96bb4f90c 100644 --- a/frontend/src/app/util/router.cljs +++ b/frontend/src/app/util/router.cljs @@ -52,7 +52,7 @@ (r/match->path match) (let [uri (.parse goog.Uri (r/match->path match)) qdt (.createFromMap QueryData (-> qparams - (d/remove-nil-vals) + (d/without-nils) (clj->js)))] (.setQueryData ^js uri qdt) (.toString ^js uri)))))) diff --git a/frontend/src/app/util/storage.cljs b/frontend/src/app/util/storage.cljs index 5ce01128c3..6950cda6c9 100644 --- a/frontend/src/app/util/storage.cljs +++ b/frontend/src/app/util/storage.cljs @@ -5,34 +5,43 @@ ;; Copyright (c) 2016 Andrey Antukh (ns app.util.storage - (:require [app.util.transit :as t])) + (:require + [app.util.transit :as t] + [app.util.timers :as tm] + [app.common.exceptions :as ex])) (defn- ^boolean is-worker? [] (or (= *target* "nodejs") (not (exists? js/window)))) +(defn- decode + [v] + (ex/ignoring (t/decode v))) + +(def local + {:get #(decode (.getItem ^js js/localStorage (name %))) + :set #(.setItem ^js js/localStorage (name %1) (t/encode %2))}) + +(def session + {:get #(decode (.getItem ^js js/sessionStorage (name %))) + :set #(.setItem ^js js/sessionStorage (name %1) (t/encode %2))}) + (defn- persist - [alias value] + [alias storage value] (when-not (is-worker?) - (let [key (name alias) - value (t/encode value)] - (.setItem js/localStorage key value)))) + (tm/schedule-on-idle + (fn [] ((:set storage) alias value))))) (defn- load - [alias] + [alias storage] (when-not (is-worker?) - (let [data (.getItem js/localStorage (name alias))] - (try - (t/decode data) - (catch :default e - (js/console.error "Error on loading data from local storage." e) - nil))))) + ((:get storage) alias))) (defn- make-storage - [alias] - (let [data (atom (load alias))] - (add-watch data :sub #(persist alias %4)) + [alias storage] + (let [data (atom (load alias storage))] + (add-watch data :sub #(persist alias storage %4)) (reify Object (toString [_] @@ -66,5 +75,9 @@ (-lookup [_ key not-found] (get @data key not-found))))) -(def storage - (make-storage "app")) + +(defonce storage + (make-storage "app" local)) + +(defonce cache + (make-storage "cache" session)) diff --git a/frontend/src/app/util/text.cljs b/frontend/src/app/util/text.cljs new file mode 100644 index 0000000000..e5226b04d0 --- /dev/null +++ b/frontend/src/app/util/text.cljs @@ -0,0 +1,91 @@ +(ns app.util.text + (:require + [cuerdas.core :as str])) + +(defonce default-text-attrs + {:font-id "sourcesanspro" + :font-family "sourcesanspro" + :font-variant-id "regular" + :font-size "14" + :font-weight "400" + :font-style "normal" + :line-height "1.2" + :letter-spacing "0" + :text-transform "none" + :text-align "left" + :text-decoration "none" + :fill-color "#000000" + :fill-opacity 1}) + +(def typography-fields + [:font-id + :font-family + :font-variant-id + :font-size + :font-weight + :font-style + :line-height + :letter-spacing + :text-transform]) + +(def default-typography + (merge + {:name "Source Sans Pro Regular"} + (select-keys default-text-attrs typography-fields))) + +(defn some-node + [predicate node] + (or (predicate node) + (some #(some-node predicate %) (:children node)))) + +(defn map-node + [map-fn node] + (cond-> (map-fn node) + (:children node) (update :children (fn [children] (mapv #(map-node map-fn %) children))))) + +(defn content->text + [node] + (str + (if (:children node) + (str/join (if (= "paragraph-set" (:type node)) "\n" "") (map content->text (:children node))) + (:text node "")))) + +(defn parse-style-text-blocks + [node attrs] + (letfn + [(rec-style-text-map [acc node style] + (let [node-style (merge style (select-keys node attrs)) + head (or (-> acc first) [{} ""]) + [head-style head-text] head + + new-acc + (cond + (:children node) + (reduce #(rec-style-text-map %1 %2 node-style) acc (:children node)) + + (not= head-style node-style) + (cons [node-style (:text node "")] acc) + + :else + (cons [node-style (str head-text "" (:text node))] (rest acc))) + + ;; We add an end-of-line when finish a paragraph + new-acc + (if (= (:type node) "paragraph") + (let [[hs ht] (first new-acc)] + (cons [hs (str ht "\n")] (rest new-acc))) + new-acc)] + new-acc))] + + (-> (rec-style-text-map [] node {}) + reverse))) + +(defn search-text-attrs + [node attrs] + + (let [rec-fn + (fn rec-fn [current node] + (let [current (reduce rec-fn current (:children node []))] + (merge current + (select-keys node attrs))))] + (rec-fn {} node))) diff --git a/frontend/src/app/util/time.cljs b/frontend/src/app/util/time.cljs index f1220e9145..d1b6e6f80c 100644 --- a/frontend/src/app/util/time.cljs +++ b/frontend/src/app/util/time.cljs @@ -11,6 +11,7 @@ (:require ["date-fns/format" :as df-format] ["date-fns/formatDistanceToNow" :as df-format-distance] + ["date-fns/formatDistanceToNowStrict" :as df-format-distance-strict] ["date-fns/locale/fr" :as df-fr-locale] ["date-fns/locale/en-US" :as df-en-locale] ["date-fns/locale/es" :as df-es-locale] @@ -44,7 +45,8 @@ ([v {:keys [seconds? locale] :or {seconds? true locale "default"}}] - (df-format-distance v - #js {:includeSeconds seconds? - :addSuffix true - :locale (gobj/get locales locale)}))) + (when v + (df-format-distance-strict v + #js {:includeSeconds seconds? + :addSuffix true + :locale (gobj/get locales locale)})))) diff --git a/frontend/src/app/util/timers.cljs b/frontend/src/app/util/timers.cljs index 7b405da7f5..ef6f39e299 100644 --- a/frontend/src/app/util/timers.cljs +++ b/frontend/src/app/util/timers.cljs @@ -8,7 +8,9 @@ ;; Copyright (c) 2020 UXBOX Labs SL (ns app.util.timers - (:require [beicon.core :as rx])) + (:require + [beicon.core :as rx] + [promesa.core :as p])) (defn schedule ([func] @@ -19,6 +21,11 @@ (-dispose [_] (js/clearTimeout sem)))))) +(defn asap + [f] + (-> (p/resolved nil) + (p/then f))) + (defn interval [ms func] (let [sem (js/setInterval #(func) ms)] diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 72e12d6155..cfb999f660 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3,9 +3,9 @@ "@babel/runtime-corejs3@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz#f29fc1990307c4c57b10dbd6ce667b27159d9e0d" - integrity sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw== + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.1.tgz#51b9092befbeeed938335a109dbe0df51451e9dc" + integrity sha512-umhPIcMrlBZ2aTWlWjUseW9LjQKxi1dpFlQS8DzsxB//5K+u6GLTC/JliPKHsd5kJVPIU6X/Hy0YvWOYPcMxBw== dependencies: core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" @@ -25,24 +25,24 @@ integrity sha512-5NI6TeGzVEy/iBcuYtcPzzIC6EqlfQ2+UZ54vT0ulq8bPNGAy8UJD+XcsAyEOcnYFUjOVWuUV+k4/rVkxt9/XQ== "@types/is-hotkey@^0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@types/is-hotkey/-/is-hotkey-0.1.1.tgz#802e294c2a02f26fbcbe8639c77ef05e38cfdc8c" - integrity sha512-QzVKww91fJv/KzARJBS/Im5GS2A8iE64E1HxOed72EmYOvPLG4PBw77QCIUjFl7VwWB3G/SVrxsHedJD/wtn1A== + version "0.1.2" + resolved "https://registry.yarnpkg.com/@types/is-hotkey/-/is-hotkey-0.1.2.tgz#94f00793b5a297a7f7e69c1ef49613da2243a987" + integrity sha512-SUw9LpI3AIwbRNXS7FYy9AlXrTPIdBZGI7y4XxfIEYqgSW1UfFCUM9cMwHE/yCfTl0qeI0UQ/q8TdIxsIFgKPg== "@types/lodash@^4.14.149": - version "4.14.157" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.157.tgz#fdac1c52448861dfde1a2e1515dbc46e54926dc8" - integrity sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ== + version "4.14.162" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.162.tgz#65d78c397e0d883f44afbf1f7ba9867022411470" + integrity sha512-alvcho1kRUnnD1Gcl4J+hK0eencvzq9rmzvFPRmP5rPHx9VVsJj6bKLTATPVf9ktgv4ujzh7T+XWKp+jhuODig== "@types/q@^1.5.1": version "1.5.4" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== -ajv@^6.5.5: - version "6.12.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" - integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== +ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -277,17 +277,16 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autoprefixer@^9.8.6: - version "9.8.6" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" - integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== +autoprefixer@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.0.1.tgz#e2d9000f84ebd98d77b7bc16f8adb2ff1f7bb946" + integrity sha512-aQo2BDIsoOdemXUAOBpFv4ZQa2DrOtEufarYhtFsK1088Ca0TUwu/aQWf0M3mrILXZ3mTIVn1lR3hPW8acacsw== dependencies: - browserslist "^4.12.0" - caniuse-lite "^1.0.30001109" + browserslist "^4.14.5" + caniuse-lite "^1.0.30001137" colorette "^1.2.1" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^7.0.32" postcss-value-parser "^4.1.0" aws-sign2@~0.7.0: @@ -296,9 +295,9 @@ aws-sign2@~0.7.0: integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" - integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== + version "1.10.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" + integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== bach@^1.0.0: version "1.2.0" @@ -484,15 +483,15 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.12.0: - version "4.12.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.2.tgz#76653d7e4c57caa8a1a28513e2f4e197dc11a711" - integrity sha512-MfZaeYqR8StRZdstAK9hCKDd2StvePCYp5rHzQCPicUjfFliDgmuaBNPHYUTpAywBN8+Wc/d7NYVFkO0aqaBUw== +browserslist@^4.14.5: + version "4.14.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.5.tgz#1c751461a102ddc60e40993639b709be7f2c4015" + integrity sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA== dependencies: - caniuse-lite "^1.0.30001088" - electron-to-chromium "^1.3.483" - escalade "^3.0.1" - node-releases "^1.1.58" + caniuse-lite "^1.0.30001135" + electron-to-chromium "^1.3.571" + escalade "^3.1.0" + node-releases "^1.1.61" buffer-crc32@~0.2.3: version "0.2.13" @@ -558,22 +557,17 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30001088: - version "1.0.30001093" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001093.tgz#833e80f64b1a0455cbceed2a4a3baf19e4abd312" - integrity sha512-0+ODNoOjtWD5eS9aaIpf4K0gQqZfILNY4WSNuYzeT1sXni+lMrrVjc0odEobJt6wrODofDZUX8XYi/5y7+xl8g== - -caniuse-lite@^1.0.30001109: - version "1.0.30001111" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001111.tgz#dd0ce822c70eb6c7c068e4a55c22e19ec1501298" - integrity sha512-xnDje2wchd/8mlJu8sXvWxOGvMgv+uT3iZ3bkIAynKOzToCssWCmkz/ZIkQBs/2pUB4uwnJKVORWQ31UkbVjOg== +caniuse-lite@^1.0.30001135, caniuse-lite@^1.0.30001137: + version "1.0.30001151" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001151.tgz#1ddfde5e6fff02aad7940b4edb7d3ac76b0cb00b" + integrity sha512-Zh3sHqskX6mHNrqUerh+fkf0N72cMxrmflzje/JyVImfpknscMnkeJrlFGJcqTmaa0iszdYptGpWMJCRQDkBVw== caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -583,9 +577,9 @@ chalk@^2.4.1, chalk@^2.4.2: supports-color "^5.3.0" "chokidar@>=2.0.0 <4.0.0": - version "3.4.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d" - integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A== + version "3.4.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b" + integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -593,7 +587,7 @@ chalk@^2.4.1, chalk@^2.4.2: is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.4.0" + readdirp "~3.5.0" optionalDependencies: fsevents "~2.1.2" @@ -732,9 +726,9 @@ color-name@^1.0.0: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + version "1.5.4" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" + integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" @@ -787,10 +781,10 @@ component-emitter@^1.2.1: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== -compute-scroll-into-view@^1.0.14: - version "1.0.14" - resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.14.tgz#80e3ebb25d6aa89f42e533956cb4b16a04cfe759" - integrity sha512-mKDjINe3tc6hGelUMNDzuhorIUZ7kS7BwyY0r2wQd2HOH2tRuJykiC06iSEX8y1TuhNzvz4GcJnK16mM2J1NMQ== +compute-scroll-into-view@^1.0.16: + version "1.0.16" + resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.16.tgz#5b7bf4f7127ea2c19b750353d7ce6776a90ee088" + integrity sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ== concat-map@0.0.1: version "0.0.1" @@ -943,9 +937,9 @@ css-tree@1.0.0-alpha.39: source-map "^0.6.1" css-what@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.3.0.tgz#10fec696a9ece2e591ac772d759aacabac38cd39" - integrity sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg== + version "3.4.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" + integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== cssmin@^0.4.3: version "0.4.3" @@ -1025,7 +1019,7 @@ default-resolution@^2.0.0: resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -1110,9 +1104,9 @@ domelementtype@1: integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== + version "2.0.2" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.2.tgz#f3b6e549201e46f588b59463dd77187131fe6971" + integrity sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA== domutils@^1.7.0: version "1.7.0" @@ -1158,10 +1152,10 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.3.483: - version "1.3.487" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.487.tgz#8075e6ea33ee2e79a2dfb2a2467033f014017258" - integrity sha512-m4QS3IDShxauFfYFpnEzRCcUI55oKB9acEnHCuY/hSCZMz9Pz2KJj+UBnGHxRxS/mS1aphqOQ5wI6gc3yDZ7ew== +electron-to-chromium@^1.3.571: + version "1.3.583" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.583.tgz#47a9fde74740b1205dba96db2e433132964ba3ee" + integrity sha512-L9BwLwJohjZW9mQESI79HRzhicPk1DFgM+8hOCfGgGCFEcA3Otpv7QK6SGtYoZvfQfE3wKLh0Hd5ptqUFv3gvQ== elliptic@^6.5.3: version "6.5.3" @@ -1189,9 +1183,9 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: once "^1.4.0" entities@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" - integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== + version "2.1.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== error-ex@^1.2.0: version "1.3.2" @@ -1200,20 +1194,38 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: - version "1.17.6" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" - integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: + version "1.17.7" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" + integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== dependencies: es-to-primitive "^1.2.1" function-bind "^1.1.1" has "^1.0.3" has-symbols "^1.0.1" - is-callable "^1.2.0" - is-regex "^1.1.0" - object-inspect "^1.7.0" + is-callable "^1.2.2" + is-regex "^1.1.1" + object-inspect "^1.8.0" object-keys "^1.1.1" - object.assign "^4.1.0" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: + version "1.18.0-next.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" + integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-negative-zero "^2.0.0" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" @@ -1267,10 +1279,10 @@ es6-weak-map@^2.0.1: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -escalade@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.1.tgz#52568a77443f6927cd0ab9c73129137533c965ed" - integrity sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA== +escalade@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: version "1.0.5" @@ -1419,6 +1431,11 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-levenshtein@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz#e6a754cc8f15e58987aa9cbd27af66fd6f4e5af9" + integrity sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk= + fast-safe-stringify@^2.0.4: version "2.0.7" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" @@ -1656,15 +1673,16 @@ glob-stream@^6.1.0: unique-stream "^2.0.2" glob-watcher@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.3.tgz#88a8abf1c4d131eb93928994bc4a593c2e5dd626" - integrity sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg== + version "5.0.5" + resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.5.tgz#aa6bce648332924d9a8489be41e3e5c52d4186dc" + integrity sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw== dependencies: anymatch "^2.0.0" async-done "^1.2.0" chokidar "^2.0.0" is-negated-glob "^1.0.0" just-debounce "^1.0.0" + normalize-path "^3.0.0" object.defaults "^1.1.0" glob@7.1.2: @@ -1828,11 +1846,11 @@ har-schema@^2.0.0: integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= har-validator@~5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== dependencies: - ajv "^6.5.5" + ajv "^6.12.3" har-schema "^2.0.0" has-flag@^3.0.0: @@ -1840,7 +1858,7 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -has-symbols@^1.0.0, has-symbols@^1.0.1: +has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== @@ -2052,10 +2070,17 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" - integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== +is-callable@^1.1.4, is-callable@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== + +is-core-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" + integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw== + dependencies: + has "^1.0.3" is-data-descriptor@^0.1.4: version "0.1.4" @@ -2147,6 +2172,11 @@ is-negated-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= +is-negative-zero@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" + integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -2176,10 +2206,10 @@ is-plain-object@^3.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== -is-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" - integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== +is-regex@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== dependencies: has-symbols "^1.0.1" @@ -2412,6 +2442,14 @@ liftoff@^3.1.0: rechoir "^0.6.2" resolve "^1.1.7" +line-column@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/line-column/-/line-column-1.0.2.tgz#d25af2936b6f4849172b312e4792d1d987bc34a2" + integrity sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI= + dependencies: + isarray "^1.0.0" + isobject "^2.0.0" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -2537,9 +2575,9 @@ lodash.pluck@^3.1.2: lodash.map "^3.0.0" lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.4: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== logform@^2.2.0: version "2.2.0" @@ -2552,7 +2590,7 @@ logform@^2.2.0: ms "^2.1.1" triple-beam "^1.3.0" -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -2779,9 +2817,14 @@ mute-stdout@^1.0.0: integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== nan@^2.12.1: - version "2.14.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" - integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== + version "2.14.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" + integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== + +nanoid@^3.1.15: + version "3.1.16" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.16.tgz#b21f0a7d031196faf75314d7c65d36352beeef64" + integrity sha512-+AK8MN0WHji40lj8AEuwLOvLSbWYApQpre/aFJZD71r43wVRLrOYS4FmJOPQYon1TqB462RzrrxlfA74XRES8w== nanomatch@^1.2.9: version "1.2.13" @@ -2839,10 +2882,10 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-releases@^1.1.58: - version "1.1.58" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.58.tgz#8ee20eef30fa60e52755fcc0942def5a734fe935" - integrity sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg== +node-releases@^1.1.61: + version "1.1.64" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.64.tgz#71b4ae988e9b1dd7c1ffce58dd9e561752dfebc5" + integrity sha512-Iec8O9166/x2HRMJyLLLWkd0sFFLrFNy+Xf+JQfSQsdBJzPcHpNl3JQ9gD4j+aJxmCa25jNsIbM4bmACtSbkSg== normalize-package-data@^2.3.2: version "2.5.0" @@ -2921,12 +2964,12 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.7.0: +object-inspect@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -2938,15 +2981,15 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.0.4, object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== +object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" + integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.0" + has-symbols "^1.0.1" + object-keys "^1.1.1" object.defaults@^1.0.0, object.defaults@^1.1.0: version "1.1.0" @@ -3258,14 +3301,15 @@ postcss-value-parser@^4.1.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss@^7.0.32: - version "7.0.34" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.34.tgz#f2baf57c36010df7de4009940f21532c16d65c20" - integrity sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw== +postcss@^8.1.2: + version "8.1.4" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.1.4.tgz#356dfef367a70f3d04347f74560c85846e20e4c1" + integrity sha512-LfqcwgMq9LOd8pX7K2+r2HPitlIGC5p6PoZhVELlqhh2YGDVcXKpkCseqan73Hrdik6nBd2OvoDPUaP/oMj9hQ== dependencies: - chalk "^2.4.2" + colorette "^1.2.1" + line-column "^1.0.2" + nanoid "^3.1.15" source-map "^0.6.1" - supports-color "^6.1.0" pretty-hrtime@^1.0.0: version "1.0.3" @@ -3292,15 +3336,6 @@ progress@^1.1.8: resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= -prop-types@^15.6.2: - version "15.7.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" - integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.8.1" - psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -3398,29 +3433,22 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -react-dom@^16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" - integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== +react-dom@17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6" + integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + scheduler "^0.20.1" -react-is@^16.8.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -react@^16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== +react@17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127" + integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" read-pkg-up@^1.0.1: version "1.0.1" @@ -3470,10 +3498,10 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" - integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== dependencies: picomatch "^2.2.1" @@ -3490,9 +3518,9 @@ rechoir@^0.6.2: resolve "^1.1.6" regenerator-runtime@^0.13.4: - version "0.13.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" - integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" @@ -3612,10 +3640,11 @@ resolve-url@^0.2.1: integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.4.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + version "1.18.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" + integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== dependencies: + is-core-module "^2.0.0" path-parse "^1.0.6" ret@~0.1.10: @@ -3638,10 +3667,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rxjs@^7.0.0-beta.4: - version "7.0.0-beta.5" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.0.0-beta.5.tgz#e2494c8d8393874e83357bb31706f6e3e5c2a2aa" - integrity sha512-cat2RoyKZ4w3W9Hl9E0YJKBL64sj0GG3Dc5CXzseCnKcz6vgYCplBxlCyt8fhlDIYchCzrmP5imrPNhImCYcCA== +rxjs@7.0.0-beta.4: + version "7.0.0-beta.4" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.0.0-beta.4.tgz#bca2bea86cf2074b56f37db202f978ae296c7517" + integrity sha512-VpZJjT9GLSTmQIYC9s1nsAYjJONg7UeSpLFd88uQJpw0VPyobs/2EwK2iTAMGeiLxJWj7BW66BcjeNA1hWbygQ== dependencies: tslib "^1.9.0" @@ -3668,9 +3697,9 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sass@^1.26.10: - version "1.26.11" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.26.11.tgz#0f22cc4ab2ba27dad1d4ca30837beb350b709847" - integrity sha512-W1l/+vjGjIamsJ6OnTe0K37U2DBO/dgsv2Z4c89XQ8ZOO6l/VwkqwLSqoYzJeJs6CLuGSTRWc91GbQFL3lvrvw== + version "1.27.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.27.0.tgz#0657ff674206b95ec20dc638a93e179c78f6ada2" + integrity sha512-0gcrER56OkzotK/GGwgg4fPrKuiFlPNitO7eUJ18Bs+/NBlofJfMxmxqpqJxjae9vu0Wq8TZzrSyxZal00WDig== dependencies: chokidar ">=2.0.0 <4.0.0" @@ -3679,20 +3708,20 @@ sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== +scheduler@^0.20.1: + version "0.20.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.1.tgz#da0b907e24026b01181ecbc75efdc7f27b5a000c" + integrity sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" scroll-into-view-if-needed@^2.2.20: - version "2.2.25" - resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.25.tgz#117b7bc7c61bc7a2b7872a0984bc73a19bc6e961" - integrity sha512-C8RKJPq9lK7eubwGpLbUkw3lklcG3Ndjmea2PyauzrA0i4DPlzAmVMGxaZrBFqCrVLfvJmP80IyHnv4jxvg1OQ== + version "2.2.26" + resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.26.tgz#e4917da0c820135ff65ad6f7e4b7d7af568c4f13" + integrity sha512-SQ6AOKfABaSchokAmmaxVnL9IArxEnLEX9j4wAZw+x4iUTb40q7irtHG3z4GtAWz5veVZcCnubXDBRyLVQaohw== dependencies: - compute-scroll-into-view "^1.0.14" + compute-scroll-into-view "^1.0.16" semver-greatest-satisfied-range@^1.1.0: version "1.1.0" @@ -3739,10 +3768,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.11.0: - version "2.11.4" - resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.11.4.tgz#11778b0cccd67be472f57278a0b870ed17ca878c" - integrity sha512-sIc1MfN/JsGenbFkDXs0+iVrKTSzAE5DzBFuUGxHc4LbcAJ9GWRQlAeE0WRT3fOCQQOlxeBLrxlZ6WiUjKlQgg== +shadow-cljs@2.11.5: + version "2.11.5" + resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.11.5.tgz#32d4318e2e2b049701d98b7249863f61edc3e9ef" + integrity sha512-Z1MV7QDPGlYBKBtVehD29UMC9P8t5XWtaKmV2UvC5uhYXugmxqkCAwlThRnYoP5smfQ08a7MQpeh60WCyP3RRA== dependencies: node-libs-browser "^2.2.1" readline-sync "^1.4.7" @@ -3819,10 +3848,10 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -slate-react@^0.58.4: - version "0.58.4" - resolved "https://registry.yarnpkg.com/slate-react/-/slate-react-0.58.4.tgz#ecde34dd322af8413c2f23f0f0a577fed2422f46" - integrity sha512-3JXWHVPmYrR/EMcH1qtmwXZtYxxCtuokQg+r/4wD1Ab9J3zRmW4UGLoaRP9/uQrZOAOvr8mMh4IgmrxV2dQskw== +slate-react@^0.59.0: + version "0.59.0" + resolved "https://registry.yarnpkg.com/slate-react/-/slate-react-0.59.0.tgz#c8043dce7ea71279f314d9951c32e4f548b1ea0b" + integrity sha512-Fx5vfTi0s1fY5PaXzPH8uA9mW8aevVVYrGGvqX/k363tlPDnQSs/QTibIyFl1Y3MPJ+GdocoyOGjAaZMUIXfIg== dependencies: "@types/is-hotkey" "^0.1.1" "@types/lodash" "^4.14.149" @@ -3832,10 +3861,10 @@ slate-react@^0.58.4: lodash "^4.17.4" scroll-into-view-if-needed "^2.2.20" -slate@^0.58.4: - version "0.58.4" - resolved "https://registry.yarnpkg.com/slate/-/slate-0.58.4.tgz#4259387e632b45b00cf88bcecf5570d7d16ddd8b" - integrity sha512-XxKwNJgCMf7S2sDT8CVJy0zYm95MiYorJo9Hah05zKjItrw0VVeCc2BGKDZSlNGcaIfM3xcfFUN7XE+c8ehAbA== +slate@^0.59.0: + version "0.59.0" + resolved "https://registry.yarnpkg.com/slate/-/slate-0.59.0.tgz#3169daf2f036e84aa149f60e0d12ef2fc4c0839e" + integrity sha512-M4UTMkXExxuq8tCD+knn7BtV2pmY8pepay++EF59rmg/v4RB6X1gNzA0xP3aw2rqYl8TmWdOBdy9InFrm3WyXw== dependencies: "@types/esrever" "^0.2.0" esrever "^0.2.0" @@ -3941,9 +3970,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + version "3.0.6" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" + integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -4044,20 +4073,20 @@ string-width@^2.0.0, string-width@^2.1.1: strip-ansi "^4.0.0" string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + version "1.0.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz#6ddd9a8796bc714b489a3ae22246a208f37bfa46" + integrity sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw== dependencies: define-properties "^1.1.3" - es-abstract "^1.17.5" + es-abstract "^1.18.0-next.1" string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + version "1.0.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz#22d45da81015309cd0cdd79787e8919fc5c613e7" + integrity sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg== dependencies: define-properties "^1.1.3" - es-abstract "^1.17.5" + es-abstract "^1.18.0-next.1" string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" @@ -4113,13 +4142,6 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - sver-compat@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8" @@ -4308,9 +4330,9 @@ triple-beam@^1.2.0, triple-beam@^1.3.0: integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== tslib@^1.9.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" - integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tty-browserify@0.0.0: version "0.0.0" @@ -4335,9 +4357,9 @@ type@^1.0.1: integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" - integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== + version "2.1.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f" + integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA== typedarray@^0.0.6: version "0.0.6" @@ -4360,15 +4382,16 @@ undertaker-registry@^1.0.0: integrity sha1-XkvaMI5KiirlhPm5pDWaSZglzFA= undertaker@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/undertaker/-/undertaker-1.2.1.tgz#701662ff8ce358715324dfd492a4f036055dfe4b" - integrity sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA== + version "1.3.0" + resolved "https://registry.yarnpkg.com/undertaker/-/undertaker-1.3.0.tgz#363a6e541f27954d5791d6fa3c1d321666f86d18" + integrity sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg== dependencies: arr-flatten "^1.0.1" arr-map "^2.0.0" bach "^1.0.0" collection-map "^1.0.0" es6-weak-map "^2.0.1" + fast-levenshtein "^1.0.0" last-run "^1.1.0" object.defaults "^1.0.0" object.reduce "^1.0.0" @@ -4411,9 +4434,9 @@ upath@^1.1.1: integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + version "4.4.0" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" + integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== dependencies: punycode "^2.1.0" @@ -4535,9 +4558,9 @@ vinyl-sourcemap@^1.1.0: vinyl "^2.0.0" vinyl@^2.0.0, vinyl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" - integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg== + version "2.2.1" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974" + integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw== dependencies: clone "^2.1.1" clone-buffer "^1.0.0"