0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-27 23:21:47 -05:00
penpot/frontend/src/app/main/ui/dashboard/fonts.cljs
2023-11-28 10:32:50 +01:00

519 lines
18 KiB
Clojure

;; 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) KALEIDOS INC
(ns app.main.ui.dashboard.fonts
(:require-macros [app.main.style :as stl])
(:require
[app.common.media :as cm]
[app.main.data.fonts :as df]
[app.main.data.modal :as modal]
[app.main.refs :as refs]
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.ui.components.context-menu :refer [context-menu]]
[app.main.ui.components.file-uploader :refer [file-uploader]]
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[beicon.core :as rx]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn- use-set-page-title
[team section]
(mf/use-effect
(mf/deps team)
(fn []
(when team
(let [tname (if (:is-default team)
(tr "dashboard.your-penpot")
(:name team))]
(case section
:fonts (dom/set-html-title (tr "title.dashboard.fonts" tname))
:providers (dom/set-html-title (tr "title.dashboard.font-providers" tname))))))))
(mf/defc header
{::mf/wrap [mf/memo]}
[{:keys [section team] :as props}]
(let [new-css-system (mf/use-ctx ctx/new-css-system)]
(use-set-page-title team section)
(if new-css-system
[:header {:class (stl/css :dashboard-header)}
[:div#dashboard-fonts-title {:class (stl/css :dashboard-title)}
[:h1 (tr "labels.fonts")]]]
;; OLD
[:header.dashboard-header
[:div.dashboard-title#dashboard-fonts-title
[:h1 (tr "labels.fonts")]]])))
(mf/defc font-variant-display-name
[{:keys [variant]}]
[:*
[:span (cm/font-weight->name (:font-weight variant))]
(when (not= "normal" (:font-style variant))
[:span " " (str/capital (:font-style variant))])])
(mf/defc fonts-upload
[{:keys [team installed-fonts] :as props}]
(let [new-css-system (mf/use-ctx ctx/new-css-system)
fonts (mf/use-state {})
input-ref (mf/use-ref)
uploading (mf/use-state #{})
on-click
(mf/use-callback #(dom/click (mf/ref-val input-ref)))
on-selected
(mf/use-callback
(mf/deps team installed-fonts)
(fn [blobs]
(->> (df/process-upload blobs (:id team))
(rx/subs (fn [result]
(swap! fonts df/merge-and-group-fonts installed-fonts result))
(fn [error]
(js/console.error "error" error))))))
on-upload
(mf/use-callback
(mf/deps team)
(fn [item]
(swap! uploading conj (:id item))
(->> (rp/cmd! :create-font-variant item)
(rx/delay-at-least 2000)
(rx/subs (fn [font]
(swap! fonts dissoc (:id item))
(swap! uploading disj (:id item))
(st/emit! (df/add-font font)))
(fn [error]
(js/console.log "error" error))))))
on-upload-all
(fn [items]
(run! on-upload items))
on-blur-name
(fn [id event]
(let [name (dom/get-target-val event)]
(swap! fonts df/rename-and-regroup id name installed-fonts)))
on-delete
(mf/use-callback
(mf/deps team)
(fn [{:keys [id] :as item}]
(swap! fonts dissoc id)))
on-dismiss-all
(fn [items]
(run! on-delete items))
problematic-fonts? (some :height-warning? (vals @fonts))
handle-upload-all
(mf/use-callback (mf/deps @fonts) #(on-upload-all (vals @fonts)))
handle-dismiss-all
(mf/use-callback (mf/deps @fonts) #(on-dismiss-all (vals @fonts)))]
(if new-css-system
[:div {:class (stl/css :dashboard-fonts-upload)}
[:div {:class (stl/css :dashboard-fonts-hero)}
[:div {:class (stl/css :desc)}
[:h2 (tr "labels.upload-custom-fonts")]
[:& i18n/tr-html {:label "dashboard.fonts.hero-text1"}]
[:button
{:class (stl/css :btn-primary)
:on-click on-click
:tab-index "0"}
[:span (tr "labels.add-custom-font")]
[:& file-uploader {:input-id "font-upload"
:accept cm/str-font-types
:multi true
:ref input-ref
:on-selected on-selected}]]
[:div {:class (stl/css :banner)}
[:div {:class (stl/css :icon)} i/msg-info]
[:div {:class (stl/css :content)}
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.hero-text2"}]]]
(when problematic-fonts?
[:div {:class (stl/css :banner :warning)}
[:div {:class (stl/css :icon)} i/msg-warning]
[:div {:class (stl/css :content)}
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.warning-text"}]]])]]
[:*
(when (some? (vals @fonts))
[:div {:class (stl/css :font-item :table-row)}
[:span (tr "dashboard.fonts.fonts-added" (i18n/c (count (vals @fonts))))]
[:div {:class (stl/css :table-field :options)}
[:button {:class (stl/css :btn-primary)
:on-click handle-upload-all :data-test "upload-all"}
[:span (tr "dashboard.fonts.upload-all")]]
[:button {:class (stl/css :btn-secondary)
:on-click handle-dismiss-all :data-test "dismiss-all"}
[:span (tr "dashboard.fonts.dismiss-all")]]]])
(for [item (sort-by :font-family (vals @fonts))]
(let [uploading? (contains? @uploading (:id item))]
[:div {:class (stl/css :font-item :table-row) :key (:id item)}
[:div {:class (stl/css :table-field :family)}
[:input {:type "text"
:on-blur #(on-blur-name (:id item) %)
:default-value (:font-family item)}]]
[:div {:class (stl/css :table-field :variants)}
[:span {:class (stl/css :label)}
[:& font-variant-display-name {:variant item}]]]
[:div {:class (stl/css :table-field :filenames)}
(for [item (:names item)]
[:span item])]
[:div {:class (stl/css :table-field :options)}
(when (:height-warning? item)
[:span {:class (stl/css :icon :failure)} i/msg-warning])
[:button
{:on-click #(on-upload item)
:class (stl/css-case :btn-primary true
:upload-button true
:disabled uploading?)
:disabled uploading?}
(if uploading?
(tr "labels.uploading")
(tr "labels.upload"))]
[:span {:class (stl/css :icon :close)
:on-click #(on-delete item)} i/close]]]))]]
;; OLD
[:div.dashboard-fonts-upload
[:div.dashboard-fonts-hero
[:div.desc
[:h2 (tr "labels.upload-custom-fonts")]
[:& i18n/tr-html {:label "dashboard.fonts.hero-text1"}]
[:div.banner
[:div.icon i/msg-info]
[:div.content
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.hero-text2"}]]]
(when problematic-fonts?
[:div.banner.warning
[:div.icon i/msg-warning]
[:div.content
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.warning-text"}]]])]
[:button.btn-primary
{:on-click on-click
:tab-index "0"}
[:span (tr "labels.add-custom-font")]
[:& file-uploader {:input-id "font-upload"
:accept cm/str-font-types
:multi true
:ref input-ref
:on-selected on-selected}]]]
[:*
(when (some? (vals @fonts))
[:div.font-item.table-row
[:span (tr "dashboard.fonts.fonts-added" (i18n/c (count (vals @fonts))))]
[:div.table-field.options
[:div.btn-primary
{:on-click #(on-upload-all (vals @fonts)) :data-test "upload-all"}
[:span (tr "dashboard.fonts.upload-all")]]
[:div.btn-secondary
{:on-click #(on-dismiss-all (vals @fonts)) :data-test "dismiss-all"}
[:span (tr "dashboard.fonts.dismiss-all")]]]])
(for [item (sort-by :font-family (vals @fonts))]
(let [uploading? (contains? @uploading (:id item))]
[:div.font-item.table-row {:key (:id item)}
[:div.table-field.family
[:input {:type "text"
:on-blur #(on-blur-name (:id item) %)
:default-value (:font-family item)}]]
[:div.table-field.variants
[:span.label
[:& font-variant-display-name {:variant item}]]]
[:div.table-field.filenames
(for [item (:names item)]
[:span item])]
[:div.table-field.options
(when (:height-warning? item)
[:span.icon.failure i/msg-warning])
[:button.btn-primary.upload-button
{:on-click #(on-upload item)
:class (dom/classnames :disabled uploading?)
:disabled uploading?}
(if uploading?
(tr "labels.uploading")
(tr "labels.upload"))]
[:span.icon.close {:on-click #(on-delete item)} i/close]]]))]])))
(mf/defc installed-font
[{:keys [font-id variants] :as props}]
(let [new-css-system (mf/use-ctx ctx/new-css-system)
font (first variants)
variants (sort-by (fn [item]
[(:font-weight item)
(if (= "normal" (:font-style item)) 1 2)])
variants)
open-menu? (mf/use-state false)
edit? (mf/use-state false)
state (mf/use-var (:font-family font))
on-change
(fn [event]
(reset! state (dom/get-target-val event)))
on-save
(fn [_]
(let [font-family @state]
(when-not (str/blank? font-family)
(st/emit! (df/update-font
{:id font-id
:name font-family})))
(reset! edit? false)))
on-key-down
(fn [event]
(when (kbd/enter? event)
(on-save event)))
on-cancel
(fn [_]
(reset! edit? false)
(reset! state (:font-family font)))
delete-font-fn
(fn [] (st/emit! (df/delete-font font-id)))
delete-variant-fn
(fn [id] (st/emit! (df/delete-font-variant id)))
on-delete
(fn []
(st/emit! (modal/show
{:type :confirm
:title (tr "modals.delete-font.title")
:message (tr "modals.delete-font.message")
:accept-label (tr "labels.delete")
:on-accept (fn [_props] (delete-font-fn))})))
on-delete-variant
(fn [id]
(st/emit! (modal/show
{:type :confirm
:title (tr "modals.delete-font-variant.title")
:message (tr "modals.delete-font-variant.message")
:accept-label (tr "labels.delete")
:on-accept (fn [_props]
(delete-variant-fn id))})))]
(if new-css-system
[:div {:class (stl/css :font-item :table-row)}
[:div {:class (stl/css :table-field :family)}
(if @edit?
[:input {:type "text"
:default-value @state
:on-key-down on-key-down
:on-change on-change}]
[:span (:font-family font)])]
[:div {:class (stl/css :table-field :variants)}
(for [item variants]
[:div {:class (stl/css :variant)}
[:span {:class (stl/css :label)}
[:& font-variant-display-name {:variant item}]]
[:span
{:class (stl/css :icon :close)
:on-click #(on-delete-variant (:id item))}
i/plus]])]
(if @edit?
[:div {:class (stl/css :table-field :options)}
[:button
{:disabled (str/blank? @state)
:on-click on-save
:class (stl/css-case :btn-primary true
:btn-disabled (str/blank? @state))}
(tr "labels.save")]
[:button {:class (stl/css :icon :close)
:on-click on-cancel} i/close]]
[:div {:class (stl/css :table-field :options)}
[:span {:class (stl/css :icon)
:on-click #(reset! open-menu? true)} i/actions]
[:& context-menu
{:on-close #(reset! open-menu? false)
:show @open-menu?
:fixed? false
:top -15
:left -115
:options [[(tr "labels.edit") #(reset! edit? true) nil "font-edit"]
[(tr "labels.delete") on-delete nil "font-delete"]]}]])]
;;OLD
[:div.font-item.table-row
[:div.table-field.family
(if @edit?
[:input {:type "text"
:default-value @state
:on-key-down on-key-down
:on-change on-change}]
[:span (:font-family font)])]
[:div.table-field.variants
(for [item variants]
[:div.variant
[:span.label
[:& font-variant-display-name {:variant item}]]
[:span.icon.close
{:on-click #(on-delete-variant (:id item))}
i/plus]])]
[:div]
(if @edit?
[:div.table-field.options
[:button.btn-primary
{:disabled (str/blank? @state)
:on-click on-save
:class (dom/classnames :btn-disabled (str/blank? @state))}
(tr "labels.save")]
[:span.icon.close {:on-click on-cancel} i/close]]
[:div.table-field.options
[:span.icon {:on-click #(reset! open-menu? true)} i/actions]
[:& context-menu
{:on-close #(reset! open-menu? false)
:show @open-menu?
:fixed? false
:top -15
:left -115
:options [[(tr "labels.edit") #(reset! edit? true) nil "font-edit"]
[(tr "labels.delete") on-delete nil "font-delete"]]}]])])))
(mf/defc installed-fonts
[{:keys [fonts] :as props}]
(let [new-css-system (mf/use-ctx ctx/new-css-system)
sterm (mf/use-state "")
matches?
#(str/includes? (str/lower (:font-family %)) @sterm)
on-change
(mf/use-callback
(fn [event]
(let [val (dom/get-target-val event)]
(reset! sterm (str/lower val)))))]
(if new-css-system
[:div {:class (stl/css :dashboard-installed-fonts)}
[:h3 (tr "labels.installed-fonts")]
[:div {:class (stl/css :installed-fonts-header)}
[:div {:class (stl/css :table-field :family)} (tr "labels.font-family")]
[:div {:class (stl/css :table-field :variants)} (tr "labels.font-variants")]
[:div {:class (stl/css :table-field :search-input)}
[:input {:placeholder (tr "labels.search-font")
:default-value ""
:on-change on-change}]]]
(cond
(seq fonts)
(for [[font-id variants] (->> (vals fonts)
(filter matches?)
(group-by :font-id))]
[:& installed-font {:key (str font-id)
:font-id font-id
:variants variants}])
(nil? fonts)
[:div {:class (stl/css :fonts-placeholder)}
[:div {:class (stl/css :icon)} i/loader]
[:div {:class (stl/css :label)} (tr "dashboard.loading-fonts")]]
:else
[:div {:class (stl/css :fonts-placeholder)}
[:div {:class (stl/css :icon)} i/text]
[:div {:class (stl/css :label)} (tr "dashboard.fonts.empty-placeholder")]])]
;; OLD
[:div.dashboard-installed-fonts
[:h3 (tr "labels.installed-fonts")]
[:div.installed-fonts-header
[:div.table-field.family (tr "labels.font-family")]
[:div.table-field.variants (tr "labels.font-variants")]
[:div]
[:div.table-field.search-input
[:input {:placeholder (tr "labels.search-font")
:default-value ""
:on-change on-change}]]]
(cond
(seq fonts)
(for [[font-id variants] (->> (vals fonts)
(filter matches?)
(group-by :font-id))]
[:& installed-font {:key (str font-id)
:font-id font-id
:variants variants}])
(nil? fonts)
[:div.fonts-placeholder
[:div.icon i/loader]
[:div.label (tr "dashboard.loading-fonts")]]
:else
[:div.fonts-placeholder
[:div.icon i/text]
[:div.label (tr "dashboard.fonts.empty-placeholder")]])])))
(mf/defc fonts-page
[{:keys [team] :as props}]
(let [new-css-system (mf/use-ctx ctx/new-css-system)
fonts (mf/deref refs/dashboard-fonts)]
(if new-css-system
[:*
[:& header {:team team :section :fonts}]
[:section {:class (stl/css :dashboard-container :dashboard-fonts)}
[:& fonts-upload {:team team :installed-fonts fonts}]
[:& installed-fonts {:team team :fonts fonts}]]]
;; OLD
[:*
[:& header {:team team :section :fonts}]
[:section.dashboard-container.dashboard-fonts
[:& fonts-upload {:team team :installed-fonts fonts}]
[:& installed-fonts {:team team :fonts fonts}]]])))
(mf/defc font-providers-page
[{:keys [team] :as props}]
(let [new-css-system (mf/use-ctx ctx/new-css-system)]
(if new-css-system
[:*
[:& header {:team team :section :providers}]
[:section {:class (stl/css :dashboard-container)}
[:span "font providers"]]]
;; OLD
[:*
[:& header {:team team :section :providers}]
[:section.dashboard-container
[:span "font providers"]]])))