penpot/frontend/src/app/main/ui/workspace.cljs
Andrey Antukh 4713d943d1 ♻️ Add efficiency refactor for workspace sidebars
The main changes are for right sidebar but left sidebar is also
slightly affected beacuse of the move where the active tokes are
resolved.
2025-08-27 17:56:35 +02:00

262 lines
9.0 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.workspace
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.data.common :as dcm]
[app.main.data.helpers :as dsh]
[app.main.data.persistence :as dps]
[app.main.data.plugins :as dpl]
[app.main.data.workspace :as dw]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.router :as-alias rt]
[app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.ds.product.loader :refer [loader*]]
[app.main.ui.hooks :as hooks]
[app.main.ui.hooks.resize :refer [use-resize-observer]]
[app.main.ui.modal :refer [modal-container*]]
[app.main.ui.workspace.colorpicker]
[app.main.ui.workspace.context-menu :refer [context-menu*]]
[app.main.ui.workspace.coordinates :as coordinates]
[app.main.ui.workspace.libraries]
[app.main.ui.workspace.nudge]
[app.main.ui.workspace.palette :refer [palette]]
[app.main.ui.workspace.plugins]
[app.main.ui.workspace.sidebar :refer [sidebar*]]
[app.main.ui.workspace.sidebar.history :refer [history-toolbox*]]
[app.main.ui.workspace.tokens.export]
[app.main.ui.workspace.tokens.export.modal]
[app.main.ui.workspace.tokens.import]
[app.main.ui.workspace.tokens.import.modal]
[app.main.ui.workspace.tokens.management.create.modals]
[app.main.ui.workspace.tokens.settings]
[app.main.ui.workspace.tokens.themes.create-modal]
[app.main.ui.workspace.viewport :refer [viewport*]]
[app.util.debug :as dbg]
[app.util.dom :as dom]
[app.util.globals :as globals]
[app.util.i18n :as i18n :refer [tr]]
[goog.events :as events]
[okulary.core :as l]
[rumext.v2 :as mf]))
(mf/defc workspace-content*
{::mf/private true}
[{:keys [file layout page wglobal]}]
(let [palete-size (mf/use-state nil)
selected (mf/deref refs/selected-shapes)
page-id (get page :id)
{:keys [vport] :as wlocal} (mf/deref refs/workspace-local)
{:keys [options-mode]} wglobal
;; FIXME: pass this down to viewport and reuse it from here
;; instead of making an other deref on viewport for the same
;; data
drawing
(mf/deref refs/workspace-drawing)
colorpalette? (:colorpalette layout)
textpalette? (:textpalette layout)
hide-ui? (:hide-ui layout)
on-resize
(mf/use-fn
(mf/deps vport)
(fn [resize-type size]
(when (and vport (not= size vport))
(st/emit! (dw/update-viewport-size resize-type size)))))
on-resize-palette
(mf/use-fn
(fn [size]
(reset! palete-size size)))
node-ref (use-resize-observer on-resize)]
[:*
(when (not ^boolean hide-ui?)
[:& palette {:layout layout
:on-change-palette-size on-resize-palette}])
[:section
{:key (dm/str "workspace-" page-id)
:class (stl/css :workspace-content)
:ref node-ref}
[:section {:class (stl/css :workspace-viewport)}
(when (dbg/enabled? :coordinates)
[:& coordinates/coordinates {:colorpalette? colorpalette?}])
(when (dbg/enabled? :history-overlay)
[:div {:class (stl/css :history-debug-overlay)}
[:button {:on-click #(st/emit! dw/reinitialize-undo)} "CLEAR"]
[:> history-toolbox*]])
[:> viewport*
{:file file
:page page
:wlocal wlocal
:wglobal wglobal
:selected selected
:layout layout
:palete-size
(when (and (or colorpalette? textpalette?) (not hide-ui?))
@palete-size)}]]]
(when-not hide-ui?
[:> sidebar* {:layout layout
;; FIXME
:file-id (get file :id)
:page-id page-id
:file file
:selected selected
:section options-mode
:drawing-tool (get drawing :tool)}])]))
(mf/defc workspace-loader*
{::mf/private true}
[]
[:> loader* {:title (tr "labels.loading")
:class (stl/css :workspace-loader)
:overlay true
:file-loading true}])
(defn- make-team-ref
[team-id]
(l/derived (fn [state]
(let [teams (get state :teams)]
(get teams team-id)))
st/state))
(defn- make-file-ref
[file-id]
(l/derived (fn [state]
;; NOTE: for ensure ordering of execution, we need to
;; wait the file initialization completly success until
;; mark this file availablea and unlock the rendering
;; of the following components
(when (= (get state :current-file-id) file-id)
(let [files (get state :files)
file (get files file-id)]
(-> file
(dissoc :data)
(assoc ::has-data (contains? file :data))))))
st/state
=))
(defn- make-page-ref
[file-id page-id]
(l/derived (fn [state]
(let [current-page-id (get state :current-page-id)]
;; NOTE: for ensure ordering of execution, we need to
;; wait the page initialization completly success until
;; mark this file availablea and unlock the rendering
;; of the following components
(when (= current-page-id page-id)
(dsh/lookup-page state file-id page-id))))
st/state))
(mf/defc workspace-page*
{::mf/private true}
[{:keys [page-id file-id file layout wglobal]}]
(let [page-ref (mf/with-memo [file-id page-id]
(make-page-ref file-id page-id))
page (mf/deref page-ref)]
(mf/with-effect []
(let [focus-out #(st/emit! (dw/workspace-focus-lost))
key (events/listen globals/window "blur" focus-out)]
(partial events/unlistenByKey key)))
(mf/with-effect [file-id page-id]
(st/emit! (dw/initialize-page file-id page-id))
(fn []
(st/emit! (dw/finalize-page file-id page-id))))
(if (some? page)
[:> workspace-content* {:file file
:page page
:wglobal wglobal
:layout layout}]
[:> workspace-loader*])))
(mf/defc workspace*
{::mf/wrap [mf/memo]}
[{:keys [team-id project-id file-id page-id layout-name]}]
(let [file-id (hooks/use-equal-memo file-id)
page-id (hooks/use-equal-memo page-id)
layout (mf/deref refs/workspace-layout)
wglobal (mf/deref refs/workspace-global)
team-ref (mf/with-memo [team-id]
(make-team-ref team-id))
file-ref (mf/with-memo [file-id]
(make-file-ref file-id))
team (mf/deref team-ref)
file (mf/deref file-ref)
file-loaded? (get file ::has-data)
file-name (:name file)
permissions (:permissions team)
read-only? (mf/deref refs/workspace-read-only?)
read-only? (or read-only? (not (:can-edit permissions)))
design-tokens? (features/use-feature "design-tokens/v1")
background-color (:background-color wglobal)]
(mf/with-effect []
(st/emit! (dps/initialize-persistence)
(dpl/update-plugins-permissions-peek)))
;; Setting the layout preset by its name
(mf/with-effect [layout-name]
(st/emit! (dw/initialize-workspace-layout layout-name)))
(mf/with-effect [file-name]
(when file-name
(dom/set-html-title (tr "title.workspace" file-name))))
(mf/with-effect [team-id file-id]
(st/emit! (dw/initialize-workspace team-id file-id))
(fn []
(st/emit! ::dps/force-persist
(dw/finalize-workspace team-id file-id))))
(mf/with-effect [file-id page-id file-loaded?]
(when (and file-loaded? (not page-id))
(st/emit! (dcm/go-to-workspace :file-id file-id ::rt/replace true))))
[:> (mf/provider ctx/current-project-id) {:value project-id}
[:> (mf/provider ctx/current-file-id) {:value file-id}
[:> (mf/provider ctx/current-page-id) {:value page-id}
[:> (mf/provider ctx/design-tokens) {:value design-tokens?}
[:> (mf/provider ctx/workspace-read-only?) {:value read-only?}
[:> modal-container*]
[:section {:class (stl/css :workspace)
:style {:background-color background-color
:touch-action "none"}}
[:> context-menu*]
(if (and file-loaded? page-id)
[:> workspace-page*
{:page-id page-id
:file-id file-id
:file file
:wglobal wglobal
:layout layout}]
[:> workspace-loader*])]]]]]]))