Adds typography to libraries

This commit is contained in:
alonso.torres 2020-09-29 16:17:45 +02:00 committed by Hirunatan
parent 4a4cff74e8
commit 718a676fa8
22 changed files with 831 additions and 642 deletions

View file

@ -15,7 +15,6 @@
(defn- load-query-services (defn- load-query-services
[] []
(require 'app.services.queries.media) (require 'app.services.queries.media)
(require 'app.services.queries.colors)
(require 'app.services.queries.projects) (require 'app.services.queries.projects)
(require 'app.services.queries.files) (require 'app.services.queries.files)
(require 'app.services.queries.profile) (require 'app.services.queries.profile)
@ -26,7 +25,6 @@
[] []
(require 'app.services.mutations.demo) (require 'app.services.mutations.demo)
(require 'app.services.mutations.media) (require 'app.services.mutations.media)
(require 'app.services.mutations.colors)
(require 'app.services.mutations.projects) (require 'app.services.mutations.projects)
(require 'app.services.mutations.files) (require 'app.services.mutations.files)
(require 'app.services.mutations.profile) (require 'app.services.mutations.profile)

View file

@ -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)))

View file

@ -245,7 +245,8 @@
[change] [change]
(or (#{:add-color :mod-color :del-color (or (#{:add-color :mod-color :del-color
:add-media :mod-media :del-media :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) (and (= (:type change) :mod-obj)
(some? (:component-id change))))) (some? (:component-id change)))))

View file

@ -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 <niwi@niwi.nz>
(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))

View file

@ -299,6 +299,31 @@
(s/def :internal.file/recent-colors (s/def :internal.file/recent-colors
(s/coll-of ::string :kind vector?)) (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/def :internal.file/pages
(s/coll-of ::uuid :kind vector?)) (s/coll-of ::uuid :kind vector?))
@ -412,6 +437,17 @@
(defmethod change-spec :del-component [_] (defmethod change-spec :del-component [_]
(s/keys :req-un [::id])) (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 ::change (s/multi-spec change-spec :type))
(s/def ::changes (s/coll-of ::change)) (s/def ::changes (s/coll-of ::change))
@ -803,6 +839,8 @@
(subvec rc 1) (subvec rc 1)
rc))))) rc)))))
;; -- Media
(defmethod process-change :add-media (defmethod process-change :add-media
[data {:keys [object]}] [data {:keys [object]}]
(update data :media assoc (:id object) object)) (update data :media assoc (:id object) object))
@ -815,6 +853,8 @@
[data {:keys [id]}] [data {:keys [id]}]
(update data :media dissoc id)) (update data :media dissoc id))
;; -- Components
(defmethod process-change :add-component (defmethod process-change :add-component
[data {:keys [id name shapes]}] [data {:keys [id name shapes]}]
(assoc-in data [:components id] (assoc-in data [:components id]
@ -833,6 +873,22 @@
[data {:keys [id]}] [data {:keys [id]}]
(d/dissoc-in data [:components 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 (defmethod process-operation :set
[shape op] [shape op]
(let [attr (:attr op) (let [attr (:attr op)

View file

@ -0,0 +1,7 @@
<svg width="40" height="41" viewBox="0 0 40 41" xmlns="http://www.w3.org/2000/svg">
<path d="M27.3455 0.855913C24.5854 1.57398 22.3027 3.61844 21.0431 6.14402C20.1365 7.69681 19.033 9.20989 18.595 10.9747C18.5791 12.5077 20.6042 13.6713 21.8504 12.6729C22.6753 11.9726 23.1234 10.9544 23.7106 10.0663C24.635 8.50138 25.4362 6.73624 26.9321 5.62399C29.1019 4.25506 32.2456 4.42792 34.0446 6.34338C35.8577 8.1232 36.2905 11.1996 34.8418 13.3314C32.9321 16.6169 31.1617 19.9943 29.0399 23.1496C27.4127 25.0133 24.4999 25.5226 22.3148 24.3806C21.3889 23.9825 20.1292 23.8473 19.4164 24.7113C18.5786 25.6499 18.7897 27.3327 19.9352 27.9313C21.5038 28.8608 23.3602 29.3035 25.1804 29.2411C28.4779 29.143 31.7684 27.3471 33.3539 24.3955C35.2416 21.2163 37.1745 18.059 38.8823 14.7782C40.1672 12.4492 40.2813 9.54151 39.3961 7.06543C38.2811 3.73956 35.2239 1.14689 31.7426 0.612769C30.2794 0.366248 28.7587 0.444615 27.3455 0.855913Z"/>
<path d="M21.5425 15.5518C18.5342 14.718 15.2111 15.4777 12.7177 17.3188C9.61691 19.2557 6.23319 20.7461 3.34262 23.0045C0.406478 25.6204 -0.667 30.0993 0.749192 33.7793C2.05062 37.3431 5.55324 39.9812 9.32072 40.1837C11.3238 40.3507 13.3742 39.8349 15.0901 38.8156C16.7842 37.9099 18.4823 36.9731 20.0268 35.832C21.0495 34.7451 20.3594 32.7262 18.9072 32.4378C17.9352 32.2308 17.0769 32.9029 16.2329 33.269C14.3839 34.2493 12.6424 35.605 10.5346 35.969C7.95596 36.1495 5.28979 34.4567 4.60879 31.9141C3.8766 29.5283 4.92467 26.6732 7.15855 25.4846C10.4996 23.5458 13.7869 21.5028 17.2426 19.7703C19.6534 18.9028 22.5372 20.0004 23.8578 22.1732C24.4664 22.9945 25.6092 23.6365 26.6126 23.1317C27.7622 22.6301 28.2785 21.0176 27.4862 19.9962C26.1927 17.801 23.9992 16.1919 21.5425 15.5518Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.06066 4.93934C5.64645 4.35355 6.59619 4.35355 7.18198 4.93934L11.0607 8.81802C11.6464 9.40381 11.6464 10.3536 11.0607 10.9393C10.4749 11.5251 9.52513 11.5251 8.93934 10.9393L5.06066 7.06066C4.47487 6.47487 4.47487 5.52513 5.06066 4.93934Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.5 0C14.3284 3.62117e-08 15 0.671573 15 1.5V6.98528C15 7.81371 14.3284 8.48528 13.5 8.48528C12.6716 8.48528 12 7.81371 12 6.98528V1.5C12 0.671573 12.6716 -3.62117e-08 13.5 0Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 13.5C0 12.6716 0.671573 12 1.5 12H6.98528C7.81371 12 8.48528 12.6716 8.48528 13.5C8.48528 14.3284 7.81371 15 6.98528 15H1.5C0.671573 15 0 14.3284 0 13.5Z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -2801,5 +2801,8 @@
"ru" : "Кликни чтобы закончить фигуру", "ru" : "Кликни чтобы закончить фигуру",
"es" : "Pulsar para cerrar la ruta" "es" : "Pulsar para cerrar la ruta"
} }
} },
"workspace.assets.typography": "Typographies",
"workspace.libraries.typography": "%s typographies"
} }

View file

@ -69,8 +69,8 @@
font-size: $fs13; font-size: $fs13;
padding: $small $x-small; padding: $small $x-small;
width: 100%; width: 100%;
align-items: center;
} }
} }
.element-list { .element-list {
@ -768,7 +768,7 @@
z-index: 10; z-index: 10;
} }
.element-set-content .advanced-options { .advanced-options {
background-color: #303236; background-color: #303236;
border-radius: 4px; border-radius: 4px;
left: -8px; left: -8px;
@ -876,3 +876,119 @@
.element-set-options-group:hover .element-set-actions { .element-set-options-group:hover .element-set-actions {
visibility: visible; 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;
}
}
}

View file

@ -25,29 +25,6 @@
[app.main.data.modal :as md] [app.main.data.modal :as md]
[app.common.pages-helpers :as cph])) [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 (def clear-color-for-rename
(ptk/reify ::clear-color-for-rename (ptk/reify ::clear-color-for-rename
ptk/UpdateEvent ptk/UpdateEvent
@ -73,44 +50,6 @@
(-> state (-> state
(update-in [:workspace-file :colors] #(d/replace-by-id % color)))))) (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] (defn change-palette-size [size]
(s/assert #{:big :small} size) (s/assert #{:big :small} size)
(ptk/reify ::change-palette-size (ptk/reify ::change-palette-size

View file

@ -73,9 +73,10 @@
(s/def ::layout-flags (s/coll-of ::layout-flag)) (s/def ::layout-flags (s/coll-of ::layout-flag))
(def default-layout (def default-layout
#{:sitemap #{;; :sitemap
:sitemap-pages ;; :sitemap-pages
:layers ;; :layers
:assets
:element-options :element-options
:rules :rules
:display-grid :display-grid

View file

@ -519,3 +519,56 @@
:callback do-dismiss}] :callback do-dismiss}]
:sync-dialog)))))) :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}))))))

View file

@ -300,8 +300,7 @@
(rx/mapcat (rx/mapcat
#(rx/zip (rp/query :file-library {:file-id library-id}) #(rx/zip (rp/query :file-library {:file-id library-id})
(rp/query :media-objects {:file-id library-id (rp/query :media-objects {:file-id library-id
:is-local false}) :is-local false}))))
(rp/query :colors {:file-id library-id}))))
(rx/map file-linked)))))) (rx/map file-linked))))))
(defn file-linked (defn file-linked

View file

@ -96,6 +96,9 @@
(register! :builtin local-fonts) (register! :builtin local-fonts)
(register! :google google-fonts) (register! :google google-fonts)
(defn get-font-data [id]
(get @fontsdb id))
(defn resolve-variants (defn resolve-variants
[id] [id]
(get-in @fontsdb [id :variants])) (get-in @fontsdb [id :variants]))
@ -164,3 +167,8 @@
(defn ready [cb] (defn ready [cb]
(-> (obj/get-in js/document ["fonts" "ready"]) (-> (obj/get-in js/document ["fonts" "ready"])
(p/then cb))) (p/then cb)))
(defn get-default-variant [{:keys [variants]}]
(or
(d/seek #(or (= (:id %) "regular") (= (:name %) "regular")) variants)
(first variants)))

View file

@ -97,6 +97,12 @@
(get-in state [:workspace-data :recent-colors] [])) (get-in state [:workspace-data :recent-colors] []))
st/state)) 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 (def workspace-project
(l/derived :workspace-project st/state)) (l/derived :workspace-project st/state))

View file

@ -28,6 +28,7 @@
(def auto-width (icon-xref :auto-width)) (def auto-width (icon-xref :auto-width))
(def box (icon-xref :box)) (def box (icon-xref :box))
(def chain (icon-xref :chain)) (def chain (icon-xref :chain))
(def unchain (icon-xref :unchain))
(def chat (icon-xref :chat)) (def chat (icon-xref :chat))
(def circle (icon-xref :circle)) (def circle (icon-xref :circle))
(def close (icon-xref :close)) (def close (icon-xref :close))

View file

@ -27,7 +27,8 @@
[library] [library]
(let [components-count (count (get-in library [:data :components] [])) (let [components-count (count (get-in library [:data :components] []))
graphics-count (count (get-in library [:data :media] [])) 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 &nbsp; so this block has always some content ;; Include a &nbsp; so this block has always some content
(str (str
(str/join " · " (str/join " · "
@ -39,7 +40,10 @@
(conj (tr "workspace.libraries.graphics" graphics-count)) (conj (tr "workspace.libraries.graphics" graphics-count))
(< 0 colors-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"))) "\u00A0")))
(mf/defc libraries-tab (mf/defc libraries-tab

View file

@ -289,12 +289,15 @@
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(let [sidebar (dom/get-element "settings-bar") (let [sidebar (dom/get-element "settings-bar")
assets (dom/get-element-by-class "assets-bar")
cpicker (dom/get-element-by-class "colorpicker-tooltip") cpicker (dom/get-element-by-class "colorpicker-tooltip")
self (mf/ref-val self-ref) self (mf/ref-val self-ref)
target (dom/get-target event) target (dom/get-target event)
selecting? (mf/ref-val selecting-ref)] selecting? (mf/ref-val selecting-ref)]
(when-not (or (.contains sidebar target) (when-not (or (.contains sidebar target)
(.contains assets target)
(.contains self target) (.contains self target)
(and cpicker (.contains cpicker target))) (and cpicker (.contains cpicker target)))
(if selecting? (if selecting?
@ -340,7 +343,8 @@
(when (not read-only?) (when (not read-only?)
(let [content (js->clj val :keywordize-keys true) (let [content (js->clj val :keywordize-keys true)
content (first content)] 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! state val)
(reset! content-var content)))))] (reset! content-var content)))))]

View file

@ -19,6 +19,7 @@
[app.config :as cfg] [app.config :as cfg]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.texts :as dwt]
[app.main.data.colors :as dc] [app.main.data.colors :as dc]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
@ -26,6 +27,7 @@
[app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.components.context-menu :refer [context-menu]]
[app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.file-uploader :refer [file-uploader]]
[app.main.ui.components.tab-container :refer [tab-container tab-element]] [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.icons :as i]
[app.main.ui.keyboard :as kbd] [app.main.ui.keyboard :as kbd]
[app.main.ui.modal :as modal] [app.main.ui.modal :as modal]
@ -41,7 +43,7 @@
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(mf/defc components-box (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 (let [state (mf/use-state {:menu-open false
:top nil :top nil
:left nil :left nil
@ -75,27 +77,28 @@
(dnd/set-allowed-effect! event "move")))] (dnd/set-allowed-effect! event "move")))]
[:div.asset-group [:div.asset-group
[:div.group-title [:div.group-title {:class (when (not open?) "closed")}
(tr "workspace.assets.components") [: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 [:span (str "\u00A0(") (count components) ")"]] ;; Unicode 00A0 is non-breaking space
[:div.group-grid.big (when open?
(for [component components] [:div.group-grid.big
[:div.grid-cell {:key (:id component) (for [component components]
:draggable true [:div.grid-cell {:key (:id component)
:on-context-menu (on-context-menu (:id component)) :draggable true
:on-drag-start (partial on-drag-start component)} :on-context-menu (on-context-menu (:id component))
[:& exports/component-svg {:group (get-in component [:objects (:id component)]) :on-drag-start (partial on-drag-start component)}
:objects (:objects component)}] [:& exports/component-svg {:group (get-in component [:objects (:id component)])
[:div.cell-name (:name component)]]) :objects (:objects component)}]
[:div.cell-name (:name component)]])])
(when local? (when local?
[:& context-menu [:& context-menu
{:selectable false {:selectable false
:show (:menu-open @state) :show (:menu-open @state)
:on-close #(swap! state assoc :menu-open false) :on-close #(swap! state assoc :menu-open false)
:top (:top @state) :top (:top @state)
:left (:left @state) :left (:left @state)
:options [[(tr "workspace.assets.delete") on-delete]]}])]])) :options [[(tr "workspace.assets.delete") on-delete]]}])]))
(mf/defc graphics-box (mf/defc graphics-box
[{:keys [file-id local? objects open? on-open on-close] :as props}] [{:keys [file-id local? objects open? on-open on-close] :as props}]
@ -326,6 +329,88 @@
:local? local? :local? local?
:locale locale}])])])) :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 (defn file-colors-ref
[id] [id]
(l/derived (fn [state] (l/derived (fn [state]
@ -354,6 +439,15 @@
(vals (get-in state [:workspace-libraries id :data :components]))))) (vals (get-in state [:workspace-libraries id :data :components])))))
st/state =)) 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 (defn apply-filters
[coll filters] [coll filters]
(->> coll (->> coll
@ -369,7 +463,10 @@
router (mf/deref refs/router) router (mf/deref refs/router)
toggle-open #(swap! open? not) toggle-open #(swap! open? not)
toggles (mf/use-state #{:graphics :colors}) toggles (mf/use-state #{:components
:graphics
:colors
:typography})
url (rt/resolve router :workspace url (rt/resolve router :workspace
{:project-id (:project-id file) {:project-id (:project-id file)
@ -379,6 +476,9 @@
colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file))) colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file)))
colors (apply-filters (mf/deref colors-ref) filters) colors (apply-filters (mf/deref colors-ref) filters)
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-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file)))
media (apply-filters (mf/deref media-ref) filters) media (apply-filters (mf/deref media-ref) filters)
@ -413,13 +513,20 @@
(str/empty? (:term filters)))) (str/empty? (:term filters))))
show-colors? (and (or (= (:box filters) :all) show-colors? (and (or (= (:box filters) :all)
(= (:box filters) :colors)) (= (: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) (or (> (count colors) 0)
(str/empty? (:term filters))))] (str/empty? (:term filters))))]
[:div.tool-window-content [:div.tool-window-content
(when show-components? (when show-components?
[:& components-box {:file-id (:id file) [:& components-box {:file-id (:id file)
:local? local? :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? (when show-graphics?
[:& graphics-box {:file-id (:id file) [:& graphics-box {:file-id (:id file)
:local? local? :local? local?
@ -436,6 +543,15 @@
:on-open #(swap! toggles conj :colors) :on-open #(swap! toggles conj :colors)
:on-close #(swap! toggles disj :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?)) (when (and (not show-components?) (not show-graphics?) (not show-colors?))
[:div.asset-group [:div.asset-group
[:div.group-title (t locale "workspace.assets.not-found")]])]))])) [:div.group-title (t locale "workspace.assets.not-found")]])]))]))
@ -495,8 +611,10 @@
[:select.input-select {:value (:box @filters) [:select.input-select {:value (:box @filters)
:on-change on-box-filter-change} :on-change on-box-filter-change}
[:option {:value ":all"} (t locale "workspace.assets.box-filter-all")] [:option {:value ":all"} (t locale "workspace.assets.box-filter-all")]
[:option {:value ":graphics"} (t locale "workspace.assets.box-filter-graphics")] [:option {:value ":components"} (t locale "workspace.assets.components")]
[:option {:value ":colors"} (t locale "workspace.assets.box-filter-colors")]]]] [: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 [:div.libraries-wrapper
[:& file-library [:& file-library

View file

@ -144,11 +144,11 @@
[:& text-menu {:ids text-ids [:& text-menu {:ids text-ids
:type :multiple :type :multiple
:editor nil :editor nil
:font-values font-values :values (merge font-values
:align-values align-values align-values
:spacing-values spacing-values spacing-values
:valign-values valign-values valign-values
:decoration-values decoration-values decoration-values
:transform-values transform-values transform-values)
:shapes shapes}])])) :shapes shapes}])]))

View file

@ -14,20 +14,23 @@
[okulary.core :as l] [okulary.core :as l]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.common.data :as d] [app.common.data :as d]
[app.common.uuid :as uuid]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc] [app.main.data.workspace.common :as dwc]
[app.main.data.workspace.texts :as dwt] [app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.libraries :as dwl]
[app.main.store :as st] [app.main.store :as st]
[app.main.refs :as refs] [app.main.refs :as refs]
[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.fill :refer [fill-menu]] [app.main.ui.workspace.sidebar.options.fill :refer [fill-menu]]
[app.main.ui.workspace.sidebar.options.shadow :refer [shadow-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.util.dom :as dom]
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.util.i18n :as i18n :refer [tr t]] [app.util.i18n :as i18n :refer [tr t]]
["slate" :refer [Transforms]])) ["slate" :refer [Transforms]]))
(def text-typography-attrs [:typography-ref-id :typography-ref-file])
(def text-fill-attrs [:fill :opacity]) (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-font-attrs [:font-id :font-family :font-variant-id :font-size :font-weight :font-style])
(def text-align-attrs [:text-align]) (def text-align-attrs [:text-align])
@ -36,205 +39,52 @@
(def text-decoration-attrs [:text-decoration]) (def text-decoration-attrs [:text-decoration])
(def text-transform-attrs [:text-transform]) (def text-transform-attrs [:text-transform])
(defn- attr->string [value] (def root-attrs (d/concat text-valign-attrs
(if (= value :multiple) text-align-attrs))
"" (def paragraph-attrs text-align-attrs)
(str value))) (def text-attrs (d/concat text-typography-attrs
text-font-attrs
(def ^:private editor-ref text-align-attrs
(l/derived :editor refs/workspace-local)) text-spacing-attrs
text-decoration-attrs
(mf/defc font-select-optgroups text-transform-attrs))
{::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)])]]]))
(mf/defc text-align-options (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 (let [{:keys [text-align]} values
text-align (or text-align "left") text-align (or text-align "left")
on-change handle-change
(fn [event new-align] (fn [event new-align]
(run! #(st/emit! (on-change {:text-align new-align}))]
(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))]
;; --- Align ;; --- Align
[:div.row-flex.align-icons [:div.row-flex.align-icons
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.align-left") {:alt (t locale "workspace.options.text-options.align-left")
:class (dom/classnames :current (= "left" text-align)) :class (dom/classnames :current (= "left" text-align))
:on-click #(on-change % "left")} :on-click #(handle-change % "left")}
i/text-align-left] i/text-align-left]
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.align-center") {:alt (t locale "workspace.options.text-options.align-center")
:class (dom/classnames :current (= "center" text-align)) :class (dom/classnames :current (= "center" text-align))
:on-click #(on-change % "center")} :on-click #(handle-change % "center")}
i/text-align-center] i/text-align-center]
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.align-right") {:alt (t locale "workspace.options.text-options.align-right")
:class (dom/classnames :current (= "right" text-align)) :class (dom/classnames :current (= "right" text-align))
:on-click #(on-change % "right")} :on-click #(handle-change % "right")}
i/text-align-right] i/text-align-right]
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.align-justify") {:alt (t locale "workspace.options.text-options.align-justify")
:class (dom/classnames :current (= "justify" text-align)) :class (dom/classnames :current (= "justify" text-align))
:on-click #(on-change % "justify")} :on-click #(handle-change % "justify")}
i/text-align-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 (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 (let [{:keys [vertical-align]} values
to-single-value (fn [coll] (if (> (count coll) 1) nil (first coll))) to-single-value (fn [coll] (if (> (count coll) 1) nil (first coll)))
@ -243,150 +93,171 @@
vertical-align (or vertical-align "top") vertical-align (or vertical-align "top")
on-change-grow handle-change-grow
(fn [event grow-type] (fn [event grow-type]
(st/emit! (dwc/update-shapes ids #(assoc % :grow-type grow-type)))) (st/emit! (dwc/update-shapes ids #(assoc % :grow-type grow-type))))
on-change handle-change
(fn [event new-align] (fn [event new-align]
(run! #(st/emit! (dwt/update-root-attrs (on-change {:vertical-align new-align}))]
{:id %
:editor editor
:attrs {:vertical-align new-align}}))
ids))]
[:div.row-flex [:div.row-flex
[:div.align-icons [:div.align-icons
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.align-top") {:alt (t locale "workspace.options.text-options.align-top")
:class (dom/classnames :current (= "top" vertical-align)) :class (dom/classnames :current (= "top" vertical-align))
:on-click #(on-change % "top")} :on-click #(handle-change % "top")}
i/align-top] i/align-top]
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.align-middle") {:alt (t locale "workspace.options.text-options.align-middle")
:class (dom/classnames :current (= "center" vertical-align)) :class (dom/classnames :current (= "center" vertical-align))
:on-click #(on-change % "center")} :on-click #(handle-change % "center")}
i/align-middle] i/align-middle]
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.align-bottom") {:alt (t locale "workspace.options.text-options.align-bottom")
:class (dom/classnames :current (= "bottom" vertical-align)) :class (dom/classnames :current (= "bottom" vertical-align))
:on-click #(on-change % "bottom")} :on-click #(handle-change % "bottom")}
i/align-bottom]] i/align-bottom]]
[:div.align-icons [:div.align-icons
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.grow-fixed") {:alt (t locale "workspace.options.text-options.grow-fixed")
:class (dom/classnames :current (= :fixed grow-type)) :class (dom/classnames :current (= :fixed grow-type))
:on-click #(on-change-grow % :fixed)} :on-click #(handle-change-grow % :fixed)}
i/auto-fix] i/auto-fix]
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.grow-auto-width") {:alt (t locale "workspace.options.text-options.grow-auto-width")
:class (dom/classnames :current (= :auto-width grow-type)) :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] i/auto-width]
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.grow-auto-height") {:alt (t locale "workspace.options.text-options.grow-auto-height")
:class (dom/classnames :current (= :auto-height grow-type)) :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]]])) i/auto-height]]]))
(mf/defc text-decoration-options (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 (let [{:keys [text-decoration]} values
text-decoration (or text-decoration "none") text-decoration (or text-decoration "none")
on-change handle-change
(fn [event type] (fn [event type]
(run! #(st/emit! (dwt/update-text-attrs (on-change {:text-decoration type}))]
{:id %
:editor editor
:attrs {:text-decoration type}}))
ids))]
[:div.row-flex [:div.row-flex
[:span.element-set-subtitle (t locale "workspace.options.text-options.decoration")] [:span.element-set-subtitle (t locale "workspace.options.text-options.decoration")]
[:div.align-icons [:div.align-icons
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.none") {:alt (t locale "workspace.options.text-options.none")
:class (dom/classnames :current (= "none" text-decoration)) :class (dom/classnames :current (= "none" text-decoration))
:on-click #(on-change % "none")} :on-click #(handle-change % "none")}
i/minus] i/minus]
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.underline") {:alt (t locale "workspace.options.text-options.underline")
:class (dom/classnames :current (= "underline" text-decoration)) :class (dom/classnames :current (= "underline" text-decoration))
:on-click #(on-change % "underline")} :on-click #(handle-change % "underline")}
i/underline] i/underline]
[:span.tooltip.tooltip-bottom [:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.strikethrough") {:alt (t locale "workspace.options.text-options.strikethrough")
:class (dom/classnames :current (= "line-through" text-decoration)) :class (dom/classnames :current (= "line-through" text-decoration))
:on-click #(on-change % "line-through")} :on-click #(handle-change % "line-through")}
i/strikethrough]]])) i/strikethrough]]]))
(mf/defc text-transform-options (defn generate-typography-name [{:keys [font-id font-variant-id] :as typography}]
[{:keys [editor ids values locale] :as props}] (let [{:keys [name]} (fonts/get-font-data font-id)]
(let [{:keys [text-transform]} values (-> typography
(assoc :name (str name " " (str/title font-variant-id))))) )
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]]]))
(mf/defc text-menu (mf/defc text-menu
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [ids [{:keys [ids
type type
editor editor
font-values values
align-values
spacing-values
valign-values
decoration-values
transform-values
shapes] :as props}] shapes] :as props}]
(let [locale (mf/deref i18n/locale) (let [locale (mf/deref i18n/locale)
typographies (mf/deref refs/workspace-file-typography)
shared-libs (mf/deref refs/workspace-libraries)
label (case type label (case type
:multiple (t locale "workspace.options.text-options.title-selection") :multiple (t locale "workspace.options.text-options.title-selection")
:group (t locale "workspace.options.text-options.title-group") :group (t locale "workspace.options.text-options.title-group")
(t locale "workspace.options.text-options.title"))] (t locale "workspace.options.text-options.title"))
[:div.element-set
[:div.element-set-title label] emit-update!
[:div.element-set-content (fn [id attrs]
[:& font-options {:editor editor :ids ids :values font-values :locale locale}] (let [attrs (select-keys attrs root-attrs)]
[:& text-align-options {:editor editor :ids ids :values align-values :locale locale}] (when-not (empty? attrs)
[:& spacing-options {:editor editor :ids ids :values spacing-values :locale locale}] (st/emit! (dwt/update-root-attrs {:id id :editor editor :attrs attrs}))))
[:& 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}] (let [attrs (select-keys attrs paragraph-attrs)]
[:& text-transform-options {:editor editor :ids ids :values transform-values :locale locale}]]])) (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 (mf/defc options
[{:keys [shape] :as props}] [{:keys [shape] :as props}]
@ -399,42 +270,25 @@
measure-values (select-keys shape measure-attrs) measure-values (select-keys shape measure-attrs)
fill-values (dwt/current-text-values fill-values (dwt/current-text-values
{:editor editor {:editor editor
:shape shape :shape shape
:attrs text-fill-attrs}) :attrs text-fill-attrs})
converted-fill-values {:fill-color (:fill fill-values) converted-fill-values {:fill-color (:fill fill-values)
:fill-opacity (:opacity 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 text-values (merge
{:editor editor (dwt/current-root-values
:shape shape {:editor editor :shape shape
:attrs text-align-attrs}) :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 [:& measures-menu {:ids ids
:type type :type type
@ -448,11 +302,5 @@
:values (select-keys shape [:shadow])}] :values (select-keys shape [:shadow])}]
[:& text-menu {:ids ids [:& text-menu {:ids ids
:type type :type type
:editor editor :values text-values
: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]}]])) :shapes [shape]}]]))

View file

@ -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}]]
)
]]))

View file

@ -76,6 +76,8 @@
[node] [node]
(.-value node)) (.-value node))
(def get-target-val (comp get-value get-target))
(defn click (defn click
"Click a node" "Click a node"
[node] [node]