diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index 69ea4da793..c5aff2510b 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -390,25 +390,49 @@ (mf/defc library-resolution* {::mf/private true} [{:keys [unresolved-file selection on-select on-disconnect]}] - (let [candidates (:pending unresolved-file) + (let [file-id (:id unresolved-file) + candidates (:pending unresolved-file) + 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 (mf/use-fn - (mf/deps selection candidates on-select on-disconnect) + (mf/deps file-selection candidates on-select-file on-disconnect-file) (fn [id] - (if (contains? selection id) - (on-disconnect id) + (if (contains? file-selection id) + (on-disconnect-file id) (let [{:keys [candidates]} (d/seek #(= id (:id %)) candidates)] (when-let [first-c (first candidates)] - (on-select id (str (:id first-c))))))))] + (on-select-file id (str (:id first-c))))))))] - ;; Pre-select first candidate for each library - (mf/with-effect [candidates] + ;; Default each pending library to another file's choice for it, if any, else the first candidate. + (mf/with-effect [candidates file-id] (doseq [{:keys [id candidates]} candidates] - (when-not (contains? selection id) - (when-let [first-c (first candidates)] - (on-select id (str (:id first-c))))))) + (when-not (contains? file-selection id) + (let [other-choice (some #(get % id) (vals selection)) + 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-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) :as "p" :typography t/body-large} @@ -435,8 +459,8 @@ {:id (str (:id c)) :label (str (:name c) " (" (:project-name c) ")")}) candidates) - selected (get selection id) - is-conected (contains? selection id)] + selected (get file-selection id) + is-conected (contains? file-selection id)] [:tr {:class (stl/css :library-resolution-item) :key (dm/str id)} [:td {:class (stl/css :library-resolution-item-name)} @@ -453,7 +477,7 @@ :class (stl/css :library-resolution-select) :default-selected (or (some-> selected str) "") :has-portal true - :on-change (partial on-select id)}] + :on-change (partial on-select-file id)}] [:> text* {:class (stl/css :library-resolution-no-selection) :as "span" @@ -465,8 +489,9 @@ (mf/defc library-resolution-summary-file* {::mf/private true} [{:keys [resolution-file selection]}] - (let [done (:done resolution-file) - pending (:pending resolution-file)] + (let [done (:done 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-header)} [:> icon* {:icon-id i/document @@ -486,6 +511,9 @@ :key (dm/str name)} [:span {:class (stl/css :summary-item-name)} name] [:span {:class (stl/css :summary-linked-badge)} + [:> icon* {:icon-id i/status-tick + :class (stl/css :summary-badge-icon) + :size "s"}] (tr "dashboard.import.summary.linked")]])]]) (when (seq pending) @@ -504,7 +532,7 @@ [:span {:class (stl/css :summary-item-name-header)} (tr "dashboard.import.summary.new")]] (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 (d/seek #(= (str (:id %)) (str selected-id)) (:candidates cand)))] [:li {:class (stl/css :summary-list-item) @@ -647,7 +675,7 @@ (mf/defc import-library-resolution-stage* {::mf/private true} [{:keys [current-unresolved-file selection on-select on-disconnect - visited all-visited? + visited last-file? on-wizard-prev on-wizard-next on-wizard-skip]}] [:* [:div {:class (stl/css :modal-content)} @@ -673,9 +701,9 @@ [:> button* {:class (stl/css :accept-btn) :variant "primary" :on-click on-wizard-next} - (if all-visited? - (tr "labels.next") - (tr "dashboard.import.connect-selected-libraries"))]]]]]) + (if last-file? + (tr "dashboard.import.connect-selected-libraries") + (tr "dashboard.import.next-file"))]]]]]) (mf/defc import-library-summary-stage* {::mf/private true} @@ -724,7 +752,7 @@ resolution* (mf/use-state nil) resolution (not-empty (deref resolution*)) - ;; User selection for multi-match candidates: {old-lib-id candidate-id} + ;; Per-file selection for multi-match candidates: {file-id {old-lib-id candidate-id}} selection* (mf/use-state {}) selection (deref selection*) @@ -745,16 +773,19 @@ [] 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-unresolved-file (mf/with-memo [unresolved-files visited] (d/seek #(not (contains? visited (:id %))) unresolved-files)) + ;; True once every other unresolved file has already been visited. + 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 (mf/use-fn (mf/deps entries) @@ -837,7 +868,7 @@ (->> (rx/from (:pending resolution-file)) (rx/merge-map (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))))))) (rx/subs! (constantly nil) (constantly nil) @@ -855,12 +886,11 @@ on-wizard-prev (mf/use-fn - (mf/deps current-unresolved-file) + (mf/deps visited) (fn [] - ;; Remove the current file from visited; it becomes current again after re-render, - ;; because it's no longer in visited. - (let [file-id (:id current-unresolved-file)] - (swap! visited* disj file-id)))) + ;; Disj makes the last-visited file current again on next render. + (let [last-id (last visited)] + (swap! visited* disj last-id)))) on-wizard-skip (mf/use-fn @@ -868,7 +898,7 @@ (fn [] (let [file-id (:id current-unresolved-file) pending-ids (mapv :id (:pending current-unresolved-file))] - (swap! selection* #(apply dissoc % pending-ids)) + (swap! selection* update file-id (fn [file-sel] (apply dissoc file-sel pending-ids))) (swap! visited* conj file-id)))) on-summary-back @@ -913,14 +943,13 @@ manage-on-select (mf/use-fn - (mf/deps selection) - (fn [old-lib-id candidate-id] - (swap! selection* assoc old-lib-id candidate-id))) + (fn [file-id old-lib-id candidate-id] + (swap! selection* assoc-in [file-id old-lib-id] candidate-id))) manage-on-disconnect (mf/use-fn - (fn [old-lib-id] - (swap! selection* dissoc old-lib-id)))] + (fn [file-id old-lib-id] + (swap! selection* update file-id (fnil dissoc {}) old-lib-id)))] (mf/with-effect [visited unresolved-files] (when (and (seq unresolved-files) @@ -991,7 +1020,7 @@ :on-select manage-on-select :on-disconnect manage-on-disconnect :visited visited - :all-visited? all-visited? + :last-file? last-unresolved-file? :on-wizard-prev on-wizard-prev :on-wizard-next on-wizard-next :on-wizard-skip on-wizard-skip}] diff --git a/frontend/src/app/main/ui/dashboard/import.scss b/frontend/src/app/main/ui/dashboard/import.scss index 35889b8389..f6765642b7 100644 --- a/frontend/src/app/main/ui/dashboard/import.scss +++ b/frontend/src/app/main/ui/dashboard/import.scss @@ -267,6 +267,21 @@ 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 { @include use-typography("body-small"); diff --git a/frontend/translations/en.po b/frontend/translations/en.po index e30569db24..378bb3aed2 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -831,6 +831,10 @@ msgstr[1] "%s files have been imported successfully." msgid "dashboard.import.import-warning" 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 msgid "dashboard.import.no-perms" msgstr "You don’t have permission to import to this team" @@ -885,6 +889,10 @@ msgstr "original library" msgid "dashboard.import.connect-selected-libraries" msgstr "Connect selected libraries" +#: src/app/main/ui/dashboard/import.cljs:703 +msgid "dashboard.import.review-links" +msgstr "Review library links" + #, unused msgid "dashboard.import.summary.auto-linked" msgstr "Auto-linked" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 6863ca36cd..4c0764aa07 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -829,6 +829,10 @@ msgstr[1] "%s ficheros se han importado correctamente." msgid "dashboard.import.import-warning" 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 msgid "dashboard.import.no-perms" msgstr "No tienes permisos para importar en este equipo" @@ -883,6 +887,10 @@ msgstr "biblioteca original" msgid "dashboard.import.connect-selected-libraries" msgstr "Conectar bibliotecas seleccionadas" +#: src/app/main/ui/dashboard/import.cljs:703 +msgid "dashboard.import.review-links" +msgstr "Revisar vínculos de biblioteca" + #, unused msgid "dashboard.import.summary.auto-linked" msgstr "Vinculadas automáticamente"