🎉 Add new font selector to workspace.

This commit is contained in:
Andrey Antukh 2021-05-12 09:48:06 +02:00 committed by Alonso Torres
parent 8831f3241c
commit 2ea200be78
19 changed files with 800 additions and 199 deletions

View file

@ -4,6 +4,7 @@
## :rocket: Next
### :sparkles: New features
### :bug: Bugs fixed
### :arrow_up: Deps updates
### :boom: Breaking changes
@ -14,21 +15,24 @@
### :sparkles: New features
- Add many performance related improvements to indexes handling on workspace.
- Add improved workspace font selector [Taiga US #292](https://tree.taiga.io/project/penpot/us/292).
- Add option to interactively scale text [Taiga #1527](https://tree.taiga.io/project/penpot/us/1527)
- Add the ability to upload/use custom fonts (and automatically generate all needed webfonts).
- Refactor dashboard state management (improves considerably the performance when you have a dashboard with a big collection of projects and files).
- Translate automatic names of new files and projects.
- Add performance improvements on dashboard data loading.
- Add performance improvements to indexes handling on workspace.
- Add the ability to upload/use custom fonts (and automatically generate all needed webfonts) [Taiga US #292](https://tree.taiga.io/project/penpot/us/292).
- Transform shapes to path on double click
- Translate automatic names of new files and projects.
- Use shift instead of ctrl/cmd to keep aspect ratio [Taiga 1697](https://tree.taiga.io/project/penpot/issue/1697).
### :bug: Bugs fixed
- Remove interactions when the destination artboard is deleted [Taiga #1656](https://tree.taiga.io/project/penpot/issue/1656)
### :arrow_up: Deps updates
- Update exporter dependencies (puppetteer), that fixes some unexpected exceptions.
- Update exporter dependencies (puppeteer), that fixes some unexpected exceptions.
- Update string manipulation library.
@ -38,8 +42,6 @@
configuration added scopes to the default set. Now it replaces it, so use with care, because
penpot requires at least `name` and `email` props found on the user info object.
### :heart: Community contributions by (Thank you!)
## 1.5.4-alpha

View file

@ -253,14 +253,8 @@
(map (fn [x] (f x) x) coll)))
(defn merge
"A faster merge."
[& maps]
(loop [res (transient (or (first maps) {}))
maps (next maps)]
(if (nil? maps)
(persistent! res)
(recur (reduce-kv assoc! res (first maps))
(next maps)))))
(reduce conj (or (first maps) {}) (rest maps)))
(defn distinct-xf
[f]

View file

@ -1 +0,0 @@
(ns cljs.user)

View file

@ -46,6 +46,7 @@
"randomcolor": "^0.6.2",
"react": "~17.0.1",
"react-dom": "~17.0.1",
"react-virtualized": "^9.22.3",
"rxjs": "~7.0.1",
"source-map-support": "^0.5.16",
"tdigest": "^0.1.1",

View file

@ -2,8 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
// Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
// Copyright (c) UXBOX Labs SL
.element-options {
display: flex;
@ -809,9 +808,10 @@
left: 0;
position: absolute;
top: 0;
width: calc(100% - 8px);
width: calc(100%);
opacity: 0.4;
z-index: 10;
display: flex;
}
.advanced-options-wrapper {
@ -1095,3 +1095,166 @@
.multiple-typography-button:hover svg {
}
}
.font-selector {
background: $color-black;
height: 100%;
left: 0;
position: absolute;
top: 0;
width: calc(100%);
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
.font-selector-dropdown {
background: #303236;
display: flex;
flex-direction: column;
flex-grow: 1;
height: 100%;
}
header {
padding: 15px 17px;
display: flex;
align-items: center;
position: relative;
.backend-filters {
padding: $small $medium;
// width: 220px;
top: 40px;
right: 20px;
}
.backend-filter {
display: flex;
align-items: center;
padding: $small 0;
cursor: pointer;
.checkbox-icon {
display: flex;
justify-content: center;
align-items: center;
width: $medium;
height: $medium;
border: 1px solid $color-gray-30;
border-radius: $br-small;
svg {
width: 8px;
display: none;
height: 8px;
fill: $color-black;
}
}
.backend-name {
margin-left: $small;
color: $color-gray-50;
}
&.selected {
.checkbox-icon {
svg {
display: inherit;
}
}
}
}
input {
display: flex;
flex-grow: 1;
padding: 4px;
font-size: $fs12;
background: $color-gray-50;
border-radius: $br-small;
color: $color-gray-20;
border: 1px solid $color-gray-30;
margin: 0px;
}
.options {
display: flex;
justify-content: center;
align-items: center;
width: 24px;
height: 24px;
margin-left: $small;
svg {
width: 16px;
height: 16px;
fill: $color-gray-20
}
&.active {
svg {
fill: $color-primary;
}
}
}
}
.fonts-list {
display: flex;
flex-direction: column;
height: 100%;
position: relative;
-webkit-box-flex: 1;
flex: 1 1 auto;
}
hr {
margin-bottom: 0px;
margin-top: 0px;
}
.font-item {
padding-left: $big;
height: $x-big;
max-height: $x-big;
width: 100%;
display: flex;
align-items: center;
cursor: pointer;
color: $color-gray-10;
&.selected {
background-color: $color-black;
color: $color-primary;
.icon svg {fill: $color-primary;}
}
&:hover {
background-color: $color-primary;
color: $color-black;
}
.icon {
display: flex;
// justify-content: center;
align-items: center;
// border: 1px solid red;
width: $big
}
.label {
font-size: 12px;
}
svg {
fill: $color-gray-10;
width: 10px;
height: 10px;
}
}
}

View file

@ -69,6 +69,7 @@ $width-settings-bar: 16rem;
height: 100%;
.tool-window {
position: relative;
border-bottom: 1px solid $color-gray-60;
display: flex;
flex-direction: column;

View file

@ -13,6 +13,7 @@
[app.main.repo :as rp]
[app.main.data.events :as ev]
[app.main.data.users :as du]
[app.main.data.fonts :as df]
[app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt]
[app.util.time :as dt]
@ -86,6 +87,7 @@
ptk/WatchEvent
(watch [_ state stream]
(rx/merge
(ptk/watch (df/load-team-fonts id) state stream)
(ptk/watch (fetch-projects) state stream)
(ptk/watch (du/fetch-teams) state stream)
(ptk/watch (du/fetch-users {:team-id id}) state stream)))))

View file

@ -0,0 +1,57 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.main.data.fonts
(:require
[app.common.media :as cm]
[app.main.fonts :as fonts]
[app.main.repo :as rp]
[app.util.i18n :as i18n :refer [tr]]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[potok.core :as ptk]))
(defn prepare-font-variant
[item]
{:id (str (:font-style item) "-" (:font-weight item))
:name (str (cm/font-weight->name (:font-weight item)) " "
(str/capital (:font-style item)))
:style (:font-style item)
:weight (str (:font-weight item))
::fonts/woff1-file-id (:woff1-file-id item)
::fonts/woff2-file-id (:woff2-file-id item)
::fonts/ttf-file-id (:ttf-file-id item)
::fonts/otf-file-id (:otf-file-id item)})
(defn prepare-font
[[id [item :as items]]]
{:id id
:name (:font-family item)
:family (:font-family item)
:variants (mapv prepare-font-variant items)})
(defn team-fonts-loaded
[fonts]
(ptk/reify ::team-fonts-loaded
ptk/EffectEvent
(effect [_ state stream]
(let [fonts (->> (group-by :font-id fonts)
(mapv prepare-font))]
(fonts/register! :custom fonts)))))
(defn load-team-fonts
[team-id]
(ptk/reify ::load-team-fonts
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :team-font-variants {:team-id team-id})
(rx/map team-fonts-loaded)))))
(defn get-fonts
[backend]
(get @fonts/fonts backend []))

View file

@ -1258,6 +1258,15 @@
(rx/of ::dwp/force-persist
(rt/nav :dashboard-projects {:team-id team-id})))))))
(defn go-to-dashboard-fonts
[]
(ptk/reify ::go-to-dashboard
ptk/WatchEvent
(watch [it state stream]
(let [team-id (:current-team-id state)]
(rx/of ::dwp/force-persist
(rt/nav :dashboard-fonts {:team-id team-id}))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Context Menu
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -15,6 +15,7 @@
[app.common.uuid :as uuid]
[app.main.data.dashboard :as dd]
[app.main.data.media :as di]
[app.main.data.fonts :as df]
[app.main.data.messages :as dm]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
@ -270,7 +271,8 @@
:project project
:libraries libraries}))
(rx/mapcat (fn [{:keys [project] :as bundle}]
(rx/of (ptk/data-event ::bundle-fetched bundle))))))))
(rx/of (ptk/data-event ::bundle-fetched bundle)
(df/load-team-fonts (:team-id project)))))))))
;; --- Set File shared

View file

@ -8,16 +8,22 @@
"Fonts management and loading logic."
(:require-macros [app.main.fonts :refer [preload-gfonts]])
(:require
[app.config :as cf]
[app.common.data :as d]
[app.util.dom :as dom]
[app.util.object :as obj]
[app.util.timers :as ts]
[app.util.logging :as log]
[lambdaisland.uri :as u]
[goog.events :as gev]
[beicon.core :as rx]
[clojure.set :as set]
[cuerdas.core :as str]
[okulary.core :as l]
[promesa.core :as p]))
(log/set-level! :trace)
(def google-fonts
(preload-gfonts "fonts/gfonts.2020.04.23.json"))
@ -38,22 +44,27 @@
{:id "blackitalic" :name "black (italic)" :weight "900" :style "italic"}]}])
(defonce fontsdb (l/atom {}))
(defonce fontsview (l/atom {}))
(defonce fonts (l/atom []))
(defn- materialize-fontsview
[db]
(reset! fontsview (reduce-kv (fn [acc k v]
(assoc acc k (sort-by :name v)))
{}
(group-by :backend (vals db)))))
(add-watch fontsdb "main"
(fn [_ _ _ db]
(ts/schedule #(materialize-fontsview db))))
(->> (vals db)
(sort-by :name)
(map-indexed #(assoc %2 :index %1))
(vec)
(reset! fonts))))
(defn- remove-fonts
[db backend]
(reduce-kv #(cond-> %1 (= backend (:backend %3)) (dissoc %2)) db db))
(defn register!
[backend fonts]
(let [fonts (map #(assoc % :backend backend) fonts)]
(swap! fontsdb #(merge % (d/index-by :id fonts)))))
(swap! fontsdb
(fn [db]
(let [db (reduce-kv #(cond-> %1 (= backend (:backend %3)) (dissoc %2)) db db)
fonts (map #(assoc % :backend backend) fonts)]
(merge db (d/index-by :id fonts))))))
(register! :builtin local-fonts)
(register! :google google-fonts)
@ -67,13 +78,15 @@
(defn resolve-fonts
[backend]
(get @fontsview backend))
(get @fonts backend))
;; --- Fonts Loader
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; FONTS LOADING
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defonce loaded (l/atom #{}))
(defn- create-link-node
(defn- create-link-element
[uri]
(let [node (.createElement js/document "link")]
(unchecked-set node "href" uri)
@ -81,32 +94,107 @@
(unchecked-set node "type" "text/css")
node))
(defn gfont-url [family variants]
(defn- create-style-element
[css]
(let [node (.createElement js/document "style")]
(unchecked-set node "innerHTML" css)
node))
(defn- load-font-css!
"Creates a link element and attaches it to the dom for correctly
load external css resource."
[url on-loaded]
(let [node (create-link-element url)
head (.-head ^js js/document)]
(gev/listenOnce node "load" (fn [event]
(when (fn? on-loaded)
(on-loaded))))
(dom/append-child! head node)))
(defn- add-font-css!
"Creates a style element and attaches it to the dom."
[css]
(let [head (.-head ^js js/document)]
(->> (create-style-element css)
(dom/append-child! head))))
;; --- LOADER: BUILTIN
(defmulti ^:private load-font :backend)
(defmethod load-font :default
[{:keys [backend] :as font}]
(log/warn :msg "no implementation found for" :backend backend))
(defmethod load-font :builtin
[{:keys [id ::on-loaded] :as font}]
(log/debug :action "load-font" :font-id id :backend "builtin")
;; (js/console.log "[debug:fonts]: loading builtin font" id)
(when (fn? on-loaded)
(on-loaded id)))
;; --- LOADER: GOOGLE
(defn generate-gfonts-url
[{:keys [family variants]}]
(let [base (str "https://fonts.googleapis.com/css?family=" family)
variants (str/join "," (map :id variants))]
(str base ":" variants "&display=block")))
(defmulti ^:private load-font :backend)
(defmethod load-font :builtin
[{:keys [id ::on-loaded] :as font}]
(js/console.log "[debug:fonts]: loading builtin font" id)
(when (fn? on-loaded)
(on-loaded id)))
(defmethod load-font :google
[{:keys [id family variants ::on-loaded] :as font}]
(when (exists? js/window)
(js/console.log "[debug:fonts]: loading google font" id)
(let [node (create-link-node (gfont-url family variants))]
(.addEventListener node "load" (fn [event] (when (fn? on-loaded)
(on-loaded id))))
(.append (.-head js/document) node)
(log/debug :action "load-font" :font-id id :backend "google")
(let [url (generate-gfonts-url font)]
(load-font-css! url (partial on-loaded id))
nil)))
(defmethod load-font :default
[{:keys [backend] :as font}]
(js/console.warn "no implementation found for" backend))
;; --- LOADER: CUSTOM
(def font-css-template
"@font-face {
font-family: '%(family)s';
font-style: %(style)s;
font-weight: %(weight)s;
font-display: block;
src: url(%(woff2-uri)s) format('woff2'),
url(%(woff1-uri)s) format('woff'),
url(%(ttf-uri)s) format('ttf'),
url(%(otf-uri)s) format('otf');
}")
(defn- font-id->uri
[font-id]
(str (u/join cf/public-uri "assets/by-id/" font-id)))
(defn generate-custom-font-variant-css
[family variant]
(str/fmt font-css-template
{:family family
:style (:style variant)
:weight (:weight variant)
:woff2-uri (font-id->uri (::woff2-file-id variant))
:woff1-uri (font-id->uri (::woff1-file-id variant))
:ttf-uri (font-id->uri (::ttf-file-id variant))
:otf-uri (font-id->uri (::otf-file-id variant))}))
(defn- generate-custom-font-css
[{:keys [family variants] :as font}]
(->> variants
(map #(generate-custom-font-variant-css family %))
(str/join "\n")))
(defmethod load-font :custom
[{:keys [id family variants ::on-loaded] :as font}]
(when (exists? js/window)
(js/console.log "[debug:fonts]: loading google font" id)
(let [css (generate-custom-font-css font)]
(add-font-css! css))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; LOAD API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn ensure-loaded!
([id]
@ -119,7 +207,8 @@
(load-font (assoc font ::on-loaded on-loaded))
(swap! loaded conj id)))))
(defn ready [cb]
(defn ready
[cb]
(-> (obj/get-in js/document ["fonts" "ready"])
(p/then cb)))

View file

@ -62,7 +62,7 @@
"Given a font and the variant-id, retrieves the style CSS for it."
[{:keys [id backend family variants] :as font} font-variant-id]
(if (= :google backend)
(let [uri (fonts/gfont-url family [{:id font-variant-id}])]
(let [uri (fonts/generate-gfonts-url {:family family :variants [{:id font-variant-id}]})]
(->> (http/send! {:method :get
:mode :cors
:omit-default-headers true

View file

@ -128,7 +128,6 @@
:on-click #(handle-change % "rtl")}
i/text-direction-rtl]]))
(mf/defc vertical-align
[{:keys [shapes ids values on-change] :as props}]
(let [{:keys [vertical-align]} values
@ -225,6 +224,7 @@
(tr "workspace.options.text-options.title"))
emit-update!
(mf/use-callback
(fn [id attrs]
(let [attrs (select-keys attrs root-attrs)]
(when-not (empty? attrs)
@ -236,9 +236,18 @@
(let [attrs (select-keys attrs text-attrs)]
(when-not (empty? attrs)
(st/emit! (dwt/update-text-attrs {:id id :attrs attrs})))))
(st/emit! (dwt/update-text-attrs {:id id :attrs attrs}))))))
on-change
(mf/use-callback
(mf/deps ids)
(fn [attrs]
(run! #(emit-update! % attrs) ids)))
typography
(mf/use-memo
(mf/deps values file-id shared-libs)
(fn []
(cond
(and (:typography-ref-id values)
(not= (:typography-ref-id values) :multiple)
@ -250,11 +259,9 @@
(and (:typography-ref-id values)
(not= (:typography-ref-id values) :multiple)
(= (:typography-ref-file values) file-id))
(get typographies (:typography-ref-id values)))
(get typographies (:typography-ref-id values)))))
on-convert-to-typography
(mf/use-callback
(mf/deps values)
(fn [event]
(let [setted-values (-> (d/without-nils values)
(select-keys
@ -266,22 +273,24 @@
(let [id (uuid/next)]
(st/emit! (dwl/add-typography (assoc typography :id id) false))
(run! #(emit-update! % {:typography-ref-id id
:typography-ref-file file-id}) ids)))))
:typography-ref-file file-id}) ids))))
handle-detach-typography
(mf/use-callback
(mf/deps on-change)
(fn []
(run! #(emit-update! % {:typography-ref-file nil
:typography-ref-id nil})
ids))
(on-change {:typography-ref-file nil
:typography-ref-id nil})))
handle-change-typography
(mf/use-callback
(mf/deps typography file-id)
(fn [changes]
(st/emit! (dwl/update-typography (merge typography changes) file-id)))
(st/emit! (dwl/update-typography (merge typography changes) file-id))))
opts #js {:ids ids
:values values
:on-change (fn [attrs]
(run! #(emit-update! % attrs) ids))}]
:on-change on-change}]
[:div.element-set
[:div.element-set-title

View file

@ -6,20 +6,30 @@
(ns app.main.ui.workspace.sidebar.options.menus.typography
(:require
["react-virtualized" :as rvt]
[app.common.exceptions :as ex]
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.text :as txt]
[app.main.data.workspace.texts :as dwt]
[app.main.data.shortcuts :as dsc]
[app.main.data.fonts :as df]
[app.main.data.workspace :as dw]
[app.main.fonts :as fonts]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.editable-select :refer [editable-select]]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.common :refer [advanced-options]]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [t]]
[app.util.object :as obj]
[app.util.timers :as tm]
[app.util.keyboard :as kbd]
[app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt]
[app.util.timers :as ts]
[goog.events :as events]
[cuerdas.core :as str]
[rumext.alpha :as mf]))
@ -28,26 +38,207 @@
""
(str value)))
(mf/defc font-select-optgroups
(defn- get-next-font
[{:keys [id] :as current} fonts]
(if (seq fonts)
(let [index (d/index-of-pred fonts #(= (:id %) id))
index (or index -1)
next (ex/ignoring (nth fonts (inc index)))]
(or next (first fonts)))
current))
(defn- get-prev-font
[{:keys [id] :as current} fonts]
(if (seq fonts)
(let [index (d/index-of-pred fonts #(= (:id %) id))
next (ex/ignoring (nth fonts (dec index)))]
(or next (peek fonts)))
current))
(mf/defc font-item
{::mf/wrap [mf/memo]}
[{:keys [locale] :as props}]
[:*
[:optgroup {:label (t locale "workspace.options.text-options.preset")}
(for [font fonts/local-fonts]
[:option {:value (:id font)
:key (:id font)}
(:name font)])]
[:optgroup {:label (t locale "workspace.options.text-options.google")}
(for [font (fonts/resolve-fonts :google)]
[:option {:value (:id font)
:key (:id font)}
(:name font)])]])
[{:keys [font current? on-click style]}]
(let [item-ref (mf/use-ref)
on-click (mf/use-callback (mf/deps font) #(on-click font))]
(mf/use-effect
(mf/deps current?)
(fn []
(when current?
(let [element (mf/ref-val item-ref)]
(when-not (dom/is-in-viewport? element)
(dom/scroll-into-view! element))))))
[:div.font-item {:ref item-ref
:style style
:class (when current? "selected")
:on-click on-click}
[:span.icon (when current? i/tick)]
[:span.label (:name font)]]))
(declare row-renderer)
(defn filter-fonts
[{:keys [term backends]} fonts]
(let [xform (cond-> (map identity)
(seq term)
(comp (filter #(str/includes? (str/lower (:name %)) term)))
(seq backends)
(comp (filter #(contains? backends (:backend %)))))]
(into [] xform fonts)))
(defn- toggle-backend
[backends id]
(if (contains? backends id)
(disj backends id)
(conj backends id)))
(mf/defc font-selector
[{:keys [on-select on-close current-font] :as props}]
(let [selected (mf/use-state current-font)
state (mf/use-state {:term "" :backends #{}})
flist (mf/use-ref)
input (mf/use-ref)
ddown (mf/use-ref)
fonts (mf/use-memo (mf/deps @state) #(filter-fonts @state @fonts/fonts))
select-next
(mf/use-callback
(mf/deps fonts)
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(swap! selected get-next-font fonts)))
select-prev
(mf/use-callback
(mf/deps fonts)
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(swap! selected get-prev-font fonts)))
on-key-down
(mf/use-callback
(mf/deps fonts)
(fn [event]
(cond
(kbd/up-arrow? event) (select-prev event)
(kbd/down-arrow? event) (select-next event)
(kbd/esc? event) (on-close)
(kbd/enter? event) (on-close)
:else (dom/focus! (mf/ref-val input)))))
on-filter-change
(mf/use-callback
(mf/deps)
(fn [event]
(let [value (dom/get-target-val event)]
(swap! state assoc :term value))))
on-select-and-close
(mf/use-callback
(mf/deps on-select on-close)
(fn [font]
(on-select font)
(on-close)))
]
(mf/use-effect
(mf/deps fonts)
(fn []
(let [key (events/listen js/document "keydown" on-key-down)]
#(events/unlistenByKey key))))
(mf/use-effect
(mf/deps @selected)
(fn []
(when-let [inst (mf/ref-val flist)]
(when-let [index (:index @selected)]
(.scrollToRow ^js inst index)))))
(mf/use-effect
(mf/deps @selected)
(fn []
(on-select @selected)))
(mf/use-effect
(fn []
(st/emit! (dsc/push-shortcuts :typography {}))
(fn []
(st/emit! (dsc/pop-shortcuts :typography)))))
(mf/use-effect
(fn []
(let [index (d/index-of-pred fonts #(= (:id %) (:id current-font)))
inst (mf/ref-val flist)]
(tm/schedule
#(let [offset (.getOffsetForRow ^js inst #js {:alignment "center" :index index})]
(.scrollToPosition ^js inst offset))))))
[:div.font-selector
[:div.font-selector-dropdown
[:header
[:input {:placeholder "Search font"
:value (:term @state)
:ref input
:spell-check false
:on-change on-filter-change}]
#_[:div.options
{:on-click #(swap! state assoc :show-options true)
:class (when (seq (:backends @state)) "active")}
i/picker-hsv]
#_[:& dropdown {:show (:show-options @state false)
:on-close #(swap! state dissoc :show-options)}
(let [backends (:backends @state)]
[:div.backend-filters.dropdown {:ref ddown}
[:div.backend-filter
{:class (when (backends :custom) "selected")
:on-click #(swap! state update :backends toggle-backend :custom)}
[:div.checkbox-icon i/tick]
[:div.backend-name (tr "labels.custom-fonts")]]
[:div.backend-filter
{:class (when (backends :google) "selected")
:on-click #(swap! state update :backends toggle-backend :google)}
[:div.checkbox-icon i/tick]
[:div.backend-name "Google Fonts"]]])]]
[:hr]
[:div.fonts-list
[:> rvt/AutoSizer {}
(fn [props]
(let [width (obj/get props "width")
height (obj/get props "height")
render #(row-renderer fonts @selected on-select-and-close %)]
(mf/html
[:> rvt/List #js {:height height
:ref flist
:width width
:rowCount (count fonts)
:rowHeight 32
:rowRenderer render}])))]]]]))
(defn row-renderer
[fonts selected on-select props]
(let [index (obj/get props "index")
key (obj/get props "key")
style (obj/get props "style")
font (nth fonts index)]
(mf/html
[:& font-item {:key key
:font font
:style style
:on-click on-select
:current? (= (:id font) (:id selected))}])))
(mf/defc font-options
[{:keys [editor ids values locale on-change] :as props}]
(let [{:keys [font-id
font-size
font-variant-id]} values
[{:keys [editor ids values on-change] :as props}]
(let [{:keys [font-id font-size font-variant-id]} values
font-id (or font-id (:font-id txt/default-text-attrs))
font-size (or font-size (:font-size txt/default-text-attrs))
@ -56,7 +247,11 @@
fonts (mf/deref fonts/fontsdb)
font (get fonts font-id)
open-selector? (mf/use-state false)
change-font
(mf/use-callback
(mf/deps on-change fonts)
(fn [new-font-id]
(let [{:keys [family] :as font} (get fonts new-font-id)
{:keys [id name weight style]} (fonts/get-default-variant font)]
@ -64,21 +259,27 @@
:font-family family
:font-variant-id (or id name)
:font-weight weight
:font-style style})))
:font-style style}))))
on-font-family-change
(mf/use-callback
(mf/deps fonts change-font)
(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))))))
(fonts/ensure-loaded! new-font-id (partial change-font new-font-id)))))))
on-font-size-change
(mf/use-callback
(mf/deps on-change)
(fn [new-font-size]
(when-not (str/empty? new-font-size)
(on-change {:font-size (str new-font-size)})))
(on-change {:font-size (str new-font-size)}))))
on-font-variant-change
(mf/use-callback
(mf/deps font on-change)
(fn [event]
(let [new-variant-id (dom/get-target-val event)
variant (d/seek #(= new-variant-id (:id %)) (:variants font))]
@ -86,16 +287,30 @@
:font-family (:family font)
:font-variant-id new-variant-id
:font-weight (:weight variant)
:font-style (:style variant)})))]
:font-style (:style variant)}))))
on-font-select
(mf/use-callback
(mf/deps change-font)
(fn [font*]
(when (not= font font*)
(change-font (:id font*)))))
on-font-selector-close
(mf/use-callback
#(reset! open-selector? false))]
[:*
(when @open-selector?
[:& font-selector
{:current-font font
:on-close on-font-selector-close
:on-select on-font-select}])
[: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 {:locale locale}]]]
[:div.input-select.font-option
{:on-click #(reset! open-selector? true)}
(:name font)]]
[:div.row-flex
(let [size-options [8 9 10 11 12 14 18 24 36 48 72]
@ -121,7 +336,7 @@
(mf/defc spacing-options
[{:keys [editor ids values locale on-change] :as props}]
[{:keys [editor ids values on-change] :as props}]
(let [{:keys [line-height
letter-spacing]} values
@ -136,7 +351,7 @@
[:div.spacing-options
[:div.input-icon
[:span.icon-before.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.line-height")}
{:alt (tr "workspace.options.text-options.line-height")}
i/line-height]
[:input.input-text
{:type "number"
@ -144,12 +359,12 @@
:min "0"
:max "200"
:value (attr->string line-height)
:placeholder (t locale "settings.multiple")
:placeholder (tr "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")}
{:alt (tr "workspace.options.text-options.letter-spacing")}
i/letter-spacing]
[:input.input-text
{:type "number"
@ -157,11 +372,11 @@
:min "0"
:max "200"
:value (attr->string letter-spacing)
:placeholder (t locale "settings.multiple")
:placeholder (tr "settings.multiple")
:on-change #(handle-change % :letter-spacing)}]]]))
(mf/defc text-transform-options
[{:keys [editor ids values locale on-change] :as props}]
[{:keys [editor ids values on-change] :as props}]
(let [{:keys [text-transform]} values
text-transform (or text-transform "none")
@ -171,35 +386,32 @@
(on-change {:text-transform type}))]
[:div.align-icons
[:span.tooltip.tooltip-bottom
{:alt (t locale "workspace.options.text-options.none")
{:alt (tr "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")
{:alt (tr "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")
{:alt (tr "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")
{:alt (tr "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
(let [opts #js {:editor editor
:ids ids
:values values
:locale locale
:on-change on-change}]
[:div.element-set-content
[:> font-options opts]
[:div.row-flex
@ -209,8 +421,7 @@
(mf/defc typography-entry
[{:keys [typography read-only? selected? on-click on-change on-detach on-context-menu editting? focus-name? file]}]
(let [locale (mf/deref i18n/locale)
open? (mf/use-state editting?)
(let [open? (mf/use-state editting?)
hover-detach (mf/use-state false)
name-input-ref (mf/use-ref nil)
value (mf/use-state (cp/merge-path-item (:path typography) (:name typography)))
@ -255,7 +466,7 @@
{:style {:font-family (:font-family typography)
:font-weight (:font-weight typography)
:font-style (:font-style typography)}}
(t locale "workspace.assets.typography.sample")]
(tr "workspace.assets.typography.sample")]
[:div.typography-name (:name typography)]]
[:div.element-set-actions
(when on-detach
@ -277,32 +488,32 @@
[:span (:name typography)]]
[:div.row-flex
[:span.label (t locale "workspace.assets.typography.font-id")]
[:span.label (tr "workspace.assets.typography.font-id")]
[:span (:font-id typography)]]
[:div.row-flex
[:span.label (t locale "workspace.assets.typography.font-variant-id")]
[:span.label (tr "workspace.assets.typography.font-variant-id")]
[:span (:font-variant-id typography)]]
[:div.row-flex
[:span.label (t locale "workspace.assets.typography.font-size")]
[:span.label (tr "workspace.assets.typography.font-size")]
[:span (:font-size typography)]]
[:div.row-flex
[:span.label (t locale "workspace.assets.typography.line-height")]
[:span.label (tr "workspace.assets.typography.line-height")]
[:span (:line-height typography)]]
[:div.row-flex
[:span.label (t locale "workspace.assets.typography.letter-spacing")]
[:span.label (tr "workspace.assets.typography.letter-spacing")]
[:span (:letter-spacing typography)]]
[:div.row-flex
[:span.label (t locale "workspace.assets.typography.text-transform")]
[:span.label (tr "workspace.assets.typography.text-transform")]
[:span (:text-transform typography)]]
[:div.go-to-lib-button
{:on-click handle-go-to-edit}
(t locale "workspace.assets.typography.go-to-edit")]]
(tr "workspace.assets.typography.go-to-edit")]]
[:*
[:div.element-set-content

View file

@ -190,7 +190,6 @@
(defn setup-shortcuts
[path-editing? drawing-path?]
(hooks/use-shortcuts ::workspace wsc/shortcuts)
(mf/use-effect
(mf/deps path-editing? drawing-path?)
(fn []

View file

@ -46,6 +46,9 @@ msgstr "Style"
msgid "labels.custom-fonts"
msgstr "Custom fonts"
msgid "labels.manage-fonts"
msgstr "Manage fonts"
msgid "labels.search-font"
msgstr "Search font"
@ -55,13 +58,14 @@ msgstr "Font providers"
msgid "labels.upload-custom-fonts"
msgstr "Upload custom fonts"
#, markdown
msgid "dashboard.fonts.hero-text1"
msgstr "Any web font you upload here will be added to the font family list available at the text properties of the files of this team. Fonts with the same font family name will be grouped as a **single font family**. You can upload fonts with the following formats: **TTF, OTF and WOFF** (only one will be needed)."
#, markdown
msgid "dashboard.fonts.hero-text2"
msgstr "You should only upload fonts you own or have license to use in Penpot. Find out more in the Content rights section of [Penpot's Terms of Service](https://penpot.app/terms.html). You also might want to read about [font licensing](2)."
msgstr "You should only upload fonts you own or have license to use in Penpot. Find out more in the Content rights section of [Penpot's Terms of Service](https://penpot.app/terms.html). You also might want to read about [font licensing](https://www.typography.com/faq)."
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"

View file

@ -47,6 +47,9 @@ msgstr "Estilo"
msgid "labels.custom-fonts"
msgstr "Fuentes personalizadas"
msgid "labels.manage-fonts"
msgstr "Administrar fuentes"
msgid "labels.search-font"
msgstr "Buscar fuente"

View file

@ -10,6 +10,13 @@
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==
dependencies:
regenerator-runtime "^0.13.4"
"@dabh/diagnostics@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31"
@ -802,6 +809,11 @@ cloneable-readable@^1.0.0:
process-nextick-args "^2.0.0"
readable-stream "^2.3.5"
clsx@^1.0.4:
version "1.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
coa@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3"
@ -1147,6 +1159,11 @@ cssom@^0.3.4:
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
csstype@^3.0.2:
version "3.0.8"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
currently-unhandled@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
@ -1302,6 +1319,14 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
dom-helpers@^5.1.3:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
dependencies:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
@ -3049,7 +3074,7 @@ logform@^2.2.0:
ms "^2.1.1"
triple-beam "^1.3.0"
loose-envify@^1.0.0, loose-envify@^1.1.0:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -4003,6 +4028,15 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.8.1"
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
@ -4119,6 +4153,28 @@ react-dom@~17.0.1:
object-assign "^4.1.1"
scheduler "^0.20.2"
react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-virtualized@^9.22.3:
version "9.22.3"
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421"
integrity sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==
dependencies:
"@babel/runtime" "^7.7.2"
clsx "^1.0.4"
dom-helpers "^5.1.3"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-lifecycles-compat "^3.0.4"
react@~17.0.1:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"