diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index c28e791a39..a59a7bf451 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -20,6 +20,7 @@ [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.controls.checkbox :refer [checkbox*]] [app.main.ui.ds.controls.select :refer [select*]] [app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]] [app.main.ui.ds.foundations.assets.raw-svg :as rsvg :refer [raw-svg*]] @@ -307,7 +308,7 @@ :typography t/body-medium} (:name entry) (when ^boolean is-shared? - [:> icon* {:icon-id i/library :class (stl/css :icon)}])] + [:> icon* {:icon-id i/library :class (stl/css :file-label-icon)}])] [:> context-notification* {:level level :appearance :ghost @@ -317,7 +318,7 @@ :typography t/body-medium} (:name entry) (when ^boolean is-shared? - [:> icon* {:icon-id i/library :class (stl/css :icon)}])]])]) + [:> icon* {:icon-id i/library :class (stl/css :file-label-icon)}])]])]) (when ^boolean (or editable? can-be-deleted) [:div {:class (stl/css :edit-entry-buttons)} @@ -392,7 +393,15 @@ (mf/defc library-resolution* {::mf/private true} [{:keys [unresolved-file selection on-select]}] - (let [candidates (:pending unresolved-file)] + (let [candidates (:pending unresolved-file) + disconnected* (mf/use-state #{}) + disconnected (deref disconnected*) + on-change-disconnected + (mf/use-fn + (fn [id] + (swap! disconnected* + (fn [s] + (if (contains? s id) (disj s id) (conj s id))))))] ;; Pre-select first candidate for each library (mf/with-effect [candidates] @@ -407,21 +416,53 @@ :typography t/body-large} "Some libraries couldn't be linked automatically. Select the correct library for each:"] - (for [{:keys [id name candidates]} candidates] - (let [options (mapv (fn [c] - {:id (str (:id c)) - :label (str (:name c) " (" (:project-name c) ")")}) - candidates) - selected (get selection id)] - [:div {:class (stl/css :library-resolution-item) - :key (dm/str id)} - [:div {:class (stl/css :library-resolution-item-name)} - name] - [:> select* {:options options - :class (stl/css :library-resolution-select) - :default-selected (or (some-> selected str) "") - :has-portal true - :on-change (partial on-select id)}]]))])) + + [:table {:class (stl/css :library-resolution-table)} + [:thead + [:tr {:class (stl/css :library-resolution-header)} + [:th {:class (stl/css :library-origin-name)} + [:> icon* {:icon-id i/library + :class (stl/css :library-resolution-icon) + :size "s"}] + "original library"] + [:th {:class (stl/css :library-resolution-arrow)}] + [:th {:class (stl/css :library-resolution-connection)} + [:> icon* {:icon-id i/library + :class (stl/css :library-resolution-icon) + :size "s"}] + "connect to"]]] + [:tbody {:class (stl/css :library-resolution-body)} + (for [{:keys [id name candidates]} candidates] + (let [options (mapv (fn [c] + {:id (str (:id c)) + :label (str (:name c) " (" (:project-name c) ")")}) + candidates) + selected (get selection id) + is-conected (not (contains? disconnected id))] + [:tr {:class (stl/css :library-resolution-item) + :key (dm/str id)} + [:td {:class (stl/css :library-resolution-item-name)} + [:> checkbox* {:id (dm/str id) + :label name + :checked is-conected + :on-change #(on-change-disconnected id)}]] + [:td {:class (stl/css :library-resolution-arrow)} + [:> icon* {:icon-id i/row + :size "m"}]] + [:td + (if is-conected + [:> select* {:options options + :class (stl/css :library-resolution-select) + :default-selected (or (some-> selected str) "") + :has-portal true + :on-change (partial on-select id)}] + + [:> text* {:class (stl/css :library-resolution-no-selection) + :as "span" + :typography t/body-small} + (let [selected-c (or (some #(when (= (str (:id %)) selected) %) candidates) + (first candidates))] + (dm/str (:name selected-c) " (" (:project-name selected-c) ")"))])]]))]]])) (mf/defc library-resolution-summary-file* {::mf/private true} @@ -430,19 +471,16 @@ pending (:pending resolution-file)] [:div {:class (stl/css :summary-file)} [:div {:class (stl/css :summary-file-header)} - [:> icon* {:icon-id i/library + [:> icon* {:icon-id i/document :class (stl/css :summary-file-icon) :size "s"}] - [:span {:class (stl/css :summary-file-name)} + [:> text* {:class (stl/css :summary-file-name) + :as "span" + :typography t/body-medium} (:name resolution-file)]] (when (seq done) [:div {:class (stl/css :summary-section)} - [:div {:class (stl/css :summary-section-header)} - [:> icon* {:icon-id i/status-tick - :class (stl/css :summary-section-icon) - :size "s"}] - [:span (tr "dashboard.import.summary.auto-linked")]] [:ul {:class (stl/css :summary-list)} (for [{:keys [name]} done] [:li {:class (stl/css :summary-list-item) @@ -457,11 +495,17 @@ (when (seq pending) [:div {:class (stl/css :summary-section)} [:div {:class (stl/css :summary-section-header)} - [:> icon* {:icon-id i/open-link - :class (stl/css :summary-section-icon) - :size "s"}] - [:span (tr "dashboard.import.summary.your-selection")]] + [:> text* {:as "span" + :typography t/headline-small} + "linked manually"]] [:ul {:class (stl/css :summary-list)} + [:li {:class (stl/css :summary-list-item) + :key "summary-list-header"} + [:span {:class (stl/css :summary-item-name-header)} + "Original"] + + [:span {:class (stl/css :summary-item-name-header)} + "New"]] (for [{:keys [id name] :as cand} pending] (let [selected-id (get selection id) selected-c (when selected-id @@ -469,6 +513,9 @@ [:li {:class (stl/css :summary-list-item) :key (dm/str id)} [:span {:class (stl/css :summary-item-name)} name] + [:> icon* {:icon-id i/row + :size "m" + :class (stl/css :summary-linked-arrow)}] (if selected-c [:span {:class (stl/css :summary-linked-info)} [:span {:class (stl/css :summary-linked-name)} @@ -835,50 +882,60 @@ (= :library-resolution status) [:* (when (seq visited) - [:input {:class (stl/css :cancel-button) - :type "button" - :value (tr "labels.previous") - :on-click on-wizard-prev}]) + [:> button* {:class (stl/css :cancel-button) + :variant "secondary" + :on-click on-wizard-prev} + (tr "labels.previous")]) ;; Label flips to "Review" when this is the last unvisited unresolved file. - [:input {:class (stl/css :accept-btn) - :type "button" - :value (if all-visited? - (tr "labels.next") - (tr "dashboard.import.review-links")) - :on-click on-wizard-next}]] + [:> button* {:class (stl/css :accept-btn) + :variant "primary" + :on-click on-wizard-next} + (if all-visited? + (tr "labels.next") + (tr "dashboard.import.review-links"))]] ;; Summary: Confirm / Back ;; Back pops the stack once and re-enters the wizard at the popped file. (= :library-summary status) [:* (when (seq visited) - [:input {:class (stl/css :cancel-button) - :type "button" - :value (tr "labels.back") - :on-click on-summary-back}]) - [:input {:class (stl/css :accept-btn) - :type "button" - :value (tr "dashboard.import.confirm-library-links") - :on-click on-confirm-library-links}]] + [:> button* {:class (stl/css :cancel-button) + :variant "secondary" + :on-click on-summary-back} + (tr "labels.back")]) + [:> button* {:class (stl/css :accept-btn) + :variant "primary" + :on-click on-confirm-library-links} + (tr "dashboard.import.confirm-library-links")]] (= :analyze status) - [:input {:class (stl/css :cancel-button) - :type "button" - :value (tr "labels.cancel") - :on-click on-cancel}] + [:> button* {:class (stl/css :cancel-button) + :variant "secondary" + :on-click on-cancel} + (tr "labels.cancel")] + + (= status :import-error) + [:> button* {:class (stl/css :accept-btn) + :variant "primary" + :on-click on-accept} + (tr "labels.accept")] + + (= status :import-success) + [:> button* {:class (stl/css :accept-btn) + :variant "primary" + :on-click on-accept} + (tr "labels.accept")] (= status :import-ready) - [:input {:class (stl/css :accept-btn) - :type "button" - :value (tr "labels.continue") - :disabled pending-analysis? - :on-click on-continue}] + [:> button* {:class (stl/css :accept-btn) + :variant "primary" + :on-click on-continue} + (tr "labels.continue")] (or (= :import-success status) (= :import-error status) (= :import-progress status)) - [:input {:class (stl/css :accept-btn) - :type "button" - :value (tr "labels.accept") - :disabled (= :import-progress status) - :on-click on-accept}])]]]])) + [:> button* {:class (stl/css :accept-btn) + :variant "primary" + :on-click on-accept} + (tr "labels.accept")])]]]])) diff --git a/frontend/src/app/main/ui/dashboard/import.scss b/frontend/src/app/main/ui/dashboard/import.scss index a0417a38f4..b90419fd0a 100644 --- a/frontend/src/app/main/ui/dashboard/import.scss +++ b/frontend/src/app/main/ui/dashboard/import.scss @@ -35,66 +35,10 @@ border: $b-2 solid var(--color-background-quaternary); min-block-size: $sz-192; inline-size: $sz-512; - max-block-size: calc(10 * px2rem(80)); + max-block-size: px2rem(800); } .modal-content { - display: flex; - flex-direction: column; - gap: var(--sp-l); - margin-block-end: var(--sp-xxl); -} - -.modal-title { - color: var(--color-foreground-primary); -} - -.modal-close-btn { - // %button-tertiary essentials - border-radius: $br-8; - background-color: transparent; - border: $b-2 solid transparent; - color: var(--button-tertiary-foreground-color-rest); - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; - position: absolute; - inset-block-start: var(--sp-s); - inset-inline-end: px2rem(6); - block-size: $sz-32; - inline-size: $sz-28; - - svg { - display: flex; - justify-content: center; - align-items: center; - block-size: $sz-16; - inline-size: $sz-16; - color: transparent; - fill: none; - stroke-width: 1px; - stroke: var(--button-tertiary-foreground-color-rest); - } - - &:hover { - background-color: var(--button-tertiary-background-color-hover); - color: var(--button-tertiary-foreground-color-hover); - border-color: var(--button-secondary-border-color-hover); - - svg { - stroke: var(--button-tertiary-foreground-color-hover); - } - } -} - -.modal-content { - // TODO: Typography does not match any existing DS typography token. - // body-small-typography: line-height 1.4 (DS body-small has line-height 1.3) - font-family: "worksans", "vazirmatn", sans-serif; - font-size: px2rem(12); - font-weight: 400; - line-height: 1.4; flex: 1; overflow: hidden auto; display: grid; @@ -104,13 +48,19 @@ min-block-size: px2rem(40); } +.modal-title { + color: var(--color-foreground-primary); +} + +.modal-close-btn { + position: absolute; + inset-block-start: var(--sp-s); + inset-inline-end: px2rem(6); +} + .status-message { - // TODO: Typography does not match any existing DS typography token. - // body-small-typography: line-height 1.4 (DS body-small has line-height 1.3) - font-family: "worksans", "vazirmatn", sans-serif; - font-size: px2rem(12); - font-weight: 400; - line-height: 1.4; + @include use-typography("body-small"); + color: var(--color-foreground-primary); font-style: italic; } @@ -121,105 +71,6 @@ gap: var(--sp-l); } -.cancel-button { - // %modal-cancel-btn essentials (these are elements) - border-radius: $br-8; - background-color: var(--button-secondary-background-color-rest); - border: $b-1 solid var(--button-secondary-border-color-rest); - color: var(--button-secondary-foreground-color-rest); - display: flex; - justify-content: center; - align-items: center; - padding: var(--sp-s) var(--sp-xxl); - block-size: $sz-32; - margin: 0; - cursor: pointer; - - // TODO: Typography does not match any existing DS typography token. - // uppercase-title-typography: 11px, weight 500, line-height 1.2, uppercase - font-family: "worksans", "vazirmatn", sans-serif; - font-size: px2rem(11); - font-weight: 500; - line-height: 1.2; - text-transform: uppercase; - - &:hover { - background-color: var(--button-secondary-background-color-hover); - border-color: var(--button-secondary-border-color-hover); - color: var(--button-secondary-foreground-color-hover); - } - - &:active { - background-color: var(--button-secondary-background-color-active); - border-color: var(--button-secondary-border-color-active); - color: var(--button-secondary-foreground-color-active); - } - - &[disabled], - &:disabled { - background-color: var(--button-background-color-disabled); - border-color: var(--button-border-color-disabled); - color: var(--button-foreground-color-disabled); - cursor: unset; - } -} - -.accept-btn { - // %modal-accept-btn essentials - border-radius: $br-8; - background-color: var(--button-primary-background-color-rest); - border: $b-1 solid var(--button-primary-border-color-rest); - color: var(--button-primary-foreground-color-rest); - display: flex; - justify-content: center; - align-items: center; - padding: var(--sp-s) var(--sp-xxl); - block-size: $sz-32; - margin: 0; - cursor: pointer; - - // TODO: Typography does not match any existing DS typography token. - // uppercase-title-typography: 11px, weight 500, line-height 1.2, uppercase - font-family: "worksans", "vazirmatn", sans-serif; - font-size: px2rem(11); - font-weight: 500; - line-height: 1.2; - text-transform: uppercase; - - &:hover { - background-color: var(--button-primary-background-color-hover); - border-color: var(--button-primary-border-color-hover); - color: var(--button-primary-foreground-color-hover); - } - - &:active { - background-color: var(--button-primary-background-color-active); - border-color: var(--button-primary-border-color-active); - color: var(--button-primary-foreground-color-active); - } - - &[disabled], - &:disabled { - background-color: var(--button-background-color-disabled); - border-color: var(--button-border-color-disabled); - color: var(--button-foreground-color-disabled); - cursor: unset; - } - - &.danger { - background-color: var(--modal-button-background-color-error); - border-color: var(--modal-button-background-color-error); - color: var(--modal-button-foreground-color-error); - } -} - -// TODO: Verify before removal. These selectors appear to be unused. -// .modal-scd-msg, -// .modal-subtitle, -// .modal-msg { -// ... -// } - .file-entry { --file-entry-fg-color: var(--color-foreground-secondary); @@ -227,57 +78,6 @@ flex-direction: column; gap: var(--sp-m); - .error-message, - .progress-message { - display: flex; - align-items: center; - min-block-size: $sz-32; - color: var(--file-entry-fg-color); - } - - .error-message { - align-items: flex-start; - white-space: pre-wrap; - overflow-wrap: anywhere; - } - - .linked-library { - display: flex; - align-items: center; - gap: var(--sp-m); - color: var(--file-entry-fg-color); - - .linked-library-tag { - display: flex; - justify-content: center; - align-items: center; - block-size: $sz-24; - inline-size: $sz-24; - - svg { - display: flex; - justify-content: center; - align-items: center; - block-size: $sz-16; - inline-size: $sz-16; - color: transparent; - fill: none; - stroke-width: 1px; - stroke: var(--file-entry-fg-color); - } - - &.error { - svg { - stroke: var(--element-foreground-error); - } - } - } - } - - &.warning { - --file-entry-fg-color: var(--color-accent-warning); - } - &.success { --file-entry-fg-color: var(--color-accent-sucess); } @@ -291,6 +91,53 @@ } } +.error-message, +.progress-message { + display: flex; + align-items: center; + min-block-size: $sz-32; + color: var(--file-entry-fg-color); +} + +.error-message { + align-items: flex-start; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.linked-library { + display: flex; + align-items: center; + gap: var(--sp-m); + color: var(--file-entry-fg-color); +} + +.linked-library-tag { + display: flex; + justify-content: center; + align-items: center; + block-size: $sz-24; + inline-size: $sz-24; + + svg { + display: flex; + justify-content: center; + align-items: center; + block-size: $sz-16; + inline-size: $sz-16; + color: transparent; + fill: none; + stroke-width: 1px; + stroke: var(--file-entry-fg-color); + } + + &.error { + svg { + stroke: var(--element-foreground-error); + } + } +} + .file-name { display: flex; align-items: center; @@ -298,24 +145,11 @@ color: var(--file-entry-fg-color); } - .edit-entry-buttons { - display: flex; - align-items: center; - gap: var(--sp-xs); - - // button { - // display: flex; - // justify-content: center; - // align-items: center; - // border-radius: $br-8; - // background-color: transparent; - // border: $b-2 solid transparent; - // inline-size: $sz-28; - // block-size: $sz-32; - // cursor: pointer; - // color: var(--button-tertiary-foreground-color-rest); - // } - } +.edit-entry-buttons { + display: flex; + align-items: center; + gap: var(--sp-xs); +} .file-name-edit { display: flex; @@ -329,24 +163,21 @@ background-color: var(--color-background-tertiary); } - .file-name-label { display: flex; align-items: center; gap: var(--sp-m); flex-grow: 1; - - .icon { - display: flex; - justify-content: center; - align-items: center; - block-size: $sz-16; - inline-size: $sz-16; - color: var(--file-entry-fg-color); - } } - +.file-label-icon { + display: flex; + justify-content: center; + align-items: center; + block-size: $sz-16; + inline-size: $sz-16; + color: var(--file-entry-fg-color); +} .file-name-input { @include use-typography("body-medium"); @@ -371,25 +202,6 @@ } } - -.file-icon, -.file-svg { - display: flex; - justify-content: center; - align-items: center; - block-size: $sz-24; - inline-size: $sz-16; - color: var(--file-entry-fg-color); -} - -.file-svg { - fill: var(--file-entry-fg-color); -} - -.context-notification-error { - --context-notification-bg-color: var(--color-background-primary); -} - .file-name-notification { flex-grow: 1; } @@ -419,6 +231,10 @@ overflow-wrap: anywhere; } +// ################################ +// LIBRARY RESOLUTION +// ################################ + .library-resolution { display: flex; flex-direction: column; @@ -430,50 +246,96 @@ margin-block-end: var(--sp-s); } +.library-resolution-header { + @include use-typography("body-medium"); + + color: var(--color-foreground-secondary); + display: grid; + grid-template-columns: 1fr 32px 1fr; + border-block-end: $b-1 solid var(--color-foreground-secondary); +} + +.library-origin-name, +.library-resolution-connection { + block-size: $sz-32; + text-align: start; + color: var(--color-foreground-primary); + display: flex; + align-items: center; +} + .library-resolution-item { + display: grid; + grid-template-columns: 1fr 32px 1fr; + block-size: $sz-32; + margin-block: var(--sp-s); +} + +.library-resolution-icon { + display: flex; + justify-content: center; + align-items: center; + margin-inline-end: var(--sp-s); + color: var(--color-foreground-secondary); +} + +.library-resolution-arrow { + color: var(--color-foreground-secondary); + display: flex; + justify-content: center; + align-items: center; + min-block-size: $sz-32; +} + +.library-resolution-body { display: flex; flex-direction: column; - gap: var(--sp-xs); - padding: var(--sp-s) 0; + gap: var(--sp-s); } .library-resolution-item-name { - // TODO: Typography does not match any existing DS typography token. - font-family: "worksans", "vazirmatn", sans-serif; - font-size: px2rem(12); - line-height: 1.4; - color: var(--color-foreground-primary); - font-weight: 700; -} + @include use-typography("body-medium"); -.library-resolution-summary-file { - margin-block-end: var(--sp-l); - padding-block-end: var(--sp-m); - border-bottom: $b-1 solid var(--color-foreground-secondary); -} - -.library-resolution-linked { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--sp-s); color: var(--color-foreground-secondary); - font-size: px2rem(12); + padding-inline-start: var(--sp-s); } -.library-resolution-project { +.library-resolution-no-selection { + display: flex; + align-items: center; color: var(--color-foreground-secondary); - font-size: px2rem(12); - margin-block-end: var(--sp-s); + padding: var(--sp-s); + margin-inline-start: var(--sp-xxs); + block-size: $sz-32; } -// Summary file card +// ################################ +// Summary file card +// ################################ + .summary-file { display: flex; flex-direction: column; gap: var(--sp-s); padding: var(--sp-m); - border: $b-1 solid var(--color-background-quaternary); border-radius: $br-8; background: var(--color-background-primary); } +.summary-list { + display: flex; + flex-direction: column; + gap: var(--sp-xxs); + list-style: none; + padding: 0; + margin: 0; + border-inline-start: $b-1 solid var(--color-background-quaternary); +} + .summary-file-header { display: flex; align-items: center; @@ -488,10 +350,7 @@ } .summary-file-name { - @include use-typography("body-medium"); - color: var(--color-foreground-primary); - font-weight: 700; } // Section within a file (auto-linked or user selection) @@ -509,20 +368,6 @@ padding: var(--sp-xs) 0; } -.summary-section-icon { - color: var(--color-foreground-secondary); - flex-shrink: 0; -} - -.summary-list { - display: flex; - flex-direction: column; - gap: var(--sp-xxs); - list-style: none; - padding: 0; - margin: 0; -} - .summary-list-item { display: flex; align-items: center; @@ -537,11 +382,6 @@ } .summary-item-name { - // TODO: Typography does not match any existing DS typography token. - font-family: "worksans", "vazirmatn", sans-serif; - font-size: px2rem(12); - font-weight: 400; - line-height: 1.4; color: var(--color-foreground-primary); flex: 1; min-inline-size: 0; @@ -552,6 +392,17 @@ white-space: nowrap; } +.summary-item-name-header { + color: var(--color-foreground-secondary); + flex: 1; + min-inline-size: 0; + display: block; + max-inline-size: 99%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + // Auto-linked badge .summary-linked-badge { display: inline-flex;