mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
🎉 Add new page separators feature (#8561)
* 🎉 Add new page separators feature * 📎 Add PR feedback changes * 🐛 Fix page sitemap icons --------- Co-authored-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
parent
adea81ceee
commit
876b8d645d
@ -43,6 +43,8 @@
|
||||
- Edit ruler guide position by double-clicking the guide pill (by @eureka0928) [Github #2311](https://github.com/penpot/penpot/issues/2311)
|
||||
- Add a search bar to filter colors in the color palette toolbar (by @eureka0928) [Github #7653](https://github.com/penpot/penpot/issues/7653)
|
||||
- Allow customising the OIDC login button label (by @wdeveloper16) [Github #7027](https://github.com/penpot/penpot/issues/7027)
|
||||
- Add page separators in Workspace [Taiga #13611](https://tree.taiga.io/project/penpot/us/13611?milestone=262806)
|
||||
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
|
||||
@ -328,11 +328,24 @@
|
||||
(ptk/reify ::rename-page
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page (dsh/lookup-page state id)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-page page)
|
||||
(pcb/mod-page page {:name name}))]
|
||||
(rx/of (dch/commit-changes changes))))))
|
||||
(let [page (dsh/lookup-page state id)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-page page)
|
||||
(pcb/mod-page page {:name name}))
|
||||
pages (-> (dsh/lookup-file-data state) :pages)
|
||||
index (d/index-of pages id)
|
||||
prev-id (when (and (some? index) (pos? index))
|
||||
(nth pages (dec index) nil))
|
||||
next-id (when (some? index)
|
||||
(nth pages (inc index) nil))
|
||||
fallback-page-id (or prev-id next-id)
|
||||
separator? (= "---" (str/trim name))]
|
||||
(rx/concat
|
||||
(rx/of (dch/commit-changes changes))
|
||||
(when (and separator?
|
||||
(= id (:current-page-id state))
|
||||
(some? fallback-page-id))
|
||||
(rx/of (dcm/go-to-workspace :page-id fallback-page-id))))))))
|
||||
|
||||
(defn- delete-page-components
|
||||
[changes page]
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
[:tooltip-class {:optional true} [:maybe :string]]
|
||||
[:type {:optional true} [:maybe [:enum "button" "submit" "reset"]]]
|
||||
[:icon-class {:optional true} :string]
|
||||
[:icon-size {:optional true} [:maybe [:enum "s" "m" "l"]]]
|
||||
[:icon
|
||||
[:and :string [:fn #(contains? icon-list %)]]]
|
||||
[:aria-label :string]
|
||||
@ -30,7 +31,7 @@
|
||||
(mf/defc icon-button*
|
||||
{::mf/schema schema:icon-button
|
||||
::mf/memo true}
|
||||
[{:keys [class icon icon-class variant aria-label children tooltip-placement tooltip-class type] :rest props}]
|
||||
[{:keys [class icon icon-class icon-size variant aria-label children tooltip-placement tooltip-class type] :rest props}]
|
||||
(let [variant
|
||||
(d/nilv variant "primary")
|
||||
|
||||
@ -60,5 +61,5 @@
|
||||
:placement tooltip-placement
|
||||
:id tooltip-id}
|
||||
[:> :button props
|
||||
[:> icon* {:icon-id icon :aria-hidden true :class icon-class}]
|
||||
[:> icon* {:icon-id icon :aria-hidden true :class icon-class :size icon-size}]
|
||||
children]]))
|
||||
|
||||
@ -322,22 +322,16 @@
|
||||
(mf/defc icon*
|
||||
{::mf/schema schema:icon}
|
||||
[{:keys [icon-id size class] :rest props}]
|
||||
(let [props (mf/spread-props props
|
||||
{:class [class (stl/css :icon)]
|
||||
:width icon-size-m
|
||||
:height icon-size-m})
|
||||
|
||||
size-px (cond (= size "l") icon-size-l
|
||||
(let [size-px (cond (= size "l") icon-size-l
|
||||
(= size "s") icon-size-s
|
||||
:else icon-size-m)
|
||||
|
||||
offset (if (or (= size "s") (= size "m"))
|
||||
(/ (- icon-size-m size-px) 2)
|
||||
0)]
|
||||
props (mf/spread-props props
|
||||
{:class [class (stl/css :icon)]
|
||||
:width size-px
|
||||
:height size-px})]
|
||||
|
||||
[:> :svg props
|
||||
[:use {:href (dm/str "#icon-" icon-id)
|
||||
:width size-px
|
||||
:height size-px
|
||||
:x offset
|
||||
:y offset}]]))
|
||||
:height size-px}]]))
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.modal :as modal]
|
||||
@ -19,9 +20,8 @@
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.notifications.badge :refer [badge-notification]]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.util.dom :as dom]
|
||||
@ -50,8 +50,10 @@
|
||||
each object change)"
|
||||
[page-id]
|
||||
(l/derived (fn [fdata]
|
||||
(-> (dsh/get-page fdata page-id)
|
||||
(dissoc :objects)))
|
||||
(let [page (dsh/get-page fdata page-id)]
|
||||
(-> page
|
||||
(assoc :empty? (ctp/is-empty? page))
|
||||
(dissoc :objects))))
|
||||
refs/workspace-data
|
||||
=))
|
||||
|
||||
@ -62,29 +64,32 @@
|
||||
(mf/defc page-item
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [page index deletable? selected? editing? hovering? current-page-id]}]
|
||||
(let [input-ref (mf/use-ref)
|
||||
id (:id page)
|
||||
delete-fn (mf/use-fn (mf/deps id) #(st/emit! (dw/delete-page id)))
|
||||
navigate-fn (mf/use-fn (mf/deps id) #(st/emit! :interrupt (dcm/go-to-workspace :page-id id)))
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
(let [input-ref (mf/use-ref)
|
||||
id (:id page)
|
||||
name (:name page "")
|
||||
is-separator? (and (= "---" (str/trim name)) (:empty? page))
|
||||
delete-fn (mf/use-fn (mf/deps id) #(st/emit! (dw/delete-page id)))
|
||||
navigate-fn (mf/use-fn (mf/deps id) #(st/emit! :interrupt (dcm/go-to-workspace :page-id id)))
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
|
||||
on-click
|
||||
(mf/use-fn
|
||||
(mf/deps id current-page-id)
|
||||
(mf/deps id current-page-id is-separator?)
|
||||
(fn []
|
||||
;; For the wasm renderer, apply a blur effect to the viewport canvas
|
||||
;; when we navigate to a different page.
|
||||
(if (and (features/active-feature? @st/state "render-wasm/v1")
|
||||
(not= id current-page-id))
|
||||
(do
|
||||
(wasm.api/capture-canvas-pixels)
|
||||
(wasm.api/apply-canvas-blur)
|
||||
;; NOTE: it seems we need two RAF so the blur is actually applied and visible
|
||||
;; in the canvas :(
|
||||
(timers/raf
|
||||
(fn []
|
||||
(timers/raf navigate-fn))))
|
||||
(navigate-fn))))
|
||||
(when-not is-separator?
|
||||
;; For the wasm renderer, apply a blur effect to the viewport canvas
|
||||
;; when we navigate to a different page.
|
||||
(if (and (features/active-feature? @st/state "render-wasm/v1")
|
||||
(not= id current-page-id))
|
||||
(do
|
||||
(wasm.api/capture-canvas-pixels)
|
||||
(wasm.api/apply-canvas-blur)
|
||||
;; NOTE: it seems we need two RAF so the blur is actually applied and visible
|
||||
;; in the canvas :(
|
||||
(timers/raf
|
||||
(fn []
|
||||
(timers/raf navigate-fn))))
|
||||
(navigate-fn)))))
|
||||
|
||||
on-delete
|
||||
(mf/use-fn
|
||||
@ -106,11 +111,14 @@
|
||||
|
||||
on-blur
|
||||
(mf/use-fn
|
||||
(mf/deps id is-separator?)
|
||||
(fn [event]
|
||||
(let [name (str/trim (dom/get-target-val event))]
|
||||
(when-not (str/empty? name)
|
||||
(st/emit! (dw/rename-page id name)))
|
||||
(st/emit! (dw/stop-rename-page-item)))))
|
||||
(let [new-name (str/trim (dom/get-target-val event))]
|
||||
(if (str/empty? new-name)
|
||||
(when is-separator?
|
||||
(st/emit! (dw/delete-page id)))
|
||||
(st/emit! (dw/rename-page id new-name))))
|
||||
(st/emit! (dw/stop-rename-page-item))))
|
||||
|
||||
on-key-down
|
||||
(mf/use-fn
|
||||
@ -166,40 +174,49 @@
|
||||
(dom/select-text! edit-input))
|
||||
nil)))
|
||||
|
||||
[:li {:class (stl/css-case
|
||||
:page-element true
|
||||
:selected selected?
|
||||
:dnd-over-top (= (:over dprops) :top)
|
||||
:dnd-over-bot (= (:over dprops) :bot))
|
||||
:ref dref}
|
||||
[:div {:class (stl/css-case
|
||||
:element-list-body true
|
||||
:hover hovering?
|
||||
:selected selected?)
|
||||
:data-testid (dm/str "page-" id)
|
||||
:tab-index "0"
|
||||
:on-click on-click
|
||||
:on-double-click on-double-click
|
||||
:on-context-menu on-context-menu}
|
||||
[:div {:class (stl/css :page-icon)}
|
||||
deprecated-icon/document]
|
||||
|
||||
(if editing?
|
||||
[:*
|
||||
[:input {:class (stl/css :element-name)
|
||||
:type "text"
|
||||
:ref input-ref
|
||||
:on-blur on-blur
|
||||
:on-key-down on-key-down
|
||||
:auto-focus true
|
||||
:default-value (:name page "")}]]
|
||||
[:*
|
||||
[:span {:class (stl/css :page-name) :title (:name page) :data-testid "page-name"}
|
||||
(:name page)]
|
||||
[:div {:class (stl/css :page-actions)}
|
||||
(when (and deletable? (not read-only?))
|
||||
[:button {:on-click on-delete}
|
||||
deprecated-icon/delete])]])]]))
|
||||
(let [selected? (and selected? (not is-separator?))]
|
||||
[:li {:class (stl/css-case
|
||||
:page-element true
|
||||
:separator is-separator?
|
||||
:selected selected?
|
||||
:dnd-over-top (= (:over dprops) :top)
|
||||
:dnd-over-bot (= (:over dprops) :bot))
|
||||
:ref dref}
|
||||
[:div {:class (stl/css-case
|
||||
:element-list-body true
|
||||
:separator-body is-separator?
|
||||
:hover (and hovering? (not is-separator?))
|
||||
:selected selected?)
|
||||
:data-testid (dm/str "page-" id)
|
||||
:tab-index "0"
|
||||
:on-click on-click
|
||||
:on-double-click on-double-click
|
||||
:on-context-menu on-context-menu}
|
||||
(if (and is-separator? (not editing?))
|
||||
[:div {:class (stl/css :page-separator)
|
||||
:data-testid "page-separator"}]
|
||||
[:*
|
||||
(when-not is-separator?
|
||||
[:div {:class (stl/css :page-icon)}
|
||||
[:> icon* {:icon-id i/document :size "s"}]])
|
||||
(if editing?
|
||||
[:input {:class (stl/css :element-name)
|
||||
:type "text"
|
||||
:ref input-ref
|
||||
:on-blur on-blur
|
||||
:on-key-down on-key-down
|
||||
:auto-focus true
|
||||
:default-value name}]
|
||||
[:*
|
||||
[:span {:class (stl/css :page-name) :title name :data-testid "page-name"}
|
||||
name]
|
||||
[:div {:class (stl/css :page-actions)}
|
||||
(when (and deletable? (not read-only?))
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "modals.delete-page.title")
|
||||
:on-click on-delete
|
||||
:icon-size "s"
|
||||
:icon i/delete}])]])])]])))
|
||||
|
||||
;; --- Page Item Wrapper
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "ds/_borders.scss" as *;
|
||||
|
||||
.sitemap {
|
||||
position: relative;
|
||||
@ -99,8 +100,6 @@
|
||||
svg {
|
||||
@extend %button-icon-small;
|
||||
|
||||
height: deprecated.$s-12;
|
||||
width: deprecated.$s-12;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
stroke: var(--icon-foreground);
|
||||
@ -109,6 +108,8 @@
|
||||
|
||||
.page-actions {
|
||||
height: deprecated.$s-32;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
@include deprecated.buttonStyle;
|
||||
@ -121,8 +122,6 @@
|
||||
svg {
|
||||
@extend %button-icon-small;
|
||||
|
||||
height: deprecated.$s-12;
|
||||
width: deprecated.$s-12;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
stroke: var(--icon-foreground);
|
||||
@ -253,6 +252,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
.element-list-body.separator-body {
|
||||
height: auto;
|
||||
min-height: var(--sp-xxxl);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page-separator {
|
||||
width: 100%;
|
||||
height: $b-1;
|
||||
margin: var(--sp-s);
|
||||
background-color: var(--color-background-quaternary);
|
||||
}
|
||||
|
||||
.page-element.separator:hover .element-list-body,
|
||||
.page-element.separator.hover .element-list-body {
|
||||
color: var(--layer-row-foreground-color);
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.title-spacing-sitemap {
|
||||
padding-inline-start: deprecated.$s-8;
|
||||
margin-block: deprecated.$s-8 deprecated.$s-4;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user