0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-13 07:21:40 -05:00

♻️ Refactor workspace state organization

Move many local to a specific global prop.
This commit is contained in:
Andrey Antukh 2022-02-24 23:42:45 +01:00 committed by Alonso Torres
parent 6e667e078c
commit f05518e357
14 changed files with 133 additions and 171 deletions

View file

@ -24,7 +24,7 @@
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.main.data.events :as ev]
[app.main.data.messages :as dm]
[app.main.data.messages :as msg]
[app.main.data.workspace.bool :as dwb]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
@ -90,7 +90,7 @@
(s/def ::layout-flags (s/coll-of ::layout-flag))
(def default-layout
(def default-workspace-layout
#{:sitemap
:layers
:element-options
@ -116,28 +116,12 @@
(s/def ::options-mode #{:design :prototype})
(def workspace-local-default
(def default-workspace-global
{:options-mode :design})
(def default-workspace-local
{:zoom 1
:flags #{}
:selected (d/ordered-set)
:selected-assets {:components #{}
:graphics #{}
:colors #{}
:typographies #{}}
:expanded {}
:tooltip nil
:options-mode :design
:draw-interaction-to nil
:left-sidebar? true
:right-sidebar? true
:color-for-rename nil
:selected-palette-colorpicker :recent
:selected-palette :recent
:selected-palette-size :big
:assets-files-open {}
:picking-color? false
:picked-color nil
:picked-color-select false})
:selected (d/ordered-set)})
(defn ensure-layout
[lname]
@ -152,13 +136,15 @@
(set/difference todel)
(set/union toadd))))))))
(defn setup-layout
(defn initialize
[lname]
(us/verify (s/nilable ::us/keyword) lname)
(ptk/reify ::setup-layout
(ptk/reify ::initialize
ptk/UpdateEvent
(update [_ state]
(update state :workspace-layout #(or % default-layout)))
(-> state
(update :workspace-layout #(or % default-workspace-layout))
(update :workspace-global #(or % default-workspace-global))))
ptk/WatchEvent
(watch [_ _ _]
@ -278,7 +264,7 @@
page (get-in state [:workspace-data :pages-index page-id])
page-id (:id page)
local (-> state
(get-in [:workspace-cache page-id] workspace-local-default)
(get-in [:workspace-cache page-id] default-workspace-local)
(assoc :selected (d/ordered-set)))]
(-> state
(assoc :current-page-id page-id)
@ -436,7 +422,7 @@
(ptk/reify ::set-options-mode
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :options-mode] mode))))
(assoc-in state [:workspace-global :options-mode] mode))))
;; --- Tooltip
@ -446,8 +432,8 @@
ptk/UpdateEvent
(update [_ state]
(if (string? content)
(assoc-in state [:workspace-local :tooltip] content)
(assoc-in state [:workspace-local :tooltip] nil)))))
(assoc-in state [:workspace-global :tooltip] content)
(assoc-in state [:workspace-global :tooltip] nil)))))
;; --- Viewport Sizing
@ -1194,41 +1180,42 @@
(rx/of (rt/nav :workspace pparams qparams))))))
(defn check-in-asset
[set element]
(if (contains? set element)
(disj set element)
(conj set element)))
[items element]
(let [items (or items #{})]
(if (contains? items element)
(disj set element)
(conj set element))))
(defn toggle-selected-assets
[asset type]
(ptk/reify ::toggle-selected-assets
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :selected-assets type] #(check-in-asset % asset)))))
(update-in state [:workspace-global :selected-assets type] #(check-in-asset % asset)))))
(defn select-single-asset
[asset type]
(ptk/reify ::select-single-asset
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :selected-assets type] #{asset}))))
(assoc-in state [:workspace-global :selected-assets type] #{asset}))))
(defn select-assets
[assets type]
(ptk/reify ::select-assets
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :selected-assets type] (into #{} assets)))))
(assoc-in state [:workspace-global :selected-assets type] (into #{} assets)))))
(defn unselect-all-assets
[]
(ptk/reify ::unselect-all-assets
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :selected-assets] {:components #{}
:graphics #{}
:colors #{}
:typographies #{}}))))
(assoc-in state [:workspace-global :selected-assets] {:components #{}
:graphics #{}
:colors #{}
:typographies #{}}))))
(defn go-to-component
[component-id]
@ -1480,7 +1467,7 @@
(catch :default e
(let [data (ex-data e)]
(if (:not-implemented data)
(rx/of (dm/warn (i18n/tr "errors.clipboard-not-implemented")))
(rx/of (msg/warn (i18n/tr "errors.clipboard-not-implemented")))
(js/console.error "ERROR" e))))))))
(defn paste-from-event

View file

@ -21,7 +21,7 @@
(ptk/reify ::clear-color-for-rename
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :color-for-rename] nil))))
(assoc-in state [:workspace-global :color-for-rename] nil))))
(declare rename-color-result)
@ -40,15 +40,6 @@
(update [_ state]
(update-in state [:workspace-file :colors] #(d/replace-by-id % color)))))
(defn change-palette-size
[size]
(s/assert #{:big :small} size)
(ptk/reify ::change-palette-size
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :selected-palette-size] size)))))
(defn change-palette-selected
"Change the library used by the general palette tool"
[selected]
@ -56,7 +47,7 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :selected-palette] selected)))))
(assoc-in [:workspace-global :selected-palette] selected)))))
(defn change-palette-selected-colorpicker
"Change the library used by the color picker"
@ -65,7 +56,7 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :selected-palette-colorpicker] selected)))))
(assoc-in [:workspace-global :selected-palette-colorpicker] selected)))))
(defn show-palette
"Show the palette tool and change the library it uses"
@ -75,7 +66,7 @@
(update [_ state]
(-> state
(update :workspace-layout conj :colorpalette)
(assoc-in [:workspace-local :selected-palette] selected)))))
(assoc-in [:workspace-global :selected-palette] selected)))))
(defn start-picker
[]
@ -83,7 +74,7 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :picking-color?] true)))))
(assoc-in [:workspace-global :picking-color?] true)))))
(defn stop-picker
[]
@ -91,8 +82,8 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-local dissoc :picked-color-select :picked-shift?)
(assoc-in [:workspace-local :picking-color?] false)))))
(update :workspace-global dissoc :picked-color-select :picked-shift?)
(assoc-in [:workspace-global :picking-color?] false)))))
(defn pick-color
[rgba]
@ -100,7 +91,7 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :picked-color] rgba)))))
(assoc-in [:workspace-global :picked-color] rgba)))))
(defn pick-color-select
[value shift?]
@ -108,8 +99,8 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :picked-color-select] value)
(assoc-in [:workspace-local :picked-shift?] shift?)))))
(assoc-in [:workspace-global :picked-color-select] value)
(assoc-in [:workspace-global :picked-shift?] shift?)))))
(defn transform-fill
[state ids color transform]
@ -295,7 +286,7 @@
(update [_ state]
(let [handle-change-color (fn [color] (rx/push! sub color))]
(-> state
(assoc-in [:workspace-local :picking-color?] true)
(assoc-in [:workspace-global :picking-color?] true)
(assoc ::md/modal {:id (random-uuid)
:data {:color clr/black :opacity 1}
:type :colorpicker
@ -309,8 +300,8 @@
(update [_ state]
(let [id (-> state wsh/lookup-selected first)]
(-> state
(assoc-in [:workspace-local :current-gradient] gradient)
(assoc-in [:workspace-local :current-gradient :shape-id] id))))))
(assoc-in [:workspace-global :current-gradient] gradient)
(assoc-in [:workspace-global :current-gradient :shape-id] id))))))
(defn stop-gradient
[]
@ -318,7 +309,7 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-local dissoc :current-gradient)))))
(update :workspace-global dissoc :current-gradient)))))
(defn update-gradient
[changes]
@ -326,7 +317,7 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(update-in [:workspace-local :current-gradient] merge changes)))))
(update-in [:workspace-global :current-gradient] merge changes)))))
(defn select-gradient-stop
[spot]
@ -334,4 +325,4 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :editing-stop] spot)))))
(assoc-in [:workspace-global :editing-stop] spot)))))

View file

@ -72,14 +72,14 @@
(ptk/reify ::set-assets-box-open
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :assets-files-open file-id box] open?))))
(assoc-in state [:workspace-global :assets-files-open file-id box] open?))))
(defn set-assets-group-open
[file-id box path open?]
(ptk/reify ::set-assets-group-open
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :assets-files-open file-id :groups box path] open?))))
(assoc-in state [:workspace-global :assets-files-open file-id :groups box path] open?))))
(defn default-color-name [color]
(or (:color color)
@ -236,7 +236,7 @@
:origin it})
#(cond-> %
edit?
(assoc-in [:workspace-local :rename-typography] (:id typography))))))))))
(assoc-in [:workspace-global :rename-typography] (:id typography))))))))))
(defn update-typography
[typography file-id]

View file

@ -8,6 +8,7 @@
"A collection of derived refs."
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.path.commands :as upc]
@ -72,12 +73,12 @@
(def dashboard-selected-project
(l/derived (fn [state]
(get-in state [:dashboard-local :selected-project]))
(dm/get-in state [:dashboard-local :selected-project]))
st/state))
(def dashboard-selected-files
(l/derived (fn [state]
(let [get-file #(get-in state [:dashboard-files %])
(let [get-file #(dm/get-in state [:dashboard-files %])
sim-file #(select-keys % [:id :name :project-id :is-shared])
selected (get-in state [:dashboard-local :selected-files])
xform (comp (map get-file)
@ -91,6 +92,9 @@
(def workspace-local
(l/derived :workspace-local st/state))
(def workspace-global
(l/derived :workspace-global st/state))
(def workspace-drawing
(l/derived :workspace-drawing st/state))
@ -101,41 +105,6 @@
[id]
(l/derived #(contains? % id) selected-shapes))
(def viewport-data
(l/derived #(select-keys % [:options-mode
:zoom
:vport
:vbox
:edition
:edit-path
:tooltip
:panning
:zooming
:picking-color?
:transform
:hover
:modifiers
:selrect
:show-distances?])
workspace-local =))
(def interactions-data
(l/derived #(select-keys % [:editing-interaction-index
:draw-interaction-to
:draw-interaction-to-frame
:move-overlay-to
:move-overlay-index])
workspace-local =))
(def typography-data
(l/derived #(select-keys % [:rename-typography
:edit-typography])
workspace-local =))
(def local-displacement
(l/derived #(select-keys % [:modifiers :selected])
workspace-local =))
(def selected-zoom
(l/derived :zoom workspace-local))
@ -167,15 +136,13 @@
(l/derived :hover-ids context-menu))
(def selected-assets
(l/derived :selected-assets workspace-local))
(l/derived :selected-assets workspace-global))
(def workspace-layout
(l/derived :workspace-layout st/state))
(def current-file-id
(l/derived :current-file-id st/state))
(def workspace-file
"A ref to a striped vision of file (without data)."
(l/derived (fn [state]
(let [file (:workspace-file state)
data (:workspace-data state)]
@ -193,12 +160,12 @@
(def workspace-recent-colors
(l/derived (fn [state]
(get-in state [:workspace-data :recent-colors] []))
(dm/get-in state [:workspace-data :recent-colors] []))
st/state))
(def workspace-recent-fonts
(l/derived (fn [state]
(get-in state [:workspace-data :recent-fonts] []))
(dm/get-in state [:workspace-data :recent-fonts] []))
st/state))
(def workspace-file-typography
@ -215,7 +182,7 @@
(def workspace-local-library
(l/derived (fn [state]
(select-keys (get state :workspace-data)
(select-keys (:workspace-data state)
[:id
:colors
:media
@ -236,7 +203,7 @@
(l/derived (fn [state]
(let [page-id (:current-page-id state)
data (:workspace-data state)]
(get-in data [:pages-index page-id])))
(dm/get-in data [:pages-index page-id])))
st/state))
(def workspace-page-objects
@ -271,7 +238,7 @@
(defn- set-content-modifiers [state]
(fn [id shape]
(let [content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers])]
(let [content-modifiers (dm/get-in state [:workspace-local :edit-path id :content-modifiers])]
(if (some? content-modifiers)
(update shape :content upc/apply-content-modifiers content-modifiers)
shape))))
@ -343,7 +310,7 @@
(def users
(l/derived :users st/state))
(def fullscreen?
(l/derived (fn [state]
(get-in state [:viewer-local :fullscreen?] []))
st/state))
(def viewer-fullscreen?
(l/derived (fn [state]
(dm/get-in state [:viewer-local :fullscreen?]))
st/state))

View file

@ -73,7 +73,7 @@
zoom (:zoom local)
frames (:frames page)
frame (get frames index)
fullscreen? (mf/deref refs/fullscreen?)
fullscreen? (mf/deref refs/viewer-fullscreen?)
overlays (:overlays local)
orig-frame

View file

@ -60,7 +60,7 @@
(mf/defc header-options
[{:keys [section zoom page file index permissions]}]
(let [fullscreen? (mf/deref refs/fullscreen?)
(let [fullscreen? (mf/deref refs/viewer-fullscreen?)
toggle-fullscreen
(mf/use-callback

View file

@ -6,7 +6,8 @@
(ns app.main.ui.workspace
(:require
[app.main.data.messages :as dm]
[app.common.data.macros :as dm]
[app.main.data.messages :as msg]
[app.main.data.workspace :as dw]
[app.main.data.workspace.persistence :as dwp]
[app.main.refs :as refs]
@ -38,21 +39,21 @@
{::mf/wrap-props false}
[props]
(let [selected (mf/deref refs/selected-shapes)
local (mf/deref refs/viewport-data)
file (obj/get props "file")
layout (obj/get props "layout")
{:keys [options-mode]} local
file (obj/get props "file")
layout (obj/get props "layout")
{:keys [vport] :as local} (mf/deref refs/workspace-local)
{:keys [options-mode] :as wstate} (mf/deref refs/workspace-global)
colorpalette? (:colorpalette layout)
textpalette? (:textpalette layout)
hide-ui? (:hide-ui layout)
textpalette? (:textpalette layout)
hide-ui? (:hide-ui layout)
on-resize
(mf/use-callback
(mf/deps (:vport local))
(fn [resize-type size]
(when (:vport local)
(when vport
(st/emit! (dw/update-viewport-size resize-type size)))))
node-ref (use-resize-observer on-resize)]
@ -70,6 +71,7 @@
[:& viewport {:file file
:local local
:wstate wstate
:selected selected
:layout layout}]]]
@ -119,7 +121,7 @@
;; Setting the layout preset by its name
(mf/with-effect [layout-name]
(st/emit! (dw/setup-layout layout-name)))
(st/emit! (dw/initialize layout-name)))
(mf/with-effect [project-id file-id]
(st/emit! (dw/initialize-file project-id file-id))
@ -129,7 +131,7 @@
;; Close any non-modal dialog that may be still open
(mf/with-effect
(st/emit! dm/hide))
(st/emit! msg/hide))
;; Set properly the page title
(mf/with-effect [(:name file)]

View file

@ -30,22 +30,22 @@
;; --- Refs
(def picking-color?
(l/derived :picking-color? refs/workspace-local))
(l/derived :picking-color? refs/workspace-global))
(def picked-color
(l/derived :picked-color refs/workspace-local))
(l/derived :picked-color refs/workspace-global))
(def picked-color-select
(l/derived :picked-color-select refs/workspace-local))
(l/derived :picked-color-select refs/workspace-global))
(def viewport
(l/derived (l/in [:workspace-local :vport]) st/state))
(l/derived :vport refs/workspace-local))
(def editing-spot-state-ref
(l/derived (l/in [:workspace-local :editing-stop]) st/state))
(l/derived :editing-stop refs/workspace-global))
(def current-gradient-ref
(l/derived (l/in [:workspace-local :current-gradient]) st/state))
(l/derived :current-gradient refs/workspace-global))
;; --- Color Picker Modal
@ -387,11 +387,12 @@
position (or position :left)
style (calculate-position vport position x y)
handle-change (fn [new-data]
(reset! dirty? (not= data new-data))
(reset! last-change new-data)
(when on-change
(on-change new-data)))]
handle-change
(fn [new-data]
(reset! dirty? (not= data new-data))
(reset! last-change new-data)
(when on-change
(on-change new-data)))]
(mf/use-effect
(fn []

View file

@ -35,9 +35,6 @@
(dom/prevent-default event)
(dom/stop-propagation event))
(mf/defc menu-entry
[{:keys [title shortcut on-click children selected? icon] :as props}]
(let [submenu-ref (mf/use-ref nil)

View file

@ -22,6 +22,7 @@
[app.util.timers :as ts]
[rumext.alpha :as mf]))
(mf/defc image-upload
{::mf/wrap [mf/memo]}
[]
@ -36,9 +37,8 @@
(mf/deps file)
(fn [blobs]
;; We don't want to add a ref because that redraws the component
;; for everychange. Better direct access on the callback
;; vbox (get-in @st/state [:workspace-local :vbox])
(let [vbox (:vbox @refs/workspace-local)
;; for everychange. Better direct access on the callback.
(let [vbox (deref @refs/vbox)
x (mth/round (+ (:x vbox) (/ (:width vbox) 2)))
y (mth/round (+ (:y vbox) (/ (:height vbox) 2)))
params {:file-id (:id file)
@ -129,7 +129,7 @@
(ts/schedule 300 #(st/emit! (dw/remove-layout-flags :colorpalette)
(dw/toggle-layout-flags :textpalette))))}
"Ag"]
[:li.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette))
:class (when (contains? layout :colorpalette) "selected")

View file

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.sidebar.assets
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.media :as cm]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
@ -56,6 +57,10 @@
;; of grouping, deleting, etc. to events in the data module, since now the
;; selection info is in the global state.
(def typography-data
(l/derived #(dm/select-keys % [:rename-typography :edit-typography])
refs/workspace-global =))
;; ---- Group assets management ----
(defn group-assets
@ -1141,7 +1146,7 @@
(let [state (mf/use-state {:detail-open? false
:id nil})
local-data (mf/deref refs/typography-data)
local-data (mf/deref typography-data)
menu-state (mf/use-state auto-pos-menu-state)
groups (group-assets typographies reverse-sort?)
@ -1258,11 +1263,11 @@
handle-rename-typography-clicked
(fn []
(st/emit! #(assoc-in % [:workspace-local :rename-typography] (:id @state))))
(st/emit! #(assoc-in % [:workspace-global :rename-typography] (:id @state))))
handle-edit-typography-clicked
(fn []
(st/emit! #(assoc-in % [:workspace-local :edit-typography] (:id @state))))
(st/emit! #(assoc-in % [:workspace-global :edit-typography] (:id @state))))
handle-delete-typography
(mf/use-callback
@ -1282,9 +1287,9 @@
(mf/deps local-data)
(fn []
(when (:rename-typography local-data)
(st/emit! #(update % :workspace-local dissoc :rename-typography)))
(st/emit! #(update % :workspace-global dissoc :rename-typography)))
(when (:edit-typography local-data)
(st/emit! #(update % :workspace-local dissoc :edit-typography)))))
(st/emit! #(update % :workspace-global dissoc :edit-typography)))))
[:& asset-section {:file-id file-id
:title (tr "workspace.assets.typography")
@ -1365,10 +1370,11 @@
(vals (get-in state [:workspace-libraries id :data :typographies])))))
st/state =))
(defn open-file-ref
(defn make-open-file-ref
[id]
(-> (l/in [:assets-files-open id])
(l/derived refs/workspace-local)))
(mf/with-memo [id]
(-> (l/in [:assets-files-open id])
(l/derived refs/workspace-global))))
(defn apply-filters
[coll filters reverse-sort?]
@ -1387,7 +1393,7 @@
(mf/defc file-library
[{:keys [file local? default-open? filters] :as props}]
(let [open-file (mf/deref (open-file-ref (:id file)))
(let [open-file (mf/deref (make-open-file-ref (:id file)))
open? (-> open-file
:library
(d/nilv default-open?))

View file

@ -42,22 +42,23 @@
;; --- Viewport
(mf/defc viewport
[{:keys [local selected layout file] :as props}]
[{:keys [local wstate selected layout file] :as props}]
(let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check
;; that the new parameter is sent
{:keys [edit-path
edition
options-mode
panning
picking-color?
selrect
show-distances?
tooltip
transform
vbox
vport
zoom]} local
{:keys [edition
options-mode
tooltip
show-distances?
picking-color?]} wstate
;; CONTEXT
page-id (mf/use-ctx ctx/current-page-id)
@ -66,9 +67,9 @@
options (mf/deref refs/workspace-page-options)
base-objects (mf/deref refs/workspace-page-objects)
modifiers (mf/deref refs/workspace-modifiers)
objects-modified (mf/use-memo
(mf/deps base-objects modifiers)
#(gsh/merge-modifiers base-objects modifiers))
objects-modified (mf/with-memo [base-objects modifiers]
(gsh/merge-modifiers base-objects modifiers))
background (get options :background clr/canvas)
;; STATE

View file

@ -32,10 +32,10 @@
(def gradient-square-stroke-color-selected "var(--color-select)")
(def editing-spot-ref
(l/derived (l/in [:workspace-local :editing-stop]) st/state))
(l/derived (l/in [:workspace-global :editing-stop]) st/state))
(def current-gradient-ref
(l/derived (l/in [:workspace-local :current-gradient]) st/state =))
(l/derived (l/in [:workspace-global :current-gradient]) st/state =))
(mf/defc shadow [{:keys [id x y width height offset]}]
[:filter {:id id

View file

@ -8,6 +8,7 @@
"Visually show shape interactions in workspace"
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.spec.interactions :as cti]
@ -17,8 +18,17 @@
[app.main.ui.workspace.viewport.outline :refer [outline]]
[app.util.dom :as dom]
[cuerdas.core :as str]
[okulary.core :as l]
[rumext.alpha :as mf]))
(def interactions-ref
(l/derived #(dm/select-keys % [:editing-interaction-index
:draw-interaction-to
:draw-interaction-to-frame
:move-overlay-to
:move-overlay-index])
refs/workspace-local =))
(defn- on-mouse-down
[event index {:keys [id] :as shape}]
(dom/stop-propagation event)
@ -251,7 +261,7 @@
draw-interaction-to
draw-interaction-to-frame
move-overlay-to
move-overlay-index]} (mf/deref refs/interactions-data)
move-overlay-index]} (mf/deref interactions-ref)
first-selected (first selected-shapes)