mirror of
https://github.com/penpot/penpot.git
synced 2025-08-02 00:28:28 +02:00
✨ Add zip file format import for tokens
This commit is contained in:
parent
f02dd9f8dc
commit
d44e4e5275
4 changed files with 181 additions and 14 deletions
|
@ -14,15 +14,17 @@
|
|||
[app.main.data.workspace.tokens.import-export :as dwti]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.controls.shared.options-dropdown :refer [options-dropdown*]]
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.webapi :as wapi]
|
||||
[app.util.zip :as uz]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]
|
||||
|
@ -42,11 +44,82 @@
|
|||
:type :toast
|
||||
:level :error})))))
|
||||
|
||||
(mf/defc import-type-dropdown*
|
||||
{::mf/private true}
|
||||
[{:keys [options on-click text-render default]}]
|
||||
(let [initial-option (or (first (filter #(= (:value %) default) options))
|
||||
(first options))
|
||||
selected-option* (mf/use-state initial-option)
|
||||
selected-option @selected-option*
|
||||
show-dropdown? (mf/use-state false)
|
||||
|
||||
js-options (mf/use-memo
|
||||
(mf/deps options)
|
||||
#(clj->js
|
||||
(mapv (fn [option]
|
||||
{:id (str (:value option))
|
||||
:label (:label option)
|
||||
:aria-label (:label option)})
|
||||
options)))
|
||||
button-text (if text-render
|
||||
(text-render selected-option)
|
||||
(:label selected-option))
|
||||
|
||||
toggle-dropdown
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(swap! show-dropdown? not)))
|
||||
|
||||
close-dropdown
|
||||
(mf/use-fn #(reset! show-dropdown? false))
|
||||
|
||||
handle-option-click
|
||||
(mf/use-fn
|
||||
(mf/deps options)
|
||||
(fn [event]
|
||||
(let [target (dom/get-current-target event)
|
||||
option-id (dom/get-attribute target "id")
|
||||
option (first (filter #(= (str (:value %)) option-id) options))]
|
||||
(close-dropdown)
|
||||
(when option
|
||||
(reset! selected-option* option)))))
|
||||
|
||||
handle-main-click
|
||||
(mf/use-fn
|
||||
(mf/deps on-click selected-option)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(when (and selected-option on-click)
|
||||
(on-click (:value selected-option)))))]
|
||||
|
||||
[:div {:class (stl/css :dropdown-btn-wrapper)}
|
||||
[:> button* {:variant "primary"
|
||||
:type "button"
|
||||
:class (stl/css :dropdown-btn)
|
||||
:on-click handle-main-click}
|
||||
button-text]
|
||||
|
||||
[:> button* {:variant "primary"
|
||||
:type "button"
|
||||
:class (stl/css :dropdown-trigger-btn)
|
||||
:icon "arrow-down"
|
||||
:on-click toggle-dropdown
|
||||
:aria-label "Show options"}]
|
||||
|
||||
[:& dropdown {:show @show-dropdown? :on-close close-dropdown}
|
||||
[:> options-dropdown* {:options js-options
|
||||
:selected (str (:value selected-option))
|
||||
:on-click handle-option-click
|
||||
:set-ref (fn [_] nil)}]]]))
|
||||
|
||||
(mf/defc import-modal-body*
|
||||
{::mf/private true}
|
||||
[]
|
||||
(let [file-input-ref (mf/use-ref)
|
||||
dir-input-ref (mf/use-ref)
|
||||
zip-input-ref (mf/use-ref)
|
||||
|
||||
on-display-file-explorer
|
||||
(mf/use-fn #(dom/click (mf/ref-val file-input-ref)))
|
||||
|
@ -54,6 +127,19 @@
|
|||
on-display-dir-explorer
|
||||
(mf/use-fn #(dom/click (mf/ref-val dir-input-ref)))
|
||||
|
||||
on-display-zip-explorer
|
||||
(mf/use-fn #(dom/click (mf/ref-val zip-input-ref)))
|
||||
|
||||
handle-import-action
|
||||
(mf/use-fn
|
||||
(mf/deps on-display-file-explorer on-display-dir-explorer on-display-zip-explorer)
|
||||
(fn [val]
|
||||
(case val
|
||||
:file (on-display-file-explorer)
|
||||
:folder (on-display-dir-explorer)
|
||||
:zip (on-display-zip-explorer)
|
||||
nil)))
|
||||
|
||||
on-import-directory
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
|
@ -77,7 +163,38 @@
|
|||
(-> (mf/ref-val dir-input-ref)
|
||||
(dom/set-value! "")))))
|
||||
|
||||
on-import-file
|
||||
on-import-zip-file
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [zipfile (-> (dom/get-target event)
|
||||
(dom/get-files)
|
||||
(first))
|
||||
zipfile-name (str/strip-suffix (.-name zipfile) ".zip")]
|
||||
(->> (wapi/read-file-as-array-buffer zipfile)
|
||||
(rx/mapcat (fn [file-content]
|
||||
(let [zip-reader (uz/reader file-content)]
|
||||
(->> (rx/from (uz/get-entries zip-reader))
|
||||
(rx/mapcat
|
||||
(fn [entries]
|
||||
(->> (rx/from entries)
|
||||
(rx/filter (fn [entry]
|
||||
(let [filename (.-filename entry)]
|
||||
(str/ends-with? filename ".json"))))
|
||||
(rx/merge-map (fn [entry]
|
||||
(let [filename (str/concat zipfile-name "/" (.-filename entry))
|
||||
content-promise (uz/read-as-text entry)]
|
||||
(-> content-promise
|
||||
(.then (fn [text]
|
||||
[filename text]))
|
||||
(rx/from))))))))
|
||||
(rx/finalize (partial uz/close zip-reader))))))
|
||||
(dwti/import-directory-stream)
|
||||
(on-stream-imported "zip"))
|
||||
|
||||
(-> (mf/ref-val zip-input-ref)
|
||||
(dom/set-value! "")))))
|
||||
|
||||
on-import-json-file
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [file (-> (dom/get-target event)
|
||||
|
@ -88,7 +205,12 @@
|
|||
(on-stream-imported "single"))
|
||||
|
||||
(-> (mf/ref-val file-input-ref)
|
||||
(dom/set-value! "")))))]
|
||||
(dom/set-value! "")))))
|
||||
|
||||
render-button-text
|
||||
(mf/use-fn
|
||||
(fn [option]
|
||||
(tr "workspace.tokens.import-button-prefix" (:label option))))]
|
||||
|
||||
[:div {:class (stl/css :import-modal-wrapper)}
|
||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :import-modal-title)}
|
||||
|
@ -109,7 +231,12 @@
|
|||
:ref file-input-ref
|
||||
:style {:display "none"}
|
||||
:accept ".json"
|
||||
:on-change on-import-file}]
|
||||
:on-change on-import-json-file}]
|
||||
[:input {:type "file"
|
||||
:ref zip-input-ref
|
||||
:style {:display "none"}
|
||||
:accept ".zip"
|
||||
:on-change on-import-zip-file}]
|
||||
[:input {:type "file"
|
||||
:ref dir-input-ref
|
||||
:style {:display "none"}
|
||||
|
@ -120,16 +247,13 @@
|
|||
:type "button"
|
||||
:on-click modal/hide!}
|
||||
(tr "labels.cancel")]
|
||||
[:> button* {:variant "primary"
|
||||
:type "button"
|
||||
:icon i/document
|
||||
:on-click on-display-file-explorer}
|
||||
(tr "workspace.tokens.choose-file")]
|
||||
[:> button* {:variant "primary"
|
||||
:type "button"
|
||||
:icon i/folder
|
||||
:on-click on-display-dir-explorer}
|
||||
(tr "workspace.tokens.choose-folder")]]]))
|
||||
[:> import-type-dropdown*
|
||||
{:options [{:label (tr "workspace.tokens.import-menu-zip-option") :value :zip}
|
||||
{:label (tr "workspace.tokens.import-menu-json-option") :value :file}
|
||||
{:label (tr "workspace.tokens.import-menu-folder-option") :value :folder}]
|
||||
:on-click handle-import-action
|
||||
:text-render render-button-text
|
||||
:default :zip}]]]))
|
||||
|
||||
(mf/defc import-modal*
|
||||
{::mf/register modal/components
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "../../../ds/typography.scss" as t;
|
||||
@use "../../../ds/_borders.scss" as *;
|
||||
@use "../../../ds/_sizes.scss" as *;
|
||||
@import "refactor/common-refactor.scss";
|
||||
|
||||
|
@ -48,3 +49,23 @@
|
|||
inset-block-start: var(--sp-s);
|
||||
inset-inline-end: var(--sp-s);
|
||||
}
|
||||
|
||||
.dropdown-trigger-btn {
|
||||
border-start-start-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
border-inline-start: $b-1 solid var(--color-accent-tertiary);
|
||||
width: var(--sp-xxxl);
|
||||
padding-inline-start: 0;
|
||||
padding-inline-end: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dropdown-btn {
|
||||
border-end-end-radius: 0;
|
||||
border-start-end-radius: 0;
|
||||
}
|
||||
|
||||
.dropdown-btn-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
|
|
@ -74,6 +74,12 @@
|
|||
(p/fmap (fn [entries]
|
||||
(array/find #(= (.-filename ^js %) path) entries)))))
|
||||
|
||||
(defn get-entries
|
||||
[reader]
|
||||
(assert (instance? zip/ZipReader reader))
|
||||
(.getEntries reader))
|
||||
|
||||
|
||||
(defn read-as-text
|
||||
[entry]
|
||||
(let [writer (new zip/TextWriter)]
|
||||
|
|
|
@ -7122,6 +7122,22 @@ msgstr "Choose file"
|
|||
msgid "workspace.tokens.choose-folder"
|
||||
msgstr "Choose folder"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/modals/import.cljs:251
|
||||
msgid "workspace.tokens.import-button-prefix"
|
||||
msgstr "Import %s"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/modals/import.cljs:245
|
||||
msgid "workspace.tokens.import-menu-zip-option"
|
||||
msgstr "ZIP file"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/modals/import.cljs:246
|
||||
msgid "workspace.tokens.import-menu-json-option"
|
||||
msgstr "Single JSON file"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/modals/import.cljs:247
|
||||
msgid "workspace.tokens.import-menu-folder-option"
|
||||
msgstr "Folder"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/context_menu.cljs:245
|
||||
msgid "workspace.tokens.color"
|
||||
msgstr "Color"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue