Add list view toggle for dashboard files (#10692)

*  Add list view toggle for dashboard files

*  Add drop files visual feedback

* ♻️ Use radio buttons component from DS

* ♻️ Use hook to keep layout status

* ♻️ Refactor code and SCSS

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
This commit is contained in:
Juan de la Cruz 2026-07-21 13:16:10 +02:00 committed by GitHub
parent 0ff73ae711
commit fa996ab240
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 759 additions and 533 deletions

View File

@ -17,6 +17,7 @@
[app.main.store :as st]
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
[app.main.ui.dashboard.grid :refer [grid*]]
[app.main.ui.dashboard.layout-toggle :as lt :refer [layout-toggle*]]
[app.main.ui.dashboard.subscription :refer [get-subscription-type]]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
@ -57,10 +58,13 @@
(mf/defc header*
{::mf/private true}
[]
[:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"}
[{:keys [layout on-change]}]
[:header {:class (stl/css :dashboard-header)
:data-testid "dashboard-header"}
[:div#dashboard-deleted-title {:class (stl/css :dashboard-title)}
[:h1 (tr "dashboard.projects-title")]]])
[:h1 (tr "dashboard.projects-title")]]
[:div {:class (stl/css :dashboard-header-actions)}
[:> layout-toggle* {:layout layout :on-change on-change}]]])
(mf/defc project-context-menu*
{::mf/private true}
@ -87,18 +91,17 @@
:id "project-delete"
:handler on-delete-project}])]
[:> context-menu*
{:on-close on-close
:show show
:fixed (or (not= top 0) (not= left 0))
:min-width true
:top top
:left left
:options options}]))
[:> context-menu* {:on-close on-close
:show show
:fixed (or (not= top 0) (not= left 0))
:min-width true
:top top
:left left
:options options}]))
(mf/defc deleted-project-item*
{::mf/private true}
[{:keys [project files]}]
[{:keys [project files layout]}]
(let [project-files (filterv #(= (:project-id %) (:id project)) files)
empty? (empty? project-files)
@ -176,14 +179,14 @@
:type 1
:subtitle (tr "dashboard.empty-placeholder-files-subtitle")}]
[:> grid*
{:project project
:files project-files
:origin :deleted
:can-edit false
:can-restore true
:limit limit
:selected-files selected-files}])]]))
[:> grid* {:project project
:files project-files
:origin :deleted
:can-edit false
:can-restore true
:limit limit
:layout layout
:selected-files selected-files}])]]))
(mf/defc menu*
{::mf/private true}
@ -217,7 +220,15 @@
(mf/defc deleted-section*
[{:keys [team projects]}]
(let [deleted-map
(let [layout* (hooks/use-persisted-state lt/layout-key lt/default-layout)
layout (deref layout*)
on-layout-change
(mf/use-fn
(fn [value]
(reset! layout* (keyword value))))
deleted-map
(mf/deref ref:deleted-files)
projects
@ -241,7 +252,7 @@
;; Calculate deletion days based on team subscription
deletion-days
(let [profile (mf/deref refs/profile)
(let [profile (mf/deref refs/profile)
subscription-type (get-subscription-type (:subscription team))
nitrate-type (get-in profile [:subscription :type])
nitrate-active? (dnt/is-valid-license? profile)]
@ -286,13 +297,16 @@
(dd/clear-selected-files)))
[:*
[:> header* {:team team}]
[:> header* {:team team
:layout layout
:on-change on-layout-change}]
[: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}]
[:> menu* {:team-id team-id
:section :dashboard-deleted}]
(if (seq projects)
[:*
@ -322,6 +336,7 @@
(sort-by :modified-at #(compare %2 %1))))]
[:> deleted-project-item* {:project project
:files files
:layout layout
:key id}]))]
;; when no deleted projects

View File

@ -16,6 +16,7 @@
[app.main.store :as st]
[app.main.ui.dashboard.grid :refer [grid*]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.layout-toggle :as lt :refer [layout-toggle*]]
[app.main.ui.dashboard.pin-button :refer [pin-button*]]
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
@ -32,7 +33,7 @@
(mf/defc header*
{::mf/private true}
[{:keys [project create-fn can-edit]}]
[{:keys [project create-fn can-edit layout on-change]}]
(let [project-id (:id project)
local
@ -73,7 +74,8 @@
(dd/clear-selected-files))))]
[:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"}
[:header {:class (stl/css :dashboard-header)
:data-testid "dashboard-header"}
(if (:is-default project)
[:div#dashboard-drafts-title {:class (stl/css :dashboard-title)}
[:h1 (tr "labels.drafts")]]
@ -95,6 +97,9 @@
(:name project)]]))
[:div {:class (stl/css :dashboard-header-actions)}
[:> layout-toggle* {:layout layout
:on-change on-change}]
(when ^boolean can-edit
[:a {:class (stl/css :btn-secondary :btn-small :new-file)
:tab-index "0"
@ -155,6 +160,14 @@
selected-files (mf/deref refs/selected-files)
layout* (hooks/use-persisted-state lt/layout-key lt/default-layout)
layout (deref layout*)
on-layout-change
(mf/use-fn
(fn [value]
(reset! layout* (keyword value))))
on-file-created
(mf/use-fn
(fn [file-data]
@ -188,7 +201,9 @@
[:> header* {:team team
:can-edit can-edit?
:project project
:create-fn create-file}]
:create-fn create-file
:layout layout
:on-change on-layout-change}]
[:section {:class (stl/css :dashboard-container :no-bg)
:ref rowref}
(if empty-state-viewer
@ -206,5 +221,5 @@
:can-edit can-edit?
:origin :files
:create-fn create-file
:limit limit}])]]))
:limit limit
:layout layout}])]]))

View File

@ -31,9 +31,9 @@
[app.main.ui.dashboard.import :refer [use-import-file]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.placeholder :refer [empty-grid-placeholder* loading-placeholder*]]
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.main.ui.ds.product.loader :refer [loader*]]
[app.main.ui.hooks :as h]
[app.main.ui.icons :as deprecated-icon]
[app.main.worker :as mw]
[app.util.color :as uc]
[app.util.dom :as dom]
@ -108,8 +108,8 @@
:message (ex-message cause)))))]
(partial rx/dispose! subscription))))
[:div {:class (stl/css-case :grid-item-th true
:deleted-item can-restore)
[:div {:class (stl/css-case :grid-item-thumbnail true
:is-deleted can-restore)
:style {:background-color bg-color}
:ref container}
(when visible?
@ -127,9 +127,6 @@
;; --- Grid Item Library
(def ^:private menu-icon
(deprecated-icon/icon-xref :menu (stl/css :menu-icon)))
(mf/defc grid-item-library*
[{:keys [file can-restore]}]
(mf/with-effect [file]
@ -137,113 +134,115 @@
(let [font-ids (map :font-id (get-in file [:library-summary :typographies :sample] []))]
(run! fonts/ensure-loaded! font-ids))))
[:div {:class (stl/css-case :grid-item-th true
:library true
:deleted-item can-restore)}
[:div {:class (stl/css-case :library-thumbnail true
:is-deleted can-restore)}
(if (nil? file)
[:> loader* {:class (stl/css :grid-loader)
:overlay true
:title (tr "labels.loading")}]
(let [summary (:library-summary file)
components (:components summary)
colors (:colors summary)
(let [summary (:library-summary file)
components (:components summary)
colors (:colors summary)
typographies (:typographies summary)]
[:*
(when (and (zero? (:count components)) (zero? (:count colors)) (zero? (:count typographies)))
[:*
[:div {:class (stl/css :asset-section)}
[:div {:class (stl/css :asset-title)}
[:div {:class (stl/css :library-asset-section)}
[:div {:class (stl/css :library-asset-title)}
[:span (tr "workspace.assets.components")]
[:span {:class (stl/css :num-assets)} (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space
[:div {:class (stl/css :asset-section)}
[:div {:class (stl/css :asset-title)}
[:span {:class (stl/css :library-num-assets)} (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space
[:div {:class (stl/css :library-asset-section)}
[:div {:class (stl/css :library-asset-title)}
[:span (tr "workspace.assets.colors")]
[:span {:class (stl/css :num-assets)} (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space
[:div {:class (stl/css :asset-section)}
[:div {:class (stl/css :asset-title)}
[:span {:class (stl/css :library-num-assets)} (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space
[:div {:class (stl/css :library-asset-section)}
[:div {:class (stl/css :library-asset-title)}
[:span (tr "workspace.assets.typography")]
[:span {:class (stl/css :num-assets)} (str "\u00A0(") 0 ")"]]]]) ;; Unicode 00A0 is non-breaking space
[:span {:class (stl/css :library-num-assets)} (str "\u00A0(") 0 ")"]]]]) ;; Unicode 00A0 is non-breaking space
(when (pos? (:count components))
[:div {:class (stl/css :asset-section)}
[:div {:class (stl/css :asset-title)}
[:div {:class (stl/css :library-asset-section)}
[:div {:class (stl/css :library-asset-title)}
[:span (tr "workspace.assets.components")]
[:span {:class (stl/css :num-assets)} (str "\u00A0(") (:count components) ")"]] ;; Unicode 00A0 is non-breaking space
[:div {:class (stl/css :asset-list)}
[:span {:class (stl/css :library-num-assets)} (str "\u00A0(") (:count components) ")"]] ;; Unicode 00A0 is non-breaking space
[:div {:class (stl/css :library-asset-list)}
(for [component (:sample components)]
(let [root-id (:main-instance-id component)]
[:div {:class (stl/css :asset-list-item)
[:div {:class (stl/css :library-asset-item)
:key (str "assets-component-" (:id component))}
[:& render/component-svg {:root-shape (get-in component [:objects root-id])
[:& render/component-svg {:class (stl/css :library-asset-icon)
:root-shape (get-in component [:objects root-id])
:objects (:objects component)}] ;; Components in the summary come loaded with objects, even in v2
[:div {:class (stl/css :name-block)}
[:span {:class (stl/css :item-name)
[:div {:class (stl/css :library-name-block)}
[:span {:class (stl/css :library-item-name)
:title (:name component)}
(:name component)]]]))
(when (> (:count components) (count (:sample components)))
[:div {:class (stl/css :asset-list-item)}
[:div {:class (stl/css :name-block)}
[:span {:class (stl/css :item-name)} "(...)"]]])]])
[:div {:class (stl/css :library-asset-item)}
[:div {:class (stl/css :library-name-block)}
[:span {:class (stl/css :library-item-name)} "(...)"]]])]])
(when (pos? (:count colors))
[:div {:class (stl/css :asset-section)}
[:div {:class (stl/css :asset-title)}
[:div {:class (stl/css :library-asset-section)}
[:div {:class (stl/css :library-asset-title)}
[:span (tr "workspace.assets.colors")]
[:span {:class (stl/css :num-assets)} (str "\u00A0(") (:count colors) ")"]] ;; Unicode 00A0 is non-breaking space
[:div {:class (stl/css :asset-list)}
[:span {:class (stl/css :library-num-assets)} (str "\u00A0(") (:count colors) ")"]] ;; Unicode 00A0 is non-breaking space
[:div {:class (stl/css :library-asset-list)}
(for [color (:sample colors)]
(let [default-name (cond
(:gradient color) (uc/gradient-type->string (get-in color [:gradient :type]))
(:color color) (:color color)
:else (:value color))]
[:div {:class (stl/css :asset-list-item :color-item)
[:div {:class (stl/css :library-asset-item :library-color-item)
:key (str "assets-color-" (:id color))}
[:> bc/color-bullet* {:color {:color (:color color)
:id (:id color)
:opacity (:opacity color)}
:mini true}]
[:div {:class (stl/css :name-block)}
[:span {:class (stl/css :color-name)} (:name color)]
[:div {:class (stl/css :library-name-block)}
[:span {:class (stl/css :library-color-name)} (:name color)]
(when-not (= (:name color) default-name)
[:span {:class (stl/css :color-value)} (:color color)])]]))
[:span {:class (stl/css :library-color-value)} (:color color)])]]))
(when (> (:count colors) (count (:sample colors)))
[:div {:class (stl/css :asset-list-item)}
[:div {:class (stl/css :name-block)}
[:span {:class (stl/css :item-name)} "(...)"]]])]])
[:div {:class (stl/css :library-asset-item)}
[:div {:class (stl/css :library-name-block)}
[:span {:class (stl/css :library-item-name)} "(...)"]]])]])
(when (pos? (:count typographies))
[:div {:class (stl/css :asset-section)}
[:div {:class (stl/css :asset-title)}
[:div {:class (stl/css :library-asset-section)}
[:div {:class (stl/css :library-asset-title)}
[:span (tr "workspace.assets.typography")]
[:span {:class (stl/css :num-assets)} (str "\u00A0(") (:count typographies) ")"]] ;; Unicode 00A0 is non-breaking space
[:div {:class (stl/css :asset-list)}
[:span {:class (stl/css :library-num-assets)} (str "\u00A0(") (:count typographies) ")"]] ;; Unicode 00A0 is non-breaking space
[:div {:class (stl/css :library-asset-list)}
(for [typography (:sample typographies)]
[:div {:class (stl/css :asset-list-item)
[:div {:class (stl/css :library-asset-item)
:key (str "assets-typography-" (:id typography))}
[:div {:class (stl/css :typography-sample)
[:div {:class (stl/css :library-typography-sample)
:style {:font-family (:font-family typography)
:font-weight (:font-weight typography)
:font-style (:font-style typography)}}
(tr "workspace.assets.typography.sample")]
[:div {:class (stl/css :name-block)}
[:span {:class (stl/css :item-name)
[:div {:class (stl/css :library-name-block)}
[:span {:class (stl/css :library-item-name)
:title (:name typography)}
(:name typography)]]])
(when (> (:count typographies) (count (:sample typographies)))
[:div {:class (stl/css :asset-list-item)}
[:div {:class (stl/css :name-block)}
[:span {:class (stl/css :item-name)} "(...)"]]])]])]))])
[:div {:class (stl/css :library-asset-item)}
[:div {:class (stl/css :library-name-block)}
[:span {:class (stl/css :library-item-name)} "(...)"]]])]])]))])
;; --- Grid Item
(mf/defc grid-item-metadata*
[{:keys [file]}]
{::mf/private true}
[{:keys [file layout]}]
(let [time (ct/timeago (or (:will-be-deleted-at file)
(:modified-at file)))]
[:span {:class (stl/css :date)
[:span {:class (stl/css-case :grid-item-date (= layout :grid)
:list-item-date (= layout :list))
:title (tr "dashboard.deleted.will-be-deleted-at" time)}
time]))
@ -255,31 +254,32 @@
counter-el))
(mf/defc grid-item*
[{:keys [file origin can-edit selected-files can-restore]}]
(let [file-id (get file :id)
state (mf/deref refs/dashboard-local)
{::mf/private true}
[{:keys [file origin can-edit selected-files can-restore layout]}]
(let [node-ref (mf/use-ref)
menu-ref (mf/use-ref)
menu-pos
(get state :menu-pos)
state (mf/deref refs/dashboard-local)
menu-open?
(and (get state :menu-open)
(= file-id (:file-id state)))
file-id (get file :id)
selected?
(contains? selected-files file-id)
menu-pos (get state :menu-pos)
menu-open? (and (get state :menu-open)
(= file-id (:file-id state)))
selected-num
(count selected-files)
selected? (contains? selected-files file-id)
selected-num (count selected-files)
node-ref (mf/use-ref)
menu-ref (mf/use-ref)
list? (= layout :list)
is-library-view?
(= origin :libraries)
editing? (and (= file-id (:file-id state))
(:edition state))
library-view? (= origin :libraries)
on-menu-close
(mf/use-fn #(st/emit! (dd/hide-file-menu)))
(mf/use-fn
#(st/emit! (dd/hide-file-menu)))
on-select
(mf/use-fn
@ -335,29 +335,32 @@
on-menu-click
(mf/use-fn
(mf/deps file selected?)
(mf/deps file selected? menu-open?)
(fn [event]
(dom/stop-propagation event)
(when-not selected?
(when-not (kbd/shift? event)
(st/emit! (dd/clear-selected-files)))
(if menu-open?
(st/emit! (dd/hide-file-menu))
(do
(st/emit! (dd/toggle-file-select file))))
(when-not selected?
(when-not (kbd/shift? event)
(st/emit! (dd/clear-selected-files)))
(st/emit! (dd/toggle-file-select file)))
(let [client-position
(dom/get-client-position event)
(let [client-position
(dom/get-client-position event)
position
(if (and (nil? (:y client-position)) (nil? (:x client-position)))
(let [target-element (dom/get-target event)
points (dom/get-bounding-rect target-element)
y (:top points)
x (:left points)]
(gpt/point x y))
client-position)]
position
(if (and (nil? (:y client-position)) (nil? (:x client-position)))
(let [target-element (dom/get-target event)
points (dom/get-bounding-rect target-element)
y (:top points)
x (:left points)]
(gpt/point x y))
client-position)]
(st/emit! (dd/show-file-menu-with-position file-id position)))))
(st/emit! (dd/show-file-menu-with-position file-id position)))))))
on-context-menu
(mf/use-fn
@ -390,7 +393,10 @@
(when (kbd/enter? event)
(on-navigate event))
(when (kbd/shift? event)
(when (or (kbd/down-arrow? event) (kbd/left-arrow? event) (kbd/up-arrow? event) (kbd/right-arrow? event))
(when (or (kbd/down-arrow? event)
(kbd/left-arrow? event)
(kbd/up-arrow? event)
(kbd/right-arrow? event))
;; TODO Fix this
(on-select event)))))
@ -401,73 +407,117 @@
(when (kbd/enter? event)
(dom/stop-propagation event)
(dom/prevent-default event)
(on-menu-click event))))]
(on-menu-click event))))
[:li {:class (stl/css-case :grid-item true
:project-th true
:library is-library-view?)}
[:div
{:class (stl/css-case :selected selected?
:library is-library-view?)
:ref node-ref
:role "button"
:title (:name file)
:aria-label (:name file)
:draggable (dm/str can-edit)
:on-click on-select
:on-key-down on-key-down
:on-double-click on-navigate
:on-drag-start on-drag-start
:on-context-menu on-context-menu}
;; The options menu is identical in both layouts, so we build it once
;; and place it where each layout needs it. NOTE: hiccup bound in a
;; let is not compiled by rumext, so it must be wrapped in mf/html.
menu-element
(mf/html
[:div {:class (stl/css-case :project-thumbnail-actions true
:is-force-display menu-open?)}
[:div {:class (stl/css :project-thumbnail-icon :menu)
:tab-index "0"
:role "button"
:aria-label (tr "dashboard.options")
:ref menu-ref
:id (dm/str file-id "-action-menu")
:on-click on-menu-click
:on-key-down on-menu-key-down}
[:div {:class (stl/css :overlay)}]
[:> icon* {:icon-id i/menu
:class (stl/css :menu-icon)}]
(if ^boolean is-library-view?
[:> grid-item-library* {:file file :can-restore can-restore}]
[:> grid-item-thumbnail* {:file file :can-edit can-edit :can-restore can-restore}])
(when (and selected? menu-open?)
;; When the menu is open we disable events in the dashboard. We need to force pointer events
;; so the menu can be handled
[:> portal-on-document* {}
[:> file-menu* {:files (vals selected-files)
:left (+ 24 (:x menu-pos))
:top (:y menu-pos)
:can-edit can-edit
:navigate true
:on-edit on-edit
:on-close on-menu-close
:origin origin
:parent-id (dm/str file-id "-action-menu")
:can-restore can-restore}]])]])]
(when (and (:is-shared file) (not is-library-view?))
[:div {:class (stl/css :item-badge)} deprecated-icon/library])
(if ^boolean list?
[:li {:class (stl/css-case :grid-item true
:list-item true
:library-item library-view?)}
[:div
{:class (stl/css-case :list-item-row true
:is-selected selected?)
:ref node-ref
:role "button"
:title (:name file)
:aria-label (:name file)
:draggable (dm/str can-edit)
:on-click on-select
:on-key-down on-key-down
:on-double-click on-navigate
:on-drag-start on-drag-start
:on-context-menu on-context-menu}
[:div {:class (stl/css :info-wrapper)}
[:div {:class (stl/css :item-info)}
(if (and (= file-id (:file-id state)) (:edition state))
(if ^boolean editing?
[:& inline-edition {:content (:name file)
:on-end edit
:max-length 250}]
[:h3 (:name file)])
[:> grid-item-metadata* {:file file}]]
[:h3 {:class (stl/css :list-item-name)} (:name file)])
[:div {:class (stl/css-case :project-th-actions true :force-display menu-open?)}
[:div
{:class (stl/css :project-th-icon :menu)
:tab-index "0"
:role "button"
:aria-label (tr "dashboard.options")
:ref menu-ref
:id (dm/str file-id "-action-menu")
:on-click on-menu-click
:on-key-down on-menu-key-down}
(when (and (:is-shared file) (not library-view?))
[:span {:class (stl/css :list-item-badge)
:aria-label (tr "workspace.assets.shared-library")
:title (tr "workspace.assets.shared-library")}
[:> icon* {:icon-id i/library}]])
menu-icon
(when (and selected? menu-open?)
;; When the menu is open we disable events in the dashboard. We need to force pointer events
;; so the menu can be handled
[:> portal-on-document* {}
[:> file-menu* {:files (vals selected-files)
:left (+ 24 (:x menu-pos))
:top (:y menu-pos)
:can-edit can-edit
:navigate true
:on-edit on-edit
:on-close on-menu-close
:origin origin
:parent-id (dm/str file-id "-action-menu")
:can-restore can-restore}]])]]]]]))
[:> grid-item-metadata* {:file file :layout :list}]
menu-element]]
[:li {:class (stl/css-case :grid-item true
:project-thumbnail true
:library-item library-view?)}
[:div {:class (stl/css-case :is-selected selected?)
:ref node-ref
:role "button"
:title (:name file)
:aria-label (:name file)
:draggable (dm/str can-edit)
:on-click on-select
:on-key-down on-key-down
:on-double-click on-navigate
:on-drag-start on-drag-start
:on-context-menu on-context-menu}
(if ^boolean library-view?
[:> grid-item-library* {:file file
:can-restore can-restore}]
[:> grid-item-thumbnail* {:file file
:can-edit can-edit
:can-restore can-restore}])
(when (and (:is-shared file) (not library-view?))
[:div {:class (stl/css :grid-item-badge)}
[:> icon* {:icon-id i/library}]])
[:div {:class (stl/css :grid-item-info)}
[:div {:class (stl/css :grid-item-meta)}
(if ^boolean editing?
[:& inline-edition {:content (:name file)
:on-end edit
:max-length 250}]
[:h3 {:class (stl/css :grid-item-title)} (:name file)])
[:> grid-item-metadata* {:file file :layout :grid}]]
menu-element]]])))
(mf/defc grid*
[{:keys [files project origin limit create-fn can-edit selected-files can-restore]}]
[{:keys [files project origin limit create-fn can-edit selected-files can-restore layout]}]
(let [dragging? (mf/use-state false)
list? (= layout :list)
project-id (get project :id)
team-id (get project :team-id)
@ -534,6 +584,19 @@
(nil? files)
[:> loading-placeholder*]
(and (seq files) list?)
[:ul {:class (stl/css :grid-row :list-view)}
(when @dragging?
[:li {:class (stl/css :list-item-dragged)}])
(for [item files]
[:> grid-item* {:file item
:key (dm/str (:id item))
:origin origin
:selected-files selected-files
:can-edit can-edit
:can-restore can-restore
:layout :list}])]
(seq files)
(for [[index slice] (d/enumerate (partition-all limit files))]
@ -541,45 +604,57 @@
(when @dragging?
[:li {:class (stl/css :grid-item)}])
(for [item slice]
[:> grid-item*
{:file item
:key (dm/str (:id item))
:origin origin
:selected-files selected-files
:can-edit can-edit
:can-restore can-restore}])])
[:> grid-item* {:file item
:key (dm/str (:id item))
:origin origin
:selected-files selected-files
:can-edit can-edit
:can-restore can-restore}])])
:else
[:> empty-grid-placeholder*
{:limit limit
:can-edit can-edit
:create-fn create-fn
:origin origin
:project-id project-id
:team-id team-id
:on-finish-import on-finish-import}])]))
[:> empty-grid-placeholder* {:limit limit
:can-edit can-edit
:create-fn create-fn
:origin origin
:project-id project-id
:team-id team-id
:on-finish-import on-finish-import}])]))
(mf/defc line-grid-row
[{:keys [files selected-files dragging? limit can-edit can-restore] :as props}]
(mf/defc line-grid-row*
{::mf/private true}
[{:keys [files selected-files is-dragging limit can-edit can-restore layout]}]
(let [elements limit
limit (if dragging? (dec limit) limit)]
[:ul {:class (stl/css :grid-row :no-wrap)
:style {:grid-template-columns (dm/str "repeat(" elements ", 1fr)")}}
limit (if is-dragging (dec limit) limit)
list? (= layout :list)]
(if ^boolean list?
[:ul {:class (stl/css :grid-row :list-view)}
(when is-dragging
[:li {:class (stl/css :list-item-dragged)}])
(for [item (take limit files)]
[:> grid-item* {:id (:id item)
:file item
:selected-files selected-files
:can-edit can-edit
:key (dm/str (:id item))
:can-restore can-restore
:layout :list}])]
(when dragging?
[:li {:class (stl/css :grid-item :dragged)}])
[:ul {:class (stl/css :grid-row :no-wrap)
:style {:grid-template-columns (dm/str "repeat(" elements ", 1fr)")}}
(for [item (take limit files)]
[:> grid-item*
{:id (:id item)
:file item
:selected-files selected-files
:can-edit can-edit
:key (dm/str (:id item))
:can-restore can-restore}])]))
(when is-dragging
[:li {:class (stl/css :grid-item :is-dragged)}])
(mf/defc line-grid
[{:keys [project team files limit create-fn can-edit can-restore] :as props}]
(for [item (take limit files)]
[:> grid-item* {:id (:id item)
:file item
:selected-files selected-files
:can-edit can-edit
:key (dm/str (:id item))
:can-restore can-restore}])])))
(mf/defc line-grid*
[{:keys [project team files limit create-fn can-edit can-restore layout]}]
(let [dragging? (mf/use-state false)
project-id (:id project)
team-id (:id team)
@ -672,20 +747,20 @@
[:> loading-placeholder*]
(seq files)
[:& line-grid-row {:files files
:team-id team-id
:selected-files selected-files
:dragging? @dragging?
:can-edit can-edit
:limit limit
:can-restore can-restore}]
[:> line-grid-row* {:files files
:team-id team-id
:selected-files selected-files
:is-dragging @dragging?
:can-edit can-edit
:limit limit
:can-restore can-restore
:layout layout}]
:else
[:> empty-grid-placeholder*
{:is-dragging @dragging?
:limit limit
:can-edit can-edit
:create-fn create-fn
:project-id project-id
:team-id team-id
:on-finish-import on-finish-import}])]))
[:> empty-grid-placeholder* {:is-dragging @dragging?
:limit limit
:can-edit can-edit
:create-fn create-fn
:project-id project-id
:team-id team-id
:on-finish-import on-finish-import}])]))

View File

@ -4,247 +4,214 @@
//
// Copyright (c) KALEIDOS INC Sucursal en España SL
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_utils.scss" as *;
@use "ds/spacing.scss" as *;
@use "ds/typography.scss" as t;
@use "ds/z-index.scss" as *;
// VARIABLES
// TODO: Legacy sass variables. We should remove them in favor of DS tokens.
$bp-max-1366: "(max-width: 1366px)";
$thumbnail-default-width: deprecated.$s-252; // Default width
$thumbnail-default-height: deprecated.$s-168; // Default width
$thumbnail-default-width: $sz-252;
$thumbnail-default-height: px2rem(168);
// DASHBOARD GRID
.dashboard-grid {
font-size: deprecated.$fs-14;
height: 100%;
@include t.use-typography("body-medium");
block-size: 100%;
overflow: hidden auto;
padding: 0 var(--sp-l) deprecated.$s-16;
padding: 0 var(--sp-l) var(--sp-l);
}
// GRID ROW
.grid-row {
display: grid;
grid-auto-flow: column;
grid-auto-columns: calc(deprecated.$s-12 + var(--th-width, #{$thumbnail-default-width}));
width: 100%;
gap: deprecated.$s-24;
grid-auto-columns: calc(var(--sp-m) + var(--thumbnail-width, #{$thumbnail-default-width}));
inline-size: 100%;
gap: var(--sp-xxl);
&.list-view {
display: flex;
flex-direction: column;
grid-auto-flow: initial;
grid-auto-columns: initial;
gap: var(--sp-s);
padding: var(--sp-s) 0;
inline-size: 100%;
}
}
// CARD VIEW: GRID ITEM
.grid-item {
align-items: center;
cursor: pointer;
display: flex;
flex-direction: column;
margin: deprecated.$s-12 0;
margin: var(--sp-m) 0;
position: relative;
text-align: center;
a,
button {
width: 100%;
font-weight: deprecated.$fw400;
}
button {
background-color: transparent;
border: none;
padding: 0 deprecated.$s-6;
}
.grid-item-th {
border-radius: deprecated.$br-8;
&.is-dragged {
border-radius: $br-4;
outline: $br-4 solid var(--color-accent-primary);
text-align: initial;
width: var(--th-width, #{$thumbnail-default-width});
height: var(--th-height, #{$thumbnail-default-height});
background-size: cover;
overflow: hidden;
img {
object-fit: contain;
}
}
&.dragged {
border-radius: deprecated.$br-4;
outline: deprecated.$br-4 solid var(--color-accent-primary);
text-align: initial;
width: calc(var(--th-width) + deprecated.$s-12);
height: var(--th-height, #{$thumbnail-default-height});
}
&.overlay {
border-radius: deprecated.$br-4;
border: deprecated.$s-2 solid var(--color-accent-tertiary);
height: 100%;
opacity: 0;
pointer-events: none;
position: absolute;
width: 100%;
z-index: deprecated.$z-index-1;
}
&:hover .overlay {
display: block;
opacity: 1;
}
.info-wrapper {
display: grid;
grid-template-columns: 1fr auto;
cursor: pointer;
max-width: var(--th-width, $thumbnail-default-width);
}
.item-info {
display: grid;
padding: deprecated.$s-8;
text-align: left;
width: 100%;
font-size: deprecated.$fs-12;
h3 {
border: deprecated.$s-1 solid transparent;
color: var(--color-foreground-primary);
font-size: deprecated.$fs-16;
font-weight: deprecated.$fw400;
height: deprecated.$s-28;
line-height: 1.92;
max-width: deprecated.$s-260;
overflow: hidden;
padding: 0;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
@media #{$bp-max-1366} {
max-width: deprecated.$s-232;
}
}
.date {
color: var(--color-foreground-secondary);
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
white-space: nowrap;
max-width: deprecated.$s-260;
&::first-letter {
text-transform: capitalize;
}
@media #{$bp-max-1366} {
max-width: deprecated.$s-232;
}
}
}
.item-badge {
background-color: var(--color-accent-primary);
border: none;
border-radius: deprecated.$br-6;
position: absolute;
top: deprecated.$s-12;
right: deprecated.$s-12;
height: deprecated.$s-32;
width: deprecated.$s-32;
display: flex;
align-items: center;
justify-content: center;
svg {
stroke: var(--color-background-secondary);
fill: none;
height: deprecated.$s-16;
width: deprecated.$s-16;
}
}
&.add-file {
border: deprecated.$s-1 dashed var(--color-foreground-secondary);
justify-content: center;
box-shadow: none;
span {
color: var(--color-background-primary);
font-size: deprecated.$fs-14;
}
&:hover {
background-color: var(--color-foreground-primary);
border: deprecated.$s-2 solid var(--color-accent-tertiary);
}
inline-size: calc(var(--thumbnail-width) + var(--sp-m));
block-size: var(--thumbnail-height, #{$thumbnail-default-height});
}
}
.drag-counter {
position: absolute;
top: deprecated.$s-4;
left: deprecated.$s-4;
width: deprecated.$s-32;
height: deprecated.$s-32;
background-color: var(--color-accent-tertiary);
border-radius: deprecated.$br-circle;
.grid-item-info {
display: grid;
grid-template-columns: 1fr auto;
max-inline-size: var(--thumbnail-width, #{$thumbnail-default-width});
}
.grid-item-meta {
@include t.use-typography("body-small");
display: grid;
padding: var(--sp-s);
text-align: left;
inline-size: 100%;
}
.grid-item-title {
@include t.use-typography("body-large");
border: $b-1 solid transparent;
color: var(--color-foreground-primary);
line-height: 1.92;
block-size: $sz-28;
max-inline-size: px2rem(260);
overflow: hidden;
padding: 0;
text-overflow: ellipsis;
white-space: nowrap;
inline-size: 100%;
@media #{$bp-max-1366} {
max-inline-size: px2rem(232);
}
}
.grid-item-date {
@include t.use-typography("body-small");
color: var(--color-foreground-secondary);
overflow: hidden;
text-overflow: ellipsis;
inline-size: 100%;
white-space: nowrap;
max-inline-size: px2rem(260);
&::first-letter {
text-transform: capitalize;
}
@media #{$bp-max-1366} {
max-inline-size: px2rem(232);
}
}
.grid-item-badge {
color: var(--color-background-secondary);
font-size: deprecated.$fs-16;
background-color: var(--color-accent-primary);
border: none;
border-radius: $br-6;
position: absolute;
inset-block-start: var(--sp-m);
inset-inline-end: var(--sp-m);
block-size: $sz-32;
inline-size: $sz-32;
display: flex;
justify-content: center;
align-items: center;
justify-content: center;
}
// PROJECTS, ELEMENTS & ICONS GRID
.project-th {
.grid-item-thumbnail {
border-radius: $br-8;
block-size: var(--thumbnail-height, #{$thumbnail-default-height});
inline-size: var(--thumbnail-width, #{$thumbnail-default-width});
overflow: hidden;
position: relative;
text-align: initial;
background-size: cover;
&.is-deleted {
opacity: 0.5;
}
}
.grid-item-thumbnail-image {
object-fit: contain;
block-size: auto;
inline-size: 100%;
}
// CARD VIEW: PROJECT THUMBNAIL
.project-thumbnail {
background-color: transparent;
border-radius: deprecated.$br-8;
padding-top: deprecated.$s-6;
border-radius: $br-8;
padding-block-start: $sz-6;
&.library-item {
block-size: px2rem(612);
}
&:hover,
&:focus,
&:focus-within {
background-color: var(--color-background-tertiary);
.project-th-actions {
.project-thumbnail-actions {
opacity: 1;
}
a {
text-decoration: none;
}
}
.selected {
.grid-item-th {
outline: deprecated.$s-4 solid var(--color-accent-tertiary);
}
.is-selected .grid-item-thumbnail {
outline: px2rem(4) solid var(--color-accent-tertiary);
}
}
.project-th-actions {
.project-thumbnail-actions {
align-items: center;
display: flex;
height: 100%;
block-size: 100%;
justify-content: center;
opacity: 0;
right: deprecated.$s-6;
width: deprecated.$s-32;
inset-inline-end: $sz-6;
inline-size: $sz-32;
span {
color: var(--color-background-secondary);
&.is-force-display {
opacity: 1;
}
}
.project-th-icon {
.project-thumbnail-icon {
align-items: center;
display: flex;
margin-right: deprecated.$s-8;
margin-top: 0;
margin-inline-end: var(--sp-s);
margin-block-start: 0;
}
// CARD VIEW: MENU
.menu {
align-items: flex-end;
display: flex;
flex-direction: column;
height: deprecated.$s-32;
block-size: $sz-32;
justify-content: center;
margin-right: 0;
margin-top: deprecated.$s-20;
width: 100%;
margin-inline-end: 0;
margin-block-start: var(--sp-xl);
inline-size: 100%;
--menu-icon-color: var(--button-tertiary-foreground-color-rest);
@ -255,130 +222,219 @@ $thumbnail-default-height: deprecated.$s-168; // Default width
}
.menu-icon {
stroke: var(--menu-icon-color);
fill: none;
margin-right: 0;
height: deprecated.$s-16;
width: deprecated.$s-16;
color: var(--menu-icon-color);
margin-inline-end: 0;
block-size: $sz-16;
inline-size: $sz-16;
}
.project-th-actions.force-display {
opacity: 1;
}
// LIST VIEW
.grid-item-th {
border-radius: deprecated.$br-4;
cursor: pointer;
height: 100%;
overflow: hidden;
position: relative;
width: 100%;
display: flex;
justify-content: center;
.list-item {
align-items: stretch;
flex-direction: row;
margin: 0;
text-align: initial;
inline-size: 100%;
.img-th {
height: auto;
width: 100%;
}
}
// LIBRARY VIEW
.library {
height: deprecated.$s-580;
}
.grid-item.project-th.library {
height: deprecated.$s-612;
}
.grid-item-th.library {
background-color: var(--color-background-tertiary);
flex-direction: column;
height: 90%;
justify-content: flex-start;
max-height: deprecated.$s-580;
padding: deprecated.$s-32;
.asset-section {
font-size: deprecated.$fs-12;
color: var(--color-foreground-secondary);
&:not(:first-child) {
margin-top: deprecated.$s-16;
}
// Reuse the grid options menu, but neutralize the card-oriented offsets so
// it sits inline at the end of the row.
.project-thumbnail-actions {
flex: 0 0 auto;
block-size: auto;
opacity: 1;
inline-size: auto;
}
.asset-title {
display: flex;
font-size: deprecated.$fs-12;
text-transform: uppercase;
.num-assets {
color: var(--color-foreground-secondary);
}
.project-thumbnail-icon {
margin: 0;
}
.asset-list-item {
.menu {
align-items: center;
border-radius: deprecated.$br-4;
border: deprecated.$s-1 solid transparent;
color: var(--color-foreground-primary);
display: flex;
font-size: deprecated.$fs-12;
margin-top: deprecated.$s-4;
padding: deprecated.$s-2;
position: relative;
.name-block {
color: var(--color-foreground-secondary);
width: calc(100% - deprecated.$s-24 - deprecated.$s-8);
}
.item-name {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
svg {
background-color: var(--color-canvas);
border-radius: deprecated.$br-4;
border: deprecated.$s-2 solid transparent;
height: deprecated.$s-24;
margin-right: deprecated.$s-8;
width: deprecated.$s-24;
}
.color-name {
color: var(--color-foreground-primary);
}
.color-value {
color: var(--color-foreground-secondary);
margin-left: deprecated.$s-4;
text-transform: uppercase;
}
.typography-sample {
height: deprecated.$s-20;
margin-right: deprecated.$s-4;
width: deprecated.$s-20;
}
flex-direction: row;
block-size: $sz-32;
margin: 0;
inline-size: auto;
}
}
.color-item {
.list-item-dragged {
border-radius: $br-8;
block-size: $sz-48;
outline: $br-4 solid var(--color-accent-primary);
outline-offset: -$br-4;
inline-size: 100%;
}
.list-item-row {
align-items: center;
border-radius: $br-8;
border: $b-1 solid var(--color-background-quaternary);
display: flex;
gap: var(--sp-m);
padding: var(--sp-s) var(--sp-m);
inline-size: 100%;
&:hover,
&:focus,
&:focus-within {
background-color: var(--color-background-tertiary);
}
&.is-selected {
background-color: var(--color-background-tertiary);
outline: $b-2 solid var(--color-accent-tertiary);
}
}
.list-item-name {
@include t.use-typography("body-medium");
color: var(--color-foreground-primary);
flex: 0 1 auto;
margin: 0;
min-inline-size: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.list-item-badge {
align-items: center;
color: var(--color-background-secondary);
background-color: var(--color-accent-primary);
border-radius: $br-6;
display: flex;
flex: 0 0 auto;
justify-content: center;
block-size: $sz-24;
inline-size: $sz-24;
}
.list-item-date {
@include t.use-typography("body-small");
color: var(--color-foreground-secondary);
flex: 0 0 auto;
margin-inline-start: auto;
white-space: nowrap;
&::first-letter {
text-transform: capitalize;
}
}
// LIBRARY
.library-thumbnail {
border-radius: $br-4;
position: relative;
overflow: hidden;
background-color: var(--color-background-tertiary);
display: flex;
flex-direction: column;
block-size: 90%;
justify-content: flex-start;
max-block-size: px2rem(580);
padding: var(--sp-xxxl);
}
.library-num-assets {
color: var(--color-foreground-secondary);
}
.library-name-block {
color: var(--color-foreground-secondary);
inline-size: calc(100% - var(--sp-xxl) - var(--sp-s));
}
.library-item-name {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.library-asset-section {
@include t.use-typography("body-small");
color: var(--color-foreground-secondary);
&:not(:first-child) {
margin-block-start: var(--sp-l);
}
}
.library-asset-item {
@include t.use-typography("body-small");
display: flex;
align-items: center;
border-radius: $br-4;
border: $b-1 solid transparent;
color: var(--color-foreground-primary);
margin-block-start: var(--sp-xs);
padding: var(--sp-xxs);
position: relative;
}
.library-asset-title {
@include t.use-typography("body-small");
display: flex;
text-transform: uppercase;
}
.library-asset-icon {
background-color: var(--color-canvas);
border-radius: $br-4;
border: $b-2 solid transparent;
margin-inline-end: var(--sp-s);
block-size: $sz-24;
inline-size: $sz-24;
}
.library-color-name {
color: var(--color-foreground-primary);
}
.library-color-value {
color: var(--color-foreground-secondary);
margin-inline-start: var(--sp-xs);
text-transform: uppercase;
}
.library-color-item {
display: grid;
grid-template-columns: auto 1fr;
gap: deprecated.$s-8;
gap: var(--sp-s);
}
.library-typography-sample {
block-size: px2rem(20);
margin-inline-end: var(--sp-xs);
inline-size: px2rem(20);
}
// MISC
.drag-counter {
@include t.use-typography("body-large");
position: absolute;
inset-block-start: var(--sp-xs);
inset-inline-start: var(--sp-xs);
inline-size: $sz-32;
block-size: $sz-32;
background-color: var(--color-accent-tertiary);
border-radius: $br-circle;
color: var(--color-background-secondary);
display: flex;
justify-content: center;
align-items: center;
}
.grid-loader {
--icon-width: calc(var(--th-width, #{$thumbnail-default-width}) * 0.25);
}
.deleted-item {
opacity: 0.5;
--icon-width: calc(var(--thumbnail-width, #{$thumbnail-default-width}) * 0.25);
}

View File

@ -0,0 +1,32 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns app.main.ui.dashboard.layout-toggle
"Reactive, persisted preference for how dashboard files are laid out
(as a grid of thumbnails or as a compact list). The preference is shared
between the team (projects) view and the project (files) view."
(:require
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(def layout-key ::dashboard-layout)
(def default-layout :grid)
(mf/defc layout-toggle*
[{:keys [layout on-change]}]
[:> radio-buttons* {:selected (name layout)
:on-change on-change
:name "dashboard-files-layout"
:options [{:id "dashboard-files-layout-list"
:value "list"
:icon i/view-as-list
:label (tr "dashboard.files-layout.list")}
{:id "dashboard-files-layout-grid"
:value "grid"
:icon i/view-as-icons
:label (tr "dashboard.files-layout.grid")}]}])

View File

@ -103,7 +103,7 @@
[:ul
{:class (stl/css :grid-row :no-wrap)
:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}}
[:li {:class (stl/css :grid-item :grid-empty-placeholder :dragged)}]]
[:li {:class (stl/css :grid-item :grid-empty-placeholder :is-dragged)}]]
(= :libraries origin)
[:> empty-placeholder*

View File

@ -49,8 +49,8 @@
cursor: pointer;
margin: deprecated.$s-8;
border: deprecated.$s-2 solid transparent;
width: var(--th-width, #{g.$thumbnail-default-width});
height: var(--th-height, #{g.$thumbnail-default-height});
width: var(--thumbnail-width, #{g.$thumbnail-default-width});
height: var(--thumbnail-height, #{g.$thumbnail-default-height});
svg {
width: deprecated.$s-32;

View File

@ -19,8 +19,9 @@
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.dashboard.deleted :as deleted]
[app.main.ui.dashboard.grid :refer [line-grid]]
[app.main.ui.dashboard.grid :refer [line-grid*]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.layout-toggle :as lt :refer [layout-toggle*]]
[app.main.ui.dashboard.pin-button :refer [pin-button*]]
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
[app.main.ui.ds.buttons.button :refer [button*]]
@ -50,16 +51,20 @@
(mf/defc header*
{::mf/wrap [mf/memo]
::mf/private true}
[{:keys [can-edit]}]
[{:keys [can-edit layout on-change]}]
(let [on-click (mf/use-fn #(st/emit! (dd/create-project)))]
[:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"}
[:header {:class (stl/css :dashboard-header)
:data-testid "dashboard-header"}
[:div#dashboard-projects-title {:class (stl/css :dashboard-title)}
[:h1 (tr "dashboard.projects-title")]]
(when can-edit
[:button {:class (stl/css :btn-secondary :btn-small)
:on-click on-click
:data-testid "new-project-button"}
(tr "dashboard.new-project")])]))
[:div {:class (stl/css :dashboard-header-actions)}
[:> layout-toggle* {:layout layout
:on-change on-change}]
(when can-edit
[:button {:class (stl/css :btn-secondary :btn-small)
:on-click on-click
:data-testid "new-project-button"}
(tr "dashboard.new-project")])]]))
(mf/defc team-hero*
{::mf/wrap [mf/memo]}
@ -99,7 +104,7 @@
(mf/defc project-item*
{::mf/private true}
[{:keys [project is-first team files can-edit]}]
[{:keys [project is-first team files can-edit layout]}]
(let [project-id (get project :id)
team-id (get team :id)
@ -285,13 +290,13 @@
(tr "dashboard.empty-placeholder-drafts-subtitle")
(tr "dashboard.empty-placeholder-files-subtitle"))}]
[:& line-grid
{:project project
:team team
:files files
:create-fn create-file
:can-edit can-edit
:limit limit}])]
[:> line-grid* {:project project
:team team
:files files
:create-fn create-file
:can-edit can-edit
:limit limit
:layout layout}])]
(when (and (> limit 0)
(> file-count limit))
@ -329,6 +334,14 @@
show-deleted? (:can-edit permisions)
layout* (hooks/use-persisted-state lt/layout-key lt/default-layout)
layout (deref layout*)
on-layout-change
(mf/use-fn
(fn [value]
(reset! layout* (keyword value))))
projects
(mf/with-memo [projects]
(->> projects
@ -360,7 +373,9 @@
(when (seq projects)
[:*
[:> header* {:can-edit can-edit}]
[:> header* {:can-edit can-edit
:layout layout
:on-change on-layout-change}]
[:div {:class (stl/css :projects-container)}
[:*
(when (and show-team-hero?
@ -377,7 +392,8 @@
can-invite))}
(when show-deleted?
[:> deleted/menu* {:team-id team-id :section :dashboard-recent}])
[:> deleted/menu* {:team-id team-id
:section :dashboard-recent}])
(for [{:keys [id] :as project} projects]
;; FIXME: refactor this, looks inneficient
@ -389,5 +405,6 @@
:team team
:files files
:can-edit can-edit
:layout layout
:is-first (= project (first projects))
:key id}]))]]]])))

View File

@ -438,8 +438,8 @@
[th-size]
(when th-size
(let [node (mf/ref-val rowref)]
(.setProperty (.-style node) "--th-width" (str th-size "px"))
(.setProperty (.-style node) "--th-height" (str (mth/ceil (* th-size (/ 2 3))) "px")))))
(.setProperty (.-style node) "--thumbnail-width" (str th-size "px"))
(.setProperty (.-style node) "--thumbnail-height" (str (mth/ceil (* th-size (/ 2 3))) "px")))))
(mf/with-effect []
(let [node (mf/ref-val rowref)

View File

@ -490,6 +490,14 @@ msgstr "Once a project member creates a file, it will be displayed here."
msgid "dashboard.empty-placeholder-files-title"
msgstr "No files yet."
#: src/app/main/ui/dashboard/files_layout.cljs
msgid "dashboard.files-layout.grid"
msgstr "Grid view"
#: src/app/main/ui/dashboard/files_layout.cljs
msgid "dashboard.files-layout.list"
msgstr "List view"
#: src/app/main/ui/dashboard/placeholder.cljs:117
#, markdown
msgid "dashboard.empty-placeholder-libraries"

View File

@ -495,6 +495,14 @@ msgstr "Cuando un miembro del equipo cree algún archivo, este aparecerá aquí.
msgid "dashboard.empty-placeholder-files-title"
msgstr "Aún no hay archivos."
#: src/app/main/ui/dashboard/files_layout.cljs
msgid "dashboard.files-layout.grid"
msgstr "Vista de cuadrícula"
#: src/app/main/ui/dashboard/files_layout.cljs
msgid "dashboard.files-layout.list"
msgstr "Vista de lista"
#: src/app/main/ui/dashboard/placeholder.cljs:117
#, markdown
msgid "dashboard.empty-placeholder-libraries"