Add zip file format import for tokens

This commit is contained in:
Andrey Fedorov 2025-06-03 12:01:12 +02:00 committed by Andrés Moya
parent f02dd9f8dc
commit d44e4e5275
4 changed files with 181 additions and 14 deletions

View file

@ -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

View file

@ -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;
}

View file

@ -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)]

View file

@ -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"