Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2024-03-05 18:53:36 +01:00
commit 0606ef1c84
29 changed files with 854 additions and 774 deletions

View file

@ -1076,14 +1076,13 @@
(->> (cells-seq parent :sort? true) (->> (cells-seq parent :sort? true)
(reduce (reduce
(fn [[parent auto?] cell] (fn [[parent auto?] cell]
(let [[cell auto?] (let [[cell auto?]
(cond (cond
(and (empty? (:shapes cell)) (and (empty? (:shapes cell))
(= :manual (:position cell)) (= :manual (:position cell))
(= (:row-span cell) 1) (= (:row-span cell) 1)
(= (:column-span cell) 1)) (= (:column-span cell) 1))
[(assoc cell :position :auto) false] [cell false]
(and (or (not= (:row-span cell) 1) (and (or (not= (:row-span cell) 1)
(not= (:column-span cell) 1)) (not= (:column-span cell) 1))

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 14H3.33333C2.97971 14 2.64057 13.8595 2.39052 13.6095C2.14048 13.3594 2 13.0203 2 12.6667V3.33333C2 2.97971 2.14048 2.64057 2.39052 2.39052C2.64057 2.14048 2.97971 2 3.33333 2H6M10.6667 11.3333L14 8M14 8L10.6667 4.66667M14 8H6"/>
</svg>

After

Width:  |  Height:  |  Size: 361 B

View file

@ -848,6 +848,7 @@
color: var(--title-foreground-color-hover); color: var(--title-foreground-color-hover);
background-color: var(--menu-background-color); background-color: var(--menu-background-color);
border: $s-2 solid var(--panel-border-color); border: $s-2 solid var(--panel-border-color);
margin: 0;
} }
.menu-item-base { .menu-item-base {

View file

@ -155,6 +155,7 @@
--icon-foreground-hover: var(--color-foreground-primary); --icon-foreground-hover: var(--color-foreground-primary);
--icon-foreground-accept: var(--status-color-success-500); --icon-foreground-accept: var(--status-color-success-500);
--icon-foreground-discard: var(--status-color-error-500); --icon-foreground-discard: var(--status-color-error-500);
--icon-foreground-active: var(--color-accent-primary);
// INPUTS, SELECTS, DROPDOWNS // INPUTS, SELECTS, DROPDOWNS
@ -201,6 +202,7 @@
--menu-background-color: var(--color-background-tertiary); --menu-background-color: var(--color-background-tertiary);
--menu-foreground-color: var(--color-foreground-primary); --menu-foreground-color: var(--color-foreground-primary);
--menu-icon-foreground-color: var(--color-foreground-secondary);
--menu-background-color-selected: var(--color-background-tertiary); --menu-background-color-selected: var(--color-background-tertiary);
--menu-background-color-hover: var(--color-background-quaternary); --menu-background-color-hover: var(--color-background-quaternary);
--menu-foreground-color-hover: var(--color-foreground-primary); --menu-foreground-color-hover: var(--color-foreground-primary);
@ -358,6 +360,11 @@
--search-bar-background-color: var(--color-background-primary); --search-bar-background-color: var(--color-background-primary);
--search-bar-input-background-color: var(--color-background-tertiary); --search-bar-input-background-color: var(--color-background-tertiary);
--search-bar-input-border-color: var(--color-background-tertiary); --search-bar-input-border-color: var(--color-background-tertiary);
--search-bar-input-border-color-focus: var(--color-accent-primary);
--search-bar-placeholder-foreground-color: var(--color-foreground-secondary);
--search-bar-foreground-color: var(--color-foreground-primary);
--search-bar-icon-foreground-color: var(--color-foreground-secondary);
--search-bar-icon-foreground-color-hover: var(--color-accent-primary);
--pill-background-color: var(--color-background-tertiary); --pill-background-color: var(--color-background-tertiary);
--pill-foreground-color: var(--color-foreground-primary); --pill-foreground-color: var(--color-foreground-primary);
@ -369,6 +376,8 @@
--resize-area-background-color: var(--color-background-primary); --resize-area-background-color: var(--color-background-primary);
--resize-area-border-color: var(--color-background-quaternary); --resize-area-border-color: var(--color-background-quaternary);
--profile-section-background-color: var(--color-background-tertiary);
--flow-tag-background-color: var(--color-background-tertiary); --flow-tag-background-color: var(--color-background-tertiary);
--flow-tag-foreground-color: var(--color-foreground-secondary); --flow-tag-foreground-color: var(--color-foreground-secondary);
--flow-tag-background-color-hover: var(--color-background-quaternary); --flow-tag-background-color-hover: var(--color-background-quaternary);
@ -393,6 +402,14 @@
// NEW TEAM BUTTON // NEW TEAM BUTTON
// TODO: we should not put these functional tokens here, but rather in the components they belong to // TODO: we should not put these functional tokens here, but rather in the components they belong to
--new-team-button-background-color: var(--color-background-primary); --new-team-button-background-color: var(--color-background-primary);
//DASHBOARD
--sidebar-element-foreground-color: var(--color-foreground-secondary);
--sidebar-element-background-color-hover: var(--color-background-secondary);
--sidebar-element-foreground-color-hover: var(--color-accent-primary);
--sidebar-element-background-color-selected: var(--color-background-quaternary);
--sidebar-element-foreground-color-selected: var(--color-accent-primary);
--profile-foreground-color: var(--color-foreground-primary);
} }
#app { #app {

View file

@ -47,6 +47,7 @@
(defn notify-start-loading (defn notify-start-loading
[] []
(st/emit! (msg/show {:content (tr "media.loading") (st/emit! (msg/show {:content (tr "media.loading")
:notification-type :toast
:type :info :type :info
:timeout nil}))) :timeout nil})))

View file

@ -808,7 +808,7 @@
component (ctkl/get-component data component-id) component (ctkl/get-component data component-id)
page-id (:main-instance-page component) page-id (:main-instance-page component)
root-id (:main-instance-id component)] root-id (:main-instance-id component)]
(dwt/request-thumbnail file-id page-id root-id tag))) (dwt/request-thumbnail file-id page-id root-id tag "update-component-thumbnail-sync")))
(defn update-component-sync (defn update-component-sync
([shape-id file-id] (update-component-sync shape-id file-id nil)) ([shape-id file-id] (update-component-sync shape-id file-id nil))

View file

@ -211,6 +211,7 @@
(watch [_ _ _] (watch [_ _ _]
(rx/concat (rx/concat
(rx/of (msg/show {:content (tr "media.loading") (rx/of (msg/show {:content (tr "media.loading")
:notification-type :toast
:type :info :type :info
:timeout nil :timeout nil
:tag :media-loading})) :tag :media-loading}))
@ -440,6 +441,7 @@
(rx/concat (rx/concat
(rx/of (msg/show {:content (tr "media.loading") (rx/of (msg/show {:content (tr "media.loading")
:notification-type :toast
:type :info :type :info
:timeout nil :timeout nil
:tag :media-loading})) :tag :media-loading}))

View file

@ -573,6 +573,7 @@
(> (:row-span cell) 1) (> (:row-span cell) 1)
(> (:column-span cell) 1)) (> (:column-span cell) 1))
(-> (d/update-in-when [:layout-grid-cells cell-id] assoc :shapes [] :position :auto) (-> (d/update-in-when [:layout-grid-cells cell-id] assoc :shapes [] :position :auto)
(d/update-in-when [:layout-grid-cells cell-id] dissoc :area-name)
(ctl/resize-cell-area (:row cell) (:column cell) (:row cell) (:column cell) 1 1) (ctl/resize-cell-area (:row cell) (:column cell) (:row cell) (:column cell) 1 1)
(ctl/assign-cells objects))))) (ctl/assign-cells objects)))))
shape)) shape))
@ -585,6 +586,7 @@
(cond-> shape (cond-> shape
(contains? #{:area :auto} (:position cell)) (contains? #{:area :auto} (:position cell))
(-> (d/assoc-in-when [:layout-grid-cells cell-id :position] :manual) (-> (d/assoc-in-when [:layout-grid-cells cell-id :position] :manual)
(d/update-in-when [:layout-grid-cells cell-id] dissoc :area-name)
(ctl/assign-cells objects))))) (ctl/assign-cells objects)))))
shape)) shape))

View file

@ -58,15 +58,17 @@
(defn request-thumbnail (defn request-thumbnail
"Enqueues a request to generate a thumbnail for the given ids." "Enqueues a request to generate a thumbnail for the given ids."
[file-id page-id shape-id tag] ([file-id page-id shape-id tag]
(request-thumbnail file-id page-id shape-id tag "unknown"))
([file-id page-id shape-id tag requester]
(ptk/reify ::request-thumbnail (ptk/reify ::request-thumbnail
ptk/EffectEvent ptk/EffectEvent
(effect [_ _ _] (effect [_ _ _]
(l/dbg :hint "request thumbnail" :file-id file-id :page-id page-id :shape-id shape-id :tag tag) (l/dbg :hint "request thumbnail" :requester requester :file-id file-id :page-id page-id :shape-id shape-id :tag tag)
(q/enqueue-unique (q/enqueue-unique
queue queue
(create-request file-id page-id shape-id tag) (create-request file-id page-id shape-id tag)
(partial find-request file-id page-id shape-id tag))))) (partial find-request file-id page-id shape-id tag))))))
;; This function first renders the HTML calling `render/render-frame` that ;; This function first renders the HTML calling `render/render-frame` that
;; returns HTML as a string, then we send that data to the iframe rasterizer ;; returns HTML as a string, then we send that data to the iframe rasterizer
@ -291,6 +293,6 @@
(->> all-changes-s (->> all-changes-s
(rx/buffer-until notifier-s) (rx/buffer-until notifier-s)
(rx/mapcat #(into #{} %)) (rx/mapcat #(into #{} %))
(rx/map #(request-thumbnail file-id page-id % "frame")))) (rx/map #(request-thumbnail file-id page-id % "frame" "watch-state-changes"))))
(rx/take-until stopper-s)))))) (rx/take-until stopper-s))))))

View file

@ -124,6 +124,7 @@
(let [message (tr "errors.paste-data-validation")] (let [message (tr "errors.paste-data-validation")]
(st/async-emit! (st/async-emit!
(msg/show {:content message (msg/show {:content message
:notification-type :toast
:type :error :type :error
:timeout 3000}))) :timeout 3000})))
@ -138,6 +139,7 @@
[error] [error]
(ts/schedule (ts/schedule
#(st/emit! (msg/show {:content "Internal Assertion Error" #(st/emit! (msg/show {:content "Internal Assertion Error"
:notification-type :toast
:type :error :type :error
:timeout 3000}))) :timeout 3000})))
@ -153,6 +155,7 @@
(ts/schedule (ts/schedule
#(st/emit! #(st/emit!
(msg/show {:content "Something wrong has happened (on worker)." (msg/show {:content "Something wrong has happened (on worker)."
:notification-type :toast
:type :error :type :error
:timeout 3000}))) :timeout 3000})))
@ -166,6 +169,7 @@
[_] [_]
(ts/schedule (ts/schedule
#(st/emit! (msg/show {:content "SVG is invalid or malformed" #(st/emit! (msg/show {:content "SVG is invalid or malformed"
:notification-type :toast
:type :error :type :error
:timeout 3000})))) :timeout 3000}))))
@ -174,6 +178,7 @@
[_] [_]
(ts/schedule (ts/schedule
#(st/emit! (msg/show {:content "There was an error with the comment" #(st/emit! (msg/show {:content "There was an error with the comment"
:notification-type :toast
:type :error :type :error
:timeout 3000})))) :timeout 3000}))))

View file

@ -18,7 +18,6 @@
[app.util.http :as http] [app.util.http :as http]
[app.util.object :as obj] [app.util.object :as obj]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[clojure.set :as set]
[cuerdas.core :as str] [cuerdas.core :as str]
[lambdaisland.uri :as u] [lambdaisland.uri :as u]
[okulary.core :as l] [okulary.core :as l]
@ -273,13 +272,17 @@
(defn get-content-fonts (defn get-content-fonts
"Extracts the fonts used by the content of a text shape" "Extracts the fonts used by the content of a text shape"
[{font-id :font-id children :children :as content}] [content]
(->> (txt/node-seq content)
(filter txt/is-text-node?)
(reduce
(fn [result {:keys [font-id] :as node}]
(let [current-font (let [current-font
(if (some? font-id) (if (some? font-id)
#{(select-keys content [:font-id :font-variant-id])} (select-keys node [:font-id :font-variant-id])
#{(select-keys txt/default-text-attrs [:font-id :font-variant-id])}) (select-keys txt/default-text-attrs [:font-id :font-variant-id]))]
children-font (->> children (mapv get-content-fonts))] (conj result current-font)))
(reduce set/union (conj children-font current-font)))) #{})))
(defn fetch-font-css (defn fetch-font-css
"Given a font and the variant-id, retrieves the fontface CSS" "Given a font and the variant-id, retrieves the fontface CSS"

View file

@ -12,10 +12,10 @@
(mf/defc link (mf/defc link
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [action klass data-test keyboard-action children]}] [{:keys [action class data-test keyboard-action children]}]
(let [keyboard-action (d/nilv keyboard-action action)] (let [keyboard-action (d/nilv keyboard-action action)]
[:a {:on-click action [:a {:on-click action
:class klass :class class
:on-key-down (fn [event] :on-key-down (fn [event]
(when ^boolean (kbd/enter? event) (when ^boolean (kbd/enter? event)
(keyboard-action event))) (keyboard-action event)))

View file

@ -24,7 +24,7 @@
[app.main.ui.dashboard.inline-edition :refer [inline-edition]] [app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.project-menu :refer [project-menu]] [app.main.ui.dashboard.project-menu :refer [project-menu]]
[app.main.ui.dashboard.team-form] [app.main.ui.dashboard.team-form]
[app.main.ui.icons :as i] [app.main.ui.icons :as i :refer [icon-xref]]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.dom.dnd :as dnd] [app.util.dom.dnd :as dnd]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
@ -39,6 +39,33 @@
[potok.v2.core :as ptk] [potok.v2.core :as ptk]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(def ^:private clear-search-icon
(icon-xref :delete-text-refactor (stl/css :clear-search-icon)))
(def ^:private search-icon
(icon-xref :search-refactor (stl/css :search-icon)))
(def ^:private tick-icon
(icon-xref :tick-refactor (stl/css :tick-icon)))
(def ^:private logo-icon
(icon-xref :logo-refactor (stl/css :logo-icon)))
(def ^:private add-icon
(icon-xref :add-refactor (stl/css :add-icon)))
(def ^:private arrow-icon
(icon-xref :arrow-refactor (stl/css :arrow-icon)))
(def ^:private menu-icon
(icon-xref :menu-refactor (stl/css :menu-icon)))
(def ^:private pin-icon
(icon-xref :pin-refactor (stl/css :pin-icon)))
(def ^:private exit-icon
(icon-xref :exit-refactor (stl/css :exit-icon)))
(mf/defc sidebar-project (mf/defc sidebar-project
[{:keys [item selected?] :as props}] [{:keys [item selected?] :as props}]
(let [dstate (mf/deref refs/dashboard-local) (let [dstate (mf/deref refs/dashboard-local)
@ -54,13 +81,13 @@
local @local* local @local*
on-click on-click
(mf/use-callback (mf/use-fn
(mf/deps item) (mf/deps item)
(fn [] (fn []
(st/emit! (dd/go-to-files (:id item))))) (st/emit! (dd/go-to-files (:id item)))))
on-key-down on-key-down
(mf/use-callback (mf/use-fn
(mf/deps item) (mf/deps item)
(fn [event] (fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
@ -74,7 +101,7 @@
(dom/set-attribute! project-title "tabindex" "-1"))))))))) (dom/set-attribute! project-title "tabindex" "-1")))))))))
on-menu-click on-menu-click
(mf/use-callback (mf/use-fn
(fn [event] (fn [event]
(let [position (dom/get-client-position event)] (let [position (dom/get-client-position event)]
(dom/prevent-default event) (dom/prevent-default event)
@ -83,13 +110,13 @@
:menu-pos position)))) :menu-pos position))))
on-menu-close on-menu-close
(mf/use-callback #(swap! local* assoc :menu-open false)) (mf/use-fn #(swap! local* assoc :menu-open false))
on-edit-open on-edit-open
(mf/use-callback #(swap! local* assoc :edition? true)) (mf/use-fn #(swap! local* assoc :edition? true))
on-edit on-edit
(mf/use-callback (mf/use-fn
(mf/deps item) (mf/deps item)
(fn [name] (fn [name]
(when-not (str/blank? name) (when-not (str/blank? name)
@ -98,7 +125,7 @@
(swap! local* assoc :edition? false))) (swap! local* assoc :edition? false)))
on-drag-enter on-drag-enter
(mf/use-callback (mf/use-fn
(mf/deps selected-project) (mf/deps selected-project)
(fn [e] (fn [e]
(when (dnd/has-type? e "penpot/files") (when (dnd/has-type? e "penpot/files")
@ -108,25 +135,25 @@
(swap! local* assoc :dragging? true)))))) (swap! local* assoc :dragging? true))))))
on-drag-over on-drag-over
(mf/use-callback (mf/use-fn
(fn [e] (fn [e]
(when (dnd/has-type? e "penpot/files") (when (dnd/has-type? e "penpot/files")
(dom/prevent-default e)))) (dom/prevent-default e))))
on-drag-leave on-drag-leave
(mf/use-callback (mf/use-fn
(fn [e] (fn [e]
(when-not (dnd/from-child? e) (when-not (dnd/from-child? e)
(swap! local* assoc :dragging? false)))) (swap! local* assoc :dragging? false))))
on-drop-success on-drop-success
(mf/use-callback (mf/use-fn
(mf/deps (:id item)) (mf/deps (:id item))
#(st/emit! (msg/success (tr "dashboard.success-move-file")) #(st/emit! (msg/success (tr "dashboard.success-move-file"))
(dd/go-to-files (:id item)))) (dd/go-to-files (:id item))))
on-drop on-drop
(mf/use-callback (mf/use-fn
(mf/deps item selected-files) (mf/deps item selected-files)
(fn [_] (fn [_]
(swap! local* assoc :dragging? false) (swap! local* assoc :dragging? false)
@ -139,6 +166,7 @@
[:* [:*
[:li {:tab-index "0" [:li {:tab-index "0"
:class (stl/css-case :project-element true :class (stl/css-case :project-element true
:sidebar-nav-item true
:current selected? :current selected?
:dragging (:dragging? local)) :dragging (:dragging? local))
:on-click on-click :on-click on-click
@ -167,19 +195,19 @@
emit! (mf/use-memo #(f/debounce st/emit! 500)) emit! (mf/use-memo #(f/debounce st/emit! 500))
on-search-blur on-search-blur
(mf/use-callback (mf/use-fn
(fn [_] (fn [_]
(reset! focused? false))) (reset! focused? false)))
on-search-change on-search-change
(mf/use-callback (mf/use-fn
(mf/deps team-id) (mf/deps team-id)
(fn [event] (fn [event]
(let [value (dom/get-target-val event)] (let [value (dom/get-target-val event)]
(emit! (dd/go-to-search value))))) (emit! (dd/go-to-search value)))))
on-clear-click on-clear-click
(mf/use-callback (mf/use-fn
(mf/deps team-id) (mf/deps team-id)
(fn [e] (fn [e]
(let [search-input (dom/get-element "search-input")] (let [search-input (dom/get-element "search-input")]
@ -190,7 +218,7 @@
(dom/stop-propagation e)))) (dom/stop-propagation e))))
on-key-press on-key-press
(mf/use-callback (mf/use-fn
(fn [e] (fn [e]
(when (kbd/enter? e) (when (kbd/enter? e)
(ts/schedule-on-idle (ts/schedule-on-idle
@ -204,15 +232,14 @@
(dom/stop-propagation e)))) (dom/stop-propagation e))))
handle-clear-search handle-clear-search
(mf/use-callback (mf/use-fn
(mf/deps on-clear-click) (mf/deps on-clear-click)
(fn [event] (fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
(on-clear-click event))))] (on-clear-click event))))]
[:form {:class (stl/css :sidebar-search)} [:form {:class (stl/css :sidebar-search)}
[:input [:input {:class (stl/css :input-text)
{:class (stl/css :input-text)
:key "images-search-box" :key "images-search-box"
:id "search-input" :id "search-input"
:type "text" :type "text"
@ -227,71 +254,85 @@
:ref #(when % (set! (.-value %) search-term))}] :ref #(when % (set! (.-value %) search-term))}]
(if (or @focused? (seq search-term)) (if (or @focused? (seq search-term))
[:div [:button {:class (stl/css :search-btn :clear-search-btn)
{:class (stl/css :clear-search)
:tab-index "0" :tab-index "0"
:on-click on-clear-click :on-click on-clear-click
:on-key-down handle-clear-search} :on-key-down handle-clear-search}
i/close] clear-search-icon]
[:div [:button {:class (stl/css :search-btn)
{:class (stl/css :search)
:on-click on-clear-click} :on-click on-clear-click}
i/search])])) search-icon])]))
(mf/defc teams-selector-dropdown-items (mf/defc teams-selector-dropdown-items
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [team profile teams] :as props}] [{:keys [team profile teams] :as props}]
(let [on-create-clicked (let [on-create-clicked
(mf/use-callback (mf/use-fn
#(st/emit! (modal/show :team-form {}))) #(st/emit! (modal/show :team-form {})))
team-selected team-selected
(mf/use-callback (mf/use-fn
(fn [team-id] (fn [event]
(st/emit! (dd/go-to-projects team-id)))) (let [team-id (-> (dom/get-current-target event)
(dom/get-data "value"))]
(st/emit! (dd/go-to-projects team-id)))))
handle-select-default handle-select-default
(mf/use-fn
(mf/deps profile team-selected)
(fn [event] (fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
(team-selected (:default-team-id profile) event))) (team-selected (:default-team-id profile) event))))
handle-select-team handle-select-team
(fn [id event] (mf/use-fn
(mf/deps team-selected)
(fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
(team-selected id event)))] (team-selected event))))
handle-creation-key-down
(mf/use-fn
(mf/deps on-create-clicked)
(fn [event]
(when (kbd/enter? event)
(on-create-clicked event))))]
[:* [:*
[:> dropdown-menu-item* {:on-click (partial team-selected (:default-team-id profile)) [:> dropdown-menu-item* {:on-click team-selected
:data-value (:default-team-id profile)
:on-key-down handle-select-default :on-key-down handle-select-default
:id "teams-selector-default-team" :id "teams-selector-default-team"
:class (stl/css :team-name)} :class (stl/css :team-dropdown-item)}
[:span {:class (stl/css :team-icon)} i/logo-icon] [:span {:class (stl/css :penpot-icon)} i/logo-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.your-penpot")] [:span {:class (stl/css :team-text)} (tr "dashboard.your-penpot")]
(when (= (:default-team-id profile) (:id team)) (when (= (:default-team-id profile) (:id team))
[:span {:class (stl/css :icon)} i/tick])] tick-icon)]
(for [team-item (remove :is-default (vals teams))] (for [team-item (remove :is-default (vals teams))]
[:> dropdown-menu-item* {:on-click (partial team-selected (:id team-item)) [:> dropdown-menu-item* {:on-click team-selected
:on-key-down (partial handle-select-team (:id team-item)) :data-value (:id team-item)
:on-key-down handle-select-team
:id (str "teams-selector-" (:id team-item)) :id (str "teams-selector-" (:id team-item))
:class (stl/css :team-name) :class (stl/css :team-dropdown-item)
:key (str "teams-selector-" (:id team-item))} :key (str "teams-selector-" (:id team-item))}
[:span {:class (stl/css :team-icon)}
[:img {:src (cf/resolve-team-photo-url team-item) [:img {:src (cf/resolve-team-photo-url team-item)
:alt (:name team-item)}]] :class (stl/css :team-picture)
:alt (:name team-item)}]
[:span {:class (stl/css :team-text) [:span {:class (stl/css :team-text)
:title (:name team-item)} (:name team-item)] :title (:name team-item)} (:name team-item)]
(when (= (:id team-item) (:id team)) (when (= (:id team-item) (:id team))
[:span {:class (stl/css :icon)} i/tick])]) tick-icon)])
[:hr {:role "separator"}]
[:hr {:role "separator"
:class (stl/css :team-separator)}]
[:> dropdown-menu-item* {:on-click on-create-clicked [:> dropdown-menu-item* {:on-click on-create-clicked
:on-key-down (fn [event] :on-key-down handle-creation-key-down
(when (kbd/enter? event)
(on-create-clicked event)))
:id "teams-selector-create-team" :id "teams-selector-create-team"
:class (stl/css :team-name :action)} :class (stl/css :team-dropdown-item :action)}
[:span {:class (stl/css :team-icon :new-team)} i/close] [:span {:class (stl/css :icon-wrapper)} add-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-team")]]])) [:span {:class (stl/css :team-text)} (tr "dashboard.create-new-team")]]]))
(s/def ::member-id ::us/uuid) (s/def ::member-id ::us/uuid)
@ -330,130 +371,200 @@
(rx/throw error))) (rx/throw error)))
leave-fn leave-fn
(mf/use-fn
(mf/deps on-success on-error)
(fn [member-id] (fn [member-id]
(let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))] (let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))]
(st/emit! (dd/leave-team (with-meta params (st/emit! (dd/leave-team (with-meta params
{:on-success on-success {:on-success on-success
:on-error on-error}))))) :on-error on-error}))))))
delete-fn delete-fn
(mf/use-fn
(mf/deps team on-success on-error)
(fn [] (fn []
(st/emit! (dd/delete-team (with-meta team {:on-success on-success (st/emit! (dd/delete-team (with-meta team {:on-success on-success
:on-error on-error})))) :on-error on-error})))))
on-rename-clicked on-rename-clicked
(mf/use-fn
(mf/deps team)
(fn [] (fn []
(st/emit! (modal/show :team-form {:team team}))) (st/emit! (modal/show :team-form {:team team}))))
on-leave-clicked on-leave-clicked
(mf/use-fn
(mf/deps leave-fn)
#(st/emit! (modal/show #(st/emit! (modal/show
{:type :confirm {:type :confirm
:title (tr "modals.leave-confirm.title") :title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-confirm.message") :message (tr "modals.leave-confirm.message")
:accept-label (tr "modals.leave-confirm.accept") :accept-label (tr "modals.leave-confirm.accept")
:on-accept leave-fn})) :on-accept leave-fn})))
on-leave-as-owner-clicked on-leave-as-owner-clicked
(mf/use-fn
(mf/deps team profile leave-fn)
(fn [] (fn []
(st/emit! (dd/fetch-team-members (:id team)) (st/emit! (dd/fetch-team-members (:id team))
(modal/show (modal/show
{:type :leave-and-reassign {:type :leave-and-reassign
:profile profile :profile profile
:team team :team team
:accept leave-fn}))) :accept leave-fn}))))
leave-and-close leave-and-close
(mf/use-fn
(mf/deps team delete-fn)
#(st/emit! (modal/show #(st/emit! (modal/show
{:type :confirm {:type :confirm
:title (tr "modals.leave-confirm.title") :title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-and-close-confirm.message" (:name team)) :message (tr "modals.leave-and-close-confirm.message" (:name team))
:scd-message (tr "modals.leave-and-close-confirm.hint") :scd-message (tr "modals.leave-and-close-confirm.hint")
:accept-label (tr "modals.leave-confirm.accept") :accept-label (tr "modals.leave-confirm.accept")
:on-accept delete-fn})) :on-accept delete-fn})))
on-delete-clicked on-delete-clicked
(mf/use-fn
(mf/deps delete-fn)
#(st/emit! #(st/emit!
(modal/show (modal/show
{:type :confirm {:type :confirm
:title (tr "modals.delete-team-confirm.title") :title (tr "modals.delete-team-confirm.title")
:message (tr "modals.delete-team-confirm.message") :message (tr "modals.delete-team-confirm.message")
:accept-label (tr "modals.delete-team-confirm.accept") :accept-label (tr "modals.delete-team-confirm.accept")
:on-accept delete-fn}))] :on-accept delete-fn})))
handle-members
(mf/use-fn
(mf/deps go-members)
(fn [event]
(when (kbd/enter? event)
(go-members))))
handle-invitations
(mf/use-fn
(mf/deps go-invitations)
(fn [event]
(when (kbd/enter? event)
(go-invitations))))
handle-webhooks
(mf/use-fn
(mf/deps go-webhooks)
(fn [event]
(when (kbd/enter? event)
(go-webhooks))))
handle-settings
(mf/use-fn
(mf/deps go-settings)
(fn [event]
(when (kbd/enter? event)
(go-settings))))
handle-rename
(mf/use-fn
(mf/deps on-rename-clicked)
(fn [event]
(when (kbd/enter? event)
(on-rename-clicked))))
handle-leave-and-close
(mf/use-fn
(mf/deps leave-and-close)
(fn [event]
(when (kbd/enter? event)
(leave-and-close))))
handle-leave-as-owner-clicked
(mf/use-fn
(mf/deps on-leave-as-owner-clicked)
(fn [event]
(when (kbd/enter? event)
(on-leave-as-owner-clicked))))
handle-on-leave-clicked
(mf/use-fn
(mf/deps on-leave-clicked)
(fn [event]
(when (kbd/enter? event)
(on-leave-clicked))))
handle-on-delete-clicked
(mf/use-fn
(mf/deps on-delete-clicked)
(fn [event]
(when (kbd/enter? event)
(on-delete-clicked))))]
[:* [:*
[:> dropdown-menu-item* {:on-click go-members [:> dropdown-menu-item* {:on-click go-members
:on-key-down (fn [event] :on-key-down handle-members
(when (kbd/enter? event) :className (stl/css :team-options-item)
(go-members)))
:id "teams-options-members" :id "teams-options-members"
:data-test "team-members"} :data-test "team-members"}
(tr "labels.members")] (tr "labels.members")]
[:> dropdown-menu-item* {:on-click go-invitations [:> dropdown-menu-item* {:on-click go-invitations
:on-key-down (fn [event] :on-key-down handle-invitations
(when (kbd/enter? event) :className (stl/css :team-options-item)
(go-invitations)))
:id "teams-options-invitations" :id "teams-options-invitations"
:data-test "team-invitations"} :data-test "team-invitations"}
(tr "labels.invitations")] (tr "labels.invitations")]
(when (contains? cf/flags :webhooks) (when (contains? cf/flags :webhooks)
[:> dropdown-menu-item* {:on-click go-webhooks [:> dropdown-menu-item* {:on-click go-webhooks
:on-key-down (fn [event] :on-key-down handle-webhooks
(when (kbd/enter? event) :className (stl/css :team-options-item)
(go-webhooks)))
:id "teams-options-webhooks"} :id "teams-options-webhooks"}
(tr "labels.webhooks")]) (tr "labels.webhooks")])
[:> dropdown-menu-item* {:on-click go-settings [:> dropdown-menu-item* {:on-click go-settings
:on-key-down (fn [event] :on-key-down handle-settings
(when (kbd/enter? event) :className (stl/css :team-options-item)
(go-settings)))
:id "teams-options-settings" :id "teams-options-settings"
:data-test "team-settings"} :data-test "team-settings"}
(tr "labels.settings")] (tr "labels.settings")]
[:hr] [:hr {:class (stl/css :team-option-separator)}]
(when can-rename? (when can-rename?
[:> dropdown-menu-item* {:on-click on-rename-clicked [:> dropdown-menu-item* {:on-click on-rename-clicked
:on-key-down (fn [event] :on-key-down handle-rename
(when (kbd/enter? event)
(on-rename-clicked)))
:id "teams-options-rename" :id "teams-options-rename"
:className (stl/css :team-options-item)
:data-test "rename-team"} :data-test "rename-team"}
(tr "labels.rename")]) (tr "labels.rename")])
(cond (cond
(= (count members) 1) (= (count members) 1)
[:> dropdown-menu-item* {:on-click leave-and-close [:> dropdown-menu-item* {:on-click leave-and-close
:on-key-down (fn [event] :on-key-down handle-leave-and-close
(when (kbd/enter? event) :className (stl/css :team-options-item)
(leave-and-close)))
:id "teams-options-leave-team"} :id "teams-options-leave-team"}
(tr "dashboard.leave-team")] (tr "dashboard.leave-team")]
(get-in team [:permissions :is-owner]) (get-in team [:permissions :is-owner])
[:> dropdown-menu-item* {:on-click on-leave-as-owner-clicked [:> dropdown-menu-item* {:on-click on-leave-as-owner-clicked
:on-key-down (fn [event] :on-key-down handle-leave-as-owner-clicked
(when (kbd/enter? event)
(on-leave-as-owner-clicked)))
:id "teams-options-leave-team" :id "teams-options-leave-team"
:className (stl/css :team-options-item)
:data-test "leave-team"} :data-test "leave-team"}
(tr "dashboard.leave-team")] (tr "dashboard.leave-team")]
(> (count members) 1) (> (count members) 1)
[:> dropdown-menu-item* {:on-click on-leave-clicked [:> dropdown-menu-item* {:on-click on-leave-clicked
:on-key-down (fn [event] :on-key-down handle-on-leave-clicked
(when (kbd/enter? event) :className (stl/css :team-options-item)
(on-leave-clicked)))
:id "teams-options-leave-team"} :id "teams-options-leave-team"}
(tr "dashboard.leave-team")]) (tr "dashboard.leave-team")])
(when (get-in team [:permissions :is-owner]) (when (get-in team [:permissions :is-owner])
[:> dropdown-menu-item* {:on-click on-delete-clicked [:> dropdown-menu-item* {:on-click on-delete-clicked
:on-key-down (fn [event] :on-key-down handle-on-delete-clicked
(when (kbd/enter? event)
(on-delete-clicked)))
:id "teams-options-delete-team" :id "teams-options-delete-team"
:class (stl/css :warning) :className (stl/css :team-options-item :warning)
:data-test "delete-team"} :data-test "delete-team"}
(tr "dashboard.delete-team")])])) (tr "dashboard.delete-team")])]))
@ -495,6 +606,10 @@
(when first-element (when first-element
(dom/focus! first-element))))))) (dom/focus! first-element)))))))
close-team-opts-ddwn
(mf/use-fn
#(reset! show-team-opts-ddwn? false))
handle-show-opts-click handle-show-opts-click
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
@ -519,34 +634,32 @@
[:div {:class (stl/css :sidebar-team-switch)} [:div {:class (stl/css :sidebar-team-switch)}
[:div {:class (stl/css :switch-content)} [:div {:class (stl/css :switch-content)}
[:button [:button {:class (stl/css :current-team)
{:class (stl/css :current-team)
:tab-index "0"
:on-click handle-show-team-click :on-click handle-show-team-click
:on-key-down handle-show-team-keydown} :on-key-down handle-show-team-keydown}
(if (:is-default team) (if (:is-default team)
[:div {:class (stl/css :team-name)} [:div {:class (stl/css :team-name)}
[:span {:class (stl/css :team-icon)} i/logo-icon] [:span {:class (stl/css :penpot-icon)} i/logo-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.default-team-name")]] [:span {:class (stl/css :team-text)} (tr "dashboard.default-team-name")]]
[:div {:class (stl/css :team-name)} [:div {:class (stl/css :team-name)}
[:span {:class (stl/css :team-icon)}
[:img {:src (cf/resolve-team-photo-url team) [:img {:src (cf/resolve-team-photo-url team)
:alt (:name team)}]] :class (stl/css :team-picture)
:alt (:name team)}]
[:span {:class (stl/css :team-text) :title (:name team)} (:name team)]]) [:span {:class (stl/css :team-text) :title (:name team)} (:name team)]])
[:span {:class (stl/css :switch-icon)} i/arrow-down]] arrow-icon]
(when-not (:is-default team) (when-not (:is-default team)
[:button [:button {:class (stl/css :switch-options)
{:class (stl/css :switch-options)
:on-click handle-show-opts-click :on-click handle-show-opts-click
:tab-index "0" :tab-index "0"
:on-key-down handle-show-opts-keydown} :on-key-down handle-show-opts-keydown}
i/actions])] menu-icon])]
;; Teams Dropdown ;; Teams Dropdown
[:& dropdown-menu {:show @show-teams-ddwn? [:& dropdown-menu {:show @show-teams-ddwn?
:on-close handle-close-team :on-close handle-close-team
:ids ids :ids ids
@ -557,7 +670,7 @@
:teams teams}]] :teams teams}]]
[:& dropdown-menu {:show @show-team-opts-ddwn? [:& dropdown-menu {:show @show-team-opts-ddwn?
:on-close #(reset! show-team-opts-ddwn? false) :on-close close-team-opts-ddwn
:ids options-ids :ids options-ids
:list-class (stl/css :dropdown :options-dropdown)} :list-class (stl/css :dropdown :options-dropdown)}
[:& team-options-dropdown {:team team [:& team-options-dropdown {:team team
@ -577,12 +690,12 @@
(= (:id project) default-project-id)) (= (:id project) default-project-id))
go-projects go-projects
(mf/use-callback (mf/use-fn
(mf/deps team) (mf/deps team)
#(st/emit! (rt/nav :dashboard-projects {:team-id (:id team)}))) #(st/emit! (rt/nav :dashboard-projects {:team-id (:id team)})))
go-projects-with-key go-projects-with-key
(mf/use-callback (mf/use-fn
(mf/deps team) (mf/deps team)
#(st/emit! (rt/nav :dashboard-projects {:team-id (:id team)}) #(st/emit! (rt/nav :dashboard-projects {:team-id (:id team)})
(ts/schedule-on-idle (ts/schedule-on-idle
@ -594,12 +707,12 @@
(dom/set-attribute! projects-title "tabindex" "-1"))))))) (dom/set-attribute! projects-title "tabindex" "-1")))))))
go-fonts go-fonts
(mf/use-callback (mf/use-fn
(mf/deps team) (mf/deps team)
#(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)}))) #(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)})))
go-fonts-with-key go-fonts-with-key
(mf/use-callback (mf/use-fn
(mf/deps team) (mf/deps team)
#(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)}) #(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)})
(ts/schedule-on-idle (ts/schedule-on-idle
@ -610,7 +723,7 @@
(dom/focus! font-title) (dom/focus! font-title)
(dom/set-attribute! font-title "tabindex" "-1"))))))) (dom/set-attribute! font-title "tabindex" "-1")))))))
go-drafts go-drafts
(mf/use-callback (mf/use-fn
(mf/deps team default-project-id) (mf/deps team default-project-id)
(fn [] (fn []
(st/emit! (rt/nav :dashboard-files (st/emit! (rt/nav :dashboard-files
@ -618,7 +731,7 @@
:project-id default-project-id})))) :project-id default-project-id}))))
go-drafts-with-key go-drafts-with-key
(mf/use-callback (mf/use-fn
(mf/deps team default-project-id) (mf/deps team default-project-id)
#(st/emit! (rt/nav :dashboard-files {:team-id (:id team) #(st/emit! (rt/nav :dashboard-files {:team-id (:id team)
:project-id default-project-id}) :project-id default-project-id})
@ -631,12 +744,12 @@
(dom/set-attribute! drafts-title "tabindex" "-1"))))))) (dom/set-attribute! drafts-title "tabindex" "-1")))))))
go-libs go-libs
(mf/use-callback (mf/use-fn
(mf/deps team) (mf/deps team)
#(st/emit! (rt/nav :dashboard-libraries {:team-id (:id team)}))) #(st/emit! (rt/nav :dashboard-libraries {:team-id (:id team)})))
go-libs-with-key go-libs-with-key
(mf/use-callback (mf/use-fn
(mf/deps team) (mf/deps team)
#(st/emit! (rt/nav :dashboard-libraries {:team-id (:id team)}) #(st/emit! (rt/nav :dashboard-libraries {:team-id (:id team)})
(ts/schedule-on-idle (ts/schedule-on-idle
@ -653,45 +766,51 @@
[:div {:class (stl/css :sidebar-content)} [:div {:class (stl/css :sidebar-content)}
[:& sidebar-team-switch {:team team :profile profile}] [:& sidebar-team-switch {:team team :profile profile}]
[:hr]
[:& sidebar-search {:search-term search-term [:& sidebar-search {:search-term search-term
:team-id (:id team)}] :team-id (:id team)}]
[:div {:class (stl/css :sidebar-content-section)} [:div {:class (stl/css :sidebar-content-section)}
[:ul {:class (stl/css :sidebar-nav :no-overflow)} [:ul {:class (stl/css :sidebar-nav)}
[:li [:li {:class (stl/css-case :recent-projects true
{:class (stl/css :recent-projects) :sidebar-nav-item true
:class-name (when projects? (stl/css :current))} :current projects?)}
[:& link {:action go-projects [:& link {:action go-projects
:class (stl/css :sidebar-link)
:keyboard-action go-projects-with-key} :keyboard-action go-projects-with-key}
[:span {:class (stl/css :element-title)} (tr "labels.projects")]]] [:span {:class (stl/css :element-title)} (tr "labels.projects")]]]
[:li {:class-name (when drafts? (stl/css :current))} [:li {:class (stl/css-case :current drafts?
:sidebar-nav-item true)}
[:& link {:action go-drafts [:& link {:action go-drafts
:class (stl/css :sidebar-link)
:keyboard-action go-drafts-with-key} :keyboard-action go-drafts-with-key}
[:span {:class (stl/css :element-title)} (tr "labels.drafts")]]] [:span {:class (stl/css :element-title)} (tr "labels.drafts")]]]
[:li {:class-name (when libs? (stl/css :current))} [:li {:class (stl/css-case :current libs?
:sidebar-nav-item true)}
[:& link {:action go-libs [:& link {:action go-libs
:class (stl/css :sidebar-link)
:keyboard-action go-libs-with-key} :keyboard-action go-libs-with-key}
[:span {:class (stl/css :element-title)} (tr "labels.shared-libraries")]]]]] [:span {:class (stl/css :element-title)} (tr "labels.shared-libraries")]]]]]
[:hr]
[:div {:class (stl/css :sidebar-content-section)} [:div {:class (stl/css :sidebar-content-section)}
[:ul {:class (stl/css :sidebar-nav :no-overflow)} [:ul {:class (stl/css :sidebar-nav)}
[:li {:class-name (when fonts? (stl/css :current))} [:li {:class (stl/css-case :sidebar-nav-item true
:current fonts?)}
[:& link {:action go-fonts [:& link {:action go-fonts
:class (stl/css :sidebar-link)
:keyboard-action go-fonts-with-key :keyboard-action go-fonts-with-key
:data-test "fonts"} :data-test "fonts"}
[:span {:class (stl/css :element-title)} (tr "labels.fonts")]]]]] [:span {:class (stl/css :element-title)} (tr "labels.fonts")]]]]]
[:hr]
[:div {:class (stl/css :sidebar-content-section) [:div {:class (stl/css :sidebar-content-section)
:data-test "pinned-projects"} :data-test "pinned-projects"}
(if (seq pinned-projects) (if (seq pinned-projects)
[:ul {:class (stl/css :sidebar-nav)} [:ul {:class (stl/css :sidebar-nav :pinned-projects)}
(for [item pinned-projects] (for [item pinned-projects]
[:& sidebar-project [:& sidebar-project
{:item item {:item item
@ -700,25 +819,26 @@
:team-id (:id team) :team-id (:id team)
:selected? (= (:id item) (:id project))}])] :selected? (= (:id item) (:id project))}])]
[:div {:class (stl/css :sidebar-empty-placeholder)} [:div {:class (stl/css :sidebar-empty-placeholder)}
[:span {:class (stl/css :icon)} i/pin-refactor] pin-icon
[:span {:class (stl/css :text)} (tr "dashboard.no-projects-placeholder")]])]])) [:span {:class (stl/css :empty-text)} (tr "dashboard.no-projects-placeholder")]])]]))
(mf/defc profile-section (mf/defc profile-section
[{:keys [profile team] :as props}] [{:keys [profile team] :as props}]
(let [show (mf/use-state false) (let [show* (mf/use-state false)
show (deref show*)
photo (cf/resolve-profile-photo-url profile) photo (cf/resolve-profile-photo-url profile)
on-click on-click
(mf/use-callback (mf/use-fn
(fn [section event] (fn [section event]
(dom/stop-propagation event) (dom/stop-propagation event)
(reset! show false) (reset! show* false)
(if (keyword? section) (if (keyword? section)
(st/emit! (rt/nav section)) (st/emit! (rt/nav section))
(st/emit! section)))) (st/emit! section))))
show-release-notes show-release-notes
(mf/use-callback (mf/use-fn
(fn [event] (fn [event]
(let [version (:main cf/version)] (let [version (:main cf/version)]
(st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version version})) (st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version version}))
@ -730,48 +850,48 @@
show-comments? @show-comments* show-comments? @show-comments*
handle-hide-comments handle-hide-comments
(mf/use-callback (mf/use-fn
(fn [] (fn []
(reset! show-comments* false))) (reset! show-comments* false)))
handle-show-comments handle-show-comments
(mf/use-callback (mf/use-fn
(fn [] (fn []
(reset! show-comments* true))) (reset! show-comments* true)))
handle-click handle-click
(mf/use-callback (mf/use-fn
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(swap! show not))) (swap! show* not)))
handle-key-down handle-key-down
(mf/use-callback (mf/use-fn
(fn [event] (fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
(reset! show true)))) (reset! show* true))))
handle-close handle-close
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(reset! show false)) (reset! show* false))
handle-key-down-profile handle-key-down-profile
(mf/use-callback (mf/use-fn
(mf/deps on-click) (mf/deps on-click)
(fn [event] (fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
(on-click :settings-profile event)))) (on-click :settings-profile event))))
handle-click-url handle-click-url
(mf/use-callback (mf/use-fn
(fn [event] (fn [event]
(let [url (-> (dom/get-current-target event) (let [url (-> (dom/get-current-target event)
(dom/get-data "url"))] (dom/get-data "url"))]
(dom/open-new-window url)))) (dom/open-new-window url))))
handle-keydown-url handle-keydown-url
(mf/use-callback (mf/use-fn
(fn [event] (fn [event]
(let [url (-> (dom/get-current-target event) (let [url (-> (dom/get-current-target event)
(dom/get-data "url"))] (dom/get-data "url"))]
@ -779,35 +899,41 @@
(dom/open-new-window url))))) (dom/open-new-window url)))))
handle-show-release-notes handle-show-release-notes
(mf/use-callback (mf/use-fn
(mf/deps show-release-notes) (mf/deps show-release-notes)
(fn [event] (fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
(show-release-notes)))) (show-release-notes))))
handle-feedback-click handle-feedback-click
(mf/use-callback (mf/use-fn
(mf/deps on-click) (mf/deps on-click)
#(on-click :settings-feedback %)) #(on-click :settings-feedback %))
handle-feedback-keydown handle-feedback-keydown
(mf/use-callback (mf/use-fn
(mf/deps on-click) (mf/deps on-click)
(fn [event] (fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
(on-click :settings-feedback event)))) (on-click :settings-feedback event))))
handle-logout-click handle-logout-click
(mf/use-callback (mf/use-fn
(mf/deps on-click) (mf/deps on-click)
#(on-click (du/logout) %)) #(on-click (du/logout) %))
handle-logout-keydown handle-logout-keydown
(mf/use-callback (mf/use-fn
(mf/deps on-click) (mf/deps on-click)
(fn [event] (fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
(on-click (du/logout) event))))] (on-click (du/logout) event))))
handle-set-profile
(mf/use-fn
(mf/deps on-click)
(fn [event]
(on-click :settings-profile event)))]
[:* [:*
(when (and team profile) (when (and team profile)
@ -825,77 +951,89 @@
:on-key-down handle-key-down :on-key-down handle-key-down
:data-test "profile-btn"} :data-test "profile-btn"}
[:img {:src photo [:img {:src photo
:class (stl/css :profile-img)
:alt (:fullname profile)}] :alt (:fullname profile)}]
[:span (:fullname profile)]] [:span {:class (stl/css :profile-fullname)} (:fullname profile)]]
[:& dropdown-menu {:on-close handle-close :show @show} [:& dropdown-menu {:on-close handle-close :show show :list-class (stl/css :profile-dropdown)}
[:ul {:class (stl/css :dropdown)} [:li {:tab-index (if show "0" "-1")
[:li {:tab-index (if @show "0" "-1") :class (stl/css :profile-dropdown-item)
:on-click (partial on-click :settings-profile) :on-click handle-set-profile
:on-key-down handle-key-down-profile :on-key-down handle-key-down-profile
:data-test "profile-profile-opt"} :data-test "profile-profile-opt"}
[:span {:class (stl/css :text)} (tr "labels.your-account")]] (tr "labels.your-account")]
[:li {:class (stl/css :separator) [:li {:class (stl/css :profile-separator)}]
:tab-index (if @show "0" "-1")
[:li {:class (stl/css :profile-dropdown-item)
:tab-index (if show "0" "-1")
:data-url "https://help.penpot.app" :data-url "https://help.penpot.app"
:on-click handle-click-url :on-click handle-click-url
:on-key-down handle-keydown-url :on-key-down handle-keydown-url
:data-test "help-center-profile-opt"} :data-test "help-center-profile-opt"}
[:span {:class (stl/css :text)} (tr "labels.help-center")]] (tr "labels.help-center")]
[:li {:tab-index (if @show "0" "-1") [:li {:tab-index (if show "0" "-1")
:class (stl/css :profile-dropdown-item)
:data-url "https://community.penpot.app" :data-url "https://community.penpot.app"
:on-click handle-click-url :on-click handle-click-url
:on-key-down handle-keydown-url} :on-key-down handle-keydown-url}
[:span {:class (stl/css :text)} (tr "labels.community")]] (tr "labels.community")]
[:li {:tab-index (if @show "0" "-1") [:li {:tab-index (if show "0" "-1")
:class (stl/css :profile-dropdown-item)
:data-url "https://www.youtube.com/c/Penpot" :data-url "https://www.youtube.com/c/Penpot"
:on-click handle-click-url :on-click handle-click-url
:on-key-down handle-keydown-url} :on-key-down handle-keydown-url}
[:span {:class (stl/css :text)} (tr "labels.tutorials")]] (tr "labels.tutorials")]
[:li {:tab-index (if @show "0" "-1") [:li {:tab-index (if show "0" "-1")
:class (stl/css :profile-dropdown-item)
:on-click show-release-notes :on-click show-release-notes
:on-key-down handle-show-release-notes} :on-key-down handle-show-release-notes}
[:span {:class (stl/css :text)} (tr "labels.release-notes")]] (tr "labels.release-notes")]
[:li {:class (stl/css :separator) [:li {:class (stl/css :profile-separator)}]
:tab-index (if @show "0" "-1")
[:li {:class (stl/css :profile-dropdown-item)
:tab-index (if show "0" "-1")
:data-url "https://penpot.app/libraries-templates" :data-url "https://penpot.app/libraries-templates"
:on-click handle-click-url :on-click handle-click-url
:on-key-down handle-keydown-url :on-key-down handle-keydown-url
:data-test "libraries-templates-profile-opt"} :data-test "libraries-templates-profile-opt"}
[:span {:class (stl/css :text)} (tr "labels.libraries-and-templates")]] (tr "labels.libraries-and-templates")]
[:li {:tab-index (if @show "0" "-1") [:li {:tab-index (if show "0" "-1")
:class (stl/css :profile-dropdown-item)
:data-url "https://github.com/penpot/penpot" :data-url "https://github.com/penpot/penpot"
:on-click handle-click-url :on-click handle-click-url
:on-key-down handle-keydown-url} :on-key-down handle-keydown-url}
[:span {:class (stl/css :text)} (tr "labels.github-repo")]] (tr "labels.github-repo")]
[:li {:tab-index (if @show "0" "-1") [:li {:tab-index (if show "0" "-1")
:class (stl/css :profile-dropdown-item)
:data-url "https://penpot.app/terms" :data-url "https://penpot.app/terms"
:on-click handle-click-url :on-click handle-click-url
:on-key-down handle-keydown-url} :on-key-down handle-keydown-url}
[:span {:class (stl/css :text)} (tr "auth.terms-of-service")]] (tr "auth.terms-of-service")]
[:li {:class (stl/css :profile-separator)}]
(when (contains? cf/flags :user-feedback) (when (contains? cf/flags :user-feedback)
[:li {:class (stl/css :separator) [:li {:class (stl/css :profile-dropdown-item)
:tab-index (if @show "0" "-1") :tab-index (if show "0" "-1")
:on-click handle-feedback-click :on-click handle-feedback-click
:on-key-down handle-feedback-keydown :on-key-down handle-feedback-keydown
:data-test "feedback-profile-opt"} :data-test "feedback-profile-opt"}
[:span {:class (stl/css :text)} (tr "labels.give-feedback")]]) (tr "labels.give-feedback")])
[:li {:class (stl/css :separator) [:li {:class (stl/css :profile-dropdown-item :item-with-icon)
:tab-index (if @show "0" "-1") :tab-index (if show "0" "-1")
:on-click handle-logout-click :on-click handle-logout-click
:on-key-down handle-logout-keydown :on-key-down handle-logout-keydown
:data-test "logout-profile-opt"} :data-test "logout-profile-opt"}
[:span {:class (stl/css :icon)} i/exit] exit-icon
[:span {:class (stl/css :text)} (tr "labels.logout")]]]] (tr "labels.logout")]]
(when (and team profile) (when (and team profile)
[:& comments-icon [:& comments-icon

View file

@ -7,498 +7,375 @@
@use "common/refactor/common-refactor.scss" as *; @use "common/refactor/common-refactor.scss" as *;
@use "common/refactor/common-dashboard"; @use "common/refactor/common-dashboard";
// SIDEBAR COMPONENT
.dashboard-sidebar { .dashboard-sidebar {
grid-row: 1 / span 2; grid-row: 1 / span 2;
grid-column: 1 / span 2; grid-column: 1 / span 2;
display: grid;
background-color: var(--panel-background-color); grid-template-rows: 1fr auto;
border-right: $s-1 solid $db-quaternary; height: 100%;
margin: 0 $s-16 0 0; width: 100%;
padding: $s-16 0 0 0; padding: $s-16 0 0 0;
margin: 0 $s-16 0 0;
border-right: $s-1 solid var(--panel-border-color);
background-color: var(--panel-background-color);
z-index: $z-index-1; z-index: $z-index-1;
display: flex;
flex-direction: column;
height: 100%;
} }
//SIDEBAR CONTENT COMPONENT
.sidebar-content { .sidebar-content {
display: flex; display: grid;
flex-direction: column; grid-template-rows: auto auto auto auto 1fr;
gap: $s-24;
height: 100%; height: 100%;
overflow-y: auto;
padding: 0; padding: 0;
overflow-y: auto;
hr {
border-color: transparent;
margin: $s-12 $s-16;
}
} }
// SIDEBAR TEAM SWITCH
.sidebar-team-switch { .sidebar-team-switch {
position: relative; position: relative;
display: flex;
margin: $s-4 $s-16; margin: $s-4 $s-16;
}
.switch-content { .switch-content {
background-color: $db-tertiary; display: grid;
border-radius: $br-8; grid-template-columns: 1fr auto;
align-items: center;
height: $s-48; height: $s-48;
display: flex;
width: 100%; width: 100%;
border: $s-1 solid $db-tertiary; border-radius: $br-8;
border: $s-1 solid var(--menu-background-color);
background-color: var(--menu-background-color);
}
.current-team {
@include buttonStyle;
display: grid;
align-items: center; align-items: center;
grid-template-columns: 1fr auto;
svg { gap: $s-8;
fill: #8f9da3;
}
}
.switch-icon {
display: flex;
align-items: center;
justify-content: center;
svg {
fill: $df-secondary;
width: $s-12;
height: $s-12;
}
}
.current-team {
height: 100%; height: 100%;
cursor: pointer;
display: flex;
align-items: center;
flex-grow: 1;
font-size: $fs-14;
padding: 0 $s-12; padding: 0 $s-12;
background-color: transparent; }
border: none;
}
.team-name { .team-name {
flex-grow: 1; display: grid;
display: flex; align-items: center;
grid-template-columns: auto 1fr;
gap: $s-12;
height: $s-40; height: $s-40;
align-items: center; }
}
.team-text { .team-text {
color: $df-primary; @include textEllipsis;
@include smallTitleTipography;
width: $s-144; width: $s-144;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-align: left; text-align: left;
} color: var(--menu-foreground-color-hover);
}
.team-icon {
display: flex;
align-items: center;
padding-right: $s-12;
img {
border-radius: 50%;
flex-shrink: 0;
height: $s-24;
width: $s-24;
}
// This icon still use the old svg
.penpot-icon {
@include flexCenter;
svg { svg {
fill: var(--icon-foreground);
width: $s-24; width: $s-24;
height: $s-24; height: $s-24;
} }
} }
.switch-options { .team-picture {
@include flexCenter;
border-radius: 50%;
height: $s-24;
width: $s-24;
}
.arrow-icon {
@extend .button-icon;
transform: rotate(90deg);
stroke: var(--icon-foreground);
}
.switch-options {
@include buttonStyle; @include buttonStyle;
@include flexCenter; @include flexCenter;
max-width: $s-24; max-width: $s-24;
min-width: $s-28; min-width: $s-28;
height: 100%; height: 100%;
border-left: $s-1 solid $db-primary; border-left: $s-1 solid var(--panel-background-color);
background-color: transparent; background-color: transparent;
}
svg { .menu-icon {
fill: $df-secondary; @extend .button-icon;
width: $s-16; stroke: var(--icon-foreground);
height: $s-12; }
}
}
.dropdown { // DROPDOWNS
.teams-dropdown {
@extend .menu-dropdown;
left: 0;
top: $s-52;
height: fit-content;
max-height: $s-480;
min-width: $s-248;
width: 100%;
overflow-x: hidden;
overflow-y: auto;
}
.team-dropdown-item {
@extend .menu-item-base;
display: grid;
grid-template-columns: $s-24 1fr auto;
gap: $s-8;
height: $s-40;
}
.action {
--sidebar-action-icon-color: var(--icon-foreground);
--sidebar-icon-backgroun-color: var(--color-background-secondary);
&:hover {
--sidebar-action-icon-color: var(--color-background-secondary);
--sidebar-icon-backgroun-color: var(--color-accent-primary);
}
}
.icon-wrapper {
@include flexCenter;
width: $s-24;
height: $s-24;
margin-right: $s-12;
border-radius: 50%;
background-color: var(--sidebar-icon-backgroun-color);
}
.add-icon {
@extend .button-icon;
width: $s-24;
height: $s-24;
stroke: var(--sidebar-action-icon-color);
}
.team-separator {
border-top: $s-1 solid var(--dropdown-separator-color);
margin: 0;
}
.tick-icon {
@extend .button-icon-small;
stroke: var(--icon-foreground);
}
.options-dropdown {
@extend .menu-dropdown;
right: $s-2; right: $s-2;
top: $s-52; top: $s-52;
max-height: $s-480; max-height: $s-480;
&:not(.teams-dropdown) { &:not(.teams-dropdown) {
min-width: $s-160; min-width: $s-160;
} }
}
} }
.dropdown { .team-options-item {
@include menuShadow; @extend .menu-item-base;
position: absolute;
z-index: $z-index-4;
background-color: $db-tertiary;
border: $s-1 solid $db-quaternary;
border-radius: $br-8;
.separator {
border-color: transparent;
margin-top: $s-12;
}
li {
border-radius: $br-8;
height: $s-40; height: $s-40;
margin: $s-6; }
display: flex; .team-option-separator {
align-items: center; height: $s-1;
cursor: pointer;
font-size: $fs-14;
padding: $s-6 $s-16;
.warning {
color: var(--element-foreground-warning);
}
&:hover {
background-color: $db-quaternary;
}
svg {
height: $s-12;
width: $s-12;
}
}
hr {
border-color: transparent;
margin: 0; margin: 0;
} border-top: $s-1 solid var(--dropdown-separator-color);
&.options-dropdown {
li {
color: $df-primary;
&.warning {
color: var(--element-foreground-warning);
}
}
}
}
.teams-dropdown {
background-color: $db-tertiary;
border-radius: $br-8;
border: $s-1 solid $db-quaternary;
min-width: $s-248;
left: 0;
top: $s-52;
max-height: $s-480;
overflow-x: hidden;
overflow-y: auto;
li {
border-radius: $br-8;
height: $s-40;
padding: 0 $s-6;
margin: $s-6;
svg {
fill: $df-secondary;
}
&:hover {
background-color: $db-quaternary;
.team-icon {
&.new-team {
background-color: $da-primary;
color: $db-primary;
svg {
fill: $db-secondary;
}
}
}
}
.team-icon {
display: flex;
align-items: center;
}
.team-text {
color: $df-primary;
width: $s-168;
}
&.action {
.team-icon {
background-color: #2e3434;
background-color: var(--new-team-button-background-color);
border-radius: 50%;
height: $s-24;
margin-right: $s-12;
padding: $s-6;
width: $s-24;
svg {
height: $s-12;
width: $s-12;
}
}
}
}
}
.sidebar-empty-placeholder {
padding: $s-12;
color: $df-secondary;
display: flex;
align-items: center;
.icon {
padding: 0 $s-12;
svg {
fill: none;
stroke: currentColor;
width: $s-12;
height: $s-12;
}
}
.text {
font-size: $fs-12;
}
}
.sidebar-search {
align-items: center;
border: $s-1 solid transparent;
display: flex;
margin: $s-6 $s-16;
background-color: $db-tertiary;
border-radius: $br-8;
margin-bottom: $s-32;
margin-top: 0;
position: relative;
.input-text {
background: transparent;
border: 0;
font-size: $fs-14;
margin: 0;
width: 100%;
height: $s-40;
border-radius: $br-8;
color: $df-primary;
max-width: 100%;
padding: $s-6 $s-12;
&:focus,
&:focus-within {
border: $s-1 solid $da-primary;
}
}
::placeholder {
color: $df-secondary;
}
.search,
.clear-search {
align-items: center;
cursor: pointer;
display: flex;
height: $s-24;
margin-left: auto;
padding: 0 $s-8;
width: $s-32;
position: absolute;
top: calc(50% - $s-12);
right: $s-2;
svg {
fill: $df-secondary;
height: $s-16;
width: $s-16;
}
}
.clear-search svg {
transform: rotate(45deg);
&:hover {
fill: $da-primary;
}
}
} }
// Sections
.sidebar-nav { .sidebar-nav {
display: flex;
flex-direction: column;
overflow-y: auto;
margin: 0; margin: 0;
user-select: none; user-select: none;
overflow: none;
}
&.no-overflow { .pinned-projects {
overflow: unset; overflow-y: auto;
} }
& > li { .sidebar-nav-item {
align-items: center;
cursor: pointer; cursor: pointer;
display: flex; &:hover {
flex-shrink: 0; background-color: var(--sidebar-element-background-color-hover);
span {
&.project-element { color: var(--sidebar-element-foreground-color-hover);
padding: $s-8 $s-8 $s-8 $s-24; }
} }
a { &.current {
background-color: var(--sidebar-element-background-color-selected);
.element-title {
color: var(--sidebar-element-foreground-color-selected);
}
}
}
.recent-projects svg {
stroke: var(--main-icon-foreground);
}
.sidebar-link {
display: block;
padding: $s-8 $s-8 $s-8 $s-24; padding: $s-8 $s-8 $s-8 $s-24;
font-weight: $fw400; font-weight: $fw400;
width: 100%; width: 100%;
&:hover { &:hover {
text-decoration: none; text-decoration: none;
} }
} }
svg { .project-element {
fill: $db-secondary; padding: $s-8 $s-8 $s-8 $s-24;
margin-right: $s-8; }
height: $s-12;
width: $s-12;
}
.element-title { .element-title {
color: $df-secondary; @include textEllipsis;
color: var(--sidebar-element-foreground-color);
font-size: $fs-14; font-size: $fs-14;
overflow: hidden; }
text-overflow: ellipsis;
white-space: nowrap;
}
&.recent-projects { // Pinned projects
svg {
fill: $df-primary;
}
}
input.element-title { .sidebar-empty-placeholder {
border: 0; padding: $s-12;
height: $s-32; color: var(--empty-message-foreground-color);
padding: $s-6; display: flex;
margin: 0; align-items: center;
}
.pin-icon {
@extend .button-icon-small;
stroke: var(--icon-foreground);
margin: 0 $s-12;
}
.empty-text {
font-size: $fs-12;
}
// Search
.sidebar-search {
position: relative;
display: grid;
grid-template-columns: 1fr;
align-items: center;
border: $s-1 solid transparent;
margin: 0 $s-16;
border-radius: $br-8;
background-color: var(--search-bar-input-background-color);
}
.input-text {
@include smallTitleTipography;
height: $s-40;
width: 100%; width: 100%;
background-color: $df-primary; padding: $s-6 $s-12;
}
.close {
background-color: $df-primary;
cursor: pointer;
padding-left: $s-6;
svg {
fill: $df-secondary;
height: $s-16;
transform: rotate(45deg) translateY(7px);
width: $s-16;
margin: 0; margin: 0;
} border: transparent;
} border-radius: $br-8;
background: transparent;
color: var(--search-bar-foreground-color);
.element-subtitle { &:focus,
color: $df-secondary; &:focus-within,
font-style: italic; &:focus-visible {
} outline: none;
border: $s-1 solid var(--search-bar-input-border-color-focus);
&:hover {
background-color: $db-secondary;
span {
color: $da-primary;
}
}
&.current {
background-color: $db-quaternary;
.element-title {
color: $da-primary;
}
} }
::placeholder {
color: var(--search-bar-placeholder-foreground-color);
} }
} }
.search-btn {
@include buttonStyle;
@include flexCenter;
position: absolute;
right: 0;
height: $s-24;
width: $s-32;
padding: 0 $s-8;
}
.search-icon,
.clear-search-btn {
@extend .button-icon;
--sidebar-search-foreground-color: var(--search-bar-icon-foreground-color);
stroke: var(--sidebar-search-foreground-color);
}
.clear-search-btn:hover {
--sidebar-search-foreground-color: var(--search-bar-icon-foreground-color-hover);
}
// Profile
.profile-section { .profile-section {
align-items: center;
cursor: pointer;
display: flex;
padding: $s-12 $s-16;
position: relative; position: relative;
display: grid;
background-color: $db-tertiary; grid-template-columns: 1fr auto;
border-top: $s-1 solid $db-quaternary; padding: $s-12 $s-16;
border-top: $s-1 solid var(--panel-border-color);
.profile { background-color: var(--profile-section-background-color);
align-items: center;
cursor: pointer; cursor: pointer;
display: flex; }
flex-grow: 1;
span { .profile {
display: grid;
grid-template-columns: auto 1fr;
gap: $s-8;
cursor: pointer;
}
.profile-fullname {
@include smallTitleTipography;
@include text-ellipsis; @include text-ellipsis;
color: $df-primary; align-self: center;
margin: $s-12;
font-size: $fs-14;
max-width: $s-160; max-width: $s-160;
} color: var(--profile-foreground-color);
}
img { .profile-img {
border-radius: 50%;
flex-shrink: 0;
height: $s-40; height: $s-40;
width: $s-40; width: $s-40;
} border-radius: $br-circle;
svg { }
height: $s-12;
margin-left: auto; .profile-dropdown {
margin-right: $s-8; @extend .menu-dropdown;
width: $s-12; left: $s-16;
} bottom: $s-72;
} min-width: $s-252;
// TODO ADD animation fadeInUp
.dropdown { }
left: $s-16;
bottom: $s-44; .profile-dropdown-item {
background-color: var(--profile-drowpdown-background-color); @extend .menu-item-base;
border: $s-1 solid $db-tertiary; @include smallTitleTipography;
border-radius: $br-8; height: $s-40;
min-width: $s-252; padding: $s-8 $s-16;
}
@include animation(0, 0.2s, fadeInUp);
.profile-separator {
li { height: $s-6;
font-size: $fs-14; }
padding: $s-8 $s-16;
.item-with-icon {
svg { display: grid;
fill: $df-secondary; grid-template-columns: auto 1fr;
margin-right: $s-8; gap: $s-8;
}
height: $s-12;
width: $s-12; .exit-icon {
} @extend .button-icon;
stroke: var(--icon-foreground);
.text {
color: $df-primary;
}
&.separator {
border-top: $s-1 solid transparent;
}
}
}
} }

View file

@ -89,7 +89,9 @@
[:div {:class (stl/css :modal-overlay)} [:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-container)} [:div {:class (stl/css :modal-container)}
[:& fm/form {:form form :on-submit on-submit} [:& fm/form {:form form
:on-submit on-submit
:class (stl/css :team-form)}
[:div {:class (stl/css :modal-header)} [:div {:class (stl/css :modal-header)}
(if team (if team

View file

@ -31,6 +31,10 @@
margin-bottom: $s-24; margin-bottom: $s-24;
} }
.team-form {
min-width: $s-400;
}
.group-name-input { .group-name-input {
@extend .input-element-label; @extend .input-element-label;
label { label {
@ -44,7 +48,6 @@
input { input {
@include bodySmallTypography; @include bodySmallTypography;
margin-top: $s-8;
} }
} }
} }

View file

@ -348,6 +348,7 @@
(def ^:icon easing-ease-in-out-refactor (icon-xref :easing-ease-in-out-refactor)) (def ^:icon easing-ease-in-out-refactor (icon-xref :easing-ease-in-out-refactor))
(def ^:icon effects-refactor (icon-xref :effects-refactor)) (def ^:icon effects-refactor (icon-xref :effects-refactor))
(def ^:icon elipse-refactor (icon-xref :elipse-refactor)) (def ^:icon elipse-refactor (icon-xref :elipse-refactor))
(def ^:icon exit-refactor (icon-xref :exit-refactor))
(def ^:icon expand-refactor (icon-xref :expand-refactor)) (def ^:icon expand-refactor (icon-xref :expand-refactor))
(def ^:icon feedback-refactor (icon-xref :feedback-refactor)) (def ^:icon feedback-refactor (icon-xref :feedback-refactor))
(def ^:icon fill-content-refactor (icon-xref :fill-content-refactor)) (def ^:icon fill-content-refactor (icon-xref :fill-content-refactor))
@ -394,6 +395,7 @@
(def ^:icon layers-refactor (icon-xref :layers-refactor)) (def ^:icon layers-refactor (icon-xref :layers-refactor))
(def ^:icon locate-refactor (icon-xref :locate-refactor)) (def ^:icon locate-refactor (icon-xref :locate-refactor))
(def ^:icon lock-refactor (icon-xref :lock-refactor)) (def ^:icon lock-refactor (icon-xref :lock-refactor))
(def ^:icon logo-refactor (icon-xref :penpot-logo-icon)) ;; This icon will not change
(def ^:icon library-refactor (icon-xref :library-refactor)) (def ^:icon library-refactor (icon-xref :library-refactor))
(def ^:icon margin-bottom-refactor (icon-xref :margin-bottom-refactor)) (def ^:icon margin-bottom-refactor (icon-xref :margin-bottom-refactor))
(def ^:icon margin-left-refactor (icon-xref :margin-left-refactor)) (def ^:icon margin-left-refactor (icon-xref :margin-left-refactor))

View file

@ -33,6 +33,7 @@
:links (:links message) :links (:links message)
:content (:content message)} :content (:content message)}
is-context-msg (and (nil? (:timeout message)) (nil? (:actions message)))
is-toast-msg (or (= :toast (:notification-type message)) (some? (:timeout message))) is-toast-msg (or (= :toast (:notification-type message)) (some? (:timeout message)))
is-inline-msg (or (= :inline (:notification-type message)) (and (some? (:position message)) (= :floating (:position message))))] is-inline-msg (or (= :inline (:notification-type message)) (and (some? (:position message)) (= :floating (:position message))))]
@ -42,5 +43,7 @@
[:& toast-notification toast-message] [:& toast-notification toast-message]
is-inline-msg is-inline-msg
[:& inline-notification inline-message] [:& inline-notification inline-message]
is-context-msg
[:& context-notification context-message]
:else :else
[:& context-notification context-message])))) [:& toast-notification toast-message]))))

View file

@ -44,10 +44,10 @@
} }
.info { .info {
--bg-color: var(--alert-background-color-info); --toast-notification-bg-color: var(--alert-background-color-info);
--fg-color: var(--alert-text-foreground-color-info); --toast-notification-fg-color: var(--alert-text-foreground-color-info);
--icon-color: var(--alert-icon-foreground-color-info); --toast-notification-icon-color: var(--alert-icon-foreground-color-info);
--border-color: var(--alert-border-color-info); --toast-notification-border-color: var(--alert-border-color-info);
} }
.default { .default {

View file

@ -21,7 +21,6 @@
(let [valign (:vertical-align node "top") (let [valign (:vertical-align node "top")
base #js {:height (fmt/format-pixels height) base #js {:height (fmt/format-pixels height)
:width (fmt/format-pixels width) :width (fmt/format-pixels width)
:fontFamily "sourcesanspro"
:display "flex" :display "flex"
:whiteSpace "break-spaces"}] :whiteSpace "break-spaces"}]
(cond-> base (cond-> base

View file

@ -135,6 +135,7 @@
(fn [_] (fn [_]
(wapi/write-to-clipboard current-link) (wapi/write-to-clipboard current-link)
(st/emit! (msg/show {:type :info (st/emit! (msg/show {:type :info
:notification-type :toast
:content (tr "common.share-link.link-copied-success") :content (tr "common.share-link.link-copied-success")
:timeout 1000}))) :timeout 1000})))

View file

@ -107,9 +107,9 @@
(-> (image-size href) (-> (image-size href)
(p/then (p/then
(fn [{:keys [width height]}] (fn [{:keys [width height]}]
(when (or (not (mth/close? width fixed-width 2)) (when (or (not (mth/close? width fixed-width 5))
(not (mth/close? height fixed-height 2))) (not (mth/close? height fixed-height 5)))
(st/emit! (dwt/request-thumbnail file-id page-id frame-id "frame")))))))) (st/emit! (dwt/request-thumbnail file-id page-id frame-id "frame" "check-thumbnail-size"))))))))
(defn root-frame-wrapper-factory (defn root-frame-wrapper-factory
[shape-wrapper] [shape-wrapper]
@ -175,7 +175,7 @@
(mf/with-effect [] (mf/with-effect []
(when-not (some? thumbnail-uri) (when-not (some? thumbnail-uri)
(tm/schedule-on-idle (tm/schedule-on-idle
#(st/emit! (dwt/request-thumbnail file-id page-id frame-id "frame")))) #(st/emit! (dwt/request-thumbnail file-id page-id frame-id "frame" "root-frame"))))
#(when-let [task (mf/ref-val task-ref)] #(when-let [task (mf/ref-val task-ref)]
(d/close! task))) (d/close! task)))

View file

@ -224,10 +224,10 @@
:on-double-click rename-color-clicked} :on-double-click rename-color-clicked}
(if (= (:name color) default-name) (if (= (:name color) default-name)
[:span {:class (stl/css :default-name-only)} default-name] [:span {:class (stl/css :default-name)} default-name]
[:* [:*
[:span {:class (stl/css :name)} (:name color)] (:name color)
[:span {:class (stl/css :default-name)} default-name]])]) [:span {:class (stl/css :default-name :default-name-with-color)} default-name]])])
(when local? (when local?
[:& cmm/assets-context-menu [:& cmm/assets-context-menu

View file

@ -6,10 +6,15 @@
@import "refactor/common-refactor.scss"; @import "refactor/common-refactor.scss";
// TODO: we should be using subgrid in the common "assets component" to avoid
// using this SCSS variable here (we cannot use a CSS var in this CSS module because
// the elements are not part of the same cascade).
$assets-button-width: $s-28;
.assets-btn { .assets-btn {
@extend .button-tertiary; @extend .button-tertiary;
height: $s-32; height: $s-32;
width: $s-28; width: $assets-button-width;
padding: 0; padding: 0;
border-radius: $br-8; border-radius: $br-8;
svg { svg {
@ -28,10 +33,12 @@
.asset-list-item { .asset-list-item {
position: relative; position: relative;
display: flex; display: grid;
grid-template-columns: auto 1fr #{$assets-button-width};
align-items: center; align-items: center;
height: $s-32; height: $s-32;
padding: $s-8; padding: $s-8;
padding-inline-end: 0;
margin-bottom: $s-4; margin-bottom: $s-4;
border-radius: $br-8; border-radius: $br-8;
background-color: var(--assets-item-background-color); background-color: var(--assets-item-background-color);
@ -48,7 +55,6 @@
@include bodySmallTypography; @include bodySmallTypography;
@include removeInputStyle; @include removeInputStyle;
flex-grow: 1; flex-grow: 1;
height: $s-28;
max-width: calc(var(--parent-size) - (var(--depth) * var(--layer-indentation-size))); max-width: calc(var(--parent-size) - (var(--depth) * var(--layer-indentation-size)));
margin: 0; margin: 0;
color: var(--layer-row-foreground-color); color: var(--layer-row-foreground-color);
@ -63,25 +69,23 @@
@include flexCenter; @include flexCenter;
height: 100%; height: 100%;
justify-content: flex-start; justify-content: flex-start;
margin-right: $s-4; margin-inline-end: $s-4;
} }
.name-block { .name-block {
@include bodySmallTypography; @include bodySmallTypography;
display: grid;
grid-template-columns: auto 1fr;
margin: 0;
overflow: hidden;
.default-name-only,
.name {
color: var(--assets-item-name-foreground-color);
margin-right: $s-6;
@include textEllipsis; @include textEllipsis;
} margin: 0;
.default-name { color: var(--assets-item-name-foreground-color);
min-width: 0; }
.default-name {
margin-inline-start: $s-4;
color: var(--assets-item-name-foreground-color-rest); color: var(--assets-item-name-foreground-color-rest);
} }
.default-name-with-color {
margin-left: $s-6;
} }
.element-name { .element-name {

View file

@ -380,7 +380,8 @@
{:value "" :label (tr "workspace.options.interaction-none")})] {:value "" :label (tr "workspace.options.interaction-none")})]
destination-options destination-options
(mf/with-memo [frames-opts default-opts] (mf/with-memo [frames-opts default-opts]
(d/concat-vec default-opts frames-opts)) (let [sorted-frames-opts (sort-by :label frames-opts)]
(d/concat-vec default-opts sorted-frames-opts)))
shape-parents-opts (get-shared-frames-options shape-parents) shape-parents-opts (get-shared-frames-options shape-parents)

View file

@ -918,8 +918,11 @@
on-gap-change on-gap-change
(fn [multiple? type val] (fn [multiple? type val]
(let [val (mth/finite val 0)] (let [val (mth/finite val 0)]
(if ^boolean multiple? (cond
^boolean multiple?
(st/emit! (dwsl/update-layout ids {:layout-gap {:row-gap val :column-gap val}})) (st/emit! (dwsl/update-layout ids {:layout-gap {:row-gap val :column-gap val}}))
(some? type)
(st/emit! (dwsl/update-layout ids {:layout-gap {type val}}))))) (st/emit! (dwsl/update-layout ids {:layout-gap {type val}})))))
;; Padding ;; Padding
@ -941,7 +944,7 @@
(and (= type :simple) (= prop :p2)) (and (= type :simple) (= prop :p2))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}})) (st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}}))
:else (some? prop)
(st/emit! (dwsl/update-layout ids {:layout-padding {prop val}})))))) (st/emit! (dwsl/update-layout ids {:layout-padding {prop val}}))))))
;; Grid-direction ;; Grid-direction
@ -1126,16 +1129,16 @@
:on-change on-column-justify-change}] :on-change on-column-justify-change}]
[:& justify-grid-row {:is-column false [:& justify-grid-row {:is-column false
:value grid-justify-content-row :value grid-justify-content-row
:on-change on-row-justify-change}]]] :on-change on-row-justify-change}]]
[:div {:class (stl/css :row)} [:div {:class (stl/css :row)}
[:& gap-section {:on-change on-gap-change [:& gap-section {:on-change on-gap-change
:value (:layout-gap values)}]] :value (:layout-gap values)}]]
[:div {:class (stl/css :row :padding-section)} [:div {:class (stl/css :row :padding-section)}
[:& padding-section {:value (:layout-padding values) [:& padding-section {:value (:layout-padding values)
:type (:layout-padding-type values) :type (:layout-padding-type values)
:on-change-style on-padding-type-change :on-change-style on-padding-type-change
:on-change on-padding-change}]] :on-change on-padding-change}]]]
nil))])) nil))]))
@ -1156,9 +1159,10 @@
(mf/use-fn (mf/use-fn
(mf/deps ids) (mf/deps ids)
(fn [multiple? type val] (fn [multiple? type val]
(let [val (mth/finite val 0)]
(if multiple? (if multiple?
(st/emit! (dwsl/update-layout ids {:layout-gap {:row-gap val :column-gap val}})) (st/emit! (dwsl/update-layout ids {:layout-gap {:row-gap val :column-gap val}}))
(st/emit! (dwsl/update-layout ids {:layout-gap {type val}}))))) (st/emit! (dwsl/update-layout ids {:layout-gap {type val}}))))))
;; Padding ;; Padding
on-padding-type-change on-padding-type-change
@ -1169,6 +1173,7 @@
on-padding-change on-padding-change
(fn [type prop val] (fn [type prop val]
(let [val (mth/finite val 0)]
(cond (cond
(and (= type :simple) (= prop :p1)) (and (= type :simple) (= prop :p1))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val}})) (st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val}}))
@ -1177,7 +1182,7 @@
(st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}})) (st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}}))
:else :else
(st/emit! (dwsl/update-layout ids {:layout-padding {prop val}})))) (st/emit! (dwsl/update-layout ids {:layout-padding {prop val}})))))
;; Align grid ;; Align grid
align-items-row (:layout-align-items values) align-items-row (:layout-align-items values)
@ -1308,7 +1313,8 @@
:on-change on-row-justify-change}] :on-change on-row-justify-change}]
[:button {:on-click handle-locate-grid [:button {:on-click handle-locate-grid
:class (stl/css :locate-button)} :class (stl/css :locate-button)
:title (tr "workspace.layout_grid.editor.top-bar.locate.tooltip")}
i/locate-refactor]] i/locate-refactor]]
[:div {:class (stl/css :row)} [:div {:class (stl/css :row)}

View file

@ -736,20 +736,23 @@
[track-type value] [track-type value]
(cond (cond
(str/ends-with? value "%") (str/ends-with? value "%")
[:percent value-int] [:percent (d/nilv value-int 50)]
(str/ends-with? value "FR") (str/ends-with? value "FR")
[:flex value-int] [:flex (d/nilv value-int 1)]
(some? value-int) (some? value-int)
[:fixed value-int] [:fixed (d/nilv value-int 100)]
(or (= value "AUTO") (= "" value)) :else
[:auto nil])] [:auto nil])
track-data (when (some? track-type) {:type track-type :value value})]
(dom/set-value! (mf/ref-val track-input-ref) (format-size track-data))
(if (some? track-type) (if (some? track-type)
(do (st/emit! (dwsl/change-layout-track [(:id shape)] type index {:type track-type :value value})) (do (st/emit! (dwsl/change-layout-track [(:id shape)] type index track-data))
(dom/set-data! target "default-value" (format-size {:type track-type :value value}))) (dom/set-data! target "default-value" (format-size track-data)))
(obj/set! target "value" (dom/get-attribute target "data-default-value")))))) (obj/set! target "value" (dom/get-attribute target "data-default-value"))))))
handle-keydown-track-input handle-keydown-track-input

View file

@ -3508,6 +3508,9 @@ msgstr "Editing grid"
msgid "workspace.layout_grid.editor.top-bar.locate" msgid "workspace.layout_grid.editor.top-bar.locate"
msgstr "Locate" msgstr "Locate"
msgid "workspace.layout_grid.editor.top-bar.locate.tooltip"
msgstr "Locate grid layout"
msgid "workspace.layout_grid.editor.top-bar.done" msgid "workspace.layout_grid.editor.top-bar.done"
msgstr "Done" msgstr "Done"

View file

@ -3570,6 +3570,9 @@ msgstr "Editando rejilla"
msgid "workspace.layout_grid.editor.top-bar.locate" msgid "workspace.layout_grid.editor.top-bar.locate"
msgstr "Mostrar" msgstr "Mostrar"
msgid "workspace.layout_grid.editor.top-bar.locate.tooltip"
msgstr "Mostrar grid layout"
msgid "workspace.layout_grid.editor.top-bar.done" msgid "workspace.layout_grid.editor.top-bar.done"
msgstr "Hecho" msgstr "Hecho"