diff --git a/backend/src/app/rpc/commands/search.clj b/backend/src/app/rpc/commands/search.clj index 801ff555b0..3830731f78 100644 --- a/backend/src/app/rpc/commands/search.clj +++ b/backend/src/app/rpc/commands/search.clj @@ -19,7 +19,7 @@ inner join team_profile_rel as tpr on (tpr.team_id = p.team_id) where tpr.profile_id = ? and p.team_id = ? - and (p.deleted_at is null or p.deleted_at > now()) + and (p.deleted_at is null) and (tpr.is_admin = true or tpr.is_owner = true or tpr.can_edit = true) @@ -29,7 +29,7 @@ inner join project_profile_rel as ppr on (ppr.project_id = p.id) where ppr.profile_id = ? and p.team_id = ? - and (p.deleted_at is null or p.deleted_at > now()) + and (p.deleted_at is null) and (ppr.is_admin = true or ppr.is_owner = true or ppr.can_edit = true) @@ -47,7 +47,7 @@ left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn) inner join projects as pr on (f.project_id = pr.id) where f.name ilike ('%' || ? || '%') - and (f.deleted_at is null or f.deleted_at > now()) + and (f.deleted_at is null) order by f.created_at asc") (defn search-files diff --git a/frontend/playwright/ui/specs/dashboard-deleted.spec.js b/frontend/playwright/ui/specs/dashboard-deleted.spec.js index 6963eac0d5..bacf85ae75 100644 --- a/frontend/playwright/ui/specs/dashboard-deleted.spec.js +++ b/frontend/playwright/ui/specs/dashboard-deleted.spec.js @@ -20,12 +20,7 @@ test.describe("Dashboard Deleted Page", () => { // Navigate directly to deleted page await dashboardPage.goToDeleted(); - // Check for the restore all and clear trash buttons - await expect( - page.getByRole("button", { name: "Restore All" }), - ).toBeVisible(); - await expect( - page.getByRole("button", { name: "Clear trash" }), - ).toBeVisible(); + // Check for the delete-page-section element + await expect(page.getByTestId("deleted-page-section")).toBeVisible(); }); }); diff --git a/frontend/src/app/main/ui/dashboard/deleted.cljs b/frontend/src/app/main/ui/dashboard/deleted.cljs index 6116d8230b..7ad6f2f374 100644 --- a/frontend/src/app/main/ui/dashboard/deleted.cljs +++ b/frontend/src/app/main/ui/dashboard/deleted.cljs @@ -32,6 +32,27 @@ (def ^:private menu-icon (deprecated-icon/icon-xref :menu (stl/css :menu-icon))) +(defn- on-restore-project + [project] + (let [on-accept #(st/emit! (dd/restore-project-immediately project))] + (st/emit! (modal/show + {:type :confirm + :title (tr "dashboard.restore-project-confirmation.title") + :message (tr "dashboard.restore-project-confirmation.description" (:name project)) + :accept-style :primary + :accept-label (tr "labels.continue") + :on-accept on-accept})))) + +(defn- on-delete-project + [project] + (let [accept-fn #(st/emit! (dd/delete-project-immediately project))] + (st/emit! (modal/show + {:type :confirm + :title (tr "dashboard.delete-forever-confirmation.title") + :message (tr "dashboard.delete-project-forever-confirmation.description" (:name project)) + :accept-label (tr "dashboard.delete-forever-confirmation.title") + :on-accept accept-fn})))) + (mf/defc header* {::mf/props :obj ::mf/private true} @@ -40,7 +61,8 @@ [:div#dashboard-deleted-title {:class (stl/css :dashboard-title)} [:h1 (tr "dashboard.projects-title")]]]) -(mf/defc deleted-project-menu* +(mf/defc project-context-menu* + {::mf/private true} [{:keys [project show on-close top left]}] (let [top (d/nilv top 0) left (d/nilv left 0) @@ -48,25 +70,13 @@ on-restore-project (mf/use-fn (mf/deps project) - (fn [] - (let [on-accept #(st/emit! (dd/restore-project-immediately project))] - (st/emit! (modal/show {:type :confirm - :title (tr "dashboard.restore-project-confirmation.title") - :message (tr "dashboard.restore-project-confirmation.description" (:name project)) - :accept-style :primary - :accept-label (tr "labels.continue") - :on-accept on-accept}))))) + (partial on-restore-project project)) on-delete-project (mf/use-fn (mf/deps project) - (fn [] - (let [accept-fn #(st/emit! (dd/delete-project-immediately project))] - (st/emit! (modal/show {:type :confirm - :title (tr "dashboard.delete-forever-confirmation.title") - :message (tr "dashboard.delete-project-forever-confirmation.description" (:name project)) - :accept-label (tr "dashboard.delete-forever-confirmation.title") - :on-accept accept-fn}))))) + (partial on-delete-project project)) + options (mf/with-memo [on-restore-project on-delete-project] [{:name (tr "dashboard.restore-project-button") @@ -151,7 +161,7 @@ menu-icon]] (when (:menu-open @local) - [:> deleted-project-menu* + [:> project-context-menu* {:project project :show (:menu-open @local) :left (+ 24 (:x (:menu-pos @local))) @@ -174,8 +184,8 @@ :limit limit :selected-files selected-files}])]])) - (mf/defc menu* + {::mf/private true} [{:keys [team-id section]}] (let [on-recent-click (mf/use-fn @@ -222,7 +232,8 @@ (some #(= (:id project) (:project-id %)) (vals deleted-map))))) (sort-by :modified-at) - (reverse))) + (reverse) + (not-empty))) team-id (get team :id) @@ -273,37 +284,44 @@ [:* [:> header* {:team team}] - [:section {:class (stl/css :dashboard-container :no-bg)} + [:section {:class (stl/css :dashboard-container :no-bg) + :data-testid "deleted-page-section"} [:* [:div {:class (stl/css :no-bg)} [:> menu* {:team-id team-id :section :dashboard-deleted}] - [:div {:class (stl/css :deleted-info-content)} - [:p {:class (stl/css :deleted-info)} - (tr "dashboard.trash-info-text-part1") - [:span {:class (stl/css :info-text-highlight)} - (tr "dashboard.trash-info-text-part2" deletion-days)] - (tr "dashboard.trash-info-text-part3") - [:br] - (tr "dashboard.trash-info-text-part4")] - [:div {:class (stl/css :deleted-options)} - [:> button* {:variant "ghost" - :type "button" - :on-click on-restore-all} - (tr "dashboard.restore-all-deleted-button")] - [:> button* {:variant "destructive" - :type "button" - :icon "delete" - :on-click on-delete-all} - (tr "dashboard.clear-trash-button")]]] + (if (seq projects) + [:* + [:div {:class (stl/css :deleted-info-content)} + [:p {:class (stl/css :deleted-info)} + (tr "dashboard.trash-info-text-part1") + [:span {:class (stl/css :info-text-highlight)} + (tr "dashboard.trash-info-text-part2" deletion-days)] + (tr "dashboard.trash-info-text-part3") + [:br] + (tr "dashboard.trash-info-text-part4")] + [:div {:class (stl/css :deleted-options)} + [:> button* {:variant "ghost" + :type "button" + :on-click on-restore-all} + (tr "dashboard.restore-all-deleted-button")] + [:> button* {:variant "destructive" + :type "button" + :icon "delete" + :on-click on-delete-all} + (tr "dashboard.clear-trash-button")]]] - (when (seq projects) - (for [{:keys [id] :as project} projects] - (let [files (when deleted-map - (->> (vals deleted-map) - (filterv #(= id (:project-id %))) - (sort-by :modified-at #(compare %2 %1))))] - [:> deleted-project-item* {:project project - :files files - :key id}])))]]]])) + (for [{:keys [id] :as project} projects] + (let [files (when deleted-map + (->> (vals deleted-map) + (filterv #(= id (:project-id %))) + (sort-by :modified-at #(compare %2 %1))))] + [:> deleted-project-item* {:project project + :files files + :key id}]))] + + ;; when no deleted projects + [:div {:class (stl/css :deleted-info-content)} + [:p {:class (stl/css :deleted-info)} + (tr "dashboard.deleted.empty-state-description")]])]]]])) diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index b08a42118e..85b3dc2ac8 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.dashboard.file-menu (:require + [app.common.data :as d] [app.main.data.common :as dcm] [app.main.data.dashboard :as dd] [app.main.data.event :as-alias ev] @@ -89,12 +90,12 @@ on-duplicate (fn [_] (apply st/emit! (map dd/duplicate-file files)) - (st/emit! (ntf/success (tr "dashboard.success-duplicate-file" (i18n/c (count files)))))) + (st/emit! (ntf/success (tr "dashboard.success-duplicate-file" (i18n/c file-count))))) on-delete-accept (fn [_] (apply st/emit! (map dd/delete-file files)) - (st/emit! (ntf/success (tr "dashboard.success-delete-file" (i18n/c (count files)))) + (st/emit! (ntf/success (tr "dashboard.success-delete-file" (i18n/c file-count))) (dd/clear-selected-files))) on-delete @@ -193,7 +194,7 @@ (fn [_] (st/emit! (dd/restore-files-immediately (with-meta {:team-id (:id current-team) - :ids #{(:id file)}} + :ids (into #{} d/xf:map-id files)} {:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file))) (dd/fetch-projects (:id current-team)) (dd/fetch-deleted-files (:id current-team))) @@ -201,6 +202,7 @@ on-restore-immediately (fn [] + (prn files) (st/emit! (modal/show {:type :confirm :title (tr "dashboard-restore-file-confirmation.title") @@ -213,7 +215,7 @@ (fn [] (let [accept-fn #(st/emit! (dd/delete-files-immediately {:team-id (:id current-team) - :ids #{(:id file)}}))] + :ids (into #{} d/xf:map-id files)}))] (st/emit! (modal/show {:type :confirm :title (tr "dashboard.delete-forever-confirmation.title") @@ -260,14 +262,12 @@ options (if can-restore - [(when can-restore - {:name (tr "dashboard.restore-file-button") - :id "restore-file" - :handler on-restore-immediately}) - (when can-restore - {:name (tr "dashboard.delete-file-button") - :id "delete-file" - :handler on-delete-immediately})] + [{:name (tr "dashboard.file-menu.restore-files-option" (i18n/c file-count)) + :id "restore-file" + :handler on-restore-immediately} + {:name (tr "dashboard.file-menu.delete-files-permanently-option" (i18n/c file-count)) + :id "delete-file" + :handler on-delete-immediately}] (if multi? [(when can-edit {:name (tr "dashboard.duplicate-multi" file-count) diff --git a/frontend/src/app/main/ui/dashboard/projects.scss b/frontend/src/app/main/ui/dashboard/projects.scss index 173a487f8b..7df6a0f9c9 100644 --- a/frontend/src/app/main/ui/dashboard/projects.scss +++ b/frontend/src/app/main/ui/dashboard/projects.scss @@ -4,35 +4,36 @@ // // Copyright (c) KALEIDOS INC -@use "common/refactor/common-refactor.scss" as deprecated; @use "common/refactor/common-dashboard"; -@use "../ds/typography.scss" as t; -@use "../ds/_borders.scss" as *; -@use "../ds/spacing.scss" as *; -@use "../ds/_sizes.scss" as *; -@use "../ds/z-index.scss" as *; +@use "ds/typography.scss" as t; +@use "ds/_borders.scss" as *; +@use "ds/spacing.scss" as *; +@use "ds/_sizes.scss" as *; +@use "ds/z-index.scss" as *; +@use "ds/mixins.scss" as *; +@use "ds/_utils.scss" as *; .dashboard-container { flex: 1 0 0; - width: 100%; + inline-size: 100%; margin-inline-end: var(--sp-l); - border-top: $b-1 solid var(--panel-border-color); + border-block-start: $b-1 solid var(--panel-border-color); overflow-y: auto; - padding-bottom: var(--sp-xxxl); + padding-block-end: var(--sp-xxxl); } .dashboard-projects { user-select: none; - height: calc(100vh - deprecated.$s-64); + block-size: calc(100vh - px2rem(64)); } .with-team-hero { - height: calc(100vh - deprecated.$s-280); + block-size: calc(100vh - px2rem(280)); } .dashboard-shared { - width: calc(100vw - deprecated.$s-320); - margin-inline-end: deprecated.$s-52; + inline-size: calc(100vw - px2rem(320)); + margin-inline-end: px2rem(52); } .search { @@ -66,8 +67,8 @@ align-items: center; justify-content: space-between; gap: var(--sp-s); - width: 99%; - max-height: $sz-40; + inline-size: 99%; + max-block-size: $sz-40; padding: var(--sp-s) var(--sp-s) var(--sp-s) var(--sp-l); margin-block-start: var(--sp-l); border-radius: $br-4; @@ -77,19 +78,19 @@ display: flex; align-items: center; justify-content: flex-start; - width: 100%; - min-height: var(--sp-xxxl); + inline-size: 100%; + min-block-size: $sz-32; margin-inline-start: var(--sp-s); } .project-name { + @include textEllipsis; @include t.use-typography("body-large"); - width: fit-content; - margin-inline-end: var(--sp-m); - line-height: 0.8; color: var(--title-foreground-color-hover); cursor: pointer; - height: var(--sp-l); + block-size: $sz-16; + line-height: 0.8; + margin-inline-end: var(--sp-m); } .info-wrapper { @@ -116,8 +117,8 @@ .add-file-btn, .options-btn { @extend .button-tertiary; - height: var(--sp-xxxl); - width: var(--sp-xxxl); + block-size: $sz-32; + inline-size: $sz-32; margin: 0 var(--sp-s); padding: var(--sp-s); } @@ -129,7 +130,7 @@ } .grid-container { - width: 100%; + inline-size: 100%; padding: 0 var(--sp-xs); } @@ -139,11 +140,13 @@ .show-more { --show-more-color: var(--button-secondary-foreground-color-rest); - @include deprecated.buttonStyle; @include t.use-typography("body-medium"); + border: none; + background: none; + cursor: pointer; position: absolute; - top: var(--sp-s); - right: deprecated.$s-52; + inset-block-start: var(--sp-s); + inset-inline-end: px2rem(52); display: flex; align-items: center; justify-content: space-between; @@ -156,8 +159,8 @@ } .show-more-icon { - height: var(--sp-l); - width: var(--sp-l); + block-size: $sz-16; + inline-size: $sz-16; fill: none; stroke: var(--show-more-color); } @@ -165,7 +168,7 @@ // Team hero .team-hero { background-color: var(--color-background-tertiary); - border-radius: deprecated.$br-8; + border-radius: $br-8; border: none; display: flex; margin: var(--sp-l); @@ -174,12 +177,11 @@ img { border-radius: $br-4; - height: var(--sp-xl) 0; - width: auto; + inline-size: auto; @media (max-width: 1200px) { display: none; - width: 0; + inline-size: 0; } } } @@ -193,9 +195,8 @@ } .title { - font-size: $sz-24; color: var(--color-foreground-primary); - font-weight: deprecated.$fw400; + font-size: px2rem(24); } .info { @@ -215,8 +216,8 @@ --close-icon-foreground-color: var(--icon-foreground); position: absolute; top: var(--sp-xl); - right: var(--sp-xxl); - width: var(--sp-xxl); + inset-inline-end: var(--sp-xxl); + inline-size: var(--sp-xxl); background-color: transparent; border: none; cursor: pointer; @@ -231,20 +232,20 @@ } .invite { - height: var(--sp-xxxl); - width: deprecated.$s-180; + block-size: $sz-32; + inline-size: px2rem(180); } .img-wrapper { display: flex; align-items: center; justify-content: center; - width: var(--sp-xl) 0; - height: var(--sp-xl) 0; + inline-size: var(--sp-xl) 0; + block-size: var(--sp-xl) 0; overflow: hidden; - border-radius: deprecated.$br-4; + border-radius: $br-4; @media (max-width: 1200px) { display: none; - width: 0; + inline-size: 0; } } diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs index 05ae7242cd..d45afcd9c2 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.tokens.management.forms.controls.input (:require [app.common.data :as d] + [app.common.files.tokens :as cft] [app.common.types.tokens-lib :as ctob] [app.main.data.style-dictionary :as sd] [app.main.data.workspace.tokens.format :as dwtf] @@ -292,7 +293,7 @@ (mf/spread-props props {:hint-formated true}) props)] - (mf/with-effect [resolve-stream tokens token input-name] + (mf/with-effect [resolve-stream tokens token input-name name] (let [subs (->> resolve-stream (rx/debounce 300) (rx/mapcat (partial resolve-value tokens token)) @@ -317,11 +318,21 @@ (reset! hint* {:message error' :type "error"})) :else - (let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value)) - input-value (get-in @form [:data :value input-name] "")] + (let [input-value (get-in @form [:data :value input-name] "") + resolved-value (if (= name :line-height) + (when-let [{:keys [unit value]} (cft/parse-token-value input-value)] + (let [font-size (get-in @form [:data :value :font-size] "") + calculated (case unit + "%" (/ (d/parse-double value) 100) + "px" (/ (d/parse-double value) (d/parse-double font-size)) + nil value + nil)] + (dwtf/format-token-value calculated))) + (dwtf/format-token-value value)) + message (tr "workspace.tokens.resolved-value" (or resolved-value value))] (swap! form update :errors dissoc :value) (swap! form update :extra-errors dissoc :value) - (if (= input-value (str value)) + (if (= input-value (str resolved-value)) (reset! hint* {}) (reset! hint* {:message message :type "hint"})))))))] (fn [] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/typography.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/typography.cljs index f8a488765d..2800578287 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/typography.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/typography.cljs @@ -95,9 +95,9 @@ line-height-sub-token (mf/with-memo [token] (if-let [value (get token :value)] - {:type :number + {:type :dimensions :value (get value :line-height)} - {:type :number})) + {:type :dimensions})) text-case-sub-token (mf/with-memo [token] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index bea451ad87..5e6b11976d 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -8717,3 +8717,14 @@ msgstr "Autosaved versions will be kept for %s days." #, unused msgid "workspace.viewport.click-to-close-path" msgstr "Click to close the path" + +msgid "dashboard.file-menu.restore-files-option" +msgstr[0] "Restore file" +msgstr[1] "Restore files" + +msgid "dashboard.file-menu.delete-files-permanently-option" +msgstr[0] "Delete file" +msgstr[1] "Delete files" + +msgid "dashboard.deleted.empty-state-description" +msgstr "Your trash is empty. Deleted files and projects will appear here." diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 176787e4bd..926f6a1f1b 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -8564,3 +8564,14 @@ msgstr "Los autoguardados duran %s días." #, unused msgid "workspace.viewport.click-to-close-path" msgstr "Pulsar para cerrar la ruta" + +msgid "dashboard.file-menu.restore-files-option" +msgstr[0] "Restaurar archivo" +msgstr[1] "Restaurar archivos" + +msgid "dashboard.file-menu.delete-files-permanently-option" +msgstr[0] "Eliminar archivo" +msgstr[1] "Eliminar archivos" + +msgid "dashboard.deleted.empty-state-description" +msgstr "Tu papelera está vacía. Los archivos y proyectos eliminados aparecerán aquí."