From 718a676fa8645508a36e0163c0a2d63b4f9b2a47 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 29 Sep 2020 16:17:45 +0200 Subject: [PATCH] :sparkles: Adds typography to libraries --- backend/src/app/services/init.clj | 2 - backend/src/app/services/mutations/colors.clj | 150 ------- backend/src/app/services/mutations/files.clj | 3 +- backend/src/app/services/queries/colors.clj | 104 ----- common/app/common/pages.cljc | 56 +++ frontend/resources/images/icons/unchain.svg | 7 + frontend/resources/locales.json | 5 +- .../partials/sidebar-element-options.scss | 120 ++++- frontend/src/app/main/data/colors.cljs | 61 --- frontend/src/app/main/data/workspace.cljs | 7 +- .../app/main/data/workspace/libraries.cljs | 53 +++ .../app/main/data/workspace/persistence.cljs | 3 +- frontend/src/app/main/fonts.cljs | 8 + frontend/src/app/main/refs.cljs | 6 + frontend/src/app/main/ui/icons.cljs | 1 + .../src/app/main/ui/workspace/libraries.cljs | 8 +- .../app/main/ui/workspace/shapes/text.cljs | 6 +- .../app/main/ui/workspace/sidebar/assets.cljs | 166 ++++++- .../workspace/sidebar/options/multiple.cljs | 12 +- .../ui/workspace/sidebar/options/text.cljs | 414 ++++++------------ .../workspace/sidebar/options/typography.cljs | 279 ++++++++++++ frontend/src/app/util/dom.cljs | 2 + 22 files changed, 831 insertions(+), 642 deletions(-) delete mode 100644 backend/src/app/services/mutations/colors.clj delete mode 100644 backend/src/app/services/queries/colors.clj create mode 100644 frontend/resources/images/icons/unchain.svg create mode 100644 frontend/src/app/main/ui/workspace/sidebar/options/typography.cljs diff --git a/backend/src/app/services/init.clj b/backend/src/app/services/init.clj index 37edf7d4e8..e08137926e 100644 --- a/backend/src/app/services/init.clj +++ b/backend/src/app/services/init.clj @@ -15,7 +15,6 @@ (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.profile) @@ -26,7 +25,6 @@ [] (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.profile) 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/files.clj b/backend/src/app/services/mutations/files.clj index 23b5392f43..fe844f165b 100644 --- a/backend/src/app/services/mutations/files.clj +++ b/backend/src/app/services/mutations/files.clj @@ -245,7 +245,8 @@ [change] (or (#{:add-color :mod-color :del-color :add-media :mod-media :del-media - :add-component :mod-component :del-component} (:type change)) + :add-component :mod-component :del-component + :add-typography :mod-typography :del-typography} (:type change)) (and (= (:type change) :mod-obj) (some? (:component-id change))))) 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/common/app/common/pages.cljc b/common/app/common/pages.cljc index 9d078f024e..809c0c9e3e 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -299,6 +299,31 @@ (s/def :internal.file/recent-colors (s/coll-of ::string :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?)) @@ -412,6 +437,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)) @@ -803,6 +839,8 @@ (subvec rc 1) rc))))) +;; -- Media + (defmethod process-change :add-media [data {:keys [object]}] (update data :media assoc (:id object) object)) @@ -815,6 +853,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] @@ -833,6 +873,22 @@ [data {:keys [id]}] (d/dissoc-in data [:components id])) +;; -- Typography + +(defmethod process-change :add-typography + [data {:keys [typography]}] + (update data :typography assoc (:id typography) typography)) + +(defmethod process-change :mod-typography + [data {:keys [typography]}] + (d/update-in-when data [:typography (:id typography)] merge typography)) + +(defmethod process-change :del-typography + [data {:keys [id]}] + (update data :typography dissoc id)) + +;; -- Operations + (defmethod process-operation :set [shape op] (let [attr (:attr op) 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 206a6da857..e37b2f668e 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -2801,5 +2801,8 @@ "ru" : "Кликни чтобы закончить фигуру", "es" : "Pulsar para cerrar la ruta" } - } + }, + + "workspace.assets.typography": "Typographies", + "workspace.libraries.typography": "%s typographies" } diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 4e6201959a..39dc4b6a36 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -69,8 +69,8 @@ font-size: $fs13; padding: $small $x-small; width: 100%; + align-items: center; } - } .element-list { @@ -768,7 +768,7 @@ z-index: 10; } -.element-set-content .advanced-options { +.advanced-options { background-color: #303236; border-radius: 4px; left: -8px; @@ -876,3 +876,119 @@ .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: 15rem; + 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; + } + } +} diff --git a/frontend/src/app/main/data/colors.cljs b/frontend/src/app/main/data/colors.cljs index ac1d1513e3..c3d65e6c95 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 diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index e809a4c0bd..0856407fe1 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -73,9 +73,10 @@ (s/def ::layout-flags (s/coll-of ::layout-flag)) (def default-layout - #{:sitemap - :sitemap-pages - :layers + #{;; :sitemap + ;; :sitemap-pages + ;; :layers + :assets :element-options :rules :display-grid diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 99309c5845..3ea1a5b3dc 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -519,3 +519,56 @@ :callback do-dismiss}] :sync-dialog)))))) + +(def default-typography + {:name "Source Sans Pro Regular" + :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"}) + +(defn add-typography + [typography] + (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 typography} + uchg {:type :del-typography + :id (:id typography)}] + (rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))) + +(defn update-typography + [typography] + (us/assert ::cp/typography typography) + + (ptk/reify ::update-typography + ptk/WatchEvent + (watch [_ state stream] + (let [prev (get-in state [:workspace-data :typography (: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 :typography 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/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 3d99ec98f3..31d9c33dc8 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -300,8 +300,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 diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index cbdf0d35ae..afec1c0fa2 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -96,6 +96,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 +167,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..e4b053f777 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -97,6 +97,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 :typography]))) + st/state)) + (def workspace-project (l/derived :workspace-project st/state)) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 157bcddb2f..34c34f550c 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -28,6 +28,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)) diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 10e9a38347..ada134ebbb 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -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 :typography] []))] ;; 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 diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs index 5df7525083..1388fac07d 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs @@ -289,12 +289,15 @@ (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 assets target) (.contains self target) (and cpicker (.contains cpicker target))) (if selecting? @@ -340,7 +343,8 @@ (when (not read-only?) (let [content (js->clj val :keywordize-keys true) content (first content)] - (st/emit! (dw/update-shape id {:content 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)))))] diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index cd6a468be1..0c6d4f3fbe 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -19,6 +19,7 @@ [app.config :as cfg] [app.main.data.workspace :as dw] [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.texts :as dwt] [app.main.data.colors :as dc] [app.main.refs :as refs] [app.main.store :as st] @@ -26,6 +27,7 @@ [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.workspace.sidebar.options.typography :refer [typography-entry]] [app.main.ui.icons :as i] [app.main.ui.keyboard :as kbd] [app.main.ui.modal :as modal] @@ -41,7 +43,7 @@ [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 @@ -75,27 +77,28 @@ (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 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}] @@ -326,6 +329,88 @@ :local? local? :locale locale}])])])) +(mf/defc typography-box + [{:keys [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) + + add-typography + (mf/use-callback + (mf/deps file-id) + (fn [value opacity] + (st/emit! (dwl/add-typography dwl/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 nil :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 []) + handle-edit-typography-clicked (fn [] ) + handle-delete-typography (fn [] + (st/emit! (dwl/delete-typography (:id @state))))] + + [:div.asset-group + [:div.group-title {:class (when (not open?) "closed")} + [:span {:on-click #(if open? (on-close) (on-open))} i/arrow-slide "Typography" #_(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 (comp - :ts) typographies)] + [:& typography-entry + {:key (:id typography) + :typography typography + :read-only? (not local?) + :on-context-menu #(on-context-menu (:id typography) %) + :on-change #(handle-change typography %) + :on-select #(handle-typography-selection typography)}])])])) + (defn file-colors-ref [id] (l/derived (fn [state] @@ -354,6 +439,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 :typography])) + (vals (get-in state [:workspace-libraries id :data :typography]))))) + st/state =)) + (defn apply-filters [coll filters] (->> coll @@ -369,7 +463,10 @@ router (mf/deref refs/router) toggle-open #(swap! open? not) - toggles (mf/use-state #{:graphics :colors}) + toggles (mf/use-state #{:components + :graphics + :colors + :typography}) url (rt/resolve router :workspace {:project-id (:project-id file) @@ -379,6 +476,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) @@ -413,13 +513,20 @@ (str/empty? (:term filters)))) 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) :typography)) (or (> (count colors) 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,6 +543,15 @@ :on-open #(swap! toggles conj :colors) :on-close #(swap! toggles disj :colors)}]) + (when show-typography? + [:& typography-box {:file-id (:id file) + :local? local? + :locale locale + :typographies typographies + :open? (contains? @toggles :typography) + :on-open #(swap! toggles conj :typography) + :on-close #(swap! toggles disj :typography)}]) + (when (and (not show-components?) (not show-graphics?) (not show-colors?)) [:div.asset-group [:div.group-title (t locale "workspace.assets.not-found")]])]))])) @@ -495,8 +611,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 ":typography"} (t locale "workspace.assets.typography")]]]] [:div.libraries-wrapper [:& file-library 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..b51ff9daf3 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/multiple.cljs @@ -144,11 +144,11 @@ [:& 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 + :values (merge font-values + align-values + spacing-values + valign-values + decoration-values + transform-values) :shapes shapes}])])) 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..b3dd41d3f9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/text.cljs @@ -14,20 +14,23 @@ [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.util.dom :as dom] [app.main.fonts :as fonts] [app.util.i18n :as i18n :refer [tr t]] ["slate" :refer [Transforms]])) +(def text-typography-attrs [:typography-ref-id :typography-ref-file]) (def text-fill-attrs [: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]) @@ -36,205 +39,52 @@ (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))) @@ -243,150 +93,171 @@ 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) + (:typography-ref-file values)) + (-> shared-libs + (get-in [(:typography-ref-file values) :data :typography (:typography-ref-id values)]) + (assoc :file-id (:typography-ref-file values))) + + (:typography-ref-id values) + (get typographies (:typography-ref-id values))) + + + handle-click + (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 dwl/default-typography setted-values) + typography (generate-typography-name typography)] + (let [id (uuid/next)] + (st/emit! (dwl/add-typography (assoc typography :id id))) + (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 + :on-change (fn [attrs] + (run! #(emit-update! % attrs) ids)) + :locale locale}] + + [:div.element-set + [:div.element-set-title + [:span label] + [:div.add-page {:on-click handle-click} i/close]] + + (if typography + [:& typography-entry {:typography typography + :on-deattach handle-deattach-typography + :on-change handle-change-typography}] + [:> 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,42 +270,25 @@ 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)} - font-values (dwt/current-text-values - {:editor editor - :shape shape - :attrs text-font-attrs}) - 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 @@ -448,11 +302,5 @@ :values (select-keys shape [:shadow])}] [:& text-menu {:ids ids :type type - :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 + :values text-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..b64eaf7f48 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/options/typography.cljs @@ -0,0 +1,279 @@ +;; 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.util.dom :as dom] + [app.main.fonts :as fonts] + [app.util.i18n :as i18n :refer [t]])) + +(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 "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] + (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 [{:value "" :label "--"}] 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 + {: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)])]]])) + + +(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]}] + (let [open? (mf/use-state false) + selected (mf/deref refs/selected-shapes) + hover-deattach (mf/use-state false)] + [:* + [: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)}} + "Ag"] + [: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 "Font"] + [:span (:font-id typography)]] + + [:div.row-flex + [:span.label "Size"] + [:span (:font-size typography)]] + + [:div.row-flex + [:span.label "Line Height"] + [:span (:line-height typography)]] + + [:div.row-flex + [:span.label "Letter spacing"] + [:span (:letter-spacing typography)]] + + [:div.row-flex + [:span.label "Text transform"] + [:span (:text-transform typography)]] + + [:div.go-to-lib-button + "Go to style library file to edit"]] + + [:* + [:div.element-set-content + [:div.row-flex + [:input.element-name.adv-typography-name + {:type "text" + :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/util/dom.cljs b/frontend/src/app/util/dom.cljs index 57ac2d37cf..8c8817671e 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]