mirror of
https://github.com/penpot/penpot.git
synced 2026-07-22 22:17:58 +00:00
✨ 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:
parent
0ff73ae711
commit
fa996ab240
@ -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
|
||||
|
||||
@ -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}])]]))
|
||||
|
||||
@ -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}])]))
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
32
frontend/src/app/main/ui/dashboard/layout_toggle.cljs
Normal file
32
frontend/src/app/main/ui/dashboard/layout_toggle.cljs
Normal 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")}]}])
|
||||
@ -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*
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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}]))]]]])))
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user