mirror of
https://github.com/penpot/penpot.git
synced 2026-09-22 20:06:16 +00:00
🐛 Fix ambiguation on import modal
This commit is contained in:
parent
76e9289fcf
commit
20243b82f4
@ -390,25 +390,55 @@
|
|||||||
|
|
||||||
(mf/defc library-resolution*
|
(mf/defc library-resolution*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [unresolved-file selection on-select]}]
|
[{:keys [unresolved-file selection on-select on-disconnect]}]
|
||||||
(let [candidates (:pending unresolved-file)
|
(let [file-id (:id unresolved-file)
|
||||||
disconnected* (mf/use-state #{})
|
candidates (:pending unresolved-file)
|
||||||
disconnected (deref disconnected*)
|
file-selection (get selection file-id)
|
||||||
|
|
||||||
|
on-select-file
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps file-id on-select)
|
||||||
|
(partial on-select file-id))
|
||||||
|
|
||||||
|
on-disconnect-file
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps file-id on-disconnect)
|
||||||
|
(partial on-disconnect file-id))
|
||||||
|
|
||||||
on-change-disconnected
|
on-change-disconnected
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
(mf/deps file-selection candidates on-select-file on-disconnect-file)
|
||||||
(fn [id]
|
(fn [id]
|
||||||
(swap! disconnected*
|
(if (contains? file-selection id)
|
||||||
(fn [s]
|
(on-disconnect-file id)
|
||||||
(if (contains? s id) (disj s id) (conj s id))))))]
|
(let [{:keys [candidates]} (d/seek #(= id (:id %)) candidates)]
|
||||||
|
(when-let [first-c (first candidates)]
|
||||||
|
(on-select-file id (str (:id first-c))))))))]
|
||||||
|
|
||||||
;; Pre-select first candidate for each library
|
;; Pre-select a candidate for each of this file's pending libraries not
|
||||||
(mf/with-effect [candidates]
|
;; yet resolved for this file: reuse another file's choice for the same
|
||||||
|
;; source library when one already exists (so files sharing a library
|
||||||
|
;; default to the same target), otherwise fall back to the first
|
||||||
|
;; candidate. Each file keeps its own entry in `selection`, so this is
|
||||||
|
;; only ever a starting point - it never overwrites another file's pick.
|
||||||
|
(mf/with-effect [candidates file-id]
|
||||||
(doseq [{:keys [id candidates]} candidates]
|
(doseq [{:keys [id candidates]} candidates]
|
||||||
(when-not (contains? selection id)
|
(when-not (contains? file-selection id)
|
||||||
(when-let [first-c (first candidates)]
|
(let [other-choice (some #(get % id) (vals selection))
|
||||||
(on-select id (str (:id first-c)))))))
|
default-id (or other-choice (some-> candidates first :id str))]
|
||||||
|
(when default-id
|
||||||
|
(on-select-file id default-id))))))
|
||||||
|
|
||||||
[:div {:class (stl/css :library-resolution)}
|
[:div {:class (stl/css :library-resolution)}
|
||||||
|
[:div {:class (stl/css :library-resolution-file-header)}
|
||||||
|
[:> icon* {:icon-id i/document
|
||||||
|
:class (stl/css :library-resolution-file-icon)
|
||||||
|
:size "s"}]
|
||||||
|
[:> text* {:class (stl/css :library-resolution-file-name)
|
||||||
|
:as "span"
|
||||||
|
:typography t/body-medium}
|
||||||
|
(:name unresolved-file)]]
|
||||||
|
|
||||||
[:> text* {:class (stl/css :library-resolution-message)
|
[:> text* {:class (stl/css :library-resolution-message)
|
||||||
:as "p"
|
:as "p"
|
||||||
:typography t/body-large}
|
:typography t/body-large}
|
||||||
@ -435,8 +465,8 @@
|
|||||||
{:id (str (:id c))
|
{:id (str (:id c))
|
||||||
:label (str (:name c) " (" (:project-name c) ")")})
|
:label (str (:name c) " (" (:project-name c) ")")})
|
||||||
candidates)
|
candidates)
|
||||||
selected (get selection id)
|
selected (get file-selection id)
|
||||||
is-conected (not (contains? disconnected id))]
|
is-conected (contains? file-selection id)]
|
||||||
[:tr {:class (stl/css :library-resolution-item)
|
[:tr {:class (stl/css :library-resolution-item)
|
||||||
:key (dm/str id)}
|
:key (dm/str id)}
|
||||||
[:td {:class (stl/css :library-resolution-item-name)}
|
[:td {:class (stl/css :library-resolution-item-name)}
|
||||||
@ -453,7 +483,7 @@
|
|||||||
:class (stl/css :library-resolution-select)
|
:class (stl/css :library-resolution-select)
|
||||||
:default-selected (or (some-> selected str) "")
|
:default-selected (or (some-> selected str) "")
|
||||||
:has-portal true
|
:has-portal true
|
||||||
:on-change (partial on-select id)}]
|
:on-change (partial on-select-file id)}]
|
||||||
|
|
||||||
[:> text* {:class (stl/css :library-resolution-no-selection)
|
[:> text* {:class (stl/css :library-resolution-no-selection)
|
||||||
:as "span"
|
:as "span"
|
||||||
@ -465,8 +495,9 @@
|
|||||||
(mf/defc library-resolution-summary-file*
|
(mf/defc library-resolution-summary-file*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [resolution-file selection]}]
|
[{:keys [resolution-file selection]}]
|
||||||
(let [done (:done resolution-file)
|
(let [done (:done resolution-file)
|
||||||
pending (:pending resolution-file)]
|
pending (:pending resolution-file)
|
||||||
|
file-selection (get selection (:id resolution-file))]
|
||||||
[:div {:class (stl/css :summary-file)}
|
[:div {:class (stl/css :summary-file)}
|
||||||
[:div {:class (stl/css :summary-file-header)}
|
[:div {:class (stl/css :summary-file-header)}
|
||||||
[:> icon* {:icon-id i/document
|
[:> icon* {:icon-id i/document
|
||||||
@ -508,7 +539,7 @@
|
|||||||
[:span {:class (stl/css :summary-item-name-header)}
|
[:span {:class (stl/css :summary-item-name-header)}
|
||||||
"New"]]
|
"New"]]
|
||||||
(for [{:keys [id name] :as cand} pending]
|
(for [{:keys [id name] :as cand} pending]
|
||||||
(let [selected-id (get selection id)
|
(let [selected-id (get file-selection id)
|
||||||
selected-c (when selected-id
|
selected-c (when selected-id
|
||||||
(d/seek #(= (str (:id %)) (str selected-id)) (:candidates cand)))]
|
(d/seek #(= (str (:id %)) (str selected-id)) (:candidates cand)))]
|
||||||
[:li {:class (stl/css :summary-list-item)
|
[:li {:class (stl/css :summary-list-item)
|
||||||
@ -647,15 +678,16 @@
|
|||||||
|
|
||||||
(mf/defc import-library-resolution-stage*
|
(mf/defc import-library-resolution-stage*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [current-unresolved-file selection on-select
|
[{:keys [current-unresolved-file selection on-select on-disconnect
|
||||||
visited all-visited?
|
visited last-file?
|
||||||
on-wizard-prev on-wizard-next]}]
|
on-wizard-prev on-wizard-next]}]
|
||||||
[:*
|
[:*
|
||||||
[:div {:class (stl/css :modal-content)}
|
[:div {:class (stl/css :modal-content)}
|
||||||
[:> library-resolution*
|
[:> library-resolution*
|
||||||
{:unresolved-file current-unresolved-file
|
{:unresolved-file current-unresolved-file
|
||||||
:selection selection
|
:selection selection
|
||||||
:on-select on-select}]]
|
:on-select on-select
|
||||||
|
:on-disconnect on-disconnect}]]
|
||||||
|
|
||||||
[:div {:class (stl/css :modal-footer)}
|
[:div {:class (stl/css :modal-footer)}
|
||||||
[:div {:class (stl/css :action-buttons)}
|
[:div {:class (stl/css :action-buttons)}
|
||||||
@ -667,9 +699,9 @@
|
|||||||
[:> button* {:class (stl/css :accept-btn)
|
[:> button* {:class (stl/css :accept-btn)
|
||||||
:variant "primary"
|
:variant "primary"
|
||||||
:on-click on-wizard-next}
|
:on-click on-wizard-next}
|
||||||
(if all-visited?
|
(if last-file?
|
||||||
(tr "labels.next")
|
(tr "dashboard.import.review-links")
|
||||||
(tr "dashboard.import.review-links"))]]]])
|
(tr "dashboard.import.next-file"))]]]])
|
||||||
|
|
||||||
(mf/defc import-library-summary-stage*
|
(mf/defc import-library-summary-stage*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
@ -717,7 +749,9 @@
|
|||||||
resolution* (mf/use-state nil)
|
resolution* (mf/use-state nil)
|
||||||
resolution (not-empty (deref resolution*))
|
resolution (not-empty (deref resolution*))
|
||||||
|
|
||||||
;; User selection for multi-match candidates: {old-lib-id candidate-id}
|
;; User selection for multi-match candidates, scoped per file so two
|
||||||
|
;; files referencing the same source library can each resolve it to
|
||||||
|
;; a different target: {file-id {old-lib-id candidate-id}}
|
||||||
selection* (mf/use-state {})
|
selection* (mf/use-state {})
|
||||||
selection (deref selection*)
|
selection (deref selection*)
|
||||||
|
|
||||||
@ -738,16 +772,21 @@
|
|||||||
[]
|
[]
|
||||||
resolution)))
|
resolution)))
|
||||||
|
|
||||||
all-visited?
|
|
||||||
(mf/with-memo [visited unresolved-files]
|
|
||||||
(when (seq unresolved-files)
|
|
||||||
(every? #(contains? visited (:id %)) unresolved-files)))
|
|
||||||
|
|
||||||
;; Current file shown in the wizard step: first unresolved file not yet visited.
|
;; Current file shown in the wizard step: first unresolved file not yet visited.
|
||||||
current-unresolved-file
|
current-unresolved-file
|
||||||
(mf/with-memo [unresolved-files visited]
|
(mf/with-memo [unresolved-files visited]
|
||||||
(d/seek #(not (contains? visited (:id %))) unresolved-files))
|
(d/seek #(not (contains? visited (:id %))) unresolved-files))
|
||||||
|
|
||||||
|
;; Whether the file currently shown is the last one still needing
|
||||||
|
;; resolution: true once every other unresolved file has already
|
||||||
|
;; been visited, so moving past this one reaches the summary.
|
||||||
|
last-unresolved-file?
|
||||||
|
(mf/with-memo [unresolved-files visited current-unresolved-file]
|
||||||
|
(when (some? current-unresolved-file)
|
||||||
|
(every? #(or (= (:id %) (:id current-unresolved-file))
|
||||||
|
(contains? visited (:id %)))
|
||||||
|
unresolved-files)))
|
||||||
|
|
||||||
continue-entries
|
continue-entries
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps entries)
|
(mf/deps entries)
|
||||||
@ -830,7 +869,7 @@
|
|||||||
(->> (rx/from (:pending resolution-file))
|
(->> (rx/from (:pending resolution-file))
|
||||||
(rx/merge-map
|
(rx/merge-map
|
||||||
(fn [{:keys [id]}]
|
(fn [{:keys [id]}]
|
||||||
(when-let [selected-lib (get slc id)]
|
(when-let [selected-lib (get-in slc [file-id id])]
|
||||||
(link-files-to-library! [file-id] selected-lib)))))))
|
(link-files-to-library! [file-id] selected-lib)))))))
|
||||||
(rx/subs! (constantly nil)
|
(rx/subs! (constantly nil)
|
||||||
(constantly nil)
|
(constantly nil)
|
||||||
@ -848,12 +887,12 @@
|
|||||||
|
|
||||||
on-wizard-prev
|
on-wizard-prev
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps current-unresolved-file)
|
(mf/deps visited)
|
||||||
(fn []
|
(fn []
|
||||||
;; Remove the current file from visited; it becomes current again after re-render,
|
;; Remove the last-visited file from visited; it becomes current
|
||||||
;; because it's no longer in visited.
|
;; again after re-render, because it's no longer in visited.
|
||||||
(let [file-id (:id current-unresolved-file)]
|
(let [last-id (last visited)]
|
||||||
(swap! visited* disj file-id))))
|
(swap! visited* disj last-id))))
|
||||||
|
|
||||||
on-summary-back
|
on-summary-back
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@ -897,9 +936,13 @@
|
|||||||
|
|
||||||
manage-on-select
|
manage-on-select
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps selection)
|
(fn [file-id old-lib-id candidate-id]
|
||||||
(fn [old-lib-id candidate-id]
|
(swap! selection* assoc-in [file-id old-lib-id] candidate-id)))
|
||||||
(swap! selection* assoc old-lib-id candidate-id)))]
|
|
||||||
|
manage-on-disconnect
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [file-id old-lib-id]
|
||||||
|
(swap! selection* update file-id (fnil dissoc {}) old-lib-id)))]
|
||||||
|
|
||||||
(mf/with-effect [visited unresolved-files]
|
(mf/with-effect [visited unresolved-files]
|
||||||
(when (and (seq unresolved-files)
|
(when (and (seq unresolved-files)
|
||||||
@ -968,8 +1011,9 @@
|
|||||||
{:current-unresolved-file current-unresolved-file
|
{:current-unresolved-file current-unresolved-file
|
||||||
:selection selection
|
:selection selection
|
||||||
:on-select manage-on-select
|
:on-select manage-on-select
|
||||||
|
:on-disconnect manage-on-disconnect
|
||||||
:visited visited
|
:visited visited
|
||||||
:all-visited? all-visited?
|
:last-file? last-unresolved-file?
|
||||||
:on-wizard-prev on-wizard-prev
|
:on-wizard-prev on-wizard-prev
|
||||||
:on-wizard-next on-wizard-next}]
|
:on-wizard-next on-wizard-next}]
|
||||||
|
|
||||||
|
|||||||
15
frontend/src/app/main/ui/dashboard/import.scss
vendored
15
frontend/src/app/main/ui/dashboard/import.scss
vendored
@ -250,6 +250,21 @@
|
|||||||
margin-block-end: var(--sp-s);
|
margin-block-end: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.library-resolution-file-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-resolution-file-icon {
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-resolution-file-name {
|
||||||
|
color: var(--color-foreground-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.library-resolution-header {
|
.library-resolution-header {
|
||||||
@include use-typography("body-medium");
|
@include use-typography("body-medium");
|
||||||
|
|
||||||
|
|||||||
@ -827,6 +827,10 @@ msgstr[1] "%s files have been imported successfully."
|
|||||||
msgid "dashboard.import.import-warning"
|
msgid "dashboard.import.import-warning"
|
||||||
msgstr "Some files containted invalid objects that have been removed."
|
msgstr "Some files containted invalid objects that have been removed."
|
||||||
|
|
||||||
|
#: src/app/main/ui/dashboard/import.cljs:704
|
||||||
|
msgid "dashboard.import.next-file"
|
||||||
|
msgstr "Next file"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard.cljs:265
|
#: src/app/main/ui/dashboard.cljs:265
|
||||||
msgid "dashboard.import.no-perms"
|
msgid "dashboard.import.no-perms"
|
||||||
msgstr "You don’t have permission to import to this team"
|
msgstr "You don’t have permission to import to this team"
|
||||||
@ -869,9 +873,9 @@ msgstr ""
|
|||||||
msgid "dashboard.import.resolve-libraries-summary"
|
msgid "dashboard.import.resolve-libraries-summary"
|
||||||
msgstr "Review the library links before confirming:"
|
msgstr "Review the library links before confirming:"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/import.cljs:672
|
#: src/app/main/ui/dashboard/import.cljs:703
|
||||||
msgid "dashboard.import.review-links"
|
msgid "dashboard.import.review-links"
|
||||||
msgstr "Review links"
|
msgstr "Review library links"
|
||||||
|
|
||||||
#, unused
|
#, unused
|
||||||
msgid "dashboard.import.summary.auto-linked"
|
msgid "dashboard.import.summary.auto-linked"
|
||||||
|
|||||||
@ -825,6 +825,10 @@ msgstr[1] "%s ficheros se han importado correctamente."
|
|||||||
msgid "dashboard.import.import-warning"
|
msgid "dashboard.import.import-warning"
|
||||||
msgstr "Algunos ficheros contenían objetos erroneos que no han sido importados."
|
msgstr "Algunos ficheros contenían objetos erroneos que no han sido importados."
|
||||||
|
|
||||||
|
#: src/app/main/ui/dashboard/import.cljs:704
|
||||||
|
msgid "dashboard.import.next-file"
|
||||||
|
msgstr "Siguiente archivo"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard.cljs:265
|
#: src/app/main/ui/dashboard.cljs:265
|
||||||
msgid "dashboard.import.no-perms"
|
msgid "dashboard.import.no-perms"
|
||||||
msgstr "No tienes permisos para importar en este equipo"
|
msgstr "No tienes permisos para importar en este equipo"
|
||||||
@ -867,9 +871,9 @@ msgstr ""
|
|||||||
msgid "dashboard.import.resolve-libraries-summary"
|
msgid "dashboard.import.resolve-libraries-summary"
|
||||||
msgstr "Revisa los vínculos de biblioteca antes de confirmar:"
|
msgstr "Revisa los vínculos de biblioteca antes de confirmar:"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/import.cljs:672
|
#: src/app/main/ui/dashboard/import.cljs:703
|
||||||
msgid "dashboard.import.review-links"
|
msgid "dashboard.import.review-links"
|
||||||
msgstr "Revisar vínculos"
|
msgstr "Revisar vínculos de biblioteca"
|
||||||
|
|
||||||
#, unused
|
#, unused
|
||||||
msgid "dashboard.import.summary.auto-linked"
|
msgid "dashboard.import.summary.auto-linked"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user