🎉 Add page multi-selection in the workspace sitemap (#10581)

* 🎉 Add page multi-selection in the workspace sitemap

* ♻️ Simplify page selection state updates with single assoc

* 🐛 Fix SCSS issue

* ♻️ Update some components to new syntax

* ♻️ Adapt SCSS to the new guidelines

* ♻️ SCSS cleanup

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
This commit is contained in:
Juan de la Cruz 2026-07-21 14:26:03 +02:00 committed by GitHub
parent fa996ab240
commit 5dff551f31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 372 additions and 271 deletions

View File

@ -1207,9 +1207,20 @@
(dm/assert! (gpt/point? position)) (dm/assert! (gpt/point? position))
(ptk/reify ::show-page-item-context-menu (ptk/reify ::show-page-item-context-menu
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ state _]
(rx/of (show-context-menu (let [id (:id page)
(-> params (assoc :kind :page :selected (:id page)))))))) selected (dm/get-in state [:workspace-local :selected-pages])
;; When the right-clicked page is part of a multi-selection we
;; keep it; otherwise the menu targets just that page.
multi? (and (contains? selected id) (> (count selected) 1))]
(rx/concat
(if multi?
(rx/empty)
(rx/of (dwpg/select-page id)))
(rx/of (show-context-menu
(-> params (assoc :kind :page
:selected id
:selected-pages (if multi? selected #{id}))))))))))
(defn show-track-context-menu (defn show-track-context-menu
[{:keys [grid-id type index] :as params}] [{:keys [grid-id type index] :as params}]
@ -1651,6 +1662,11 @@
(dm/export dwpg/duplicate-page) (dm/export dwpg/duplicate-page)
(dm/export dwpg/rename-page) (dm/export dwpg/rename-page)
(dm/export dwpg/delete-page) (dm/export dwpg/delete-page)
(dm/export dwpg/delete-pages)
(dm/export dwpg/select-page)
(dm/export dwpg/toggle-page-selection)
(dm/export dwpg/select-pages-range)
(dm/export dwpg/clear-page-selection)
;; Shapes ;; Shapes
(dm/export dwsh/delete-shapes) (dm/export dwsh/delete-shapes)

View File

@ -135,7 +135,8 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [local (-> (:workspace-local state) (let [local (-> (:workspace-local state)
(dissoc :edition :edit-path :selected)) (dissoc :edition :edit-path :selected
:selected-pages :selected-pages-anchor))
exit? (not= :workspace (rt/lookup-name state)) exit? (not= :workspace (rt/lookup-name state))
state (-> state state (-> state
(update :workspace-cache assoc [file-id page-id] local) (update :workspace-cache assoc [file-id page-id] local)
@ -395,3 +396,104 @@
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(when (= id (:current-page-id state)) (when (= id (:current-page-id state))
(dcm/go-to-workspace {:page-id (first pages)}))))))))) (dcm/go-to-workspace {:page-id (first pages)})))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Page selection (sitemap multi-selection)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn clear-page-selection
"Clear the sitemap multi-selection state."
[]
(ptk/reify ::clear-page-selection
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local dissoc :selected-pages :selected-pages-anchor))))
(defn select-page
"Set the sitemap selection to a single page (plain click). It also
sets the anchor used by range (shift+click) selection."
[id]
(ptk/reify ::select-page
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local assoc
:selected-pages (d/ordered-set id)
:selected-pages-anchor id))))
(defn toggle-page-selection
"Add or remove a page from the sitemap multi-selection (ctrl/cmd + click)."
[id]
(ptk/reify ::toggle-page-selection
ptk/UpdateEvent
(update [_ state]
(let [current-id (:current-page-id state)
selected (or (not-empty (dm/get-in state [:workspace-local :selected-pages]))
(d/ordered-set current-id))
selected (if (contains? selected id)
(disj selected id)
(conj selected id))]
(update state :workspace-local assoc
:selected-pages selected
:selected-pages-anchor id)))))
(defn select-pages-range
"Select every page between the current anchor and `id`, both included
(shift + click). The anchor is kept unchanged."
[id]
(ptk/reify ::select-pages-range
ptk/UpdateEvent
(update [_ state]
(let [current-id (:current-page-id state)
pages (-> (dsh/lookup-file-data state) :pages vec)
anchor (or (dm/get-in state [:workspace-local :selected-pages-anchor])
current-id)
a-idx (d/index-of pages anchor)
b-idx (d/index-of pages id)]
(if (and (some? a-idx) (some? b-idx))
(let [start (min a-idx b-idx)
end (inc (max a-idx b-idx))
range (subvec pages start end)]
(update state :workspace-local assoc
:selected-pages (into (d/ordered-set) range)))
state)))))
(defn delete-pages
"Delete a collection of pages in a single change (single undo entry),
always keeping at least one page in the file."
[ids]
(ptk/reify ::delete-pages
ptk/WatchEvent
(watch [it state _]
(let [file-id (:current-file-id state)
fdata (dsh/lookup-file-data state file-id)
pindex (:pages-index fdata)
pages (:pages fdata)
ids (set ids)
;; A file must keep at least one page: if every page is
;; selected, keep the first one in page order.
ids (if (>= (count ids) (count pages))
(set (rest (filter ids pages)))
ids)
;; Pages to delete, in page order.
del-ids (filter ids pages)
remaining (remove ids pages)
changes (reduce
(fn [changes id]
(let [page (-> (get pindex id)
(assoc :index (d/index-of pages id)))]
(-> changes
(delete-page-components page)
(pcb/del-page page))))
(-> (pcb/empty-changes it)
(pcb/with-library-data fdata))
del-ids)]
(if (empty? del-ids)
(rx/empty)
(rx/of (dch/commit-changes changes)
(clear-page-selection)
(when (contains? ids (:current-page-id state))
(dcm/go-to-workspace {:page-id (first remaining)}))))))))

View File

@ -253,6 +253,10 @@
(def editing-page-item (def editing-page-item
(l/derived :page-item workspace-local)) (l/derived :page-item workspace-local))
;; set of pages selected in the sitemap (multi-selection)
(def selected-pages
(l/derived :selected-pages workspace-local))
(def current-hover-ids (def current-hover-ids
(l/derived :hover-ids context-menu)) (l/derived :hover-ids context-menu))

View File

@ -705,6 +705,8 @@
[{:keys [mdata]}] [{:keys [mdata]}]
(let [page (:page mdata) (let [page (:page mdata)
deletable? (:deletable? mdata) deletable? (:deletable? mdata)
selected-pages (:selected-pages mdata)
multi? (> (count selected-pages) 1)
id (:id page) id (:id page)
delete-fn #(st/emit! (dw/delete-page id)) delete-fn #(st/emit! (dw/delete-page id))
do-delete #(st/emit! (modal/show do-delete #(st/emit! (modal/show
@ -712,20 +714,31 @@
:title (tr "modals.delete-page.title") :title (tr "modals.delete-page.title")
:message (tr "modals.delete-page.body") :message (tr "modals.delete-page.body")
:on-accept delete-fn})) :on-accept delete-fn}))
delete-many-fn #(st/emit! (dw/delete-pages selected-pages))
do-delete-many #(st/emit! (modal/show
{:type :confirm
:title (tr "modals.delete-pages.title")
:message (tr "modals.delete-pages.body")
:on-accept delete-many-fn}))
do-duplicate #(st/emit! do-duplicate #(st/emit!
(dw/duplicate-page id) (dw/duplicate-page id)
(ev/event {::ev/name "duplicate-page"})) (ev/event {::ev/name "duplicate-page"}))
do-rename #(st/emit! (dw/start-rename-page-item id))] do-rename #(st/emit! (dw/start-rename-page-item id))]
[:* (if multi?
(when deletable? ;; When several pages are selected, the only available action is
[:> menu-entry* {:title (tr "workspace.assets.delete") ;; deleting all of them at once.
:on-click do-delete}]) [:> menu-entry* {:title (tr "workspace.assets.delete-pages")
:on-click do-delete-many}]
[:*
(when deletable?
[:> menu-entry* {:title (tr "workspace.assets.delete")
:on-click do-delete}])
[:> menu-entry* {:title (tr "workspace.assets.rename") [:> menu-entry* {:title (tr "workspace.assets.rename")
:on-click do-rename}] :on-click do-rename}]
[:> menu-entry* {:title (tr "workspace.assets.duplicate") [:> menu-entry* {:title (tr "workspace.assets.duplicate")
:on-click do-duplicate}]])) :on-click do-duplicate}]])))
(mf/defc viewport-context-menu* (mf/defc viewport-context-menu*
[] []

View File

@ -62,9 +62,10 @@
;; --- Page Item ;; --- Page Item
(mf/defc page-item (mf/defc page-item*
{::mf/wrap-props false} {::mf/private true
[{:keys [page index deletable? selected? editing? hovering? current-page-id]}] ::mf/wrap-props false}
[{:keys [page index is-deletable is-selected is-editing is-hovering current-page-id]}]
(let [input-ref (mf/use-ref) (let [input-ref (mf/use-ref)
id (:id page) id (:id page)
name (:name page "") name (:name page "")
@ -76,38 +77,53 @@
on-click on-click
(mf/use-fn (mf/use-fn
(mf/deps id current-page-id is-separator?) (mf/deps id current-page-id is-separator?)
(fn [] (fn [event]
(when-not is-separator? (when-not is-separator?
;; WASM page transitions: (cond
;; - Capture the current page (A) once ;; Shift + click: select the range of pages from the anchor
;; - Show a blurred snapshot while the target page (B/C/...) renders ;; to this page (does not navigate).
;; - If the user clicks again during the transition, keep showing the original (A) snapshot (kbd/shift? event)
(if (and (features/active-feature? @st/state "render-wasm/v1") (st/emit! (dw/select-pages-range id))
(not= id current-page-id))
(-> (if @wasm.api/page-transition? ;; Ctrl/Cmd + click: add/remove this page to/from the
(p/resolved nil) ;; multi-selection (does not navigate).
;; Blur with Skia, then capture the already-blurred frame. (kbd/mod? event)
(do (wasm.api/render-blurred-snapshot!) (st/emit! (dw/toggle-page-selection id))
(wasm.api/capture-canvas-snapshot)))
(p/finally ;; Plain click: reset the selection to this page and
(fn [] ;; navigate to it.
(wasm.api/apply-canvas-blur) :else
;; Two RAF so the overlay paints before navigation. (do
(timers/raf (st/emit! (dw/select-page id))
(fn [] ;; WASM page transitions:
(timers/raf navigate-fn)))))) ;; - Capture the current page (A) once
(navigate-fn))))) ;; - Show a blurred snapshot while the target page (B/C/...) renders
;; - If the user clicks again during the transition, keep showing the original (A) snapshot
(if (and (features/active-feature? @st/state "render-wasm/v1")
(not= id current-page-id))
(-> (if @wasm.api/page-transition?
(p/resolved nil)
;; Blur with Skia, then capture the already-blurred frame.
(do (wasm.api/render-blurred-snapshot!)
(wasm.api/capture-canvas-snapshot)))
(p/finally
(fn []
(wasm.api/apply-canvas-blur)
;; Two RAF so the overlay paints before navigation.
(timers/raf
(fn []
(timers/raf navigate-fn))))))
(navigate-fn)))))))
on-delete on-delete
(mf/use-fn (mf/use-fn
(mf/deps id) (mf/deps id)
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(st/emit! (modal/show (st/emit! (modal/show {:type :confirm
{:type :confirm :title (tr "modals.delete-page.title")
:title (tr "modals.delete-page.title") :message (tr "modals.delete-page.body")
:message (tr "modals.delete-page.body") :on-accept delete-fn}))))
:on-accept delete-fn}))))
on-double-click on-double-click
(mf/use-fn (mf/use-fn
@ -153,7 +169,7 @@
:data {:id id :data {:id id
:index index :index index
:name (:name page)} :name (:name page)}
:draggable? (and (not read-only?) (not editing?))) :draggable? (and (not read-only?) (not is-editing)))
on-context-menu on-context-menu
(mf/use-fn (mf/use-fn
@ -166,50 +182,49 @@
(st/emit! (dw/show-page-item-context-menu (st/emit! (dw/show-page-item-context-menu
{:position position {:position position
:page page :page page
:deletable? deletable?}))))))] :deletable? is-deletable}))))))]
(mf/use-effect (mf/use-effect
(mf/deps selected?) (mf/deps is-selected)
(fn [] (fn []
(when selected? (when is-selected
(let [node (mf/ref-val dref)] (let [node (mf/ref-val dref)]
(dom/scroll-into-view-if-needed! node))))) (dom/scroll-into-view-if-needed! node)))))
(mf/use-layout-effect (mf/use-layout-effect
(mf/deps editing?) (mf/deps is-editing)
(fn [] (fn []
(when editing? (when is-editing
(let [edit-input (mf/ref-val input-ref)] (let [edit-input (mf/ref-val input-ref)]
(dom/select-text! edit-input)) (dom/select-text! edit-input))
nil))) nil)))
(let [selected? (and selected? (not is-separator?))] (let [selected? (and is-selected (not is-separator?))]
[:li {:class (stl/css-case [:li {:class (stl/css-case :page-item true
:page-element true :separator is-separator?
:separator is-separator? :selected selected?
:selected selected? :dnd-over-top (= (:over dprops) :top)
:dnd-over-top (= (:over dprops) :top) :dnd-over-bot (= (:over dprops) :bot))
:dnd-over-bot (= (:over dprops) :bot))
:ref dref} :ref dref}
[:div {:class (stl/css-case [:div {:class (stl/css-case :page-item-body true
:element-list-body true :separator is-separator?
:separator-body is-separator? :hover (and is-hovering (not is-separator?))
:hover (and hovering? (not is-separator?)) :selected selected?)
:selected selected?)
:data-testid (dm/str "page-" id) :data-testid (dm/str "page-" id)
:tab-index "0" :tab-index "0"
:on-click on-click :on-click on-click
:on-double-click on-double-click :on-double-click on-double-click
:on-context-menu on-context-menu} :on-context-menu on-context-menu}
(if (and is-separator? (not editing?)) (if (and is-separator? (not is-editing))
[:div {:class (stl/css :page-separator) [:div {:class (stl/css :page-divider)
:data-testid "page-separator"}] :data-testid "page-separator"}]
[:* [:*
(when-not is-separator? (when-not is-separator?
[:div {:class (stl/css :page-icon)} [:div {:class (stl/css :page-item-icon)}
[:> icon* {:icon-id i/document :size "s"}]]) [:> icon* {:icon-id i/document
(if editing? :size "s"}]])
[:input {:class (stl/css :element-name) (if is-editing
[:input {:class (stl/css :page-item-input)
:type "text" :type "text"
:ref input-ref :ref input-ref
:on-blur on-blur :on-blur on-blur
@ -217,32 +232,35 @@
:auto-focus true :auto-focus true
:default-value name}] :default-value name}]
[:* [:*
[:span {:class (stl/css :page-name) :title name :data-testid "page-name"} [:span {:class (stl/css :page-item-label)
:title name
:data-testid "page-name"}
name] name]
[:div {:class (stl/css :page-actions)} [:div {:class (stl/css :page-item-actions)}
(when (and deletable? (not read-only?)) (when (and is-deletable (not read-only?))
[:> icon-button* {:variant "action" [:> icon-button* {:variant "action"
:aria-label (tr "modals.delete-page.title") :aria-label (tr "modals.delete-page.title")
:on-click on-delete :on-click on-delete
:icon-size "s" :icon-size "s"
:class (stl/css :page-delete-button) :class (stl/css :page-delete-btn)
:icon-class (stl/css :page-delete-button-icon) :icon-class (stl/css :page-delete-icon)
:icon i/delete}])]])])]]))) :icon i/delete}])]])])]])))
;; --- Page Item Wrapper ;; --- Page Item Wrapper
(mf/defc page-item-wrapper (mf/defc page-item-wrapper*
{::mf/wrap-props false} {::mf/private true
[{:keys [page-id index deletable? selected? editing? current-page-id]}] ::mf/wrap-props false}
[{:keys [page-id index is-deletable is-selected is-editing current-page-id]}]
(let [page-ref (mf/with-memo [page-id] (let [page-ref (mf/with-memo [page-id]
(make-page-ref page-id)) (make-page-ref page-id))
page (mf/deref page-ref)] page (mf/deref page-ref)]
[:& page-item {:page page [:> page-item* {:page page
:index index :index index
:current-page-id current-page-id :current-page-id current-page-id
:deletable? deletable? :is-deletable is-deletable
:selected? selected? :is-selected is-selected
:editing? editing?}])) :is-editing is-editing}]))
;; --- Pages List ;; --- Pages List
@ -252,18 +270,23 @@
(let [pages (:pages file) (let [pages (:pages file)
deletable? (> (count pages) 1) deletable? (> (count pages) 1)
editing-page-id (mf/deref refs/editing-page-item) editing-page-id (mf/deref refs/editing-page-item)
current-page-id (mf/use-ctx ctx/current-page-id)] selected-pages (mf/deref refs/selected-pages)
[:ul {:class (stl/css :page-list)} current-page-id (mf/use-ctx ctx/current-page-id)
;; When there is no explicit multi-selection, the current page
;; is the selected one.
selected-pages (if (seq selected-pages)
selected-pages
#{current-page-id})]
[:ul
[:> hooks/sortable-container* {} [:> hooks/sortable-container* {}
(for [[index page-id] (d/enumerate pages)] (for [[index page-id] (d/enumerate pages)]
[:& page-item-wrapper [:> page-item-wrapper* {:page-id page-id
{:page-id page-id :index index
:index index :is-deletable deletable?
:deletable? deletable? :is-editing (= page-id editing-page-id)
:editing? (= page-id editing-page-id) :is-selected (contains? selected-pages page-id)
:selected? (= page-id current-page-id) :current-page-id current-page-id
:current-page-id current-page-id :key page-id}])]]))
:key page-id}])]]))
;; --- Sitemap Toolbox ;; --- Sitemap Toolbox
@ -276,7 +299,8 @@
on-create (mf/use-fn on-create (mf/use-fn
(mf/deps file-id project-id) (mf/deps file-id project-id)
(fn [event] (fn [event]
(st/emit! (dw/create-page {:file-id file-id :project-id project-id})) (st/emit! (dw/create-page {:file-id file-id
:project-id project-id}))
(-> event dom/get-current-target dom/blur!))) (-> event dom/get-current-target dom/blur!)))
read-only? (mf/use-ctx ctx/workspace-read-only?) read-only? (mf/use-ctx ctx/workspace-read-only?)
@ -289,7 +313,7 @@
:collapsed collapsed :collapsed collapsed
:on-collapsed on-toggle-collapsed :on-collapsed on-toggle-collapsed
:title (tr "workspace.sidebar.sitemap") :title (tr "workspace.sidebar.sitemap")
:class (stl/css :title-spacing-sitemap)} :class (stl/css :sitemap-title)}
(if ^boolean read-only? (if ^boolean read-only?
(when ^boolean (:can-edit permissions) (when ^boolean (:can-edit permissions)
@ -297,12 +321,11 @@
:size :small :size :small
:content (tr "labels.view-only")}]) :content (tr "labels.view-only")}])
[:> icon-button* {:variant "ghost" [:> icon-button* {:variant "ghost"
:class (stl/css :add-page)
:aria-label (tr "workspace.sidebar.sitemap.add-page") :aria-label (tr "workspace.sidebar.sitemap.add-page")
:on-click on-create :on-click on-create
:icon i/add}])] :icon i/add}])]
(when-not ^boolean collapsed (when-not ^boolean collapsed
[:div {:class (stl/css :tool-window-content)} [:div {:class (stl/css :sitemap-content)}
[:> pages-list* {:file file :key (dm/str (:id file))}]])])) [:> pages-list* {:key (dm/str (:id file))
:file file}]])]))

View File

@ -4,236 +4,155 @@
// //
// Copyright (c) KALEIDOS INC Sucursal en España SL // Copyright (c) KALEIDOS INC Sucursal en España SL
@use "refactor/common-refactor.scss" as deprecated; @use "ds/_utils.scss" as *;
@use "ds/_borders.scss" as *; @use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/spacing.scss" as *;
@use "ds/typography.scss" as t;
@use "ds/mixins.scss" as *;
.sitemap { .sitemap {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
width: 100%; inline-size: 100%;
height: var(--height, deprecated.$s-200); block-size: var(--height, $sz-200);
} }
.title { .sitemap-title {
margin-left: deprecated.$s-2; padding-inline-start: var(--sp-s);
color: var(--title-foreground-color-hover); margin-block: var(--sp-s) var(--sp-xs);
} }
.resize-area { .sitemap-content {
position: absolute;
bottom: calc(-1 * deprecated.$s-8);
left: 0;
width: 100%;
height: deprecated.$s-12;
border-top: deprecated.$s-2 solid var(--resize-area-border-color);
background-color: var(--resize-area-background-color);
cursor: ns-resize;
&:hover {
border-color: var(--resize-area-border-color);
}
}
.tool-window-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: calc(-38px + var(--height, deprecated.$s-200)); inline-size: var(--left-sidebar-width);
width: var(--left-sidebar-width);
overflow: hidden auto; overflow: hidden auto;
scrollbar-gutter: stable; scrollbar-gutter: stable;
.element-list {
display: grid;
}
} }
.pages-list { .page-item {
width: 100%; @include t.use-typography("body-small");
max-height: deprecated.$s-152;
margin-bottom: deprecated.$s-12;
}
.page-delete-button { min-block-size: $sz-32;
--delete-button-display: none; inline-size: 100%;
}
.page-delete-button-icon {
display: var(--delete-button-display);
}
.page-element {
@include deprecated.body-small-typography;
min-height: deprecated.$s-32;
width: 100%;
cursor: pointer;
&.dnd-over-top { &.dnd-over-top {
border-top: deprecated.$s-1 solid var(--layer-row-foreground-color-drag); border-block-start: $b-1 solid var(--layer-row-foreground-color-drag);
} }
&.dnd-over-bot { &.dnd-over-bot {
border-bottom: deprecated.$s-1 solid var(--layer-row-foreground-color-drag); border-block-end: $b-1 solid var(--layer-row-foreground-color-drag);
} }
.dnd-over > .element-list-body { .page-item-body {
border: deprecated.$s-1 solid var(--layer-row-foreground-color-drag);
}
.element-list-body {
display: flex; display: flex;
align-items: center; align-items: center;
height: deprecated.$s-32; block-size: $sz-32;
width: 100%; inline-size: 100%;
padding: 0 deprecated.$s-12 0 0; padding: 0 var(--sp-m) 0 0;
transition: none; transition: none;
color: var(--layer-row-foreground-color); color: var(--layer-row-foreground-color);
user-select: none;
.page-name { &.separator {
@include deprecated.text-ellipsis; block-size: auto;
min-block-size: var(--sp-xxxl);
flex-grow: 1; padding: 0;
padding-left: deprecated.$s-2;
}
.page-icon {
@include deprecated.flex-center;
padding: 0 deprecated.$s-4 0 deprecated.$s-8;
}
.page-actions {
height: deprecated.$s-32;
display: flex;
align-items: center;
}
.element-name {
@include deprecated.text-ellipsis;
color: var(--layer-row-foreground-color-focus);
}
input.element-name {
@include deprecated.text-ellipsis;
@include deprecated.body-small-typography;
@include deprecated.remove-input-style;
flex-grow: 1;
height: deprecated.$s-28;
max-width: calc(var(--parent-size) - (var(--depth) * var(--layer-indentation-size)));
padding-left: deprecated.$s-6;
margin: 0;
border-radius: deprecated.$br-8;
border: deprecated.$s-1 solid var(--input-border-color-focus);
color: var(--layer-row-foreground-color);
} }
} }
&:active, .page-item-label {
&.on-drag { @include text-ellipsis;
.element-list-body {
color: var(--layer-row-foreground-color-drag);
background-color: var(--layer-row-background-color-drag);
.page-icon svg { flex-grow: 1;
stroke: var(--layer-row-foreground-color-drag); padding-inline-start: var(--sp-xxs);
}
}
} }
&.selected, .page-item-icon {
&.selected:hover { display: flex;
.element-list-body { justify-content: center;
color: var(--layer-row-foreground-color-selected); align-items: center;
background-color: var(--layer-row-background-color-selected); padding: 0 var(--sp-xs) 0 var(--sp-s);
box-shadow: deprecated.$s-16 deprecated.$s-0 deprecated.$s-0 deprecated.$s-0
var(--layer-row-background-color-selected);
.page-icon svg {
stroke: var(--layer-row-foreground-color-selected);
}
}
} }
&:hover, .page-item-actions {
&.hover { block-size: $sz-32;
.element-list-body { display: flex;
color: var(--layer-row-foreground-color-hover); align-items: center;
background-color: var(--layer-row-background-color-hover);
box-shadow: deprecated.$s-16 deprecated.$s-0 deprecated.$s-0 deprecated.$s-0
var(--layer-row-background-color-hover);
.page-actions button {
opacity: deprecated.$op-10;
--delete-button-display: initial;
}
.page-icon svg {
stroke: var(--layer-row-foreground-color-hover);
}
}
} }
&:focus { .page-item-input {
.element-list-body { @include text-ellipsis;
color: var(--layer-row-foreground-color-focus); @include t.use-typography("body-small");
border: deprecated.$s-1 solid var(--layer-row-border-color-focus);
outline: none;
.page-actions button { background: none;
opacity: deprecated.$op-10; outline: none;
} flex-grow: 1;
} block-size: $sz-28;
max-inline-size: calc(var(--parent-size) - (var(--depth) * var(--layer-indentation-size)));
padding-inline-start: $sz-6;
margin: 0;
border-radius: $br-8;
border: $b-1 solid var(--input-border-color-focus);
color: var(--layer-row-foreground-color);
} }
&:focus-within { &:active .page-item-body {
.element-list-body { color: var(--layer-row-foreground-color-drag);
outline: none; background-color: var(--layer-row-background-color-drag);
.page-actions button {
opacity: deprecated.$op-10;
}
}
} }
&.hidden { &.selected .page-item-body,
.element-list-body { &.selected:hover .page-item-body {
color: var(--layer-row-foreground-color-hidden); color: var(--layer-row-foreground-color-selected);
background-color: var(--layer-row-background-color-hidden); background-color: var(--layer-row-background-color-selected);
opacity: deprecated.$op-7; box-shadow: $sz-16 0 0 0 var(--layer-row-background-color-selected);
}
.page-icon svg { &:hover .page-item-body,
stroke: var(--layer-row-foreground-color-hidden); &.hover .page-item-body {
} color: var(--layer-row-foreground-color-hover);
} background-color: var(--layer-row-background-color-hover);
box-shadow: $sz-16 0 0 0 var(--layer-row-background-color-hover);
}
&:hover .page-delete-btn,
&.hover .page-delete-btn {
--delete-button-display: initial;
}
&:focus .page-item-body {
color: var(--layer-row-foreground-color-focus);
border: $b-1 solid var(--layer-row-border-color-focus);
outline: none;
}
&:focus-within .page-item-body {
outline: none;
}
&.separator:hover .page-item-body,
&.separator.hover .page-item-body {
color: var(--layer-row-foreground-color);
background-color: transparent;
box-shadow: none;
} }
} }
.element-list-body.separator-body { .page-divider {
height: auto; inline-size: 100%;
min-height: var(--sp-xxxl); block-size: $b-1;
padding: 0;
}
.page-separator {
width: 100%;
height: $b-1;
margin: var(--sp-s); margin: var(--sp-s);
background-color: var(--color-background-quaternary); background-color: var(--color-background-quaternary);
} }
.page-element.separator:hover .element-list-body, .page-delete-btn {
.page-element.separator.hover .element-list-body { --delete-button-display: none;
color: var(--layer-row-foreground-color);
background-color: transparent;
box-shadow: none;
} }
.title-spacing-sitemap { .page-delete-icon {
padding-inline-start: deprecated.$s-8; display: var(--delete-button-display);
margin-block: deprecated.$s-8 deprecated.$s-4;
} }

View File

@ -3926,6 +3926,14 @@ msgstr "Are you sure you want to delete this page?"
msgid "modals.delete-page.title" msgid "modals.delete-page.title"
msgstr "Delete page" msgstr "Delete page"
#: src/app/main/ui/workspace/context_menu.cljs:712
msgid "modals.delete-pages.body"
msgstr "Are you sure you want to delete the selected pages?"
#: src/app/main/ui/workspace/context_menu.cljs:711
msgid "modals.delete-pages.title"
msgstr "Delete pages"
#: src/app/main/ui/dashboard/project_menu.cljs:73 #: src/app/main/ui/dashboard/project_menu.cljs:73
msgid "modals.delete-project-confirm.accept" msgid "modals.delete-project-confirm.accept"
msgstr "Delete project" msgstr "Delete project"
@ -6441,6 +6449,10 @@ msgstr "Your items are going to be named automatically as \"group name / item na
msgid "workspace.assets.delete" msgid "workspace.assets.delete"
msgstr "Delete" msgstr "Delete"
#: src/app/main/ui/workspace/context_menu.cljs:733
msgid "workspace.assets.delete-pages"
msgstr "Delete pages"
#: src/app/main/ui/workspace/sidebar/assets/groups.cljs:74 #: src/app/main/ui/workspace/sidebar/assets/groups.cljs:74
msgid "workspace.assets.delete-group" msgid "workspace.assets.delete-group"
msgstr "Delete group" msgstr "Delete group"

View File

@ -3810,6 +3810,14 @@ msgstr "¿Seguro que quieres borrar esta página?"
msgid "modals.delete-page.title" msgid "modals.delete-page.title"
msgstr "Borrar página" msgstr "Borrar página"
#: src/app/main/ui/workspace/context_menu.cljs:712
msgid "modals.delete-pages.body"
msgstr "¿Seguro que quieres borrar las páginas seleccionadas?"
#: src/app/main/ui/workspace/context_menu.cljs:711
msgid "modals.delete-pages.title"
msgstr "Borrar páginas"
#: src/app/main/ui/dashboard/project_menu.cljs:73 #: src/app/main/ui/dashboard/project_menu.cljs:73
msgid "modals.delete-project-confirm.accept" msgid "modals.delete-project-confirm.accept"
msgstr "Eliminar proyecto" msgstr "Eliminar proyecto"
@ -6317,6 +6325,10 @@ msgstr ""
msgid "workspace.assets.delete" msgid "workspace.assets.delete"
msgstr "Borrar" msgstr "Borrar"
#: src/app/main/ui/workspace/context_menu.cljs:733
msgid "workspace.assets.delete-pages"
msgstr "Borrar páginas"
#: src/app/main/ui/workspace/context_menu.cljs:721, src/app/main/ui/workspace/sidebar/assets/colors.cljs:262, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:471, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:537 #: src/app/main/ui/workspace/context_menu.cljs:721, src/app/main/ui/workspace/sidebar/assets/colors.cljs:262, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:471, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:537
msgid "workspace.assets.duplicate" msgid "workspace.assets.duplicate"
msgstr "Duplicar" msgstr "Duplicar"