mirror of
https://github.com/penpot/penpot.git
synced 2026-05-24 01:13:43 +00:00
311 lines
8.7 KiB
Clojure
311 lines
8.7 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) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
|
|
|
(ns uxbox.main.data.workspace
|
|
(:require [cljs.spec :as s]
|
|
[beicon.core :as rx]
|
|
[potok.core :as ptk]
|
|
[lentes.core :as l]
|
|
[uxbox.main.store :as st]
|
|
[uxbox.main.constants :as c]
|
|
[uxbox.main.lenses :as ul]
|
|
[uxbox.main.workers :as uwrk]
|
|
[uxbox.main.data.projects :as dp]
|
|
[uxbox.main.data.pages :as udp]
|
|
[uxbox.main.data.shapes :as uds]
|
|
[uxbox.main.data.icons :as udi]
|
|
[uxbox.main.data.shapes-impl :as shimpl]
|
|
[uxbox.main.data.lightbox :as udl]
|
|
[uxbox.main.data.history :as udh]
|
|
[uxbox.main.data.workspace.scroll :as wscroll]
|
|
[uxbox.main.data.workspace.drawing :as wdrawing]
|
|
[uxbox.main.data.workspace.selrect :as wselrect]
|
|
[uxbox.util.uuid :as uuid]
|
|
[uxbox.util.spec :as us]
|
|
[uxbox.util.forms :as sc]
|
|
[uxbox.util.geom.point :as gpt]
|
|
[uxbox.util.time :as dt]
|
|
[uxbox.util.math :as mth]
|
|
[uxbox.util.data :refer (index-of)]))
|
|
|
|
;; --- Expose inner functions
|
|
|
|
(def start-viewport-positioning wscroll/start-viewport-positioning)
|
|
(def stop-viewport-positioning wscroll/stop-viewport-positioning)
|
|
(def start-drawing wdrawing/start-drawing)
|
|
(def close-drawing-path wdrawing/close-drawing-path)
|
|
(def select-for-drawing wdrawing/select-for-drawing)
|
|
(def start-selrect wselrect/start-selrect)
|
|
|
|
;; --- Initialize Workspace
|
|
|
|
(declare initialize-alignment)
|
|
|
|
(defrecord InitializeWorkspace [project page]
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(if (:workspace state)
|
|
(update state :workspace merge
|
|
{:project project
|
|
:page page
|
|
:selected #{}
|
|
:drawing nil
|
|
:tooltip nil})
|
|
(assoc state :workspace
|
|
{:project project
|
|
:zoom 1
|
|
:page page
|
|
:flags #{:sitemap :drawtools :layers :element-options}
|
|
:selected #{}
|
|
:drawing nil
|
|
:tooltip nil})))
|
|
|
|
ptk/WatchEvent
|
|
(watch [_ state s]
|
|
(let [page-id page
|
|
page (get-in state [:pages page-id])]
|
|
|
|
;; Activate loaded if page is not fetched.
|
|
(when-not page (reset! st/loader true))
|
|
|
|
(rx/merge
|
|
(if page
|
|
(rx/of (initialize-alignment page-id))
|
|
(rx/merge
|
|
(rx/of (udp/fetch-pages project))
|
|
(->> (rx/filter udp/pages-fetched? s)
|
|
(rx/take 1)
|
|
(rx/do #(reset! st/loader false))
|
|
(rx/map #(initialize-alignment page-id)))))
|
|
|
|
;; Initial history loading
|
|
(rx/of
|
|
(udh/fetch-page-history page-id)
|
|
(udh/fetch-pinned-page-history page-id)))))
|
|
|
|
ptk/EffectEvent
|
|
(effect [_ state stream]
|
|
;; Optimistic prefetch of projects if them are not already fetched
|
|
(when-not (seq (:projects state))
|
|
(st/emit! (dp/fetch-projects)))))
|
|
|
|
(defn initialize
|
|
"Initialize the workspace state."
|
|
[project page]
|
|
(InitializeWorkspace. project page))
|
|
|
|
;; --- Workspace Flags
|
|
|
|
(deftype ActivateFlag [flag]
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(update-in state [:workspace :flags] conj flag)))
|
|
|
|
(defn activate-flag
|
|
[flag]
|
|
{:pre [(keyword? flag)]}
|
|
(ActivateFlag. flag))
|
|
|
|
(deftype DeactivateFlag [flag]
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(update-in state [:workspace :flags] disj flag)))
|
|
|
|
(defn deactivate-flag
|
|
[flag]
|
|
{:pre [(keyword? flag)]}
|
|
(DeactivateFlag. flag))
|
|
|
|
(deftype ToggleFlag [flag]
|
|
ptk/WatchEvent
|
|
(watch [_ state stream]
|
|
(let [flags (get-in state [:workspace :flags])]
|
|
(if (contains? flags flag)
|
|
(rx/of (deactivate-flag flag))
|
|
(rx/of (activate-flag flag))))))
|
|
|
|
(defn toggle-flag
|
|
[flag]
|
|
(ToggleFlag. flag))
|
|
|
|
;; --- Icons Toolbox
|
|
|
|
(deftype SelectIconsToolboxCollection [id]
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(assoc-in state [:workspace :icons-toolbox] id))
|
|
|
|
ptk/WatchEvent
|
|
(watch [_ state stream]
|
|
(rx/of (udi/fetch-icons id))))
|
|
|
|
(defn select-icons-toolbox-collection
|
|
[id]
|
|
{:pre [(or (nil? id) (uuid? id))]}
|
|
(SelectIconsToolboxCollection. id))
|
|
|
|
(deftype InitializeIconsToolbox []
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
state)
|
|
|
|
ptk/WatchEvent
|
|
(watch [_ state stream]
|
|
(letfn [(get-first-with-icons [colls]
|
|
(->> (sort-by :name colls)
|
|
(filter #(> (:num-icons %) 0))
|
|
(first)
|
|
(:id)))
|
|
(on-fetched [event]
|
|
(let [coll (get-first-with-icons @event)]
|
|
(println "first" coll)
|
|
(select-icons-toolbox-collection coll)))]
|
|
(rx/merge
|
|
(rx/of (udi/fetch-collections)
|
|
(udi/fetch-icons nil))
|
|
|
|
;; Only perform the autoselection if it is not
|
|
;; previously already selected by the user.
|
|
(when-not (contains? (:workspace state) :icons-toolbox)
|
|
(->> stream
|
|
(rx/filter udi/collections-fetched?)
|
|
(rx/take 1)
|
|
(rx/map on-fetched)))))))
|
|
|
|
(defn initialize-icons-toolbox
|
|
[]
|
|
(InitializeIconsToolbox.))
|
|
|
|
;; --- Clipboard Management
|
|
|
|
(defrecord CopyToClipboard []
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(let [selected (get-in state [:workspace :selected])
|
|
item {:id (uuid/random)
|
|
:created-at (dt/now)
|
|
:items selected}
|
|
clipboard (-> (:clipboard state)
|
|
(conj item))]
|
|
(assoc state :clipboard
|
|
(if (> (count clipboard) 5)
|
|
(pop clipboard)
|
|
clipboard)))))
|
|
|
|
(defn copy-to-clipboard
|
|
"Copy selected shapes to clipboard."
|
|
[]
|
|
(CopyToClipboard.))
|
|
|
|
(defrecord PasteFromClipboard [id]
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(let [page (get-in state [:workspace :page])
|
|
selected (if (nil? id)
|
|
(first (:clipboard state))
|
|
(->> (:clipboard state)
|
|
(filter #(= id (:id %)))
|
|
(first)))]
|
|
(shimpl/duplicate-shapes state (:items selected) page))))
|
|
|
|
(defn paste-from-clipboard
|
|
"Copy selected shapes to clipboard."
|
|
([] (PasteFromClipboard. nil))
|
|
([id] (PasteFromClipboard. id)))
|
|
|
|
;; --- Zoom Management
|
|
|
|
(deftype IncreaseZoom []
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(let [increase #(nth c/zoom-levels
|
|
(+ (index-of c/zoom-levels %) 1)
|
|
(last c/zoom-levels))]
|
|
(update-in state [:workspace :zoom] (fnil increase 1)))))
|
|
|
|
(defn increase-zoom
|
|
[]
|
|
(IncreaseZoom.))
|
|
|
|
(deftype DecreaseZoom []
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(let [decrease #(nth c/zoom-levels
|
|
(- (index-of c/zoom-levels %) 1)
|
|
(first c/zoom-levels))]
|
|
(update-in state [:workspace :zoom] (fnil decrease 1)))))
|
|
|
|
(defn decrease-zoom
|
|
[]
|
|
(DecreaseZoom.))
|
|
|
|
(deftype ResetZoom []
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(assoc-in state [:workspace :zoom] 1)))
|
|
|
|
(defn reset-zoom
|
|
[]
|
|
(ResetZoom.))
|
|
|
|
;; --- Tooltips
|
|
|
|
(defrecord SetTooltip [text]
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(assoc-in state [:workspace :tooltip] text)))
|
|
|
|
(defn set-tooltip
|
|
[text]
|
|
(SetTooltip. text))
|
|
|
|
;; --- Grid Alignment
|
|
|
|
(declare initialize-alignment?)
|
|
|
|
(defrecord InitializeAlignment [id]
|
|
ptk/WatchEvent
|
|
(watch [_ state stream]
|
|
(let [page (get-in state [:pages id])
|
|
opts (:metadata page)
|
|
params {:width c/viewport-width
|
|
:height c/viewport-height
|
|
:x-axis (:grid-x-axis opts c/grid-x-axis)
|
|
:y-axis (:grid-y-axis opts c/grid-y-axis)}
|
|
stoper (->> (rx/filter initialize-alignment? stream)
|
|
(rx/take 1))]
|
|
(->> (rx/just nil)
|
|
(rx/delay 1000)
|
|
(rx/take-until stoper)
|
|
(rx/flat-map (fn [_]
|
|
(rx/merge (->> (uwrk/initialize-alignment params)
|
|
(rx/map #(activate-flag :grid-indexed)))
|
|
(when (:grid-alignment opts)
|
|
(rx/of (activate-flag :grid-alignment))))))))))
|
|
|
|
(defn initialize-alignment?
|
|
[v]
|
|
(instance? InitializeAlignment v))
|
|
|
|
(defn initialize-alignment
|
|
[id]
|
|
(InitializeAlignment. id))
|
|
|
|
;; --- Update Metadata
|
|
|
|
;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event.
|
|
|
|
(defrecord UpdateMetadata [id metadata]
|
|
ptk/WatchEvent
|
|
(watch [_ state s]
|
|
(rx/of (udp/update-metadata id metadata)
|
|
(initialize-alignment id))))
|
|
|
|
(defn update-metadata
|
|
[id metadata]
|
|
{:pre [(uuid? id) (us/valid? ::udp/metadata metadata)]}
|
|
(UpdateMetadata. id metadata))
|