mirror of
https://github.com/penpot/penpot.git
synced 2026-02-12 14:42:56 +00:00
✨ Add option to download user uploaded custom fonts
Allow users download any of the manually installed fonts. When there is more than one font in the family download as a .zip. Signed-off-by: Dalai Felinto <dalai@blender.org>
This commit is contained in:
committed by
Andrey Antukh
parent
12e5d8d8c4
commit
0f4c8a7da8
@@ -8,6 +8,8 @@
|
||||
|
||||
### :heart: Community contributions (Thank you!)
|
||||
|
||||
- Option to download custom fonts (by @dfelinto) [Github #8320](https://github.com/penpot/penpot/issues/8320)
|
||||
|
||||
### :sparkles: New features & Enhancements
|
||||
|
||||
- Access to design tokens in Penpot Plugins [Taiga #8990](https://tree.taiga.io/project/penpot/us/8990)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
[app.binfile.common :as bfc]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.media :as cmedia]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as ct]
|
||||
[app.common.uuid :as uuid]
|
||||
@@ -31,10 +32,13 @@
|
||||
[app.util.services :as sv]
|
||||
[datoteka.io :as io])
|
||||
(:import
|
||||
java.io.ByteArrayOutputStream
|
||||
java.io.InputStream
|
||||
java.io.OutputStream
|
||||
java.io.SequenceInputStream
|
||||
java.util.Collections))
|
||||
java.util.Collections
|
||||
java.util.zip.ZipEntry
|
||||
java.util.zip.ZipOutputStream))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
@@ -296,3 +300,93 @@
|
||||
(rph/with-meta (rph/wrap)
|
||||
{::audit/props {:font-family (:font-family variant)
|
||||
:font-id (:font-id variant)}})))
|
||||
|
||||
;; --- DOWNLOAD FONT
|
||||
|
||||
(def ^:private schema:download-font
|
||||
[:map {:title "download-font"}
|
||||
[:id ::sm/uuid]])
|
||||
|
||||
(sv/defmethod ::download-font
|
||||
{::doc/added "1.18"
|
||||
::sm/params schema:download-font}
|
||||
[{:keys [::sto/storage ::db/pool] :as cfg} {:keys [::rpc/profile-id id]}]
|
||||
(dm/with-open [conn (db/open pool)]
|
||||
(let [variant (db/get conn :team-font-variant
|
||||
{:id id
|
||||
:deleted-at nil})]
|
||||
(when-not variant
|
||||
(ex/raise :type :not-found
|
||||
:code :object-not-found))
|
||||
|
||||
(teams/check-read-permissions! conn profile-id (:team-id variant))
|
||||
|
||||
;; Try to get the best available font format (prefer TTF for broader compatibility).
|
||||
(let [file-id (or (:ttf-file-id variant)
|
||||
(:otf-file-id variant)
|
||||
(:woff2-file-id variant)
|
||||
(:woff1-file-id variant))]
|
||||
(when-not file-id
|
||||
(ex/raise :type :not-found
|
||||
:code :font-file-not-found))
|
||||
|
||||
(let [font-obj (sto/get-object storage file-id)
|
||||
font-bytes (sto/get-object-bytes storage font-obj)]
|
||||
(when-not font-obj
|
||||
(ex/raise :type :not-found
|
||||
:code :font-file-not-found))
|
||||
|
||||
;; Return base64-encoded string and mime-type for transit serialization.
|
||||
(let [data (.encodeToString (java.util.Base64/getEncoder) font-bytes)
|
||||
mtype (or (:content-type font-obj) (-> font-obj meta :content-type) "application/octet-stream")]
|
||||
{:data data :mtype mtype}))))))
|
||||
|
||||
(def ^:private schema:download-font-family
|
||||
[:map {:title "download-font-family"}
|
||||
[:font-id ::sm/uuid]])
|
||||
|
||||
(sv/defmethod ::download-font-family
|
||||
{::doc/added "1.18"
|
||||
::sm/params schema:download-font-family}
|
||||
[{:keys [::sto/storage ::db/pool] :as cfg} {:keys [::rpc/profile-id font-id]}]
|
||||
(dm/with-open [conn (db/open pool)]
|
||||
(let [variants (db/query conn :team-font-variant
|
||||
{:font-id font-id
|
||||
:deleted-at nil})]
|
||||
(when-not (seq variants)
|
||||
(ex/raise :type :not-found
|
||||
:code :object-not-found))
|
||||
|
||||
(teams/check-read-permissions! conn profile-id (:team-id (first variants)))
|
||||
|
||||
(let [entries
|
||||
(->> variants
|
||||
(map (fn [v]
|
||||
(let [file-id (or (:ttf-file-id v)
|
||||
(:otf-file-id v)
|
||||
(:woff2-file-id v)
|
||||
(:woff1-file-id v))]
|
||||
(when-not file-id
|
||||
(ex/raise :type :not-found :code :font-file-not-found))
|
||||
|
||||
(let [sobj (sto/get-object storage file-id)
|
||||
bytes (sto/get-object-bytes storage sobj)
|
||||
mtype (or (:content-type sobj) (-> sobj meta :content-type) "application/octet-stream")
|
||||
ext (cmedia/mtype->extension mtype)
|
||||
name (str (:font-family v) "-" (:font-weight v)
|
||||
(when-not (= "normal" (:font-style v)) (str "-" (:font-style v)))
|
||||
(or ext ""))]
|
||||
{:name name :bytes bytes})))))]
|
||||
|
||||
;; Build zip in memory.
|
||||
(let [baos (ByteArrayOutputStream.)
|
||||
zos (ZipOutputStream. baos)]
|
||||
(doseq [{:keys [name bytes]} entries]
|
||||
(let [entry (ZipEntry. name)]
|
||||
(.putNextEntry zos entry)
|
||||
(.write zos ^bytes bytes)
|
||||
(.closeEntry zos)))
|
||||
(.close zos)
|
||||
(let [zip-bytes (.toByteArray baos)
|
||||
data (.encodeToString (java.util.Base64/getEncoder) zip-bytes)]
|
||||
{:data data :mtype "application/zip"}))))))
|
||||
@@ -24,6 +24,8 @@
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.timers :as tm]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
@@ -259,11 +261,14 @@
|
||||
(mf/defc installed-font-context-menu
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [is-open on-close on-edit on-delete]}]
|
||||
(let [options (mf/with-memo [on-edit on-delete]
|
||||
[{:keys [is-open on-close on-edit on-download on-delete]}]
|
||||
(let [options (mf/with-memo [on-edit on-download on-delete]
|
||||
[{:name (tr "labels.edit")
|
||||
:id "font-edit"
|
||||
:handler on-edit}
|
||||
{:name (tr "labels.download-simple")
|
||||
:id "font-download"
|
||||
:handler on-download}
|
||||
{:name (tr "labels.delete")
|
||||
:id "font-delete"
|
||||
:handler on-delete}])]
|
||||
@@ -345,6 +350,34 @@
|
||||
(st/emit! (df/delete-font font-id)))}]
|
||||
(st/emit! (modal/show options)))))
|
||||
|
||||
on-download
|
||||
(mf/use-fn
|
||||
(mf/deps variants)
|
||||
(fn [_event]
|
||||
(let [variant (first variants)
|
||||
variant-id (:id variant)
|
||||
multiple? (> (count variants) 1)
|
||||
cmd (if multiple? :download-font-family :download-font)
|
||||
params (if multiple? {:font-id font-id} {:id variant-id})]
|
||||
(->> (rp/cmd! cmd params)
|
||||
(rx/subs! (fn [font-data]
|
||||
;; font-data is base64-encoded or a map {:data :mtype}
|
||||
(let [b64 (if (string? font-data) font-data (:data font-data))
|
||||
default-mtype "application/octet-stream"
|
||||
mtype (if (string? font-data) default-mtype (or (:mtype font-data) default-mtype))
|
||||
binary-str (js/atob b64)
|
||||
bytes (js/Uint8Array.
|
||||
(for [i (range (.-length binary-str))]
|
||||
(.charCodeAt binary-str i)))
|
||||
blob (wapi/create-blob bytes mtype)
|
||||
uri (wapi/create-uri blob)
|
||||
name (:font-family font)]
|
||||
(dom/trigger-download-uri name mtype uri)
|
||||
(tm/schedule-on-idle #(wapi/revoke-uri uri))))
|
||||
(fn [error]
|
||||
(js/console.error "error downloading font" error)
|
||||
(st/emit! (ntf/error (tr "errors.download-font")))))))))
|
||||
|
||||
on-delete-variant
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
@@ -407,6 +440,7 @@
|
||||
{:on-close on-menu-close
|
||||
:is-open menu-open?
|
||||
:on-delete on-delete-font
|
||||
:on-download on-download
|
||||
:on-edit on-edit}]]))]))
|
||||
|
||||
(mf/defc installed-fonts*
|
||||
|
||||
@@ -2343,6 +2343,9 @@ msgstr "Discard"
|
||||
msgid "labels.download"
|
||||
msgstr "Download %s"
|
||||
|
||||
msgid "labels.download-simple"
|
||||
msgstr "Download"
|
||||
|
||||
#: src/app/main/ui/dashboard/file_menu.cljs:30, src/app/main/ui/dashboard/files.cljs:80, src/app/main/ui/dashboard/files.cljs:179, src/app/main/ui/dashboard/projects.cljs:229, src/app/main/ui/dashboard/projects.cljs:233, src/app/main/ui/dashboard/sidebar.cljs:726
|
||||
msgid "labels.drafts"
|
||||
msgstr "Drafts"
|
||||
|
||||
@@ -2314,6 +2314,9 @@ msgstr "Descartar"
|
||||
msgid "labels.download"
|
||||
msgstr "Descargar %s"
|
||||
|
||||
msgid "labels.download-simple"
|
||||
msgstr "Descargar"
|
||||
|
||||
#: src/app/main/ui/dashboard/file_menu.cljs:30, src/app/main/ui/dashboard/files.cljs:80, src/app/main/ui/dashboard/files.cljs:179, src/app/main/ui/dashboard/projects.cljs:229, src/app/main/ui/dashboard/projects.cljs:233, src/app/main/ui/dashboard/sidebar.cljs:726
|
||||
msgid "labels.drafts"
|
||||
msgstr "Borradores"
|
||||
|
||||
Reference in New Issue
Block a user