Handsoff mode basic structure.

This commit is contained in:
alonso.torres 2020-10-21 09:31:48 +02:00 committed by Hirunatan
parent aaaf099a3f
commit 04f620ec00
24 changed files with 720 additions and 25 deletions

View file

@ -51,7 +51,11 @@
:page-id page-id
:file-id file-id
:interactions-mode :hide
:show-interactions? false}))
:show-interactions? false
:selected #{}
:collapsed #{}
:hover #{}}))
ptk/WatchEvent
(watch [_ state stream]
@ -170,24 +174,25 @@
ptk/WatchEvent
(watch [_ state stream]
(let [route (:route state)
screen (-> route :data :name keyword)
qparams (get-in route [:params :query])
pparams (get-in route [:params :path])
index (d/parse-integer (:index qparams))]
(when (pos? index)
(rx/of (rt/nav :viewer pparams (assoc qparams :index (dec index)))))))))
(rx/of (rt/nav screen pparams (assoc qparams :index (dec index)))))))))
(def select-next-frame
(ptk/reify ::select-prev-frame
ptk/WatchEvent
(watch [_ state stream]
(let [route (:route state)
screen (-> route :data :name keyword)
qparams (get-in route [:params :query])
pparams (get-in route [:params :path])
index (d/parse-integer (:index qparams))
total (count (get-in state [:viewer-data :frames]))]
(when (< index (dec total))
(rx/of (rt/nav :viewer pparams (assoc qparams :index (inc index)))))))))
(rx/of (rt/nav screen pparams (assoc qparams :index (inc index)))))))))
(defn set-interactions-mode
[mode]
@ -249,3 +254,36 @@
"shift+2" #(st/emit! zoom-to-200)
"left" #(st/emit! select-prev-frame)
"right" #(st/emit! select-next-frame)})
(defn deselect-all []
(ptk/reify ::deselect-all
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:viewer-local :selected] #{}))))
(defn select-shape
([id] (select-shape id false))
([id toggle?]
(ptk/reify ::select-shape
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:viewer-local :selected] #{id}))))))
;; TODO
(defn collapse-all []
(ptk/reify ::collapse-all))
(defn toggle-collapse [id]
(ptk/reify ::toggle-collapse
ptk/UpdateEvent
(update [_ state]
(let [toggled? (contains? (get-in state [:viewer-local :collapsed]) id)]
(update-in state [:viewer-local :collapsed] (if toggled? disj conj) id)))))
(defn hover-shape [id hover?]
(ptk/reify ::hover-shape
ptk/UpdateEvent
(update [_ state]
(update-in state [:viewer-local :hover] (if hover? conj disj) id))))

View file

@ -27,6 +27,7 @@
[app.main.ui.settings :as settings]
[app.main.ui.static :refer [not-found-page not-authorized-page]]
[app.main.ui.viewer :refer [viewer-page]]
[app.main.ui.viewer.handoff :refer [handoff]]
[app.main.ui.workspace :as workspace]
[app.util.i18n :as i18n :refer [tr t]]
[app.util.timers :as ts]
@ -53,6 +54,7 @@
["/options" :settings-options]]
["/view/:file-id/:page-id" :viewer]
["/handoff/:file-id/:page-id" :handoff]
["/not-found" :not-found]
["/not-authorized" :not-authorized]
@ -127,6 +129,14 @@
:index index
:token token}])
:handoff
(let [index (d/parse-integer (get-in route [:params :query :index]))
file-id (uuid (get-in route [:params :path :file-id]))
page-id (uuid (get-in route [:params :path :page-id]))]
[:& handoff {:page-id page-id
:file-id file-id
:index index}])
:render-object
(do
(let [file-id (uuid (get-in route [:params :path :file-id]))

View file

@ -17,7 +17,7 @@
(def align-middle (icon-xref :align-middle))
(def align-top (icon-xref :align-top))
(def alignment (icon-xref :alignment))
(def arrow (icon-xref :arrow))
#_(def arrow (icon-xref :arrow))
(def arrow-down (icon-xref :arrow-down))
(def arrow-end (icon-xref :arrow-end))
(def arrow-slide (icon-xref :arrow-slide))
@ -91,7 +91,7 @@
(def rotate (icon-xref :rotate))
(def ruler (icon-xref :ruler))
(def ruler-tool (icon-xref :ruler-tool))
(def save (icon-xref :save))
#_(def save (icon-xref :save))
(def search (icon-xref :search))
(def shape-halign-center (icon-xref :shape-halign-center))
(def shape-halign-left (icon-xref :shape-halign-left))
@ -128,6 +128,7 @@
(def picker-ramp (icon-xref :picker-ramp))
(def checkbox-checked (icon-xref :checkbox-checked))
(def checkbox-unchecked (icon-xref :checkbox-unchecked))
(def code (icon-xref :code))
(def loader-pencil
(mf/html
@ -149,9 +150,10 @@
(mf/defc debug-icons-preview
{::mf/wrap-props false}
[props]
[:section.debug-icons-preview
[:section.debug-icons-preview {:style {:background-color "black"}}
(for [[key val] (sort-by first (ns-publics 'app.main.ui.icons))]
(when (not= key 'debug-icons-preview)
[:div.icon-item {:key key}
[:div.icon-item {:key key
:style {:fill "white"}}
(deref val)
[:span (pr-str key)]]))])

View file

@ -92,10 +92,12 @@
:toggle-fullscreen toggle-fullscreen
:fullscreen? fullscreen?
:local local
:index index}]
:index index
:screen :viewer}]
[:div.viewer-content {:on-click on-click}
(when (:show-thumbnails local)
[:& thumbnails-panel {:index index
[:& thumbnails-panel {:screen :viewer
:index index
:data data}])
[:& main-panel {:data data
:local local

View file

@ -0,0 +1,121 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff
(:require
[rumext.alpha :as mf]
[beicon.core :as rx]
[goog.events :as events]
[okulary.core :as l]
[app.common.exceptions :as ex]
[app.util.data :refer [classnames]]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [t tr]]
[app.main.data.viewer :as dv]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.main.ui.keyboard :as kbd]
[app.main.ui.viewer.header :refer [header]]
[app.main.ui.viewer.thumbnails :refer [thumbnails-panel]]
[app.main.ui.viewer.handoff.render :refer [render-frame-svg]]
[app.main.ui.viewer.handoff.layers-sidebar :refer [layers-sidebar]]
[app.main.ui.viewer.handoff.attributes-sidebar :refer [attributes-sidebar]])
(:import goog.events.EventType))
(defn handle-select-frame [frame]
#(do (dom/prevent-default %)
(dom/stop-propagation %)
(st/emit! (dv/select-shape (:id frame)))))
(mf/defc render-panel
[{:keys [data local index]}]
(let [locale (mf/deref i18n/locale)
frames (:frames data [])
objects (:objects data)
frame (get frames index)]
[:section.viewer-preview
(cond
(empty? frames)
[:section.empty-state
[:span (t locale "viewer.empty-state")]]
(nil? frame)
[:section.empty-state
[:span (t locale "viewer.frame-not-found")]]
:else
[:*
[:& layers-sidebar {:frame frame}]
[:div.handoff-svg-wrapper {:on-click (handle-select-frame frame)}
[:& render-frame-svg {:frame-id (:id frame)
:zoom (:zoom local)
:objects objects}]]
[:& attributes-sidebar]])]))
(mf/defc handoff-content
[{:keys [data local index] :as props}]
(let [container (mf/use-ref)
[toggle-fullscreen fullscreen?] (hooks/use-fullscreen container)
on-mouse-wheel
(fn [event]
(when (kbd/ctrl? event)
(dom/prevent-default event)
(let [event (.getBrowserEvent ^js event)]
(if (pos? (.-deltaY ^js event))
(st/emit! dv/decrease-zoom)
(st/emit! dv/increase-zoom)))))
on-mount
(fn []
;; bind with passive=false to allow the event to be cancelled
;; https://stackoverflow.com/a/57582286/3219895
(let [key1 (events/listen goog/global EventType.WHEEL
on-mouse-wheel #js {"passive" false})]
(fn []
(events/unlistenByKey key1))))]
(mf/use-effect on-mount)
(hooks/use-shortcuts dv/shortcuts)
[:div.handoff-layout {:class (classnames :fullscreen fullscreen?)
:ref container}
[:& header {:data data
:toggle-fullscreen toggle-fullscreen
:fullscreen? fullscreen?
:local local
:index index
:screen :handoff}]
[:div.viewer-content
(when (:show-thumbnails local)
[:& thumbnails-panel {:index index
:data data
:screen :handoff}])
[:& render-panel {:data data
:local local
:index index}]]]))
(mf/defc handoff
[{:keys [file-id page-id index] :as props}]
(mf/use-effect
(mf/deps file-id page-id)
(fn []
(st/emit! (dv/initialize props))))
(let [data (mf/deref refs/viewer-data)
local (mf/deref refs/viewer-local)]
(when data
[:& handoff-content {:index index
:local local
:data data}])))

View file

@ -0,0 +1,38 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attributes-sidebar
(:require
[rumext.alpha :as mf]
[app.main.ui.icons :as i]
[app.main.ui.components.tab-container :refer [tab-container tab-element]]))
(mf/defc info-panel []
[:div.element-options])
(mf/defc code-panel []
[:div.element-options])
(mf/defc attributes-sidebar []
(let [section (mf/use-state :info #_:code)]
[:aside.settings-bar.settings-bar-right
[:div.settings-bar-inside
[:div.tool-window
[:div.tool-window-bar.big
[:span.tool-window-bar-icon i/text]
[:span.tool-window-bar-title "Text"]]
[:div.tool-window-content
[:& tab-container {:on-change-tab #(reset! section %)
:selected @section}
[:& tab-element {:id :info :title "Info"}
[:& info-panel]]
[:& tab-element {:id :code :title "Code"}
[:& code-panel]]]]]]]))

View file

@ -0,0 +1,121 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.layers-sidebar
(:require
[rumext.alpha :as mf]
[okulary.core :as l]
[app.common.data :as d]
[app.common.uuid :as uuid]
[app.main.store :as st]
[app.util.dom :as dom]
[app.main.data.viewer :as dv]
[app.main.ui.icons :as i]
[app.main.ui.keyboard :as kbd]
[app.main.ui.workspace.sidebar.layers :refer [element-icon layer-name frame-wrapper]]))
(def selected-shapes
(l/derived (comp :selected :viewer-local) st/state))
(def page-ref
(l/derived (comp :page :viewer-data) st/state))
(defn- make-collapsed-iref
[id]
#(-> (l/in [:viewer-local :collapsed id])
(l/derived st/state) ))
(mf/defc layer-item
[{:keys [index item selected objects disable-collapse?] :as props}]
(let [id (:id item)
selected? (contains? selected id)
item-ref (mf/use-ref nil)
collapsed-iref (mf/use-memo
(mf/deps id)
(make-collapsed-iref id))
expanded? (not (mf/deref collapsed-iref))
toggle-collapse
(fn [event]
(dom/stop-propagation event)
(if (and expanded? (kbd/shift? event))
(st/emit! (dv/collapse-all))
(st/emit! (dv/toggle-collapse id))))
select-shape
(fn [event]
(dom/prevent-default event)
(let [id (:id item)]
(st/emit! (dv/select-shape id))
#_(cond
(or (:blocked item)
(:hidden item))
nil
(.-shiftKey event)
(st/emit! (dv/select-shape id true))
(> (count selected) 1)
(st/emit! (dv/deselect-all)
(dv/select-shape id))
:else
(st/emit! (dv/deselect-all)
(dv/select-shape id)))))
]
(mf/use-effect
(mf/deps selected)
(fn []
(when (and (= (count selected) 1) selected?)
(.scrollIntoView (mf/ref-val item-ref) false))))
[:li {:ref item-ref
:class (dom/classnames
:component (not (nil? (:component-id item)))
:masked (:masked-group? item)
:selected selected?)}
[:div.element-list-body {:class (dom/classnames :selected selected?
:icon-layer (= (:type item) :icon))
:on-click select-shape}
[:& element-icon {:shape item}]
[:& layer-name {:shape item}]
(when (and (not disable-collapse?) (:shapes item))
[:span.toggle-content
{:on-click toggle-collapse
:class (when expanded? "inverse")}
i/arrow-slide])]
(when (and (:shapes item) expanded?)
[:ul.element-children
(for [[index id] (reverse (d/enumerate (:shapes item)))]
(when-let [item (get objects id)]
[:& layer-item
{:item item
:selected selected
:index index
:objects objects
:key (:id item)}]))])]))
(mf/defc layers-sidebar [{:keys [frame]}]
(let [page (mf/deref page-ref)
selected (mf/deref selected-shapes)
objects (:objects page)]
[:aside.settings-bar.settings-bar-left
[:div.settings-bar-inside
[:ul.element-list
[:& layer-item
{:item frame
:selected selected
:index 0
:objects objects
:disable-collapse? true}]]]]))

View file

@ -0,0 +1,170 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.render
"The main container for a frame in handoff mode"
(:require
[rumext.alpha :as mf]
[app.util.object :as obj]
[app.util.dom :as dom]
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.pages-helpers :as cph]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as geom]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.data.viewer :as dv]
[app.main.ui.shapes.filters :as filters]
[app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.frame :as frame]
[app.main.ui.shapes.group :as group]
[app.main.ui.shapes.icon :as icon]
[app.main.ui.shapes.image :as image]
[app.main.ui.shapes.path :as path]
[app.main.ui.shapes.rect :as rect]
[app.main.ui.shapes.text :as text]
[app.main.ui.viewer.handoff.selection-feedback :refer [selection-feedback]]
[app.main.ui.shapes.shape :refer [shape-container]]))
(declare shape-container-factory)
(defn handle-hover-shape [{:keys [type id]} hover?]
#(when-not (#{:group :frame} type)
(do
(dom/prevent-default %)
(dom/stop-propagation %)
(st/emit! (dv/hover-shape id hover?)))))
(defn select-shape [{:keys [type id]}]
#(when-not (#{:group :frame} type)
(dom/prevent-default %)
(dom/stop-propagation %)
(st/emit! (dv/select-shape id))))
(defn shape-wrapper-factory
[component]
(mf/fnc shape-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
childs (unchecked-get props "childs")
frame (unchecked-get props "frame")]
[:> shape-container {:shape shape
:on-mouse-enter (handle-hover-shape shape true)
:on-mouse-leave (handle-hover-shape shape false)
:on-click (select-shape shape)}
[:& component {:shape shape
:frame frame
:childs childs
:is-child-selected? true}]])))
(defn frame-container-factory
[objects]
(let [shape-container (shape-container-factory objects)
frame-shape (frame/frame-shape shape-container)
frame-wrapper (shape-wrapper-factory frame-shape)]
(mf/fnc frame-container
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
childs (mapv #(get objects %) (:shapes shape))
shape (geom/transform-shape shape)
props (-> (obj/new)
(obj/merge! props)
(obj/merge! #js {:shape shape
:childs childs}))]
[:> frame-wrapper props]))))
(defn group-container-factory
[objects]
(let [shape-container (shape-container-factory objects)
group-shape (group/group-shape shape-container)
group-wrapper (shape-wrapper-factory group-shape)]
(mf/fnc group-container
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
childs (mapv #(get objects %) (:shapes shape))
props (-> (obj/new)
(obj/merge! props)
(obj/merge! #js {:childs childs}))]
[:> group-wrapper props]))))
(defn shape-container-factory
[objects show-interactions?]
(let [path-wrapper (shape-wrapper-factory path/path-shape)
text-wrapper (shape-wrapper-factory text/text-shape)
icon-wrapper (shape-wrapper-factory icon/icon-shape)
rect-wrapper (shape-wrapper-factory rect/rect-shape)
image-wrapper (shape-wrapper-factory image/image-shape)
circle-wrapper (shape-wrapper-factory circle/circle-shape)]
(mf/fnc shape-container
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
group-container (mf/use-memo
(mf/deps objects)
#(group-container-factory objects))]
(when (and shape (not (:hidden shape)))
(let [shape (geom/transform-shape frame shape)
opts #js {:shape shape
:frame frame}]
(case (:type shape)
:curve [:> path-wrapper opts]
:text [:> text-wrapper opts]
:icon [:> icon-wrapper opts]
:rect [:> rect-wrapper opts]
:path [:> path-wrapper opts]
:image [:> image-wrapper opts]
:circle [:> circle-wrapper opts]
:group [:> group-container opts])))))))
(defn adjust-frame-position [frame-id objects]
(let [frame (get objects frame-id)
modifier (-> (gpt/point (:x frame) (:y frame))
(gpt/negate)
(gmt/translate-matrix))
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
modifier-ids (d/concat [frame-id] (cph/get-children frame-id objects))]
(reduce update-fn objects modifier-ids)))
(defn make-vbox [frame]
(str "0 0 " (:width frame 0) " " (:height frame 0)))
(mf/defc render-frame-svg
{::mf/wrap [mf/memo]}
[{:keys [objects frame-id zoom] :or {zoom 1} :as props}]
(let [objects (adjust-frame-position frame-id objects)
frame (get objects frame-id)
width (* (:width frame) zoom)
height (* (:height frame) zoom)
vbox (make-vbox frame)
render-frame (mf/use-memo
(mf/deps objects)
#(frame-container-factory objects))]
[:svg {:view-box vbox
:width width
:height height
:version "1.1"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
[:& render-frame {:shape frame
:view-box vbox}]
[:& selection-feedback {:frame frame}]]))

View file

@ -0,0 +1,65 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.selection-feedback
(:require
[rumext.alpha :as mf]
[okulary.core :as l]
#_[app.util.object :as obj]
#_[app.common.data :as d]
#_[app.common.pages :as cp]
#_[app.common.pages-helpers :as cph]
#_[app.common.geom.matrix :as gmt]
#_[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
#_[app.main.refs :as refs]
[app.main.store :as st]
#_[app.main.data.viewer :as dv]
#_[app.main.ui.shapes.filters :as filters]
#_[app.main.ui.shapes.circle :as circle]
#_[app.main.ui.shapes.frame :as frame]
#_[app.main.ui.shapes.group :as group]
#_[app.main.ui.shapes.icon :as icon]
#_[app.main.ui.shapes.image :as image]
#_[app.main.ui.shapes.path :as path]
#_[app.main.ui.shapes.rect :as rect]
#_[app.main.ui.shapes.text :as text]
#_[app.main.ui.shapes.shape :refer [shape-container]]))
(def selection-rect-color-normal "#1FDEA7")
(def selection-rect-color-component "#00E0FF")
(def selection-rect-width 1)
#_(def hover-ref
(l/derived (l/in [:viewer-local :hover]) st/state))
(defn make-hover-shapes-iref
[]
(let [hover->shapes
(fn [state]
(let [hover (get-in state [:viewer-local :hover])
objects (get-in state [:viewer-data :page :objects])
resolve-shape #(get objects %)]
(map resolve-shape hover)))]
#(l/derived hover->shapes st/state)))
(mf/defc selection-feedback [{:keys [frame]}]
(let [hover-shapes-ref (mf/use-memo (make-hover-shapes-iref))
hover-shapes (->> (mf/deref hover-shapes-ref)
(map #(gsh/translate-to-frame % frame)))]
(for [shape hover-shapes]
(let [{:keys [x y width height]} (:selrect shape)]
[:rect {:x x
:y y
:width width
:height height
:fill "transparent"
:stroke selection-rect-color-normal
:stroke-width selection-rect-width
:pointer-events "none"}]))))

View file

@ -122,7 +122,7 @@
(t locale "viewer.header.share.create-link")])]]]]))
(mf/defc header
[{:keys [data index local fullscreen? toggle-fullscreen] :as props}]
[{:keys [data index local fullscreen? toggle-fullscreen screen] :as props}]
(let [{:keys [project file page frames]} data
total (count frames)
on-click #(st/emit! dv/toggle-thumbnails-panel)
@ -141,7 +141,14 @@
on-edit #(st/emit! (rt/nav :workspace
{:project-id project-id
:file-id file-id}
{:page-id page-id}))]
{:page-id page-id}))
change-screen
(fn [screen]
(st/emit!
(rt/nav screen
{:file-id file-id :page-id page-id}
{:index index})))]
[:header.viewer-header
[:div.main-icon
[:a {:on-click on-edit} i/logo-icon]]
@ -156,6 +163,14 @@
[:span.dropdown-button i/arrow-down]
[:span.counters (str (inc index) " / " total)]]
[:div.mode-zone
[:button.mode-zone-button {:on-click #(when (not= screen :viewer)
(change-screen :viewer))
:class (when (= screen :viewer) "active")} i/play]
[:button.mode-zone-button {:on-click #(when (not= screen :handoff)
(change-screen :handoff))
:class (when (= screen :handoff) "active")} i/code]]
[:div.options-zone
[:& interactions-menu {:interactions-mode interactions-mode}]

View file

@ -94,7 +94,7 @@
[:span.name (:name frame)]]])
(mf/defc thumbnails-panel
[{:keys [data index] :as props}]
[{:keys [data index screen] :as props}]
(let [expanded? (mf/use-state false)
container (mf/use-ref)
page-id (get-in data [:page :id])
@ -111,7 +111,7 @@
on-item-click
(fn [event index]
(compare-and-set! selected false true)
(st/emit! (rt/nav :viewer {:file-id file-id
(st/emit! (rt/nav screen {:file-id file-id
:page-id page-id} {:index index}))
(when @expanded?
(on-close)))]

View file

@ -331,8 +331,7 @@
(mf/defc layers-toolbox
{:wrap [mf/memo]}
[]
(let [locale (mf/deref i18n/locale)
page (mf/deref refs/workspace-page)]
(let [page (mf/deref refs/workspace-page)]
[:div#layers.tool-window
[:div.tool-window-bar
[:div.tool-window-icon i/layers]