mirror of
https://github.com/penpot/penpot.git
synced 2025-08-06 15:08:27 +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.import-export :as dwti]
|
||||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||||
[app.main.store :as st]
|
[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.button :refer [button*]]
|
||||||
[app.main.ui.ds.buttons.icon-button :refer [icon-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.heading :refer [heading*]]
|
||||||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
[app.util.webapi :as wapi]
|
[app.util.webapi :as wapi]
|
||||||
|
[app.util.zip :as uz]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[potok.v2.core :as ptk]
|
[potok.v2.core :as ptk]
|
||||||
|
@ -42,11 +44,82 @@
|
||||||
:type :toast
|
:type :toast
|
||||||
:level :error})))))
|
: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/defc import-modal-body*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[]
|
[]
|
||||||
(let [file-input-ref (mf/use-ref)
|
(let [file-input-ref (mf/use-ref)
|
||||||
dir-input-ref (mf/use-ref)
|
dir-input-ref (mf/use-ref)
|
||||||
|
zip-input-ref (mf/use-ref)
|
||||||
|
|
||||||
on-display-file-explorer
|
on-display-file-explorer
|
||||||
(mf/use-fn #(dom/click (mf/ref-val file-input-ref)))
|
(mf/use-fn #(dom/click (mf/ref-val file-input-ref)))
|
||||||
|
@ -54,6 +127,19 @@
|
||||||
on-display-dir-explorer
|
on-display-dir-explorer
|
||||||
(mf/use-fn #(dom/click (mf/ref-val dir-input-ref)))
|
(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
|
on-import-directory
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [event]
|
(fn [event]
|
||||||
|
@ -77,7 +163,38 @@
|
||||||
(-> (mf/ref-val dir-input-ref)
|
(-> (mf/ref-val dir-input-ref)
|
||||||
(dom/set-value! "")))))
|
(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
|
(mf/use-fn
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(let [file (-> (dom/get-target event)
|
(let [file (-> (dom/get-target event)
|
||||||
|
@ -88,7 +205,12 @@
|
||||||
(on-stream-imported "single"))
|
(on-stream-imported "single"))
|
||||||
|
|
||||||
(-> (mf/ref-val file-input-ref)
|
(-> (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)}
|
[:div {:class (stl/css :import-modal-wrapper)}
|
||||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :import-modal-title)}
|
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :import-modal-title)}
|
||||||
|
@ -109,7 +231,12 @@
|
||||||
:ref file-input-ref
|
:ref file-input-ref
|
||||||
:style {:display "none"}
|
:style {:display "none"}
|
||||||
:accept ".json"
|
: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"
|
[:input {:type "file"
|
||||||
:ref dir-input-ref
|
:ref dir-input-ref
|
||||||
:style {:display "none"}
|
:style {:display "none"}
|
||||||
|
@ -120,16 +247,13 @@
|
||||||
:type "button"
|
:type "button"
|
||||||
:on-click modal/hide!}
|
:on-click modal/hide!}
|
||||||
(tr "labels.cancel")]
|
(tr "labels.cancel")]
|
||||||
[:> button* {:variant "primary"
|
[:> import-type-dropdown*
|
||||||
:type "button"
|
{:options [{:label (tr "workspace.tokens.import-menu-zip-option") :value :zip}
|
||||||
:icon i/document
|
{:label (tr "workspace.tokens.import-menu-json-option") :value :file}
|
||||||
:on-click on-display-file-explorer}
|
{:label (tr "workspace.tokens.import-menu-folder-option") :value :folder}]
|
||||||
(tr "workspace.tokens.choose-file")]
|
:on-click handle-import-action
|
||||||
[:> button* {:variant "primary"
|
:text-render render-button-text
|
||||||
:type "button"
|
:default :zip}]]]))
|
||||||
:icon i/folder
|
|
||||||
:on-click on-display-dir-explorer}
|
|
||||||
(tr "workspace.tokens.choose-folder")]]]))
|
|
||||||
|
|
||||||
(mf/defc import-modal*
|
(mf/defc import-modal*
|
||||||
{::mf/register modal/components
|
{::mf/register modal/components
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "../../../ds/typography.scss" as t;
|
@use "../../../ds/typography.scss" as t;
|
||||||
|
@use "../../../ds/_borders.scss" as *;
|
||||||
@use "../../../ds/_sizes.scss" as *;
|
@use "../../../ds/_sizes.scss" as *;
|
||||||
@import "refactor/common-refactor.scss";
|
@import "refactor/common-refactor.scss";
|
||||||
|
|
||||||
|
@ -48,3 +49,23 @@
|
||||||
inset-block-start: var(--sp-s);
|
inset-block-start: var(--sp-s);
|
||||||
inset-inline-end: 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]
|
(p/fmap (fn [entries]
|
||||||
(array/find #(= (.-filename ^js %) path) entries)))))
|
(array/find #(= (.-filename ^js %) path) entries)))))
|
||||||
|
|
||||||
|
(defn get-entries
|
||||||
|
[reader]
|
||||||
|
(assert (instance? zip/ZipReader reader))
|
||||||
|
(.getEntries reader))
|
||||||
|
|
||||||
|
|
||||||
(defn read-as-text
|
(defn read-as-text
|
||||||
[entry]
|
[entry]
|
||||||
(let [writer (new zip/TextWriter)]
|
(let [writer (new zip/TextWriter)]
|
||||||
|
|
|
@ -7122,6 +7122,22 @@ msgstr "Choose file"
|
||||||
msgid "workspace.tokens.choose-folder"
|
msgid "workspace.tokens.choose-folder"
|
||||||
msgstr "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
|
#: src/app/main/ui/workspace/tokens/context_menu.cljs:245
|
||||||
msgid "workspace.tokens.color"
|
msgid "workspace.tokens.color"
|
||||||
msgstr "Color"
|
msgstr "Color"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue