mirror of
https://github.com/penpot/penpot.git
synced 2025-06-13 05:11:38 +02:00
♻️ Refactor file persistence layer.
This commit is contained in:
parent
182afedc54
commit
4e694ff194
86 changed files with 3205 additions and 3313 deletions
|
@ -201,26 +201,17 @@
|
|||
|
||||
;; --- Fetch Files
|
||||
|
||||
(declare files-fetched)
|
||||
|
||||
(defn fetch-files
|
||||
[project-id]
|
||||
(ptk/reify ::fetch-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [params {:project-id project-id}]
|
||||
(->> (rp/query :files params)
|
||||
(rx/map files-fetched))))))
|
||||
|
||||
(defn files-fetched
|
||||
[files]
|
||||
(us/verify (s/every ::file) files)
|
||||
(ptk/reify ::files-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [state (dissoc state :files)
|
||||
files (d/index-by :id files)]
|
||||
(assoc state :files files)))))
|
||||
(us/assert ::us/uuid project-id)
|
||||
(letfn [(on-fetched [files state]
|
||||
(assoc state :files (d/index-by :id files)))]
|
||||
(ptk/reify ::fetch-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [params {:project-id project-id}]
|
||||
(->> (rp/query :files params)
|
||||
(rx/map #(partial on-fetched %))))))))
|
||||
|
||||
;; --- Fetch Shared Files
|
||||
|
||||
|
@ -241,14 +232,13 @@
|
|||
|
||||
(defn fetch-recent-files
|
||||
[team-id]
|
||||
(us/assert ::us/uuid team-id)
|
||||
(ptk/reify ::fetch-recent-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [params {:team-id team-id}]
|
||||
(->> (rp/query :recent-files params)
|
||||
(rx/map recent-files-fetched)
|
||||
(rx/catch (fn [e]
|
||||
(rx/of (rt/nav' :auth-login)))))))))
|
||||
(rx/map recent-files-fetched))))))
|
||||
|
||||
(defn recent-files-fetched
|
||||
[recent-files]
|
||||
|
@ -415,9 +405,10 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (rt/nav :workspace {:project-id (:project-id data)
|
||||
:file-id (:id data)}
|
||||
{:page-id (first (:pages data))})))))
|
||||
(let [pparams {:project-id (:project-id data)
|
||||
:file-id (:id data)}
|
||||
qparams {:page-id (get-in data [:data :pages 0])}]
|
||||
(rx/of (rt/nav :workspace pparams qparams))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
(s/def ::project (s/keys ::req-un [::id ::name]))
|
||||
(s/def ::file (s/keys :req-un [::id ::name]))
|
||||
(s/def ::page (s/keys :req-un [::id ::name ::cp/data]))
|
||||
(s/def ::page ::cp/page)
|
||||
|
||||
(s/def ::interactions-mode #{:hide :show :show-on-click})
|
||||
|
||||
|
@ -43,37 +43,38 @@
|
|||
(declare bundle-fetched)
|
||||
|
||||
(defn initialize
|
||||
[page-id share-token]
|
||||
[{:keys [page-id file-id] :as params}]
|
||||
(ptk/reify ::initialize
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :viewer-local {:zoom 1
|
||||
:page-id page-id
|
||||
:file-id file-id
|
||||
:interactions-mode :hide
|
||||
:show-interactions? false}))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (fetch-bundle page-id share-token)))))
|
||||
(rx/of (fetch-bundle params)))))
|
||||
|
||||
;; --- Data Fetching
|
||||
|
||||
(defn fetch-bundle
|
||||
[page-id share-token]
|
||||
[{:keys [page-id file-id token]}]
|
||||
(ptk/reify ::fetch-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [params (cond-> {:page-id page-id}
|
||||
(string? share-token) (assoc :share-token share-token))]
|
||||
(let [params (cond-> {:page-id page-id
|
||||
:file-id file-id}
|
||||
(string? token) (assoc :share-token token))]
|
||||
(->> (rp/query :viewer-bundle params)
|
||||
(rx/map bundle-fetched)
|
||||
(rx/catch (fn [error-data]
|
||||
#_(rx/catch (fn [error-data]
|
||||
(rx/of (rt/nav :not-found)))))))))
|
||||
|
||||
(defn- extract-frames
|
||||
[page]
|
||||
(let [objects (get-in page [:data :objects])
|
||||
root (get objects uuid/zero)]
|
||||
[objects]
|
||||
(let [root (get objects uuid/zero)]
|
||||
(->> (:shapes root)
|
||||
(map #(get objects %))
|
||||
(filter #(= :frame (:type %)))
|
||||
|
@ -81,37 +82,41 @@
|
|||
(vec))))
|
||||
|
||||
(defn bundle-fetched
|
||||
[{:keys [project file page images] :as bundle}]
|
||||
[{:keys [project file page] :as bundle}]
|
||||
(us/verify ::bundle bundle)
|
||||
(ptk/reify ::file-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [frames (extract-frames page)
|
||||
objects (get-in page [:data :objects])]
|
||||
(let [objects (:objects page)
|
||||
frames (extract-frames objects)]
|
||||
(assoc state :viewer-data {:project project
|
||||
:objects objects
|
||||
:file file
|
||||
:page page
|
||||
:images images
|
||||
:frames frames})))))
|
||||
|
||||
(def create-share-link
|
||||
(ptk/reify ::create-share-link
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (get-in state [:viewer-local :page-id])]
|
||||
(->> (rp/mutation :generate-page-share-token {:id id})
|
||||
(rx/map (fn [{:keys [share-token]}]
|
||||
#(assoc-in % [:viewer-data :page :share-token] share-token))))))))
|
||||
(let [file-id (get-in state [:viewer-local :file-id])
|
||||
page-id (get-in state [:viewer-local :page-id])]
|
||||
(->> (rp/mutation :create-file-share-token {:file-id file-id
|
||||
:page-id page-id})
|
||||
(rx/map (fn [{:keys [token]}]
|
||||
#(assoc-in % [:viewer-data :share-token] token))))))))
|
||||
|
||||
(def delete-share-link
|
||||
(ptk/reify ::delete-share-link
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (get-in state [:viewer-local :page-id])]
|
||||
(->> (rp/mutation :clear-page-share-token {:id id})
|
||||
(rx/map (fn [_]
|
||||
#(assoc-in % [:viewer-data :page :share-token] nil))))))))
|
||||
(let [file-id (get-in state [:viewer-local :file-id])
|
||||
page-id (get-in state [:viewer-local :page-id])
|
||||
token (get-in state [:viewer-data :share-token])]
|
||||
(->> (rp/mutation :delete-file-share-token {:file-id file-id
|
||||
:page-id page-id
|
||||
:token token})
|
||||
(rx/map (fn [_] #(update % :viewer-data dissoc :share-token))))))))
|
||||
|
||||
;; --- Zoom Management
|
||||
|
||||
|
@ -226,9 +231,10 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (get-in state [:viewer-local :page-id])
|
||||
frames (get-in state [:viewer-data :frames])
|
||||
index (d/index-of-pred frames #(= (:id %) frame-id))]
|
||||
(rx/of (rt/nav :viewer {:page-id page-id} {:index index}))))))
|
||||
file-id (get-in state [:viewer-local :file-id])
|
||||
frames (get-in state [:viewer-data :frames])
|
||||
index (d/index-of-pred frames #(= (:id %) frame-id))]
|
||||
(rx/of (rt/nav :viewer {:page-id page-id :file-id file-id} {:index index}))))))
|
||||
|
||||
;; --- Shortcuts
|
||||
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
|
||||
(ns app.main.data.workspace
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[clojure.set :as set]
|
||||
[potok.core :as ptk]
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
|
@ -24,21 +24,21 @@
|
|||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.notifications :as dwn]
|
||||
[app.main.data.workspace.persistence :as dwp]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.texts :as dwtxt]
|
||||
[app.main.data.workspace.transforms :as dwt]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
[app.main.worker :as uw]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.math :as mth]
|
||||
[app.util.timers :as ts]
|
||||
[app.util.router :as rt]
|
||||
[app.util.timers :as ts]
|
||||
[app.util.transit :as t]
|
||||
[app.util.webapi :as wapi]))
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[clojure.set :as set]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
;; --- Specs
|
||||
|
||||
|
@ -85,13 +85,16 @@
|
|||
|
||||
(s/def ::options-mode #{:design :prototype})
|
||||
|
||||
(def workspace-file-local-default
|
||||
{:left-sidebar? true
|
||||
:right-sidebar? true
|
||||
:color-for-rename nil})
|
||||
|
||||
(def workspace-local-default
|
||||
{:zoom 1
|
||||
:flags #{}
|
||||
:selected (d/ordered-set)
|
||||
:expanded {}
|
||||
:drawing nil
|
||||
:drawing-tool nil
|
||||
:tooltip nil
|
||||
:options-mode :design
|
||||
:draw-interaction-to nil
|
||||
|
@ -113,32 +116,41 @@
|
|||
(ptk/reify ::initialize-file
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :workspace-presence {}))
|
||||
(assoc state
|
||||
:workspace-presence {}
|
||||
:workspace-file-local workspace-file-local-default))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/merge
|
||||
(rx/of (dwp/fetch-bundle project-id file-id))
|
||||
|
||||
;; Initialize notifications (websocket connection) and the file persistence
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dwp/bundle-fetched))
|
||||
(rx/mapcat (fn [_] (rx/of (dwn/initialize file-id))))
|
||||
(rx/first))
|
||||
(rx/first)
|
||||
(rx/mapcat #(rx/of (dwn/initialize file-id)
|
||||
(dwp/initialize-file-persistence file-id))))
|
||||
|
||||
;; Initialize Indexes (webworker)
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dwp/bundle-fetched))
|
||||
(rx/map deref)
|
||||
(rx/map dwc/setup-selection-index)
|
||||
(rx/map dwc/initialize-indices)
|
||||
(rx/first))
|
||||
|
||||
;; Mark file initialized when indexes are ready
|
||||
(->> stream
|
||||
(rx/filter #(= ::dwc/index-initialized %))
|
||||
(rx/map (constantly
|
||||
(file-initialized project-id file-id))))))))
|
||||
(file-initialized project-id file-id))))
|
||||
))))
|
||||
|
||||
|
||||
|
||||
(defn- file-initialized
|
||||
[project-id file-id]
|
||||
(ptk/reify ::initialized
|
||||
(ptk/reify ::file-initialized
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-file
|
||||
|
@ -152,11 +164,12 @@
|
|||
(ptk/reify ::finalize
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(dissoc state :workspace-file :workspace-project))
|
||||
(dissoc state :workspace-file :workspace-project :workspace-media-objects :workspace-users))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (dwn/finalize file-id)))))
|
||||
(rx/of (dwn/finalize file-id)
|
||||
::dwp/finalize))))
|
||||
|
||||
|
||||
(defn initialize-page
|
||||
|
@ -164,17 +177,14 @@
|
|||
(ptk/reify ::initialize-page
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page (get-in state [:workspace-pages page-id])
|
||||
;; TODO: looks workspace-page is unused
|
||||
(let [page (get-in state [:workspace-data :pages-index page-id])
|
||||
local (get-in state [:workspace-cache page-id] workspace-local-default)]
|
||||
(-> state
|
||||
(assoc :current-page-id page-id ; mainly used by events
|
||||
:workspace-local local
|
||||
:workspace-page (dissoc page :data))
|
||||
(assoc-in [:workspace-data page-id] (:data page)))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (dwp/initialize-page-persistence page-id)))))
|
||||
(assoc state
|
||||
:current-page-id page-id ; mainly used by events
|
||||
:workspace-page page
|
||||
:workspace-local local
|
||||
)))))
|
||||
|
||||
(defn finalize-page
|
||||
[page-id]
|
||||
|
@ -185,11 +195,67 @@
|
|||
(let [local (:workspace-local state)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-cache page-id] local)
|
||||
(update :workspace-data dissoc page-id))))
|
||||
(dissoc :current-page-id :workspace-page))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Workspace Page CRUD
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def create-empty-page
|
||||
(ptk/reify ::create-empty-page
|
||||
ptk/WatchEvent
|
||||
(watch [this state stream]
|
||||
(let [id (uuid/next)
|
||||
pages (get-in state [:workspace-data :pages-index])
|
||||
unames (dwc/retrieve-used-names pages)
|
||||
name (dwc/generate-unique-name unames "Page")
|
||||
|
||||
rchange {:type :add-page
|
||||
:id id
|
||||
:name name}
|
||||
uchange {:type :del-page
|
||||
:id id}]
|
||||
(rx/of (dwc/commit-changes [rchange] [uchange] {:commit-local? true}))))))
|
||||
|
||||
(s/def ::rename-page
|
||||
(s/keys :req-un [::id ::name]))
|
||||
|
||||
(defn rename-page
|
||||
[id name]
|
||||
(us/verify ::us/uuid id)
|
||||
(us/verify string? name)
|
||||
(ptk/reify ::rename-page
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of ::dwp/finalize))))
|
||||
(let [page (get-in state [:workspace-data :pages-index id])
|
||||
rchg {:type :mod-page
|
||||
:id id
|
||||
:name name}
|
||||
uchg {:type :mod-page
|
||||
:id id
|
||||
:name (:name page)}]
|
||||
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
||||
|
||||
(declare purge-page)
|
||||
(declare go-to-file)
|
||||
|
||||
;; TODO: properly handle positioning on undo.
|
||||
|
||||
(defn delete-page
|
||||
[id]
|
||||
(ptk/reify ::delete-page
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [page (get-in state [:workspace-data :pages-index id])
|
||||
rchg {:type :del-page
|
||||
:id id}
|
||||
uchg {:type :add-page
|
||||
:page page}]
|
||||
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})
|
||||
(when (= id (:current-page-id state))
|
||||
go-to-file))))))
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Workspace State Manipulation
|
||||
|
@ -212,8 +278,8 @@
|
|||
(update :height #(/ % hprop))))))))
|
||||
|
||||
(initialize [state local]
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
shapes (cph/select-toplevel-shapes objects {:include-frames? true})
|
||||
srect (geom/selection-rect shapes)
|
||||
local (assoc local :vport size)]
|
||||
|
@ -397,8 +463,8 @@
|
|||
(ptk/reify ::zoom-to-fit-all
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
shapes (cph/select-toplevel-shapes objects {:include-frames? true})
|
||||
srect (geom/selection-rect shapes)]
|
||||
|
||||
|
@ -420,11 +486,11 @@
|
|||
(let [selected (get-in state [:workspace-local :selected])]
|
||||
(if (empty? selected)
|
||||
state
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
srect (->> selected
|
||||
(map #(get objects %))
|
||||
(geom/selection-rect))]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
srect (->> selected
|
||||
(map #(get objects %))
|
||||
(geom/selection-rect))]
|
||||
(update state :workspace-local
|
||||
(fn [{:keys [vbox vport] :as local}]
|
||||
(let [srect (geom/adjust-to-viewport vport srect {:padding 40})
|
||||
|
@ -433,31 +499,8 @@
|
|||
(assoc :zoom zoom)
|
||||
(update :vbox merge srect)))))))))))
|
||||
|
||||
|
||||
;; --- Add shape to Workspace
|
||||
|
||||
(defn- retrieve-used-names
|
||||
[objects]
|
||||
(into #{} (map :name) (vals objects)))
|
||||
|
||||
(defn- extract-numeric-suffix
|
||||
[basename]
|
||||
(if-let [[match p1 p2] (re-find #"(.*)-([0-9]+)$" basename)]
|
||||
[p1 (+ 1 (d/parse-integer p2))]
|
||||
[basename 1]))
|
||||
|
||||
(defn- generate-unique-name
|
||||
"A unique name generator"
|
||||
[used basename]
|
||||
(s/assert ::set-of-string used)
|
||||
(s/assert ::us/string basename)
|
||||
(let [[prefix initial] (extract-numeric-suffix basename)]
|
||||
(loop [counter initial]
|
||||
(let [candidate (str prefix "-" counter)]
|
||||
(if (contains? used candidate)
|
||||
(recur (inc counter))
|
||||
candidate)))))
|
||||
|
||||
(declare start-edition-mode)
|
||||
|
||||
(defn add-shape
|
||||
|
@ -466,14 +509,14 @@
|
|||
(ptk/reify ::add-shape
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
|
||||
id (uuid/next)
|
||||
shape (geom/setup-proportions attrs)
|
||||
|
||||
unames (retrieve-used-names objects)
|
||||
name (generate-unique-name unames (:name shape))
|
||||
unames (dwc/retrieve-used-names objects)
|
||||
name (dwc/generate-unique-name unames (:name shape))
|
||||
|
||||
frames (cph/select-frames objects)
|
||||
|
||||
|
@ -492,9 +535,11 @@
|
|||
|
||||
rchange {:type :add-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:obj shape}
|
||||
uchange {:type :del-obj
|
||||
:page-id page-id
|
||||
:id id}]
|
||||
|
||||
(rx/concat
|
||||
|
@ -614,9 +659,9 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
|
||||
del-change #(array-map :type :del-obj :id %)
|
||||
del-change #(array-map :type :del-obj :page-id page-id :id %)
|
||||
|
||||
get-empty-parents
|
||||
(fn get-empty-parents [parents]
|
||||
|
@ -637,7 +682,9 @@
|
|||
(map del-change (reverse children))
|
||||
[(del-change id)]
|
||||
(map del-change (get-empty-parents parents))
|
||||
[{:type :reg-objects :shapes (vec parents)}])))
|
||||
[{:type :reg-objects
|
||||
:page-id page-id
|
||||
:shapes (vec parents)}])))
|
||||
[]
|
||||
ids)
|
||||
|
||||
|
@ -649,6 +696,7 @@
|
|||
(let [item (get objects id)]
|
||||
{:type :add-obj
|
||||
:id (:id item)
|
||||
:page-id page-id
|
||||
:index (cph/position-on-parent id objects)
|
||||
:frame-id (:frame-id item)
|
||||
:parent-id (:parent-id item)
|
||||
|
@ -657,7 +705,9 @@
|
|||
(map add-chg (reverse (get-empty-parents parents)))
|
||||
[(add-chg id)]
|
||||
(map add-chg children)
|
||||
[{:type :reg-objects :shapes (vec parents)}])))
|
||||
[{:type :reg-objects
|
||||
:page-id page-id
|
||||
:shapes (vec parents)}])))
|
||||
[]
|
||||
ids)
|
||||
]
|
||||
|
@ -673,16 +723,10 @@
|
|||
(ptk/reify ::delete-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
lookup #(get-in state [:workspace-data page-id :objects %])
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
|
||||
shapes (map lookup selected)
|
||||
shape? #(not= (:type %) :frame)]
|
||||
(let [selected (get-in state [:workspace-local :selected])]
|
||||
(rx/of (delete-shapes selected)
|
||||
dws/deselect-all)))))
|
||||
|
||||
|
||||
;; --- Shape Vertical Ordering
|
||||
|
||||
(s/def ::loc #{:up :down :bottom :top})
|
||||
|
@ -694,7 +738,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
rchanges (mapv (fn [id]
|
||||
(let [obj (get objects id)
|
||||
|
@ -709,6 +753,7 @@
|
|||
{:type :mov-objects
|
||||
:parent-id (:parent-id obj)
|
||||
:frame-id (:frame-id obj)
|
||||
:page-id page-id
|
||||
:index nindex
|
||||
:shapes [id]}))
|
||||
selected)
|
||||
|
@ -718,9 +763,11 @@
|
|||
{:type :mov-objects
|
||||
:parent-id (:parent-id obj)
|
||||
:frame-id (:frame-id obj)
|
||||
:page-id page-id
|
||||
:shapes [id]
|
||||
:index (cph/position-on-parent id objects)}))
|
||||
selected)]
|
||||
selected)]
|
||||
;; TODO: maybe missing the :reg-objects event?
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
|
||||
|
||||
|
||||
|
@ -736,8 +783,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
parents (loop [res #{parent-id}
|
||||
ids (seq ids)]
|
||||
(if (nil? ids)
|
||||
|
@ -748,9 +794,11 @@
|
|||
|
||||
rchanges [{:type :mov-objects
|
||||
:parent-id parent-id
|
||||
:page-id page-id
|
||||
:index to-index
|
||||
:shapes (vec (reverse ids))}
|
||||
{:type :reg-objects
|
||||
:page-id page-id
|
||||
:shapes parents}]
|
||||
|
||||
uchanges
|
||||
|
@ -759,11 +807,13 @@
|
|||
(conj res
|
||||
{:type :mov-objects
|
||||
:parent-id (:parent-id obj)
|
||||
:page-id page-id
|
||||
:index (cph/position-on-parent id objects)
|
||||
:shapes [id]})))
|
||||
[] (reverse ids))
|
||||
uchanges (conj uchanges
|
||||
{:type :reg-objects
|
||||
:page-id page-id
|
||||
:shapes parents})]
|
||||
|
||||
;; (println "================ rchanges")
|
||||
|
@ -787,23 +837,17 @@
|
|||
(defn relocate-page
|
||||
[id index]
|
||||
(ptk/reify ::relocate-pages
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pages (get-in state [:workspace-file :pages])
|
||||
[before after] (split-at index pages)
|
||||
p? (partial = id)
|
||||
pages' (d/concat []
|
||||
(remove p? before)
|
||||
[id]
|
||||
(remove p? after))]
|
||||
(assoc-in state [:workspace-file :pages] pages')))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [file (:workspace-file state)]
|
||||
(->> (rp/mutation! :reorder-pages {:page-ids (:pages file)
|
||||
:file-id (:id file)})
|
||||
(rx/ignore))))))
|
||||
(let [cidx (-> (get-in state [:workspace-data :pages])
|
||||
(d/index-of id))
|
||||
rchg {:type :mov-page
|
||||
:id id
|
||||
:index index}
|
||||
uchg {:type :mov-page
|
||||
:id id
|
||||
:index cidx}]
|
||||
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
||||
|
||||
;; --- Shape / Selection Alignment and Distribution
|
||||
|
||||
|
@ -817,7 +861,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
moved (if (= 1 (count selected))
|
||||
(align-object-to-frame objects (first selected) axis)
|
||||
|
@ -838,9 +882,11 @@
|
|||
ops2 (dwc/generate-operations curr prev)]
|
||||
(recur (next moved)
|
||||
(conj rchanges {:type :mod-obj
|
||||
:page-id page-id
|
||||
:operations ops1
|
||||
:id (:id curr)})
|
||||
(conj uchanges {:type :mod-obj
|
||||
:page-id page-id
|
||||
:operations ops2
|
||||
:id (:id curr)})))))))))
|
||||
|
||||
|
@ -863,9 +909,8 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
|
||||
moved (-> (map #(get objects %) selected)
|
||||
(geom/distribute-space axis objects))]
|
||||
(loop [moved (seq moved)
|
||||
|
@ -884,9 +929,11 @@
|
|||
ops2 (dwc/generate-operations curr prev)]
|
||||
(recur (next moved)
|
||||
(conj rchanges {:type :mod-obj
|
||||
:page-id page-id
|
||||
:operations ops1
|
||||
:id (:id curr)})
|
||||
(conj uchanges {:type :mod-obj
|
||||
:page-id page-id
|
||||
:operations ops2
|
||||
:id (:id curr)})))))))))
|
||||
|
||||
|
@ -921,7 +968,7 @@
|
|||
(ptk/reify ::clear-drawing
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local dissoc :drawing-tool :drawing))))
|
||||
(update state :workspace-drawing dissoc :tool :object))))
|
||||
|
||||
(defn select-for-drawing
|
||||
([tool] (select-for-drawing tool nil))
|
||||
|
@ -929,7 +976,7 @@
|
|||
(ptk/reify ::select-for-drawing
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local assoc :drawing-tool tool :drawing data))
|
||||
(update state :workspace-drawing assoc :tool tool :object data))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -963,7 +1010,8 @@
|
|||
|
||||
(defn set-shape-proportion-lock
|
||||
[id lock]
|
||||
(ptk/reify ::set-shape-proportion-lock
|
||||
(js/alert "TODO: broken")
|
||||
#_(ptk/reify ::set-shape-proportion-lock
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
|
@ -988,11 +1036,13 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
shape (get-in state [:workspace-data page-id :objects id])
|
||||
current-position (gpt/point (:x shape) (:y shape))
|
||||
position (gpt/point (or (:x position) (:x shape)) (or (:y position) (:y shape)))
|
||||
displacement (gmt/translate-matrix (gpt/subtract position current-position))]
|
||||
(rx/of (dwt/set-modifiers [id] {:displacement displacement})
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
shape (get objects id)
|
||||
cpos (gpt/point (:x shape) (:y shape))
|
||||
pos (gpt/point (or (:x position) (:x shape))
|
||||
(or (:y position) (:y shape)))
|
||||
displ (gmt/translate-matrix (gpt/subtract pos cpos))]
|
||||
(rx/of (dwt/set-modifiers [id] {:displacement displ})
|
||||
(dwt/apply-modifiers [id]))))))
|
||||
|
||||
;; --- Path Modifications
|
||||
|
@ -1003,7 +1053,8 @@
|
|||
(us/verify ::us/uuid id)
|
||||
(us/verify ::us/integer index)
|
||||
(us/verify gpt/point? delta)
|
||||
(ptk/reify ::update-path
|
||||
(js/alert "TODO: broken")
|
||||
#_(ptk/reify ::update-path
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)]
|
||||
|
@ -1047,24 +1098,21 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [project-id (get-in state [:workspace-project :id])
|
||||
file-id (get-in state [:workspace-page :file-id])
|
||||
path-params {:file-id file-id :project-id project-id}
|
||||
query-params {:page-id page-id}]
|
||||
(rx/of (rt/nav :workspace path-params query-params))))))
|
||||
file-id (get-in state [:workspace-file :id])
|
||||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id}]
|
||||
(rx/of (rt/nav :workspace pparams qparams))))))
|
||||
|
||||
|
||||
(def go-to-file
|
||||
(ptk/reify ::go-to-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [file (:workspace-file state)
|
||||
|
||||
file-id (:id file)
|
||||
project-id (:project-id file)
|
||||
page-ids (:pages file)
|
||||
|
||||
path-params {:project-id project-id :file-id file-id}
|
||||
query-params {:page-id (first page-ids)}]
|
||||
(rx/of (rt/nav :workspace path-params query-params))))))
|
||||
(let [{:keys [id project-id data] :as file} (:workspace-file state)
|
||||
page-id (get-in data [:pages 0])
|
||||
pparams {:project-id project-id :file-id id}
|
||||
qparams {:page-id page-id}]
|
||||
(rx/of (rt/nav :workspace pparams qparams))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Context Menu
|
||||
|
@ -1128,8 +1176,7 @@
|
|||
(ptk/reify ::copy-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
(let [objects (dwc/lookup-page-objects state)
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
cdata (prepare-selected objects selected)]
|
||||
(->> (t/encode cdata)
|
||||
|
@ -1144,18 +1191,18 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected-objs (map #(get objects %) selected)
|
||||
wrapper (geom/selection-rect selected-objs)
|
||||
orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))
|
||||
wrapper (geom/selection-rect selected-objs)
|
||||
orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))
|
||||
mouse-pos @ms/mouse-position
|
||||
delta (gpt/subtract mouse-pos orig-pos)
|
||||
delta (gpt/subtract mouse-pos orig-pos)
|
||||
|
||||
page-id (:current-page-id state)
|
||||
unames (-> (get-in state [:workspace-data page-id :objects])
|
||||
(retrieve-used-names))
|
||||
page-id (:current-page-id state)
|
||||
unames (-> (dwc/lookup-page-objects state page-id)
|
||||
(dwc/retrieve-used-names))
|
||||
|
||||
rchanges (dws/prepare-duplicate-changes objects unames selected delta)
|
||||
uchanges (mapv #(array-map :type :del-obj :id (:id %))
|
||||
(reverse rchanges))
|
||||
rchanges (dws/prepare-duplicate-changes objects page-id unames selected delta)
|
||||
uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
|
||||
(reverse rchanges))
|
||||
|
||||
selected (->> rchanges
|
||||
(filter #(selected (:old-id %)))
|
||||
|
@ -1203,22 +1250,6 @@
|
|||
(js/console.error "Clipboard error:" err)
|
||||
(rx/empty)))))))
|
||||
|
||||
|
||||
;; --- Change Page Order (D&D Ordering)
|
||||
|
||||
(defn change-page-order
|
||||
[{:keys [id index] :as params}]
|
||||
{:pre [(uuid? id) (number? index)]}
|
||||
(ptk/reify ::change-page-order
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page (get-in state [:pages id])
|
||||
pages (get-in state [:projects (:project-id page) :pages])
|
||||
pages (into [] (remove #(= % id)) pages)
|
||||
[before after] (split-at index pages)
|
||||
pages (vec (concat before [id] after))]
|
||||
(assoc-in state [:projects (:project-id page) :pages] pages)))))
|
||||
|
||||
(defn update-shape-flags
|
||||
[id {:keys [blocked hidden] :as flags}]
|
||||
(s/assert ::us/uuid id)
|
||||
|
@ -1252,10 +1283,10 @@
|
|||
(ptk/reify ::group-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (uuid/next)
|
||||
page-id (get-in state [:workspace-page :id])
|
||||
(let [id (uuid/next)
|
||||
page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
items (->> selected
|
||||
(map #(get objects %))
|
||||
(filter #(not= :frame (:type %)))
|
||||
|
@ -1273,11 +1304,13 @@
|
|||
|
||||
rchanges [{:type :add-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:parent-id parent-id
|
||||
:obj group
|
||||
:index index}
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id id
|
||||
:shapes (->> items
|
||||
(map :id)
|
||||
|
@ -1287,13 +1320,14 @@
|
|||
uchanges
|
||||
(reduce (fn [res obj]
|
||||
(conj res {:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id (:parent-id obj)
|
||||
:index (::index obj)
|
||||
:shapes [(:id obj)]}))
|
||||
[]
|
||||
items)
|
||||
|
||||
uchanges (conj uchanges {:type :del-obj :id id})]
|
||||
uchanges (conj uchanges {:type :del-obj :id id :page-id page-id})]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dws/select-shapes (d/ordered-set id)))))))))
|
||||
|
@ -1303,7 +1337,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
group-id (first selected)
|
||||
group (get objects group-id)]
|
||||
|
@ -1317,17 +1351,21 @@
|
|||
(filter #(#{group-id} (second %)))
|
||||
(ffirst))
|
||||
rchanges [{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id parent-id
|
||||
:shapes shapes
|
||||
:index index-in-parent}]
|
||||
uchanges [{:type :add-obj
|
||||
:page-id page-id
|
||||
:id group-id
|
||||
:frame-id (:frame-id group)
|
||||
:obj (assoc group :shapes [])}
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id group-id
|
||||
:shapes shapes}
|
||||
{:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id parent-id
|
||||
:shapes [group-id]
|
||||
:index index-in-parent}]]
|
||||
|
@ -1361,12 +1399,12 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
selected-shape-id (-> state (get-in [:workspace-local :selected]) first)
|
||||
selected-shape (get objects selected-shape-id)
|
||||
selected-shape-frame-id (:frame-id selected-shape)
|
||||
start-frame (get objects selected-shape-frame-id)
|
||||
end-frame (dwc/get-frame-at-point objects position)]
|
||||
end-frame (dwc/get-frame-at-point objects position)]
|
||||
(cond-> state
|
||||
(not= position initial-pos) (assoc-in [:workspace-local :draw-interaction-to] position)
|
||||
(not= start-frame end-frame) (assoc-in [:workspace-local :draw-interaction-to-frame] end-frame))))))
|
||||
|
@ -1383,12 +1421,12 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [position @ms/mouse-position
|
||||
page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
frame (dwc/get-frame-at-point objects position)
|
||||
page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
frame (dwc/get-frame-at-point objects position)
|
||||
|
||||
shape-id (first (get-in state [:workspace-local :selected]))
|
||||
shape (get objects shape-id)]
|
||||
shape (get objects shape-id)]
|
||||
|
||||
(when-not (= position initial-pos)
|
||||
(if (and frame shape-id
|
||||
|
@ -1410,15 +1448,17 @@
|
|||
(ptk/reify ::change-canvas-color
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pid (get state :current-page-id)
|
||||
current-color (get-in state [:workspace-data pid :options :background])]
|
||||
(let [page-id (get state :current-page-id)
|
||||
options (dwc/lookup-page-options state page-id)
|
||||
ccolor (:background options)]
|
||||
(rx/of (dwc/commit-changes
|
||||
[{:type :set-option
|
||||
:page-id page-id
|
||||
:option :background
|
||||
:value color}]
|
||||
[{:type :set-option
|
||||
:option :background
|
||||
:value current-color}]
|
||||
:value ccolor}]
|
||||
{:commit-local? true}))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1444,9 +1484,6 @@
|
|||
(def unlink-file-from-library dwp/unlink-file-from-library)
|
||||
(def upload-media-objects dwp/upload-media-objects)
|
||||
(def delete-media-object dwp/delete-media-object)
|
||||
(def rename-page dwp/rename-page)
|
||||
(def delete-page dwp/delete-page)
|
||||
(def create-empty-page dwp/create-empty-page)
|
||||
|
||||
;; Selection
|
||||
|
||||
|
|
|
@ -25,10 +25,27 @@
|
|||
;; --- Protocols
|
||||
|
||||
(declare setup-selection-index)
|
||||
(declare update-page-indices)
|
||||
(declare update-indices)
|
||||
(declare reset-undo)
|
||||
(declare append-undo)
|
||||
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn lookup-page-objects
|
||||
([state]
|
||||
(lookup-page-objects state (:current-page-id state)))
|
||||
([state page-id]
|
||||
(get-in state [:workspace-data :pages-index page-id :objects])))
|
||||
|
||||
(defn lookup-page-options
|
||||
([state]
|
||||
(lookup-page-options state (:current-page-id state)))
|
||||
([state page-id]
|
||||
(get-in state [:workspace-data :pages-index page-id :options])))
|
||||
|
||||
|
||||
|
||||
;; --- Changes Handling
|
||||
|
||||
(defn commit-changes
|
||||
|
@ -41,24 +58,23 @@
|
|||
:as opts}]
|
||||
(us/verify ::cp/changes changes)
|
||||
(us/verify ::cp/changes undo-changes)
|
||||
|
||||
(ptk/reify ::commit-changes
|
||||
cljs.core/IDeref
|
||||
(-deref [_] changes)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
state (update-in state [:workspace-pages page-id :data] cp/process-changes changes)]
|
||||
(let [state (update-in state [:workspace-file :data] cp/process-changes changes)]
|
||||
(cond-> state
|
||||
commit-local? (update-in [:workspace-data page-id] cp/process-changes changes))))
|
||||
commit-local? (update :workspace-data cp/process-changes changes))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page (:workspace-page state)
|
||||
uidx (get-in state [:workspace-local :undo-index] ::not-found)]
|
||||
(let [page-id (:current-page-id state)
|
||||
uidx (get-in state [:workspace-undo :index] ::not-found)]
|
||||
(rx/concat
|
||||
(rx/of (update-page-indices (:id page)))
|
||||
(when (some :page-id changes)
|
||||
(rx/of (update-indices page-id)))
|
||||
|
||||
(when (and save-undo? (not= uidx ::not-found))
|
||||
(rx/of (reset-undo uidx)))
|
||||
|
@ -93,7 +109,7 @@
|
|||
result)))))
|
||||
|
||||
(defn generate-changes
|
||||
[objects1 objects2]
|
||||
[page-id objects1 objects2]
|
||||
(letfn [(impl-diff [res id]
|
||||
(let [obj1 (get objects1 id)
|
||||
obj2 (get objects2 id)
|
||||
|
@ -102,6 +118,7 @@
|
|||
(if (empty? ops)
|
||||
res
|
||||
(conj res {:type :mod-obj
|
||||
:page-id page-id
|
||||
:operations ops
|
||||
:id id}))))]
|
||||
(reduce impl-diff [] (set/union (set (keys objects1))
|
||||
|
@ -109,25 +126,23 @@
|
|||
|
||||
;; --- Selection Index Handling
|
||||
|
||||
(defn- setup-selection-index
|
||||
[{:keys [file pages] :as bundle}]
|
||||
(defn initialize-indices
|
||||
[{:keys [file] :as bundle}]
|
||||
(ptk/reify ::setup-selection-index
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [msg {:cmd :create-page-indices
|
||||
(let [msg {:cmd :initialize-indices
|
||||
:file-id (:id file)
|
||||
:pages pages}]
|
||||
:data (:data file)}]
|
||||
(->> (uw/ask! msg)
|
||||
(rx/map (constantly ::index-initialized)))))))
|
||||
|
||||
|
||||
(defn update-page-indices
|
||||
(defn update-indices
|
||||
[page-id]
|
||||
(ptk/reify ::update-page-indices
|
||||
(ptk/reify ::update-indices
|
||||
ptk/EffectEvent
|
||||
(effect [_ state stream]
|
||||
(let [objects (get-in state [:workspace-pages page-id :data :objects])
|
||||
lookup #(get objects %)]
|
||||
(let [objects (lookup-page-objects state page-id)]
|
||||
(uw/ask! {:cmd :update-page-indices
|
||||
:page-id page-id
|
||||
:objects objects})))))
|
||||
|
@ -143,7 +158,7 @@
|
|||
(or (:id frame) uuid/zero)))
|
||||
|
||||
(defn- calculate-shape-to-frame-relationship-changes
|
||||
[frames shapes]
|
||||
[page-id frames shapes]
|
||||
(loop [shape (first shapes)
|
||||
shapes (rest shapes)
|
||||
rch []
|
||||
|
@ -155,9 +170,11 @@
|
|||
(recur (first shapes)
|
||||
(rest shapes)
|
||||
(conj rch {:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id fid
|
||||
:shapes [(:id shape)]})
|
||||
(conj uch {:type :mov-objects
|
||||
:page-id page-id
|
||||
:parent-id (:frame-id shape)
|
||||
:shapes [(:id shape)]}))
|
||||
(recur (first shapes)
|
||||
|
@ -171,12 +188,12 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
objects (lookup-page-objects state page-id)
|
||||
|
||||
shapes (cph/select-toplevel-shapes objects)
|
||||
frames (cph/select-frames objects)
|
||||
|
||||
[rch uch] (calculate-shape-to-frame-relationship-changes frames shapes)]
|
||||
[rch uch] (calculate-shape-to-frame-relationship-changes page-id frames shapes)]
|
||||
(when-not (empty? rch)
|
||||
(rx/of (commit-changes rch uch {:commit-local? true})))))))
|
||||
|
||||
|
@ -184,11 +201,34 @@
|
|||
(defn get-frame-at-point
|
||||
[objects point]
|
||||
(let [frames (cph/select-frames objects)]
|
||||
(loop [frame (first frames)
|
||||
rest (rest frames)]
|
||||
(d/seek #(geom/has-point? % point) frames))))
|
||||
(d/seek #(geom/has-point? % point) frames)))
|
||||
|
||||
|
||||
(defn- extract-numeric-suffix
|
||||
[basename]
|
||||
(if-let [[match p1 p2] (re-find #"(.*)-([0-9]+)$" basename)]
|
||||
[p1 (+ 1 (d/parse-integer p2))]
|
||||
[basename 1]))
|
||||
|
||||
(defn retrieve-used-names
|
||||
[objects]
|
||||
(into #{} (map :name) (vals objects)))
|
||||
|
||||
(s/def ::set-of-string
|
||||
(s/every string? :kind set?))
|
||||
|
||||
(defn generate-unique-name
|
||||
"A unique name generator"
|
||||
[used basename]
|
||||
(s/assert ::set-of-string used)
|
||||
(s/assert ::us/string basename)
|
||||
(let [[prefix initial] (extract-numeric-suffix basename)]
|
||||
(loop [counter initial]
|
||||
(let [candidate (str prefix "-" counter)]
|
||||
(if (contains? used candidate)
|
||||
(recur (inc counter))
|
||||
candidate)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Undo / Redo
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -212,10 +252,9 @@
|
|||
(ptk/reify ::materialize-undo
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)]
|
||||
(-> state
|
||||
(update-in [:workspace-data page-id] cp/process-changes changes)
|
||||
(assoc-in [:workspace-local :undo-index] index))))))
|
||||
(-> state
|
||||
(update :workspace-data cp/process-changes changes)
|
||||
(assoc-in [:workspace-undo :index] index)))))
|
||||
|
||||
(defn- reset-undo
|
||||
[index]
|
||||
|
@ -223,10 +262,8 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :workspace-local dissoc :undo-index)
|
||||
(update-in [:workspace-local :undo]
|
||||
(fn [queue]
|
||||
(into [] (take (inc index) queue))))))))
|
||||
(update :workspace-undo dissoc :undo-index)
|
||||
(update-in [:workspace-undo :items] (fn [queue] (into [] (take (inc index) queue))))))))
|
||||
|
||||
(defn- append-undo
|
||||
[entry]
|
||||
|
@ -234,18 +271,17 @@
|
|||
(ptk/reify ::append-undo
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-local :undo] (fnil conj-undo-entry []) entry))))
|
||||
(update-in state [:workspace-undo :items] (fnil conj-undo-entry []) entry))))
|
||||
|
||||
(def undo
|
||||
(ptk/reify ::undo
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [local (:workspace-local state)
|
||||
undo (:undo local [])
|
||||
index (or (:undo-index local)
|
||||
(dec (count undo)))]
|
||||
(when-not (or (empty? undo) (= index -1))
|
||||
(let [changes (get-in undo [index :undo-changes])]
|
||||
(let [undo (:workspace-undo state)
|
||||
items (:items undo)
|
||||
index (or (:index undo) (dec (count items)))]
|
||||
(when-not (or (empty? items) (= index -1))
|
||||
(let [changes (get-in items [index :undo-changes])]
|
||||
(rx/of (materialize-undo changes (dec index))
|
||||
(commit-changes changes [] {:save-undo? false}))))))))
|
||||
|
||||
|
@ -253,12 +289,11 @@
|
|||
(ptk/reify ::redo
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [local (:workspace-local state)
|
||||
undo (:undo local [])
|
||||
index (or (:undo-index local)
|
||||
(dec (count undo)))]
|
||||
(when-not (or (empty? undo) (= index (dec (count undo))))
|
||||
(let [changes (get-in undo [(inc index) :redo-changes])]
|
||||
(let [undo (:workspace-undo state)
|
||||
items (:items undo)
|
||||
index (or (:index undo) (dec (count items)))]
|
||||
(when-not (or (empty? items) (= index (dec items)))
|
||||
(let [changes (get-in items [(inc index) :redo-changes])]
|
||||
(rx/of (materialize-undo changes (inc index))
|
||||
(commit-changes changes [] {:save-undo? false}))))))))
|
||||
|
||||
|
@ -266,7 +301,13 @@
|
|||
(ptk/reify ::reset-undo
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local dissoc :undo-index :undo))))
|
||||
(assoc state :workspace-undo {}))))
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Shapes
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(defn expand-all-parents
|
||||
|
@ -301,23 +342,25 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])]
|
||||
objects (lookup-page-objects state page-id)]
|
||||
(loop [ids (seq ids)
|
||||
rch []
|
||||
uch []]
|
||||
(if (nil? ids)
|
||||
(rx/of (commit-changes
|
||||
(cond-> rch reg-objects? (conj {:type :reg-objects :shapes (vec ids)}))
|
||||
(cond-> uch reg-objects? (conj {:type :reg-objects :shapes (vec ids)}))
|
||||
(cond-> rch reg-objects? (conj {:type :reg-objects :page-id page-id :shapes (vec ids)}))
|
||||
(cond-> uch reg-objects? (conj {:type :reg-objects :page-id page-id :shapes (vec ids)}))
|
||||
{:commit-local? true}))
|
||||
|
||||
(let [id (first ids)
|
||||
obj1 (get objects id)
|
||||
obj2 (f obj1)
|
||||
rchg {:type :mod-obj
|
||||
:page-id page-id
|
||||
:operations (generate-operations obj1 obj2)
|
||||
:id id}
|
||||
uchg {:type :mod-obj
|
||||
:page-id page-id
|
||||
:operations (generate-operations obj2 obj1)
|
||||
:id id}]
|
||||
(recur (next ids)
|
||||
|
@ -332,7 +375,7 @@
|
|||
(letfn [(impl-get-children [objects id]
|
||||
(cons id (cph/get-children id objects)))
|
||||
|
||||
(impl-gen-changes [objects ids]
|
||||
(impl-gen-changes [objects page-id ids]
|
||||
(loop [sids (seq ids)
|
||||
cids (seq (impl-get-children objects (first sids)))
|
||||
rchanges []
|
||||
|
@ -354,9 +397,11 @@
|
|||
rops (generate-operations obj1 obj2)
|
||||
uops (generate-operations obj2 obj1)
|
||||
rchg {:type :mod-obj
|
||||
:page-id page-id
|
||||
:operations rops
|
||||
:id id}
|
||||
uchg {:type :mod-obj
|
||||
:page-id page-id
|
||||
:operations uops
|
||||
:id id}]
|
||||
(recur sids
|
||||
|
@ -366,10 +411,7 @@
|
|||
(ptk/reify ::update-shapes-recursive
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
[rchanges uchanges] (impl-gen-changes objects (seq ids))]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (lookup-page-objects state page-id)
|
||||
[rchanges uchanges] (impl-gen-changes objects page-id (seq ids))]
|
||||
(rx/of (commit-changes rchanges uchanges {:commit-local? true})))))))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -15,9 +15,11 @@
|
|||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.snap :as snap]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.geom.path :as path]))
|
||||
|
@ -29,25 +31,27 @@
|
|||
(declare handle-finish-drawing)
|
||||
(declare conditional-align)
|
||||
|
||||
;; NOTE/TODO: when an exception is raised in some point of drawing the
|
||||
;; draw lock is not released so the user need to refresh in order to
|
||||
;; be able draw again. THIS NEED TO BE REVISITED
|
||||
|
||||
(defn start-drawing
|
||||
[type]
|
||||
{:pre [(keyword? type)]}
|
||||
(let [id (gensym "drawing")]
|
||||
(let [id (uuid/next)]
|
||||
(ptk/reify ::start-drawing
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-local :drawing-lock] #(if (nil? %) id %)))
|
||||
(update-in state [:workspace-drawing :lock] #(if (nil? %) id %)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [lock (get-in state [:workspace-local :drawing-lock])]
|
||||
(if (= lock id)
|
||||
(rx/merge
|
||||
(->> (rx/filter #(= % handle-finish-drawing) stream)
|
||||
(rx/take 1)
|
||||
(rx/map (fn [_] #(update % :workspace-local dissoc :drawing-lock))))
|
||||
(rx/of (handle-drawing type)))
|
||||
(rx/empty)))))))
|
||||
(let [lock (get-in state [:workspace-drawing :lock])]
|
||||
(when (= lock id)
|
||||
(rx/merge (->> (rx/filter #(= % handle-finish-drawing) stream)
|
||||
(rx/take 1)
|
||||
(rx/map (fn [_] #(update % :workspace-drawing dissoc :lock))))
|
||||
(rx/of (handle-drawing type)))))))))
|
||||
|
||||
(defn handle-drawing
|
||||
[type]
|
||||
|
@ -55,7 +59,7 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [data (cp/make-minimal-shape type)]
|
||||
(update-in state [:workspace-local :drawing] merge data)))
|
||||
(update-in state [:workspace-drawing :object] merge data)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -81,7 +85,7 @@
|
|||
(assoc-in [:modifiers :resize-rotation] 0))))
|
||||
|
||||
(update-drawing [state point lock? point-snap]
|
||||
(update-in state [:workspace-local :drawing] resize-shape point lock? point-snap))]
|
||||
(update-in state [:workspace-drawing :object] resize-shape point lock? point-snap))]
|
||||
|
||||
(ptk/reify ::handle-drawing-generic
|
||||
ptk/WatchEvent
|
||||
|
@ -92,8 +96,9 @@
|
|||
stoper (rx/filter stoper? stream)
|
||||
initial @ms/mouse-position
|
||||
|
||||
page-id (get state :current-page-id)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
|
||||
page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
layout (get state :workspace-layout)
|
||||
|
||||
frames (cph/select-frames objects)
|
||||
|
@ -104,18 +109,18 @@
|
|||
uuid/zero)
|
||||
|
||||
shape (-> state
|
||||
(get-in [:workspace-local :drawing])
|
||||
(get-in [:workspace-drawing :object])
|
||||
(geom/setup {:x (:x initial) :y (:y initial) :width 1 :height 1})
|
||||
(assoc :frame-id fid)
|
||||
(assoc ::initialized? true))]
|
||||
(rx/concat
|
||||
;; Add shape to drawing state
|
||||
(rx/of #(assoc-in state [:workspace-local :drawing] shape))
|
||||
(rx/of #(assoc-in state [:workspace-drawing :object] shape))
|
||||
|
||||
;; Initial SNAP
|
||||
(->> (snap/closest-snap-point page-id [shape] layout initial)
|
||||
(rx/map (fn [{:keys [x y]}]
|
||||
#(update-in % [:workspace-local :drawing] assoc :x x :y y))))
|
||||
#(update-in % [:workspace-drawing :object] assoc :x x :y y))))
|
||||
|
||||
(->> ms/mouse-position
|
||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||
|
@ -143,22 +148,22 @@
|
|||
|
||||
(initialize-drawing [state point]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :drawing :segments] [point point])
|
||||
(assoc-in [:workspace-local :drawing ::initialized?] true)))
|
||||
(assoc-in [:workspace-drawing :object :segments] [point point])
|
||||
(assoc-in [:workspace-drawing :object ::initialized?] true)))
|
||||
|
||||
(insert-point-segment [state point]
|
||||
(-> state
|
||||
(update-in [:workspace-local :drawing :segments] (fnil conj []) point)))
|
||||
(update-in [:workspace-drawing :object :segments] (fnil conj []) point)))
|
||||
|
||||
(update-point-segment [state index point]
|
||||
(let [segments (count (get-in state [:workspace-local :drawing :segments]))
|
||||
(let [segments (count (get-in state [:workspace-drawing :object :segments]))
|
||||
exists? (< -1 index segments)]
|
||||
(cond-> state
|
||||
exists? (assoc-in [:workspace-local :drawing :segments index] point))))
|
||||
exists? (assoc-in [:workspace-drawing :object :segments index] point))))
|
||||
|
||||
(finish-drawing-path [state]
|
||||
(update-in
|
||||
state [:workspace-local :drawing]
|
||||
state [:workspace-drawing :object]
|
||||
(fn [shape] (-> shape
|
||||
(update :segments #(vec (butlast %)))
|
||||
(geom/update-path-selrect)))))]
|
||||
|
@ -229,14 +234,14 @@
|
|||
(ms/mouse-event? event) (= type :up))
|
||||
|
||||
(initialize-drawing [state]
|
||||
(assoc-in state [:workspace-local :drawing ::initialized?] true))
|
||||
(assoc-in state [:workspace-drawing :object ::initialized?] true))
|
||||
|
||||
(insert-point-segment [state point]
|
||||
(update-in state [:workspace-local :drawing :segments] (fnil conj []) point))
|
||||
(update-in state [:workspace-drawing :object :segments] (fnil conj []) point))
|
||||
|
||||
(finish-drawing-curve [state]
|
||||
(update-in
|
||||
state [:workspace-local :drawing]
|
||||
state [:workspace-drawing :object]
|
||||
(fn [shape]
|
||||
(-> shape
|
||||
(update :segments #(path/simplify % simplify-tolerance))
|
||||
|
@ -260,7 +265,7 @@
|
|||
(ptk/reify ::handle-finish-drawing
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [shape (get-in state [:workspace-local :drawing])]
|
||||
(let [shape (get-in state [:workspace-drawing :object])]
|
||||
(rx/concat
|
||||
(rx/of dw/clear-drawing)
|
||||
(when (::initialized? shape)
|
||||
|
@ -281,5 +286,5 @@
|
|||
(ptk/reify ::close-drawing-path
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-local :drawing :close?] true))))
|
||||
(assoc-in state [:workspace-drawing :object :close?] true))))
|
||||
|
||||
|
|
99
frontend/src/app/main/data/workspace/libraries.cljs
Normal file
99
frontend/src/app/main/data/workspace/libraries.cljs
Normal file
|
@ -0,0 +1,99 @@
|
|||
;; 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.data.workspace.libraries
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.common.pages :as cp]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.util.color :as color]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
(defn add-color
|
||||
[color]
|
||||
(us/assert ::us/string color)
|
||||
(ptk/reify ::add-color
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [id (uuid/next)
|
||||
rchg {:type :add-color
|
||||
:color {:id id
|
||||
:name color
|
||||
:value color}}
|
||||
uchg {:type :del-color
|
||||
:id id}]
|
||||
(rx/of #(assoc-in % [:workspace-local :color-for-rename] id)
|
||||
(dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
||||
|
||||
|
||||
(def clear-color-for-rename
|
||||
(ptk/reify ::clear-color-for-rename
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-local :color-for-rename] nil))))
|
||||
|
||||
(defn update-color
|
||||
[{:keys [id] :as color}]
|
||||
(us/assert ::cp/color color)
|
||||
(ptk/reify ::update-color
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [prev (get-in state [:workspace-data :colors id])
|
||||
rchg {:type :mod-color
|
||||
:color color}
|
||||
uchg {:type :mod-color
|
||||
:color prev}]
|
||||
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
||||
|
||||
(defn delete-color
|
||||
[{:keys [id] :as color}]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::delete-color
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [prev (get-in state [:workspace-data :colors id])
|
||||
rchg {:type :del-color
|
||||
:id id}
|
||||
uchg {:type :add-color
|
||||
:color prev}]
|
||||
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
||||
|
||||
(defn add-media
|
||||
[{:keys [id] :as media}]
|
||||
(us/assert ::cp/media-object media)
|
||||
(ptk/reify ::add-media
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [rchg {:type :add-media
|
||||
:object media}
|
||||
uchg {:type :del-media
|
||||
:id id}]
|
||||
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
||||
|
||||
|
||||
(defn delete-media
|
||||
[{:keys [id] :as media}]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::delete-media
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [prev (get-in state [:workspace-data :media id])
|
||||
rchg {:type :del-media
|
||||
:id id}
|
||||
uchg {:type :add-media
|
||||
:object prev}]
|
||||
(rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
||||
|
|
@ -9,26 +9,28 @@
|
|||
|
||||
(ns app.main.data.workspace.notifications
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[clojure.set :as set]
|
||||
[potok.core :as ptk]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.spec :as us]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.persistence :as dwp]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.persistence :as dwp]
|
||||
[app.util.avatars :as avatars]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.util.time :as dt]
|
||||
[app.util.transit :as t]
|
||||
[app.util.websockets :as ws]))
|
||||
[app.util.websockets :as ws]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[clojure.set :as set]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
;; TODO: this module need to be revisited.
|
||||
|
||||
(declare handle-presence)
|
||||
(declare handle-pointer-update)
|
||||
(declare handle-page-change)
|
||||
(declare handle-file-change)
|
||||
(declare handle-pointer-send)
|
||||
(declare send-keepalive)
|
||||
|
||||
|
@ -62,7 +64,7 @@
|
|||
(case type
|
||||
:presence (handle-presence msg)
|
||||
:pointer-update (handle-pointer-update msg)
|
||||
:page-change (handle-page-change msg)
|
||||
:file-change (handle-file-change msg)
|
||||
::unknown))))
|
||||
|
||||
(->> stream
|
||||
|
@ -120,22 +122,29 @@
|
|||
avail (set/difference presence-palette used)
|
||||
color (or (first avail) "#000000")]
|
||||
(assoc session :color color))))
|
||||
|
||||
(assign-session [sessions {:keys [id profile]}]
|
||||
(let [session {:id id
|
||||
:fullname (:fullname profile)
|
||||
:updated-at (dt/now)
|
||||
:photo-uri (or (:photo-uri profile)
|
||||
(avatars/generate {:name (:fullname profile)}))}
|
||||
session (assign-color sessions session)]
|
||||
(assoc sessions id session)))
|
||||
|
||||
(update-sessions [previous profiles]
|
||||
(reduce (fn [current [session-id profile-id]]
|
||||
(let [profile (get profiles profile-id)
|
||||
session {:id session-id
|
||||
:fullname (:fullname profile)
|
||||
:photo-uri (or (:photo-uri profile)
|
||||
(avatars/generate {:name (:fullname profile)}))}
|
||||
session (assign-color current session)]
|
||||
(assoc current session-id session)))
|
||||
(select-keys previous (map first sessions))
|
||||
(filter (fn [[sid]] (not (contains? previous sid))) sessions)))]
|
||||
(let [previous (select-keys previous (map first sessions)) ; Initial clearing
|
||||
pending (->> sessions
|
||||
(filter #(not (contains? previous (first %))))
|
||||
(map (fn [[session-id profile-id]]
|
||||
{:id session-id
|
||||
:profile (get profiles profile-id)})))]
|
||||
(reduce assign-session previous pending)))]
|
||||
|
||||
(ptk/reify ::handle-presence
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [profiles (:workspace-users state)]
|
||||
(let [profiles (:workspace-users state)]
|
||||
(update state :workspace-presence update-sessions profiles))))))
|
||||
|
||||
(defn handle-pointer-update
|
||||
|
@ -143,13 +152,12 @@
|
|||
(ptk/reify ::handle-pointer-update
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [profile (get-in state [:workspace-users profile-id])]
|
||||
(update-in state [:workspace-presence session-id]
|
||||
(fn [session]
|
||||
(assoc session
|
||||
:point (gpt/point x y)
|
||||
:updated-at (dt/now)
|
||||
:page-id page-id)))))))
|
||||
(update-in state [:workspace-presence session-id]
|
||||
(fn [session]
|
||||
(assoc session
|
||||
:point (gpt/point x y)
|
||||
:updated-at (dt/now)
|
||||
:page-id page-id))))))
|
||||
|
||||
(defn handle-pointer-send
|
||||
[file-id point]
|
||||
|
@ -158,19 +166,24 @@
|
|||
(effect [_ state stream]
|
||||
(let [ws (get-in state [:ws file-id])
|
||||
sid (:session-id state)
|
||||
pid (get-in state [:workspace-page :id])
|
||||
pid (:current-page-id state)
|
||||
msg {:type :pointer-update
|
||||
:page-id pid
|
||||
:x (:x point)
|
||||
:y (:y point)}]
|
||||
(ws/-send ws (t/encode msg))))))
|
||||
|
||||
(defn handle-page-change
|
||||
[msg]
|
||||
(ptk/reify ::handle-page-change
|
||||
;; TODO: add specs
|
||||
|
||||
(defn handle-file-change
|
||||
[{:keys [file-id changes] :as msg}]
|
||||
(ptk/reify ::handle-file-change
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (dwp/shapes-changes-persisted msg)
|
||||
(dwc/update-page-indices (:page-id msg))))))
|
||||
|
||||
|
||||
(let [page-ids (into #{} (comp (map :page-id)
|
||||
(filter identity))
|
||||
changes)]
|
||||
(rx/merge
|
||||
(rx/of (dwp/shapes-changes-persisted file-id msg))
|
||||
(when (seq page-ids)
|
||||
(rx/from (map dwc/update-indices page-ids))))))))
|
||||
|
|
|
@ -9,17 +9,15 @@
|
|||
|
||||
(ns app.main.data.workspace.persistence
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[potok.core :as ptk]
|
||||
[app.common.data :as d]
|
||||
[app.common.media :as cm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.media :as cm]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.messages :as dm]
|
||||
[app.main.data.media :as di]
|
||||
[app.main.data.messages :as dm]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
|
@ -27,25 +25,23 @@
|
|||
[app.util.object :as obj]
|
||||
[app.util.router :as rt]
|
||||
[app.util.time :as dt]
|
||||
[app.util.transit :as t]))
|
||||
[app.util.transit :as t]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
(declare persist-changes)
|
||||
(declare update-selection-index)
|
||||
(declare shapes-changes-persisted)
|
||||
|
||||
;; --- Persistence
|
||||
|
||||
(defn initialize-page-persistence
|
||||
[page-id]
|
||||
(defn initialize-file-persistence
|
||||
[file-id]
|
||||
(letfn [(enable-reload-stoper []
|
||||
(obj/set! js/window "onbeforeunload" (constantly false)))
|
||||
(disable-reload-stoper []
|
||||
(obj/set! js/window "onbeforeunload" nil))]
|
||||
(ptk/reify ::initialize-persistence
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :current-page-id page-id))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (rx/filter #(= ::finalize %) stream)
|
||||
|
@ -61,7 +57,7 @@
|
|||
(rx/buffer-until notifier)
|
||||
(rx/map vec)
|
||||
(rx/filter (complement empty?))
|
||||
(rx/map #(persist-changes page-id %))
|
||||
(rx/map #(persist-changes file-id %))
|
||||
(rx/take-until (rx/delay 100 stoper)))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::changes-persisted))
|
||||
|
@ -70,40 +66,44 @@
|
|||
(rx/take-until stoper))))))))
|
||||
|
||||
(defn persist-changes
|
||||
[page-id changes]
|
||||
[file-id changes]
|
||||
(ptk/reify ::persist-changes
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [sid (:session-id state)
|
||||
page (get-in state [:workspace-pages page-id])
|
||||
changes (into [] (mapcat identity) changes)
|
||||
params {:id (:id page)
|
||||
:revn (:revn page)
|
||||
:session-id sid
|
||||
:changes changes}]
|
||||
(->> (rp/mutation :update-page params)
|
||||
(rx/map (fn [lagged]
|
||||
(if (= #{sid} (into #{} (map :session-id) lagged))
|
||||
(map #(assoc % :changes []) lagged)
|
||||
lagged)))
|
||||
(rx/mapcat seq)
|
||||
(rx/map shapes-changes-persisted))))))
|
||||
file (:workspace-file state)]
|
||||
(when (= (:id file) file-id)
|
||||
(let [changes (into [] (mapcat identity) changes)
|
||||
params {:id (:id file)
|
||||
:revn (:revn file)
|
||||
:session-id sid
|
||||
:changes changes}]
|
||||
(->> (rp/mutation :update-file params)
|
||||
(rx/map (fn [lagged]
|
||||
(if (= #{sid} (into #{} (map :session-id) lagged))
|
||||
(map #(assoc % :changes []) lagged)
|
||||
lagged)))
|
||||
(rx/mapcat seq)
|
||||
(rx/map #(shapes-changes-persisted file-id %)))))))))
|
||||
|
||||
(s/def ::shapes-changes-persisted
|
||||
(s/keys :req-un [::page-id ::revn ::cp/changes]))
|
||||
(s/keys :req-un [::revn ::cp/changes]))
|
||||
|
||||
(defn shapes-changes-persisted
|
||||
[{:keys [page-id revn changes] :as params}]
|
||||
[file-id {:keys [revn changes] :as params}]
|
||||
(us/verify ::us/uuid file-id)
|
||||
(us/verify ::shapes-changes-persisted params)
|
||||
(ptk/reify ::changes-persisted
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [sid (:session-id state)
|
||||
page (get-in state [:workspace-pages page-id])
|
||||
state (update-in state [:workspace-pages page-id :revn] #(max % revn))]
|
||||
(-> state
|
||||
(update-in [:workspace-data page-id] cp/process-changes changes)
|
||||
(update-in [:workspace-pages page-id :data] cp/process-changes changes))))))
|
||||
file (:workspace-file state)]
|
||||
(if (= file-id (:id file))
|
||||
(let [state (update-in state [:workspace-file :revn] #(max % revn))]
|
||||
(-> state
|
||||
(update :workspace-data cp/process-changes changes)
|
||||
(update-in [:workspace-file :data] cp/process-changes changes)))
|
||||
state)))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -148,18 +148,9 @@
|
|||
(->> (rx/zip (rp/query :file {:id file-id})
|
||||
(rp/query :file-users {:id file-id})
|
||||
(rp/query :project-by-id {:project-id project-id})
|
||||
(rp/query :pages {:file-id file-id})
|
||||
(rp/query :media-objects {:file-id file-id :is-local false})
|
||||
(rp/query :colors {:file-id file-id})
|
||||
(rp/query :file-libraries {:file-id file-id}))
|
||||
(rx/first)
|
||||
(rx/mapcat
|
||||
(fn [bundle]
|
||||
(->> (fetch-libraries-content (get bundle 6))
|
||||
(rx/map (fn [[lib-media-objects lib-colors]]
|
||||
(conj bundle lib-media-objects lib-colors))))))
|
||||
(rx/map (fn [bundle]
|
||||
(apply bundle-fetched bundle)))
|
||||
(rx/map (fn [bundle] (apply bundle-fetched bundle)))
|
||||
(rx/catch (fn [{:keys [type code] :as error}]
|
||||
(cond
|
||||
(= :not-found type)
|
||||
|
@ -172,76 +163,25 @@
|
|||
:else
|
||||
(throw error))))))))
|
||||
|
||||
(defn- fetch-libraries-content
|
||||
[libraries]
|
||||
(if (empty? libraries)
|
||||
(rx/of [{} {}])
|
||||
(rx/zip
|
||||
(->> ;; fetch media-objects list of each library, and concatenate in a sequence
|
||||
(apply rx/zip (for [library libraries]
|
||||
(->> (rp/query :media-objects {:file-id (:id library)
|
||||
:is-local false})
|
||||
(rx/map (fn [media-objects]
|
||||
[(:id library) media-objects])))))
|
||||
|
||||
;; reorganize the sequence as a map {library-id -> media-objects}
|
||||
(rx/map (fn [media-list]
|
||||
(reduce (fn [result, [library-id media-objects]]
|
||||
(assoc result library-id media-objects))
|
||||
{}
|
||||
media-list))))
|
||||
|
||||
(->> ;; fetch colorss list of each library, and concatenate in a vector
|
||||
(apply rx/zip (for [library libraries]
|
||||
(->> (rp/query :colors {:file-id (:id library)})
|
||||
(rx/map (fn [colors]
|
||||
[(:id library) colors])))))
|
||||
|
||||
;; reorganize the sequence as a map {library-id -> colors}
|
||||
(rx/map (fn [colors-list]
|
||||
(reduce (fn [result, [library-id colors]]
|
||||
(assoc result library-id colors))
|
||||
{}
|
||||
colors-list)))))))
|
||||
|
||||
(defn- bundle-fetched
|
||||
[file users project pages media-objects colors libraries lib-media-objects lib-colors]
|
||||
[file users project libraries]
|
||||
(ptk/reify ::bundle-fetched
|
||||
IDeref
|
||||
(-deref [_]
|
||||
{:file file
|
||||
:users users
|
||||
:project project
|
||||
:pages pages
|
||||
:media-objects media-objects
|
||||
:colors colors
|
||||
:libraries libraries})
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [assoc-page
|
||||
#(assoc-in %1 [:workspace-pages (:id %2)] %2)
|
||||
|
||||
assoc-media-objects
|
||||
#(assoc-in %1 [:workspace-libraries %2 :media-objects]
|
||||
(get lib-media-objects %2))
|
||||
|
||||
assoc-colors
|
||||
#(assoc-in %1 [:workspace-libraries %2 :colors]
|
||||
(get lib-colors %2))]
|
||||
|
||||
(as-> state $$
|
||||
(assoc $$
|
||||
:workspace-file (assoc file
|
||||
:media-objects media-objects
|
||||
:colors colors)
|
||||
:workspace-users (d/index-by :id users)
|
||||
:workspace-pages {}
|
||||
:workspace-project project
|
||||
:workspace-libraries (d/index-by :id libraries))
|
||||
(reduce assoc-media-objects $$ (keys lib-media-objects))
|
||||
(reduce assoc-colors $$ (keys lib-colors))
|
||||
(reduce assoc-page $$ pages))))))
|
||||
(assoc state
|
||||
:workspace-undo {}
|
||||
:workspace-project project
|
||||
:workspace-file file
|
||||
:workspace-data (:data file)
|
||||
:workspace-users (d/index-by :id users)
|
||||
:workspace-libraries (d/index-by :id libraries)))))
|
||||
|
||||
|
||||
;; --- Set File shared
|
||||
|
@ -358,80 +298,6 @@
|
|||
(assoc-in state [:workspace-pages id] page))))
|
||||
|
||||
|
||||
;; --- Page Crud
|
||||
|
||||
(declare page-created)
|
||||
|
||||
(def create-empty-page
|
||||
(ptk/reify ::create-empty-page
|
||||
ptk/WatchEvent
|
||||
(watch [this state stream]
|
||||
(let [file-id (get-in state [:workspace-file :id])
|
||||
name (name (gensym "Page "))
|
||||
ordering (count (get-in state [:workspace-file :pages]))
|
||||
params {:name name
|
||||
:file-id file-id
|
||||
:ordering ordering
|
||||
:data cp/default-page-data}]
|
||||
(->> (rp/mutation :create-page params)
|
||||
(rx/map page-created))))))
|
||||
|
||||
(defn page-created
|
||||
[{:keys [id file-id] :as page}]
|
||||
(us/verify ::page page)
|
||||
(ptk/reify ::page-created
|
||||
cljs.core/IDeref
|
||||
(-deref [_] page)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update-in [:workspace-file :pages] (fnil conj []) id)
|
||||
(assoc-in [:workspace-pages id] page)))))
|
||||
|
||||
(s/def ::rename-page
|
||||
(s/keys :req-un [::id ::name]))
|
||||
|
||||
(defn rename-page
|
||||
[id name]
|
||||
(us/verify ::us/uuid id)
|
||||
(us/verify string? name)
|
||||
(ptk/reify ::rename-page
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace-page :id])
|
||||
state (assoc-in state [:workspace-pages id :name] name)]
|
||||
(cond-> state
|
||||
(= pid id) (assoc-in [:workspace-page :name] name))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [params {:id id :name name}]
|
||||
(->> (rp/mutation :rename-page params)
|
||||
(rx/map #(ptk/data-event ::page-renamed params)))))))
|
||||
|
||||
(declare purge-page)
|
||||
(declare go-to-file)
|
||||
|
||||
(defn delete-page
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(purge-page state id))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [page (:workspace-page state)]
|
||||
(rx/merge
|
||||
(->> (rp/mutation :delete-page {:id id})
|
||||
(rx/flat-map (fn [_]
|
||||
(if (= id (:id page))
|
||||
(rx/of go-to-file)
|
||||
(rx/empty))))))))))
|
||||
|
||||
|
||||
;; --- Upload local media objects
|
||||
|
||||
(s/def ::local? ::us/boolean)
|
||||
|
@ -462,23 +328,7 @@
|
|||
(fn [uri]
|
||||
{:file-id file-id
|
||||
:is-local local?
|
||||
:url uri})
|
||||
|
||||
assoc-to-library
|
||||
(fn [media-object state]
|
||||
(cond
|
||||
(true? local?)
|
||||
state
|
||||
|
||||
(true? is-library)
|
||||
(update-in state
|
||||
[:workspace-libraries file-id :media-objects]
|
||||
#(conj % media-object))
|
||||
|
||||
:else
|
||||
(update-in state
|
||||
[:workspace-file :media-objects]
|
||||
#(conj % media-object))))]
|
||||
:url uri})]
|
||||
|
||||
(rx/concat
|
||||
(rx/of (dm/show {:content (tr "media.loading")
|
||||
|
@ -493,7 +343,6 @@
|
|||
(rx/map prepare-js-file)
|
||||
(rx/mapcat #(rp/mutation! :upload-media-object %))))
|
||||
(rx/do on-success)
|
||||
(rx/map (fn [mobj] (partial assoc-to-library mobj)))
|
||||
(rx/catch (fn [error]
|
||||
(cond
|
||||
(= (:code error) :media-type-not-allowed)
|
||||
|
@ -518,17 +367,6 @@
|
|||
(defn delete-media-object
|
||||
[file-id id]
|
||||
(ptk/reify ::delete-media-object
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [is-library (not= file-id (:id (:workspace-file state)))]
|
||||
(if is-library
|
||||
(update-in state
|
||||
[:workspace-libraries file-id :media-objects]
|
||||
(fn [media-objects] (filter #(not= (:id %) id) media-objects)))
|
||||
(update-in state
|
||||
[:workspace-file :media-objects]
|
||||
(fn [media-objects] (filter #(not= (:id %) id) media-objects))))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [params {:id id}]
|
||||
|
|
|
@ -125,8 +125,8 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)]
|
||||
(rx/of (dwc/expand-all-parents [id] objects)))))))
|
||||
|
||||
(defn select-shapes
|
||||
|
@ -139,8 +139,8 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)]
|
||||
(rx/of (dwc/expand-all-parents ids objects))))))
|
||||
|
||||
(def deselect-all
|
||||
|
@ -159,7 +159,7 @@
|
|||
(ptk/reify ::select-shapes-by-current-selrect
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
(let [page-id (:current-page-id state)
|
||||
selrect (get-in state [:workspace-local :selrect])]
|
||||
(rx/merge
|
||||
(rx/of (update-selrect nil))
|
||||
|
@ -174,17 +174,19 @@
|
|||
(ptk/reify ::select-inside-group
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
group (get objects group-id)
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
group (get objects group-id)
|
||||
children (map #(get objects %) (:shapes group))
|
||||
|
||||
;; TODO: consider using d/seek instead of filter+first
|
||||
selected (->> children (filter #(geom/has-point? % position)) first)]
|
||||
(when selected
|
||||
(rx/of deselect-all (select-shape (:id selected))))))))
|
||||
|
||||
|
||||
;; --- Duplicate Shapes
|
||||
(declare prepare-duplicate-changes)
|
||||
;; (declare prepare-duplicate-changes)
|
||||
(declare prepare-duplicate-change)
|
||||
(declare prepare-duplicate-frame-change)
|
||||
(declare prepare-duplicate-shape-change)
|
||||
|
@ -195,13 +197,13 @@
|
|||
"Prepare objects to paste: generate new id, give them unique names,
|
||||
move to the position of mouse pointer, and find in what frame they
|
||||
fit."
|
||||
[objects names ids delta]
|
||||
[objects page-id names ids delta]
|
||||
(loop [names names
|
||||
ids (seq ids)
|
||||
chgs []]
|
||||
(if ids
|
||||
(let [id (first ids)
|
||||
result (prepare-duplicate-change objects names id delta)
|
||||
result (prepare-duplicate-change objects page-id names id delta)
|
||||
result (if (vector? result) result [result])]
|
||||
(recur
|
||||
(into names (map change->name) result)
|
||||
|
@ -210,24 +212,24 @@
|
|||
chgs)))
|
||||
|
||||
(defn- prepare-duplicate-change
|
||||
[objects names id delta]
|
||||
[objects page-id names id delta]
|
||||
(let [obj (get objects id)]
|
||||
(if (= :frame (:type obj))
|
||||
(prepare-duplicate-frame-change objects names obj delta)
|
||||
(prepare-duplicate-shape-change objects names obj delta (:frame-id obj) (:parent-id obj)))))
|
||||
(prepare-duplicate-frame-change objects page-id names obj delta)
|
||||
(prepare-duplicate-shape-change objects page-id names obj delta (:frame-id obj) (:parent-id obj)))))
|
||||
|
||||
(defn- prepare-duplicate-shape-change
|
||||
[objects names obj delta frame-id parent-id]
|
||||
(let [id (uuid/next)
|
||||
name (generate-unique-name names (:name obj))
|
||||
[objects page-id names obj delta frame-id parent-id]
|
||||
(let [id (uuid/next)
|
||||
name (generate-unique-name names (:name obj))
|
||||
renamed-obj (assoc obj :id id :name name)
|
||||
moved-obj (geom/move renamed-obj delta)
|
||||
frames (cph/select-frames objects)
|
||||
frame-id (if frame-id
|
||||
frame-id
|
||||
(dwc/calculate-frame-overlap frames moved-obj))
|
||||
moved-obj (geom/move renamed-obj delta)
|
||||
frames (cph/select-frames objects)
|
||||
frame-id (if frame-id
|
||||
frame-id
|
||||
(dwc/calculate-frame-overlap frames moved-obj))
|
||||
|
||||
parent-id (or parent-id frame-id)
|
||||
parent-id (or parent-id frame-id)
|
||||
|
||||
children-changes
|
||||
(loop [names names
|
||||
|
@ -237,7 +239,7 @@
|
|||
(if (nil? cid)
|
||||
result
|
||||
(let [obj (get objects cid)
|
||||
changes (prepare-duplicate-shape-change objects names obj delta frame-id id)]
|
||||
changes (prepare-duplicate-shape-change objects page-id names obj delta frame-id id)]
|
||||
(recur
|
||||
(into names (map change->name changes))
|
||||
(into result changes)
|
||||
|
@ -249,6 +251,7 @@
|
|||
(dissoc :shapes))]
|
||||
(into [{:type :add-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:old-id (:id obj)
|
||||
:frame-id frame-id
|
||||
:parent-id parent-id
|
||||
|
@ -256,25 +259,25 @@
|
|||
children-changes)))
|
||||
|
||||
(defn- prepare-duplicate-frame-change
|
||||
[objects names obj delta]
|
||||
[objects page-id names obj delta]
|
||||
(let [frame-id (uuid/next)
|
||||
frame-name (generate-unique-name names (:name obj))
|
||||
sch (->> (map #(get objects %) (:shapes obj))
|
||||
(mapcat #(prepare-duplicate-shape-change objects names % delta frame-id frame-id)))
|
||||
sch (->> (map #(get objects %) (:shapes obj))
|
||||
(mapcat #(prepare-duplicate-shape-change objects page-id names % delta frame-id frame-id)))
|
||||
|
||||
renamed-frame (-> obj
|
||||
(assoc :id frame-id)
|
||||
(assoc :name frame-name)
|
||||
(assoc :frame-id uuid/zero)
|
||||
(dissoc :shapes))
|
||||
|
||||
moved-frame (geom/move renamed-frame delta)
|
||||
frame (-> obj
|
||||
(assoc :id frame-id)
|
||||
(assoc :name frame-name)
|
||||
(assoc :frame-id uuid/zero)
|
||||
(dissoc :shapes)
|
||||
(geom/move delta))
|
||||
|
||||
fch {:type :add-obj
|
||||
:old-id (:id obj)
|
||||
:page-id page-id
|
||||
:id frame-id
|
||||
:frame-id uuid/zero
|
||||
:obj moved-frame}]
|
||||
:obj frame}]
|
||||
|
||||
(into [fch] sch)))
|
||||
|
||||
|
@ -283,13 +286,14 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
delta (gpt/point 0 0)
|
||||
unames (retrieve-used-names objects)
|
||||
|
||||
rchanges (prepare-duplicate-changes objects unames selected delta)
|
||||
uchanges (mapv #(array-map :type :del-obj :id (:id %))
|
||||
rchanges (prepare-duplicate-changes objects page-id unames selected delta)
|
||||
uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
|
||||
(reverse rchanges))
|
||||
|
||||
selected (->> rchanges
|
||||
|
|
|
@ -10,23 +10,23 @@
|
|||
(ns app.main.data.workspace.transforms
|
||||
"Events related with shapes transformations"
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[app.common.data :as d]
|
||||
[app.common.spec :as us]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.snap :as snap]))
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.snap :as snap]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
[beicon.core :as rx]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
;; -- Declarations
|
||||
|
||||
|
@ -135,10 +135,11 @@
|
|||
(watch [_ state stream]
|
||||
(let [current-pointer @ms/mouse-position
|
||||
initial-position (merge current-pointer initial)
|
||||
stoper (rx/filter ms/mouse-up? stream)
|
||||
page-id (get state :current-page-id)
|
||||
resizing-shapes (map #(get-in state [:workspace-data page-id :objects %]) ids)
|
||||
layout (get state :workspace-layout)]
|
||||
stoper (rx/filter ms/mouse-up? stream)
|
||||
layout (:workspace-layout state)
|
||||
page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
resizing-shapes (map #(get objects %) ids)]
|
||||
(rx/concat
|
||||
(->> ms/mouse-position
|
||||
(rx/with-latest vector ms/mouse-position-shift)
|
||||
|
@ -148,12 +149,10 @@
|
|||
(rx/map #(conj current %)))))
|
||||
(rx/mapcat (partial resize shape initial-position resizing-shapes))
|
||||
(rx/take-until stoper))
|
||||
#_(rx/empty)
|
||||
(rx/of (apply-modifiers ids)
|
||||
finish-transform)))))))
|
||||
|
||||
|
||||
;; -- ROTATE
|
||||
(defn start-rotate
|
||||
[shapes]
|
||||
(ptk/reify ::start-rotate
|
||||
|
@ -164,10 +163,10 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (rx/filter ms/mouse-up? stream)
|
||||
group (gsh/selection-rect shapes)
|
||||
group-center (gsh/center group)
|
||||
initial-angle (gpt/angle @ms/mouse-position group-center)
|
||||
(let [stoper (rx/filter ms/mouse-up? stream)
|
||||
group (gsh/selection-rect shapes)
|
||||
group-center (gsh/center group)
|
||||
initial-angle (gpt/angle @ms/mouse-position group-center)
|
||||
calculate-angle (fn [pos ctrl?]
|
||||
(let [angle (- (gpt/angle pos group-center) initial-angle)
|
||||
angle (if (neg? angle) (+ 360 angle) angle)
|
||||
|
@ -201,9 +200,9 @@
|
|||
(ptk/reify ::start-move-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [initial @ms/mouse-position
|
||||
(let [initial (deref ms/mouse-position)
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
stopper (rx/filter ms/mouse-up? stream)]
|
||||
stopper (rx/filter ms/mouse-up? stream)]
|
||||
(->> ms/mouse-position
|
||||
(rx/take-until stopper)
|
||||
(rx/map #(gpt/to-vec initial %))
|
||||
|
@ -240,8 +239,8 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (get state :current-page-id)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
ids (if (nil? ids) (get-in state [:workspace-local :selected]) ids)
|
||||
shapes (mapv #(get objects %) ids)
|
||||
stopper (rx/filter ms/mouse-up? stream)
|
||||
|
@ -342,22 +341,24 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
not-frame-id? (fn [shape-id]
|
||||
(let [shape (get objects shape-id)]
|
||||
(or recurse-frames? (not (= :frame (:type shape))))))
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
|
||||
not-frame-id?
|
||||
(fn [shape-id]
|
||||
(let [shape (get objects shape-id)]
|
||||
(or recurse-frames? (not (= :frame (:type shape))))))
|
||||
|
||||
;; For each shape updates the modifiers given as arguments
|
||||
update-shape
|
||||
(fn [objects shape-id]
|
||||
(update-in objects [shape-id :modifiers] #(merge % modifiers)))
|
||||
|
||||
;; ID's + Children but remove frame children if the flag is set to false
|
||||
ids-with-children (concat ids (mapcat #(cph/get-children % objects)
|
||||
(filter not-frame-id? ids)))
|
||||
(filter not-frame-id? ids)))]
|
||||
|
||||
;; For each shape updates the modifiers given as arguments
|
||||
update-shape (fn [state shape-id]
|
||||
(update-in
|
||||
state
|
||||
[:workspace-data page-id :objects shape-id :modifiers]
|
||||
#(merge % modifiers)))]
|
||||
(reduce update-shape state ids-with-children))))))
|
||||
(d/update-in-when state [:workspace-data :pages-index page-id :objects]
|
||||
#(reduce update-shape % ids-with-children)))))))
|
||||
|
||||
(defn rotation-modifiers [center shape angle]
|
||||
(let [displacement (let [shape-center (gsh/center shape)]
|
||||
|
@ -376,25 +377,23 @@
|
|||
(set-rotation delta-rotation shapes (-> shapes gsh/selection-rect gsh/center)))
|
||||
|
||||
([delta-rotation shapes center]
|
||||
(ptk/reify ::set-rotation
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)]
|
||||
(letfn [(rotate-shape [state angle shape center]
|
||||
(let [objects (get-in state [:workspace-data page-id :objects])
|
||||
path [:workspace-data page-id :objects (:id shape) :modifiers]
|
||||
modifiers (rotation-modifiers center shape angle)]
|
||||
(-> state
|
||||
(update-in path merge modifiers))))
|
||||
(letfn [(rotate-shape [objects angle shape center]
|
||||
(update-in objects [(:id shape) :modifiers] merge (rotation-modifiers center shape angle)))
|
||||
|
||||
(rotate-around-center [state angle center shapes]
|
||||
(reduce #(rotate-shape %1 angle %2 center) state shapes))]
|
||||
(rotate-around-center [objects angle center shapes]
|
||||
(reduce #(rotate-shape %1 angle %2 center) objects shapes))
|
||||
|
||||
(let [objects (get-in state [:workspace-data page-id :objects])
|
||||
id->obj #(get objects %)
|
||||
get-children (fn [shape] (map id->obj (cph/get-children (:id shape) objects)))
|
||||
shapes (concat shapes (mapcat get-children shapes))]
|
||||
(rotate-around-center state delta-rotation center shapes))))))))
|
||||
(set-rotation [objects]
|
||||
(let [id->obj #(get objects %)
|
||||
get-children (fn [shape] (map id->obj (cph/get-children (:id shape) objects)))
|
||||
shapes (concat shapes (mapcat get-children shapes))]
|
||||
(rotate-around-center objects delta-rotation center shapes)))]
|
||||
|
||||
(ptk/reify ::set-rotation
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)]
|
||||
(d/update-in-when state [:workspace-data :pages-index page-id :objects] set-rotation)))))))
|
||||
|
||||
(defn apply-modifiers
|
||||
[ids]
|
||||
|
@ -403,8 +402,9 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects0 (get-in state [:workspace-pages page-id :data :objects])
|
||||
objects1 (get-in state [:workspace-data page-id :objects])
|
||||
|
||||
objects0 (get-in state [:workspace-file :data :pages-index page-id :objects])
|
||||
objects1 (get-in state [:workspace-data :pages-index page-id :objects])
|
||||
|
||||
;; ID's + Children ID's
|
||||
ids-with-children (d/concat [] (mapcat #(cph/get-children % objects1) ids) ids)
|
||||
|
@ -413,7 +413,9 @@
|
|||
update-shape #(update %1 %2 gsh/transform-shape)
|
||||
objects2 (reduce update-shape objects1 ids-with-children)
|
||||
|
||||
regchg {:type :reg-objects :shapes (vec ids)}
|
||||
regchg {:type :reg-objects
|
||||
:page-id page-id
|
||||
:shapes (vec ids)}
|
||||
|
||||
;; we need to generate redo chages from current
|
||||
;; state (with current temporal values) to new state but
|
||||
|
@ -421,8 +423,8 @@
|
|||
;; state (without temporal values in it, for this reason
|
||||
;; we have 3 different objects references).
|
||||
|
||||
rchanges (conj (dwc/generate-changes objects1 objects2) regchg)
|
||||
uchanges (conj (dwc/generate-changes objects2 objects0) regchg)
|
||||
rchanges (conj (dwc/generate-changes page-id objects1 objects2) regchg)
|
||||
uchanges (conj (dwc/generate-changes page-id objects2 objects0) regchg)
|
||||
]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
|
|
|
@ -73,7 +73,8 @@
|
|||
[objects]
|
||||
(mf/fnc shape-wrapper
|
||||
[{:keys [frame shape] :as props}]
|
||||
(let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper-factory objects))]
|
||||
(let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper-factory objects))
|
||||
frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (geom/transform-shape frame shape)
|
||||
opts #js {:shape shape}]
|
||||
|
@ -85,6 +86,7 @@
|
|||
:path [:> path/path-shape opts]
|
||||
:image [:> image/image-shape opts]
|
||||
:circle [:> circle/circle-shape opts]
|
||||
:frame [:> frame-wrapper {:shape shape}]
|
||||
:group [:> group-wrapper {:shape shape :frame frame}]
|
||||
nil))))))
|
||||
|
||||
|
|
|
@ -39,20 +39,65 @@
|
|||
|
||||
;; ---- Workspace refs
|
||||
|
||||
|
||||
;; (def workspace-local
|
||||
;; (l/derived :workspace-local st/state))
|
||||
|
||||
(def workspace-drawing
|
||||
(l/derived :workspace-drawing st/state))
|
||||
|
||||
(def workspace-local
|
||||
(l/derived :workspace-local st/state))
|
||||
(l/derived (fn [state]
|
||||
(merge (:workspace-local state)
|
||||
(:workspace-file-local state)))
|
||||
st/state =))
|
||||
|
||||
(def selected-shapes
|
||||
(l/derived :selected workspace-local))
|
||||
|
||||
(def selected-zoom
|
||||
(l/derived :zoom workspace-local))
|
||||
|
||||
|
||||
(def selected-drawing-tool
|
||||
(l/derived :tool workspace-drawing))
|
||||
|
||||
(def current-drawing-shape
|
||||
(l/derived :object workspace-drawing))
|
||||
|
||||
(def selected-edition
|
||||
(l/derived :edition workspace-local))
|
||||
|
||||
(def current-transform
|
||||
(l/derived :transform workspace-local))
|
||||
|
||||
(def options-mode
|
||||
(l/derived :options-mode workspace-local))
|
||||
|
||||
(def vbox
|
||||
(l/derived :vbox workspace-local))
|
||||
|
||||
(def current-hover
|
||||
(l/derived :hover workspace-local))
|
||||
|
||||
|
||||
(def workspace-layout
|
||||
(l/derived :workspace-layout st/state))
|
||||
|
||||
(def workspace-page
|
||||
(l/derived :workspace-page st/state))
|
||||
|
||||
(def workspace-page-id
|
||||
(l/derived :id workspace-page))
|
||||
|
||||
(def workspace-file
|
||||
(l/derived :workspace-file st/state))
|
||||
(l/derived (fn [state]
|
||||
(when-let [file (:workspace-file state)]
|
||||
(-> file
|
||||
(dissoc :data)
|
||||
(assoc :pages (get-in file [:data :pages])))))
|
||||
st/state =))
|
||||
|
||||
|
||||
(def workspace-file-colors
|
||||
(l/derived (fn [state]
|
||||
(when-let [file (:workspace-file state)]
|
||||
(get-in file [:data :colors])))
|
||||
st/state))
|
||||
|
||||
(def workspace-project
|
||||
(l/derived :workspace-project st/state))
|
||||
|
@ -72,110 +117,85 @@
|
|||
(def workspace-snap-data
|
||||
(l/derived :workspace-snap-data st/state))
|
||||
|
||||
;; TODO: BROKEN & TO BE REMOVED
|
||||
(def workspace-data
|
||||
(-> #(let [page-id (get-in % [:workspace-page :id])]
|
||||
(get-in % [:workspace-data page-id]))
|
||||
(l/derived st/state)))
|
||||
|
||||
(def workspace-page-options
|
||||
(l/derived :options workspace-data))
|
||||
(def workspace-page
|
||||
(l/derived (fn [state]
|
||||
(let [page-id (:current-page-id state)
|
||||
data (:workspace-data state)]
|
||||
(get-in data [:pages-index page-id])))
|
||||
st/state))
|
||||
|
||||
(def workspace-page-objects
|
||||
(l/derived :objects workspace-page))
|
||||
|
||||
(def workspace-page-options
|
||||
(l/derived :options workspace-page))
|
||||
|
||||
;; TODO: revisit
|
||||
(def workspace-saved-grids
|
||||
(l/derived :saved-grids workspace-page-options))
|
||||
|
||||
(def workspace-objects
|
||||
(l/derived :objects workspace-data))
|
||||
|
||||
(def workspace-frames
|
||||
(l/derived cph/select-frames workspace-objects))
|
||||
(l/derived cph/select-frames workspace-page-objects))
|
||||
|
||||
(defn object-by-id
|
||||
[id]
|
||||
(letfn [(selector [state]
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])]
|
||||
(get objects id)))]
|
||||
(l/derived selector st/state =)))
|
||||
(l/derived #(get % id) workspace-page-objects))
|
||||
|
||||
(defn objects-by-id
|
||||
[ids]
|
||||
(letfn [(selector [state]
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])]
|
||||
(->> (set ids)
|
||||
(map #(get objects %))
|
||||
(filter identity)
|
||||
(vec))))]
|
||||
(l/derived selector st/state =)))
|
||||
(l/derived (fn [objects]
|
||||
(into [] (comp (map #(get objects %))
|
||||
(filter identity))
|
||||
(set ids)))
|
||||
workspace-page-objects =))
|
||||
|
||||
(defn is-child-selected?
|
||||
[id]
|
||||
(letfn [(selector [state]
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
(let [page-id :current-page-id
|
||||
objects (get-in state [:workspace-data :pages-index page-id :objects])
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
shape (get objects id)
|
||||
children (cph/get-children id objects)]
|
||||
(some selected children)))]
|
||||
(l/derived selector st/state)))
|
||||
|
||||
(def selected-shapes
|
||||
(l/derived :selected workspace-local))
|
||||
|
||||
;; TODO: can be replaced by objects-by-id
|
||||
(def selected-objects
|
||||
(letfn [(selector [state]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])]
|
||||
(mapv #(get objects %) selected)))]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data :pages-index page-id :objects])]
|
||||
(mapv #(get objects %) selected)))]
|
||||
(l/derived selector st/state =)))
|
||||
|
||||
(def selected-shapes-with-children
|
||||
(letfn [(selector [state]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data :pages-index page-id :objects])
|
||||
children (mapcat #(cph/get-children % objects) selected)]
|
||||
(into selected children)))]
|
||||
(l/derived selector st/state)))
|
||||
(l/derived selector st/state =)))
|
||||
|
||||
|
||||
;; TODO: looks very inneficient access method, revisit the usage of this ref
|
||||
(def selected-objects-with-children
|
||||
(letfn [(selector [state]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data :pages-index page-id :objects])
|
||||
children (mapcat #(cph/get-children % objects) selected)
|
||||
accumulated (into selected children)]
|
||||
(mapv #(get objects %) accumulated)))]
|
||||
(l/derived selector st/state)))
|
||||
|
||||
(defn make-selected
|
||||
[id]
|
||||
(l/derived #(contains? % id) selected-shapes))
|
||||
|
||||
(def selected-zoom
|
||||
(l/derived :zoom workspace-local))
|
||||
|
||||
(def selected-drawing-tool
|
||||
(l/derived :drawing-tool workspace-local))
|
||||
|
||||
(def current-drawing-shape
|
||||
(l/derived :drawing workspace-local))
|
||||
|
||||
(def selected-edition
|
||||
(l/derived :edition workspace-local))
|
||||
|
||||
(def current-transform
|
||||
(l/derived :transform workspace-local))
|
||||
|
||||
(def options-mode
|
||||
(l/derived :options-mode workspace-local))
|
||||
|
||||
(def vbox
|
||||
(l/derived :vbox workspace-local))
|
||||
|
||||
(def current-hover
|
||||
(l/derived :hover workspace-local))
|
||||
shapes (into selected children)]
|
||||
(mapv #(get objects %) shapes)))]
|
||||
(l/derived selector st/state =)))
|
||||
|
||||
;; ---- Viewer refs
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
(ns app.main.ui
|
||||
(:require
|
||||
[expound.alpha :as expound]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]
|
||||
|
@ -49,7 +50,7 @@
|
|||
["/password" :settings-password]
|
||||
["/options" :settings-options]]
|
||||
|
||||
["/view/:page-id" :viewer]
|
||||
["/view/:file-id/:page-id" :viewer]
|
||||
["/not-found" :not-found]
|
||||
["/not-authorized" :not-authorized]
|
||||
|
||||
|
@ -57,7 +58,7 @@
|
|||
["/debug/icons-preview" :debug-icons-preview])
|
||||
|
||||
;; Used for export
|
||||
["/render-object/:page-id/:object-id" :render-object]
|
||||
["/render-object/:file-id/:page-id/:object-id" :render-object]
|
||||
|
||||
["/dashboard"
|
||||
["/team/:team-id"
|
||||
|
@ -112,16 +113,20 @@
|
|||
:viewer
|
||||
(let [index (d/parse-integer (get-in route [:params :query :index]))
|
||||
token (get-in route [:params :query :token])
|
||||
file-id (uuid (get-in route [:params :path :file-id]))
|
||||
page-id (uuid (get-in route [:params :path :page-id]))]
|
||||
[:& viewer-page {:page-id page-id
|
||||
:file-id file-id
|
||||
:index index
|
||||
:token token}])
|
||||
|
||||
:render-object
|
||||
(do
|
||||
(let [page-id (uuid (get-in route [:params :path :page-id]))
|
||||
object-id (uuid (get-in route [:params :path :object-id]))]
|
||||
[:& render/render-object {:page-id page-id
|
||||
(let [file-id (uuid (get-in route [:params :path :file-id]))
|
||||
page-id (uuid (get-in route [:params :path :page-id]))
|
||||
object-id (uuid (get-in route [:params :path :object-id]))]
|
||||
[:& render/render-object {:file-id file-id
|
||||
:page-id page-id
|
||||
:object-id object-id}]))
|
||||
|
||||
:workspace
|
||||
|
@ -163,9 +168,18 @@
|
|||
[error]
|
||||
(ts/schedule 0 #(st/emit! logout)))
|
||||
|
||||
(defmethod ptk/handle-error :assertion
|
||||
[{:keys [data stack] :as error}]
|
||||
(js/console.error stack)
|
||||
(js/console.error (with-out-str
|
||||
(expound/printer data))))
|
||||
|
||||
(defmethod ptk/handle-error :default
|
||||
[error]
|
||||
(js/console.error (if (map? error) (pr-str error) error))
|
||||
(ts/schedule 100 #(st/emit! (dm/show {:content "Something wrong has happened."
|
||||
:type :error
|
||||
:timeout 5000}))))
|
||||
(if (instance? ExceptionInfo error)
|
||||
(ptk/handle-error (ex-data error))
|
||||
(do
|
||||
(js/console.error (if (map? error) (pr-str error) error))
|
||||
(ts/schedule 100 #(st/emit! (dm/show {:content "Something wrong has happened."
|
||||
:type :error
|
||||
:timeout 5000}))))))
|
||||
|
|
|
@ -1,22 +1,34 @@
|
|||
;; 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.dashboard.grid
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[beicon.core :as rx]
|
||||
[rumext.alpha :as mf]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.dashboard :as dsh]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.modal :as modal]
|
||||
[app.main.ui.keyboard :as kbd]
|
||||
[app.main.ui.confirm :refer [confirm-dialog]]
|
||||
[app.main.ui.components.context-menu :refer [context-menu]]
|
||||
[app.main.worker :as wrk]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu :refer [context-menu]]
|
||||
[app.main.ui.confirm :refer [confirm-dialog]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.keyboard :as kbd]
|
||||
[app.main.ui.modal :as modal]
|
||||
[app.main.worker :as wrk]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [t tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.time :as dt]
|
||||
[app.util.timers :as ts]
|
||||
[app.util.time :as dt]))
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[lambdaisland.uri :as uri]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
;; --- Grid Item Thumbnail
|
||||
|
||||
|
@ -25,15 +37,15 @@
|
|||
[{:keys [file] :as props}]
|
||||
(let [container (mf/use-ref)]
|
||||
(mf/use-effect
|
||||
(mf/deps file)
|
||||
(mf/deps (:id file))
|
||||
(fn []
|
||||
(-> (wrk/ask! {:cmd :thumbnails/generate
|
||||
:id (first (:pages file))
|
||||
})
|
||||
(rx/subscribe (fn [{:keys [svg fonts]}]
|
||||
(run! fonts/ensure-loaded! fonts)
|
||||
(when-let [node (mf/ref-val container)]
|
||||
(set! (.-innerHTML ^js node) svg)))))))
|
||||
(->> (wrk/ask! {:cmd :thumbnails/generate
|
||||
:file-id (:id file)
|
||||
:page-id (get-in file [:data :pages 0])})
|
||||
(rx/subs (fn [{:keys [svg fonts]}]
|
||||
(run! fonts/ensure-loaded! fonts)
|
||||
(when-let [node (mf/ref-val container)]
|
||||
(set! (.-innerHTML ^js node) svg)))))))
|
||||
[:div.grid-item-th {:style {:background-color (get-in file [:data :options :background])}
|
||||
:ref container}]))
|
||||
|
||||
|
@ -41,61 +53,90 @@
|
|||
|
||||
(mf/defc grid-item-metadata
|
||||
[{:keys [modified-at]}]
|
||||
(let [locale (i18n/use-locale)
|
||||
time (dt/timeago modified-at {:locale locale})]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
time (dt/timeago modified-at {:locale locale})]
|
||||
(str (t locale "ds.updated-at" time))))
|
||||
|
||||
(mf/defc grid-item
|
||||
{:wrap [mf/memo]}
|
||||
[{:keys [file] :as props}]
|
||||
(let [local (mf/use-state {:menu-open false
|
||||
:edition false})
|
||||
locale (i18n/use-locale)
|
||||
on-navigate #(st/emit! (rt/nav :workspace
|
||||
{:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
{:page-id (first (:pages file))}))
|
||||
delete-fn #(st/emit! nil (dsh/delete-file (:id file)))
|
||||
on-delete #(do
|
||||
(dom/stop-propagation %)
|
||||
(modal/show! confirm-dialog {:on-accept delete-fn}))
|
||||
[{:keys [id file] :as props}]
|
||||
(let [local (mf/use-state {:menu-open false :edition false})
|
||||
locale (mf/deref i18n/locale)
|
||||
|
||||
delete (mf/use-callback (mf/deps id) #(st/emit! nil (dsh/delete-file id)))
|
||||
add-shared (mf/use-callback (mf/deps id) #(st/emit! (dsh/set-file-shared id true)))
|
||||
del-shared (mf/use-callback (mf/deps id) #(st/emit! (dsh/set-file-shared id false)))
|
||||
on-close (mf/use-callback #(swap! local assoc :menu-open false))
|
||||
|
||||
on-delete
|
||||
(mf/use-callback
|
||||
(mf/deps id)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(modal/show! confirm-dialog {:on-accept delete})))
|
||||
|
||||
on-navigate
|
||||
(mf/use-callback
|
||||
(mf/deps id)
|
||||
(fn []
|
||||
(let [pparams {:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
qparams {:page-id (first (get-in file [:data :pages]))}]
|
||||
(st/emit! (rt/nav :workspace pparams qparams)))))
|
||||
|
||||
add-shared-fn #(st/emit! nil (dsh/set-file-shared (:id file) true))
|
||||
on-add-shared
|
||||
#(do
|
||||
(dom/stop-propagation %)
|
||||
(mf/use-callback
|
||||
(mf/deps id)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(modal/show! confirm-dialog
|
||||
{:message (t locale "dashboard.grid.add-shared-message" (:name file))
|
||||
:hint (t locale "dashboard.grid.add-shared-hint")
|
||||
:accept-text (t locale "dashboard.grid.add-shared-accept")
|
||||
:not-danger? true
|
||||
:on-accept add-shared-fn}))
|
||||
:on-accept add-shared})))
|
||||
|
||||
remove-shared-fn #(st/emit! nil (dsh/set-file-shared (:id file) false))
|
||||
on-remove-shared
|
||||
#(do
|
||||
(dom/stop-propagation %)
|
||||
on-edit
|
||||
(mf/use-callback
|
||||
(mf/deps id)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(swap! local assoc :edition true)))
|
||||
|
||||
on-del-shared
|
||||
(mf/use-callback
|
||||
(mf/deps id)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(modal/show! confirm-dialog
|
||||
{:message (t locale "dashboard.grid.remove-shared-message" (:name file))
|
||||
:hint (t locale "dashboard.grid.remove-shared-hint")
|
||||
:accept-text (t locale "dashboard.grid.remove-shared-accept")
|
||||
:not-danger? false
|
||||
:on-accept remove-shared-fn}))
|
||||
:on-accept del-shared})))
|
||||
|
||||
on-blur #(let [name (-> % dom/get-target dom/get-value)]
|
||||
(st/emit! (dsh/rename-file (:id file) name))
|
||||
(swap! local assoc :edition false))
|
||||
on-menu-click
|
||||
(mf/use-callback
|
||||
(mf/deps id)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(swap! local assoc :menu-open true)))
|
||||
|
||||
on-key-down #(cond
|
||||
(kbd/enter? %) (on-blur %)
|
||||
(kbd/esc? %) (swap! local assoc :edition false))
|
||||
on-menu-click #(do
|
||||
(dom/stop-propagation %)
|
||||
(swap! local assoc :menu-open true))
|
||||
on-menu-close #(swap! local assoc :menu-open false)
|
||||
on-edit #(do
|
||||
(dom/stop-propagation %)
|
||||
(swap! local assoc :edition true))]
|
||||
on-blur
|
||||
(mf/use-callback
|
||||
(mf/deps id)
|
||||
(fn [event]
|
||||
(let [name (-> event dom/get-target dom/get-value)]
|
||||
(st/emit! (dsh/rename-file id name))
|
||||
(swap! local assoc :edition false))))
|
||||
|
||||
on-key-down
|
||||
(mf/use-callback
|
||||
#(cond
|
||||
(kbd/enter? %) (on-blur %)
|
||||
(kbd/esc? %) (swap! local assoc :edition false)))
|
||||
|
||||
]
|
||||
[:div.grid-item.project-th {:on-click on-navigate}
|
||||
[:div.overlay]
|
||||
[:& grid-item-thumbnail {:file file}]
|
||||
|
@ -113,42 +154,39 @@
|
|||
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
|
||||
[:div.project-th-actions {:class (dom/classnames
|
||||
:force-display (:menu-open @local))}
|
||||
;; [:div.project-th-icon.pages
|
||||
;; i/page
|
||||
;; #_[:span (:total-pages project)]]
|
||||
;; [:div.project-th-icon.comments
|
||||
;; i/chat
|
||||
;; [:span "0"]]
|
||||
[:div.project-th-icon.menu
|
||||
{:on-click on-menu-click}
|
||||
i/actions]
|
||||
[:& context-menu {:on-close on-menu-close
|
||||
[:& context-menu {:on-close on-close
|
||||
:show (:menu-open @local)
|
||||
:options [[(t locale "dashboard.grid.rename") on-edit]
|
||||
[(t locale "dashboard.grid.delete") on-delete]
|
||||
(if (:is-shared file)
|
||||
[(t locale "dashboard.grid.remove-shared") on-remove-shared]
|
||||
[(t locale "dashboard.grid.remove-shared") on-del-shared]
|
||||
[(t locale "dashboard.grid.add-shared") on-add-shared])]}]]]))
|
||||
|
||||
;; --- Grid
|
||||
|
||||
(mf/defc grid
|
||||
[{:keys [id opts files hide-new?] :as props}]
|
||||
(let [locale (i18n/use-locale)
|
||||
order (:order opts :modified)
|
||||
filter (:filter opts "")
|
||||
on-click #(do
|
||||
(dom/prevent-default %)
|
||||
(st/emit! (dsh/create-file id)))]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
click #(st/emit! (dsh/create-file id))]
|
||||
[:section.dashboard-grid
|
||||
(if (> (count files) 0)
|
||||
[:div.dashboard-grid-row
|
||||
(when (not hide-new?)
|
||||
[:div.grid-item.add-file {:on-click on-click}
|
||||
[:span (tr "ds.new-file")]])
|
||||
(for [item files]
|
||||
[:& grid-item {:file item :key (:id item)}])]
|
||||
[:div.grid-files-empty
|
||||
[:div.grid-files-desc (t locale "dashboard.grid.empty-files")]
|
||||
[:div.grid-files-link
|
||||
[:a.btn-secondary.btn-small {:on-click on-click} (t locale "ds.new-file")]]])]))
|
||||
(cond
|
||||
(pos? (count files))
|
||||
[:div.dashboard-grid-row
|
||||
(when (not hide-new?)
|
||||
[:div.grid-item.add-file {:on-click click}
|
||||
[:span (t locale "ds.new-file")]])
|
||||
|
||||
(for [item files]
|
||||
[:& grid-item
|
||||
{:id (:id item)
|
||||
:file item
|
||||
:key (:id item)}])]
|
||||
|
||||
(zero? (count files))
|
||||
[:div.grid-files-empty
|
||||
[:div.grid-files-desc (t locale "dashboard.grid.empty-files")]
|
||||
[:div.grid-files-link
|
||||
[:a.btn-secondary.btn-small {:on-click click} (t locale "ds.new-file")]]])]))
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
(mf/deps objects)
|
||||
#(exports/shape-wrapper-factory objects))
|
||||
]
|
||||
|
||||
[:svg {:id "screenshot"
|
||||
:view-box vbox
|
||||
:width width
|
||||
|
@ -77,17 +78,35 @@
|
|||
:group [:& group-wrapper {:shape object}]
|
||||
[:& shape-wrapper {:shape object}])]))
|
||||
|
||||
(defn- adapt-root-frame
|
||||
[objects object-id]
|
||||
(if (uuid/zero? object-id)
|
||||
(let [object (get objects object-id)
|
||||
shapes (cph/select-toplevel-shapes objects {:include-frames? true})
|
||||
srect (geom/selection-rect shapes)
|
||||
object (merge object (select-keys srect [:x :y :width :height]))
|
||||
object (geom/transform-shape object)
|
||||
object (assoc object :fill-color "#f0f0f0")]
|
||||
(assoc objects (:id object) object))
|
||||
objects))
|
||||
|
||||
|
||||
;; NOTE: for now, it is ok download the entire file for render only
|
||||
;; single page but in a future we need consider to add a specific
|
||||
;; backend entry point for download only the data of single page.
|
||||
|
||||
(mf/defc render-object
|
||||
[{:keys [page-id object-id] :as props}]
|
||||
(let [data (mf/use-state nil)]
|
||||
[{:keys [file-id page-id object-id] :as props}]
|
||||
(let [objects (mf/use-state nil)]
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(let [subs (->> (repo/query! :page {:id page-id})
|
||||
(rx/subs (fn [result]
|
||||
(reset! data (:data result)))))]
|
||||
#(rx/dispose! subs))))
|
||||
(when @data
|
||||
[:& object-svg {:objects (:objects @data)
|
||||
#(let [subs (->> (repo/query! :file {:id file-id})
|
||||
(rx/subs (fn [{:keys [data]}]
|
||||
(let [objs (get-in data [:pages-index page-id :objects])
|
||||
objs (adapt-root-frame objs object-id)]
|
||||
(reset! objects objs)))))]
|
||||
(fn [] (rx/dispose! subs))))
|
||||
|
||||
(when @objects
|
||||
[:& object-svg {:objects @objects
|
||||
:object-id object-id
|
||||
:zoom 1}])))
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
|
||||
(ns app.main.ui.shapes.frame
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[app.common.data :as d]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.util.object :as obj]))
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.util.object :as obj]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(def frame-default-props {:fill-color "#ffffff"})
|
||||
|
||||
|
@ -22,8 +22,8 @@
|
|||
(mf/fnc frame-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [childs (unchecked-get props "childs")
|
||||
shape (unchecked-get props "shape")
|
||||
(let [childs (unchecked-get props "childs")
|
||||
shape (unchecked-get props "shape")
|
||||
{:keys [id x y width height]} shape
|
||||
|
||||
props (-> (merge frame-default-props shape)
|
||||
|
|
|
@ -9,25 +9,24 @@
|
|||
|
||||
(ns app.main.ui.viewer
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[goog.events :as events]
|
||||
[goog.object :as gobj]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.common.exceptions :as ex]
|
||||
[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.shapes :refer [frame-svg]]
|
||||
[app.main.ui.viewer.thumbnails :refer [thumbnails-panel]]
|
||||
[app.util.data :refer [classnames]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [t tr]])
|
||||
[app.util.i18n :as i18n :refer [t tr]]
|
||||
[beicon.core :as rx]
|
||||
[goog.events :as events]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
(mf/defc main-panel
|
||||
|
@ -106,10 +105,11 @@
|
|||
;; --- Component: Viewer Page
|
||||
|
||||
(mf/defc viewer-page
|
||||
[{:keys [page-id index token] :as props}]
|
||||
[{:keys [file-id page-id index token] :as props}]
|
||||
(mf/use-effect
|
||||
(mf/deps page-id token)
|
||||
#(st/emit! (dv/initialize page-id token)))
|
||||
(mf/deps file-id page-id token)
|
||||
(fn []
|
||||
(st/emit! (dv/initialize props))))
|
||||
|
||||
(let [data (mf/deref refs/viewer-data)
|
||||
local (mf/deref refs/viewer-local)]
|
||||
|
|
|
@ -75,12 +75,10 @@
|
|||
(t locale "viewer.header.show-interactions-on-click")]]]]]))
|
||||
|
||||
(mf/defc share-link
|
||||
[{:keys [page] :as props}]
|
||||
[{:keys [page token] :as props}]
|
||||
(let [show-dropdown? (mf/use-state false)
|
||||
dropdown-ref (mf/use-ref)
|
||||
token (:share-token page)
|
||||
|
||||
locale (i18n/use-locale)
|
||||
dropdown-ref (mf/use-ref)
|
||||
locale (mf/deref i18n/locale)
|
||||
|
||||
create #(st/emit! dv/create-share-link)
|
||||
delete #(st/emit! dv/delete-share-link)
|
||||
|
@ -158,8 +156,11 @@
|
|||
|
||||
[:div.options-zone
|
||||
[:& interactions-menu {:interactions-mode interactions-mode}]
|
||||
|
||||
(when-not anonymous?
|
||||
[:& share-link {:page (:page data)}])
|
||||
[:& share-link {:token (:share-token data)
|
||||
:page (:page data)}])
|
||||
|
||||
(when-not anonymous?
|
||||
[:a.btn-text-basic.btn-small {:on-click on-edit}
|
||||
(t locale "viewer.header.edit-page")])
|
||||
|
|
|
@ -9,44 +9,44 @@
|
|||
|
||||
(ns app.main.ui.workspace
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[rumext.alpha :as mf]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.main.constants :as c]
|
||||
[app.main.data.history :as udh]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
[app.main.ui.keyboard :as kbd]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.workspace.viewport :refer [viewport coordinates]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.keyboard :as kbd]
|
||||
[app.main.ui.workspace.colorpalette :refer [colorpalette]]
|
||||
[app.main.ui.workspace.context-menu :refer [context-menu]]
|
||||
[app.main.ui.workspace.header :refer [header]]
|
||||
[app.main.ui.workspace.left-toolbar :refer [left-toolbar]]
|
||||
[app.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]]
|
||||
[app.main.ui.workspace.scroll :as scroll]
|
||||
[app.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]]
|
||||
[app.main.ui.workspace.sidebar.history :refer [history-dialog]]
|
||||
[app.main.ui.workspace.left-toolbar :refer [left-toolbar]]
|
||||
[app.util.data :refer [classnames]]
|
||||
[app.main.ui.workspace.viewport :refer [viewport coordinates]]
|
||||
[app.util.dom :as dom]
|
||||
[app.common.geom.point :as gpt]))
|
||||
[beicon.core :as rx]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
;; --- Workspace
|
||||
|
||||
(mf/defc workspace-content
|
||||
[{:keys [page file layout project] :as params}]
|
||||
(let [local (mf/deref refs/workspace-local)
|
||||
left-sidebar? (:left-sidebar? local)
|
||||
[{:keys [page-id file layout project] :as params}]
|
||||
(let [local (mf/deref refs/workspace-local)
|
||||
left-sidebar? (:left-sidebar? local)
|
||||
right-sidebar? (:right-sidebar? local)
|
||||
classes (classnames
|
||||
:no-tool-bar-right (not right-sidebar?)
|
||||
:no-tool-bar-left (not left-sidebar?))]
|
||||
classes (dom/classnames
|
||||
:no-tool-bar-right (not right-sidebar?)
|
||||
:no-tool-bar-left (not left-sidebar?))]
|
||||
[:*
|
||||
(when (:colorpalette layout)
|
||||
[:& colorpalette {:left-sidebar? left-sidebar?
|
||||
:project project}])
|
||||
:team-id (:team-id project)}])
|
||||
|
||||
[:section.workspace-content {:class classes}
|
||||
[:& history-dialog]
|
||||
|
@ -63,36 +63,52 @@
|
|||
:vport (:vport local)}]
|
||||
[:& coordinates]])
|
||||
|
||||
|
||||
[:& viewport {:page page
|
||||
:key (:id page)
|
||||
[:& viewport {:page-id page-id
|
||||
:key (str page-id)
|
||||
:file file
|
||||
:local local
|
||||
:layout layout}]]]
|
||||
|
||||
[:& left-toolbar {:page page :layout layout}]
|
||||
[:& left-toolbar {:layout layout}]
|
||||
|
||||
;; Aside
|
||||
(when left-sidebar?
|
||||
[:& left-sidebar {:file file :page page :layout layout}])
|
||||
[:& left-sidebar
|
||||
{:file file
|
||||
:page-id page-id
|
||||
:project project
|
||||
:layout layout}])
|
||||
(when right-sidebar?
|
||||
[:& right-sidebar {:page page
|
||||
:local local
|
||||
:layout layout}])]))
|
||||
[:& right-sidebar
|
||||
{:page-id page-id
|
||||
:file-id (:id file)
|
||||
:local local
|
||||
:layout layout}])]))
|
||||
|
||||
(defn trimmed-page-ref
|
||||
[id]
|
||||
(l/derived (fn [state]
|
||||
(let [page-id (:current-page-id state)
|
||||
data (:workspace-data state)]
|
||||
(select-keys (get-in data [:pages-index page-id]) [:id :name])))
|
||||
st/state =))
|
||||
|
||||
(mf/defc workspace-page
|
||||
[{:keys [project file layout page-id] :as props}]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps page-id)
|
||||
(fn []
|
||||
(st/emit! (dw/initialize-page page-id))
|
||||
#(st/emit! (dw/finalize-page page-id))))
|
||||
(when-let [page (mf/deref refs/workspace-page)]
|
||||
[:& workspace-content {:page page
|
||||
:project project
|
||||
:file file
|
||||
:layout layout}]))
|
||||
|
||||
(let [page-ref (mf/use-memo (mf/deps page-id) #(trimmed-page-ref page-id))
|
||||
page (mf/deref page-ref)]
|
||||
(when page
|
||||
[:& workspace-content {:page page
|
||||
:page-id (:id page)
|
||||
:project project
|
||||
:file file
|
||||
:layout layout}])))
|
||||
|
||||
(mf/defc workspace-loader
|
||||
[]
|
||||
|
@ -102,6 +118,7 @@
|
|||
(mf/defc workspace
|
||||
[{:keys [project-id file-id page-id] :as props}]
|
||||
(mf/use-effect #(st/emit! dw/initialize-layout))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps project-id file-id)
|
||||
(fn []
|
||||
|
@ -110,11 +127,13 @@
|
|||
|
||||
(hooks/use-shortcuts dw/shortcuts)
|
||||
|
||||
(let [file (mf/deref refs/workspace-file)
|
||||
(let [file (mf/deref refs/workspace-file)
|
||||
project (mf/deref refs/workspace-project)
|
||||
layout (mf/deref refs/workspace-layout)]
|
||||
layout (mf/deref refs/workspace-layout)]
|
||||
|
||||
[:section#workspace
|
||||
[:& header {:file file
|
||||
:page-id page-id
|
||||
:project project
|
||||
:layout layout}]
|
||||
|
||||
|
@ -122,8 +141,9 @@
|
|||
|
||||
(if (and (and file project)
|
||||
(:initialized file))
|
||||
[:& workspace-page {:file file
|
||||
|
||||
[:& workspace-page {:page-id page-id
|
||||
:project project
|
||||
:layout layout
|
||||
:page-id page-id}]
|
||||
:file file
|
||||
:layout layout}]
|
||||
[:& workspace-loader])]))
|
||||
|
|
|
@ -155,9 +155,8 @@
|
|||
[:span.right-arrow {:on-click on-right-arrow-click} i/arrow-slide]]))
|
||||
|
||||
(mf/defc colorpalette
|
||||
[{:keys [left-sidebar? project] :as props}]
|
||||
(let [team-id (:team-id project)
|
||||
palettes (->> (mf/deref palettes-ref)
|
||||
[{:keys [left-sidebar? team-id] :as props}]
|
||||
(let [palettes (->> (mf/deref palettes-ref)
|
||||
(vals)
|
||||
(mapcat identity))
|
||||
selected (or (mf/deref selected-palette-ref)
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.workspace.header
|
||||
(:require
|
||||
|
@ -150,15 +149,14 @@
|
|||
;; --- Header Component
|
||||
|
||||
(mf/defc header
|
||||
[{:keys [file layout project] :as props}]
|
||||
[{:keys [file layout project page-id] :as props}]
|
||||
(let [locale (i18n/use-locale)
|
||||
team-id (:team-id project)
|
||||
go-back #(st/emit! (rt/nav :dashboard-team {:team-id team-id}))
|
||||
zoom (mf/deref refs/selected-zoom)
|
||||
page (mf/deref refs/workspace-page)
|
||||
locale (i18n/use-locale)
|
||||
router (mf/deref refs/router)
|
||||
view-url (rt/resolve router :viewer {:page-id (:id page)} {:index 0})]
|
||||
view-url (rt/resolve router :viewer {:page-id page-id :file-id (:id file)} {:index 0})]
|
||||
[:header.workspace-header
|
||||
[:div.main-icon
|
||||
[:a {:on-click go-back} i/logo-icon]]
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2015-2020 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2020 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.workspace.left-toolbar
|
||||
(:require
|
||||
|
@ -23,7 +22,7 @@
|
|||
;; --- Component: Left toolbar
|
||||
|
||||
(mf/defc left-toolbar
|
||||
[{:keys [page layout] :as props}]
|
||||
[{:keys [layout] :as props}]
|
||||
(let [file-input (mf/use-ref nil)
|
||||
selected-drawtool (mf/deref refs/selected-drawing-tool)
|
||||
select-drawtool #(st/emit! :interrupt
|
||||
|
|
|
@ -10,8 +10,11 @@
|
|||
(ns app.main.ui.workspace.presence
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[beicon.core :as rx]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.util.time :as dt]
|
||||
[app.util.timers :as tm]
|
||||
[app.util.router :as rt]))
|
||||
|
||||
(def pointer-icon-path
|
||||
|
@ -52,12 +55,21 @@
|
|||
|
||||
(mf/defc active-cursors
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [page] :as props}]
|
||||
(let [sessions (mf/deref refs/workspace-presence)
|
||||
[{:keys [page-id] :as props}]
|
||||
(let [counter (mf/use-state 0)
|
||||
sessions (mf/deref refs/workspace-presence)
|
||||
sessions (->> (vals sessions)
|
||||
(filter #(= (:id page) (:page-id %))))]
|
||||
(filter #(= page-id (:page-id %)))
|
||||
(filter #(>= 3000 (- (inst-ms (dt/now)) (inst-ms (:updated-at %))))))]
|
||||
(mf/use-effect
|
||||
nil
|
||||
(fn []
|
||||
(let [sem (tm/schedule 1000 #(swap! counter inc))]
|
||||
(fn [] (rx/dispose! sem)))))
|
||||
|
||||
(for [session sessions]
|
||||
[:& session-cursor {:session session :key (:id session)}])))
|
||||
(when (:point session)
|
||||
[:& session-cursor {:session session :key (:id session)}]))))
|
||||
|
||||
(mf/defc session-widget
|
||||
[{:keys [session self?] :as props}]
|
||||
|
@ -72,10 +84,13 @@
|
|||
(mf/defc active-sessions
|
||||
{::mf/wrap [mf/memo]}
|
||||
[]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
(let [profile (mf/deref refs/profile)
|
||||
sessions (mf/deref refs/workspace-presence)]
|
||||
[:ul.active-users
|
||||
(for [session (vals sessions)]
|
||||
[:& session-widget {:session session :key (:id session)}])]))
|
||||
[:& session-widget
|
||||
{:session session
|
||||
:self? (= (:id session) (:id profile))
|
||||
:key (:id session)}])]))
|
||||
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
(ns app.main.ui.workspace.shapes.frame
|
||||
(:require
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[app.common.data :as d]
|
||||
[app.main.constants :as c]
|
||||
|
@ -43,6 +44,10 @@
|
|||
(recur (first ids) (rest ids))
|
||||
false))))))
|
||||
|
||||
(defn make-selected-ref
|
||||
[id]
|
||||
(l/derived #(contains? % id) refs/selected-shapes))
|
||||
|
||||
(defn frame-wrapper-factory
|
||||
[shape-wrapper]
|
||||
(let [frame-shape (frame/frame-shape shape-wrapper)]
|
||||
|
@ -55,7 +60,7 @@
|
|||
objects (unchecked-get props "objects")
|
||||
|
||||
selected-iref (mf/use-memo (mf/deps (:id shape))
|
||||
#(refs/make-selected (:id shape)))
|
||||
#(make-selected-ref (:id shape)))
|
||||
selected? (mf/deref selected-iref)
|
||||
zoom (mf/deref refs/selected-zoom)
|
||||
|
||||
|
|
|
@ -14,34 +14,38 @@
|
|||
[app.main.ui.workspace.sidebar.history :refer [history-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.layers :refer [layers-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.options :refer [options-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.sitemap :refer [sitemap-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.sitemap :refer [sitemap]]
|
||||
[app.main.ui.workspace.sidebar.assets :refer [assets-toolbox]]))
|
||||
|
||||
;; --- Left Sidebar (Component)
|
||||
|
||||
(mf/defc left-sidebar
|
||||
{:wrap [mf/memo]}
|
||||
[{:keys [layout page file] :as props}]
|
||||
[{:keys [layout page-id file project] :as props}]
|
||||
[:aside.settings-bar.settings-bar-left
|
||||
[:div.settings-bar-inside
|
||||
{:data-layout (str/join "," layout)}
|
||||
(when (contains? layout :sitemap)
|
||||
[:& sitemap-toolbox {:file file
|
||||
:page page
|
||||
:layout layout}])
|
||||
(when (contains? layout :document-history)
|
||||
[:& history-toolbox])
|
||||
[:& sitemap {:file file
|
||||
:page-id page-id
|
||||
:layout layout}])
|
||||
#_(when (contains? layout :document-history)
|
||||
[:& history-toolbox])
|
||||
(when (contains? layout :layers)
|
||||
[:& layers-toolbox {:page page}])
|
||||
[:& layers-toolbox])
|
||||
(when (contains? layout :assets)
|
||||
[:& assets-toolbox])]])
|
||||
[:& assets-toolbox {:team-id (:team-id project)
|
||||
:file file}])]])
|
||||
|
||||
;; --- Right Sidebar (Component)
|
||||
|
||||
;; TODO: revisit page prop
|
||||
|
||||
(mf/defc right-sidebar
|
||||
[{:keys [layout page local] :as props}]
|
||||
[{:keys [layout page-id file-id local] :as props}]
|
||||
[:aside#settings-bar.settings-bar
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? layout :element-options)
|
||||
[:& options-toolbox {:page page
|
||||
:local local}])]])
|
||||
[:& options-toolbox {:page-id page-id
|
||||
:file-id file-id
|
||||
:local local}])]])
|
||||
|
|
|
@ -9,35 +9,35 @@
|
|||
|
||||
(ns app.main.ui.workspace.sidebar.assets
|
||||
(:require
|
||||
[okulary.core :as l]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[app.config :as cfg]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.media :as cm]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.colors :as dcol]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.colorpicker :refer [colorpicker most-used-colors]]
|
||||
[app.main.ui.components.context-menu :refer [context-menu]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.keyboard :as kbd]
|
||||
[app.main.ui.modal :as modal]
|
||||
[app.main.ui.shapes.icon :as icon]
|
||||
[app.main.ui.workspace.libraries :refer [libraries-dialog]]
|
||||
[app.util.data :refer [matches-search]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.dom.dnd :as dnd]
|
||||
[app.util.timers :as timers]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.data :refer [classnames matches-search]]
|
||||
[app.util.i18n :as i18n :refer [tr t]]
|
||||
[app.util.router :as rt]
|
||||
[app.main.ui.modal :as modal]
|
||||
[app.main.ui.colorpicker :refer [colorpicker most-used-colors]]
|
||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.components.context-menu :refer [context-menu]]
|
||||
[app.main.ui.workspace.libraries :refer [libraries-dialog]]))
|
||||
[app.util.timers :as timers]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(mf/defc modal-edit-color
|
||||
[{:keys [color-value on-accept on-cancel] :as ctx}]
|
||||
|
@ -67,58 +67,73 @@
|
|||
[:a.close {:href "#" :on-click cancel} i/close]]])))
|
||||
|
||||
(mf/defc graphics-box
|
||||
[{:keys [file-id local-library? media-objects] :as props}]
|
||||
(let [state (mf/use-state {:menu-open false
|
||||
:top nil
|
||||
:left nil
|
||||
:object-id nil})
|
||||
|
||||
file-input (mf/use-ref nil)
|
||||
[{:keys [file-id local? objects] :as props}]
|
||||
(let [input-ref (mf/use-ref nil)
|
||||
state (mf/use-state {:menu-open false
|
||||
:top nil
|
||||
:left nil
|
||||
:object-id nil})
|
||||
|
||||
add-graphic
|
||||
#(dom/click (mf/ref-val file-input))
|
||||
(mf/use-callback
|
||||
(fn [] (dom/click (mf/ref-val input-ref))))
|
||||
|
||||
delete-graphic
|
||||
#(st/emit! (dw/delete-media-object file-id (:object-id @state)))
|
||||
on-media-uploaded
|
||||
(mf/use-callback
|
||||
(mf/deps file-id)
|
||||
(fn [data]
|
||||
(st/emit! (dwl/add-media data))))
|
||||
|
||||
on-files-selected
|
||||
(fn [js-files]
|
||||
(let [params {:file-id file-id
|
||||
:local? false
|
||||
:js-files js-files}]
|
||||
(st/emit! (dw/upload-media-objects params))))
|
||||
on-selected
|
||||
(mf/use-callback
|
||||
(mf/deps file-id)
|
||||
(fn [js-files]
|
||||
(let [params (with-meta {:file-id file-id
|
||||
:local? false
|
||||
:js-files js-files}
|
||||
{:on-success on-media-uploaded})]
|
||||
(st/emit! (dw/upload-media-objects params)))))
|
||||
|
||||
on-delete
|
||||
(mf/use-callback
|
||||
(mf/deps state)
|
||||
(fn []
|
||||
(let [params {:id (:object-id @state)}]
|
||||
(st/emit! (dwl/delete-media params)))))
|
||||
|
||||
on-context-menu
|
||||
(fn [object-id]
|
||||
(fn [event]
|
||||
(when local-library?
|
||||
(let [pos (dom/get-client-position event)
|
||||
top (:y pos)
|
||||
left (- (:x pos) 20)]
|
||||
(dom/prevent-default event)
|
||||
(swap! state assoc :menu-open true
|
||||
:top top
|
||||
:left left
|
||||
:object-id object-id)))))
|
||||
(mf/use-callback
|
||||
(fn [object-id]
|
||||
(fn [event]
|
||||
(when local?
|
||||
(let [pos (dom/get-client-position event)
|
||||
top (:y pos)
|
||||
left (- (:x pos) 20)]
|
||||
(dom/prevent-default event)
|
||||
(swap! state assoc :menu-open true
|
||||
:top top
|
||||
:left left
|
||||
:object-id object-id))))))
|
||||
|
||||
on-drag-start
|
||||
(fn [path event]
|
||||
(dnd/set-data! event "text/uri-list" (cfg/resolve-media-path path))
|
||||
(dnd/set-allowed-effect! event "move"))]
|
||||
(mf/use-callback
|
||||
(fn [path event]
|
||||
(dnd/set-data! event "text/uri-list" (cfg/resolve-media-path path))
|
||||
(dnd/set-allowed-effect! event "move")))]
|
||||
|
||||
[:div.asset-group
|
||||
[:div.group-title
|
||||
(tr "workspace.assets.graphics")
|
||||
[:span (str "\u00A0(") (count media-objects) ")"] ;; Unicode 00A0 is non-breaking space
|
||||
(when local-library?
|
||||
[:span (str "\u00A0(") (count objects) ")"] ;; Unicode 00A0 is non-breaking space
|
||||
(when local?
|
||||
[:div.group-button {:on-click add-graphic}
|
||||
i/plus
|
||||
[:& file-uploader {:accept cm/str-media-types
|
||||
:multi true
|
||||
:input-ref file-input
|
||||
:on-selected on-files-selected}]])]
|
||||
:input-ref input-ref
|
||||
:on-selected on-selected}]])]
|
||||
[:div.group-grid
|
||||
(for [object media-objects]
|
||||
(for [object objects]
|
||||
[:div.grid-cell {:key (:id object)
|
||||
:draggable true
|
||||
:on-context-menu (on-context-menu (:id object))
|
||||
|
@ -127,39 +142,37 @@
|
|||
:draggable false}] ;; Also need to add css pointer-events: none
|
||||
[:div.cell-name (:name object)]])
|
||||
|
||||
(when local-library?
|
||||
(when local?
|
||||
[:& context-menu
|
||||
{:selectable false
|
||||
:show (:menu-open @state)
|
||||
:on-close #(swap! state assoc :menu-open false)
|
||||
:top (:top @state)
|
||||
:left (:left @state)
|
||||
:options [[(tr "workspace.assets.delete") delete-graphic]]}])]]))
|
||||
:options [[(tr "workspace.assets.delete") on-delete]]}])]]))
|
||||
|
||||
|
||||
(mf/defc color-item
|
||||
[{:keys [color file-id local-library?] :as props}]
|
||||
(let [workspace-local @refs/workspace-local
|
||||
color-for-rename (:color-for-rename workspace-local)
|
||||
|
||||
edit-input-ref (mf/use-ref)
|
||||
|
||||
state (mf/use-state {:menu-open false
|
||||
:top nil
|
||||
:left nil
|
||||
:editing (= color-for-rename (:id color))})
|
||||
[{:keys [color local? locale] :as props}]
|
||||
(let [rename? (= (:color-for-rename @refs/workspace-local) (:id color))
|
||||
id (:id color)
|
||||
input-ref (mf/use-ref)
|
||||
state (mf/use-state {:menu-open false
|
||||
:top nil
|
||||
:left nil
|
||||
:editing rename?})
|
||||
|
||||
rename-color
|
||||
(fn [name]
|
||||
(st/emit! (dcol/rename-color file-id (:id color) name)))
|
||||
(st/emit! (dwl/update-color (assoc color :name name))))
|
||||
|
||||
edit-color
|
||||
(fn [value opacity]
|
||||
(st/emit! (dcol/update-color file-id (:id color) value)))
|
||||
(st/emit! (dwl/update-color (assoc color :value name))))
|
||||
|
||||
delete-color
|
||||
(fn []
|
||||
(st/emit! (dcol/delete-color file-id (:id color))))
|
||||
(st/emit! (dwl/delete-color color)))
|
||||
|
||||
rename-color-clicked
|
||||
(fn [event]
|
||||
|
@ -171,13 +184,13 @@
|
|||
(let [target (dom/event->target event)
|
||||
name (dom/get-value target)]
|
||||
(rename-color name)
|
||||
(st/emit! dcol/clear-color-for-rename)
|
||||
(st/emit! dwl/clear-color-for-rename)
|
||||
(swap! state assoc :editing false)))
|
||||
|
||||
input-key-down
|
||||
(fn [event]
|
||||
(when (kbd/esc? event)
|
||||
(st/emit! dcol/clear-color-for-rename)
|
||||
(st/emit! dwl/clear-color-for-rename)
|
||||
(swap! state assoc :editing false))
|
||||
(when (kbd/enter? event)
|
||||
(input-blur event)))
|
||||
|
@ -185,12 +198,12 @@
|
|||
edit-color-clicked
|
||||
(fn [event]
|
||||
(modal/show! modal-edit-color
|
||||
{:color-value (:content color)
|
||||
{:color-value (:value color)
|
||||
:on-accept edit-color}))
|
||||
|
||||
on-context-menu
|
||||
(fn [event]
|
||||
(when local-library?
|
||||
(when local?
|
||||
(let [pos (dom/get-client-position event)
|
||||
top (:y pos)
|
||||
left (- (:x pos) 20)]
|
||||
|
@ -203,16 +216,16 @@
|
|||
(mf/use-effect
|
||||
(mf/deps (:editing @state))
|
||||
#(when (:editing @state)
|
||||
(let [edit-input (mf/ref-val edit-input-ref)]
|
||||
(dom/select-text! edit-input))
|
||||
(let [input (mf/ref-val input-ref)]
|
||||
(dom/select-text! input))
|
||||
nil))
|
||||
|
||||
[:div.group-list-item {:on-context-menu on-context-menu}
|
||||
[:div.color-block {:style {:background-color (:content color)}}]
|
||||
[:div.color-block {:style {:background-color (:value color)}}]
|
||||
(if (:editing @state)
|
||||
[:input.element-name
|
||||
{:type "text"
|
||||
:ref edit-input-ref
|
||||
:ref input-ref
|
||||
:on-blur input-blur
|
||||
:on-key-down input-key-down
|
||||
:auto-focus true
|
||||
|
@ -220,179 +233,201 @@
|
|||
[:div.name-block
|
||||
{:on-double-click rename-color-clicked}
|
||||
(:name color)
|
||||
(when-not (= (:name color) (:content color))
|
||||
[:span (:content color)])])
|
||||
(when local-library?
|
||||
(when-not (= (:name color) (:value color))
|
||||
[:span (:value color)])])
|
||||
(when local?
|
||||
[:& context-menu
|
||||
{:selectable false
|
||||
:show (:menu-open @state)
|
||||
:on-close #(swap! state assoc :menu-open false)
|
||||
:top (:top @state)
|
||||
:left (:left @state)
|
||||
:options [[(tr "workspace.assets.rename") rename-color-clicked]
|
||||
[(tr "workspace.assets.edit") edit-color-clicked]
|
||||
[(tr "workspace.assets.delete") delete-color]]}])]))
|
||||
:options [[(t locale "workspace.assets.rename") rename-color-clicked]
|
||||
[(t locale "workspace.assets.edit") edit-color-clicked]
|
||||
[(t locale "workspace.assets.delete") delete-color]]}])]))
|
||||
|
||||
(mf/defc colors-box
|
||||
[{:keys [file-id local-library? colors] :as props}]
|
||||
[{:keys [file-id local? colors locale] :as props}]
|
||||
(let [add-color
|
||||
(fn [value opacity]
|
||||
(st/emit! (dcol/create-color file-id value)))
|
||||
(mf/use-callback
|
||||
(mf/deps file-id)
|
||||
(fn [value opacity]
|
||||
(st/emit! (dwl/add-color value))))
|
||||
|
||||
add-color-clicked
|
||||
(fn [event]
|
||||
(modal/show! modal-edit-color
|
||||
{:color-value "#406280"
|
||||
:on-accept add-color}))]
|
||||
(mf/use-callback
|
||||
(mf/deps file-id)
|
||||
(fn [event]
|
||||
(modal/show! modal-edit-color
|
||||
{:color-value "#406280"
|
||||
|
||||
:on-accept add-color})))]
|
||||
[:div.asset-group
|
||||
[:div.group-title
|
||||
(tr "workspace.assets.colors")
|
||||
[:span (str "\u00A0(") (count colors) ")"] ;; Unicode 00A0 is non-breaking space
|
||||
(when local-library?
|
||||
[:div.group-button {:on-click add-color-clicked} i/plus])]
|
||||
(t locale "workspace.assets.colors")
|
||||
[:span (str "\u00A0(") (count colors) ")"] ;; Unicode 00A0 is non-breaking space
|
||||
(when local?
|
||||
[:div.group-button {:on-click add-color-clicked} i/plus])]
|
||||
[:div.group-list
|
||||
(for [color colors]
|
||||
[:& color-item {:key (:id color)
|
||||
:color color
|
||||
:file-id file-id
|
||||
:local-library? local-library?}])]]))
|
||||
:local? local?}])]]))
|
||||
|
||||
(mf/defc file-library-toolbox
|
||||
[{:keys [library
|
||||
local-library?
|
||||
shared?
|
||||
media-objects
|
||||
colors
|
||||
initial-open?
|
||||
search-term
|
||||
box-filter] :as props}]
|
||||
(let [open? (mf/use-state initial-open?)
|
||||
(defn file-colors-ref
|
||||
[id]
|
||||
(l/derived (fn [state]
|
||||
(let [wfile (:workspace-file state)]
|
||||
(if (= (:id wfile) id)
|
||||
(vals (get-in wfile [:data :colors]))
|
||||
(vals (get-in state [:workspace-libraries id :data :colors])))))
|
||||
st/state =))
|
||||
|
||||
|
||||
(defn file-media-ref
|
||||
[id]
|
||||
(l/derived (fn [state]
|
||||
(let [wfile (:workspace-file state)]
|
||||
(if (= (:id wfile) id)
|
||||
(vals (get-in wfile [:data :media]))
|
||||
(vals (get-in state [:workspace-libraries id :data :media])))))
|
||||
st/state =))
|
||||
|
||||
(defn apply-filters
|
||||
[coll filters]
|
||||
(filter (fn [item]
|
||||
(or (matches-search (:name item "!$!") (:term filters))
|
||||
(matches-search (:value item "!$!") (:term filters))))
|
||||
coll))
|
||||
|
||||
(mf/defc file-library
|
||||
[{:keys [file local? open? filters locale] :as props}]
|
||||
(let [open? (mf/use-state open?)
|
||||
shared? (:is-shared file)
|
||||
router (mf/deref refs/router)
|
||||
toggle-open #(swap! open? not)
|
||||
router (mf/deref refs/router)
|
||||
library-url (rt/resolve router :workspace
|
||||
{:project-id (:project-id library)
|
||||
:file-id (:id library)}
|
||||
{:page-id (first (:pages library))})]
|
||||
|
||||
url (rt/resolve router :workspace
|
||||
{:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
{:page-id (get-in file [:data :pages 0])})
|
||||
|
||||
colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file)))
|
||||
colors (apply-filters (mf/deref colors-ref) filters)
|
||||
|
||||
media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file)))
|
||||
media (apply-filters (mf/deref media-ref) filters)]
|
||||
[:div.tool-window
|
||||
[:div.tool-window-bar
|
||||
[:div.collapse-library
|
||||
{:class (classnames :open @open?)
|
||||
{:class (dom/classnames :open @open?)
|
||||
:on-click toggle-open}
|
||||
i/arrow-slide]
|
||||
(if local-library?
|
||||
|
||||
(if local?
|
||||
[:*
|
||||
[:span (tr "workspace.assets.file-library")]
|
||||
[:span (t locale "workspace.assets.file-library")]
|
||||
(when shared?
|
||||
[:span.tool-badge (tr "workspace.assets.shared")])]
|
||||
[:span.tool-badge (t locale "workspace.assets.shared")])]
|
||||
[:*
|
||||
[:span (:name library)]
|
||||
[:span (:name file)]
|
||||
[:span.tool-link
|
||||
[:a {:href (str "#" library-url) :target "_blank"} i/chain]]])]
|
||||
[:a {:href (str "#" url) :target "_blank"} i/chain]]])]
|
||||
|
||||
(when @open?
|
||||
(let [show-graphics (and (or (= box-filter :all) (= box-filter :graphics))
|
||||
(or (> (count media-objects) 0) (str/empty? search-term)))
|
||||
show-colors (and (or (= box-filter :all) (= box-filter :colors))
|
||||
(or (> (count colors) 0) (str/empty? search-term)))]
|
||||
(let [show-graphics? (and (or (= (:box filters) :all)
|
||||
(= (:box filters) :graphics))
|
||||
(or (> (count media) 0)
|
||||
(str/empty? (:term filters))))
|
||||
show-colors? (and (or (= (:box filters) :all)
|
||||
(= (:box filters) :colors))
|
||||
(or (> (count colors) 0)
|
||||
(str/empty? (:term filters))))]
|
||||
[:div.tool-window-content
|
||||
(when show-graphics
|
||||
[:& graphics-box {:file-id (:id library)
|
||||
:local-library? local-library?
|
||||
:media-objects media-objects}])
|
||||
(when show-colors
|
||||
[:& colors-box {:file-id (:id library)
|
||||
:local-library? local-library?
|
||||
(when show-graphics?
|
||||
[:& graphics-box {:file-id (:id file)
|
||||
:local? local?
|
||||
:objects media}])
|
||||
(when show-colors?
|
||||
[:& colors-box {:file-id (:id file)
|
||||
:local? local?
|
||||
:locale locale
|
||||
:colors colors}])
|
||||
(when (and (not show-graphics) (not show-colors))
|
||||
|
||||
(when (and (not show-graphics?) (not show-colors?))
|
||||
[:div.asset-group
|
||||
[:div.group-title (tr "workspace.assets.not-found")]])]))]))
|
||||
[:div.group-title (t locale "workspace.assets.not-found")]])]))]))
|
||||
|
||||
(mf/defc assets-toolbox
|
||||
[]
|
||||
(let [team-id (-> refs/workspace-project mf/deref :team-id)
|
||||
file (mf/deref refs/workspace-file)
|
||||
libraries (mf/deref refs/workspace-libraries)
|
||||
sorted-libraries (->> (vals libraries)
|
||||
(sort-by #(str/lower (:name %))))
|
||||
[{:keys [team-id file] :as props}]
|
||||
(let [libraries (mf/deref refs/workspace-libraries)
|
||||
locale (mf/deref i18n/locale)
|
||||
filters (mf/use-state {:term "" :box :all})
|
||||
|
||||
state (mf/use-state {:search-term ""
|
||||
:box-filter :all})
|
||||
on-search-term-change
|
||||
(mf/use-callback
|
||||
(mf/deps team-id)
|
||||
(fn [event]
|
||||
(let [value (-> (dom/get-target event)
|
||||
(dom/get-value))]
|
||||
(swap! filters assoc :term value))))
|
||||
|
||||
filtered-media-objects (fn [library-id]
|
||||
(as-> libraries $$
|
||||
(assoc $$ (:id file) file)
|
||||
(get-in $$ [library-id :media-objects])
|
||||
(filter #(matches-search (:name %) (:search-term @state)) $$)
|
||||
(sort-by #(str/lower (:name %)) $$)))
|
||||
on-search-clear-click
|
||||
(mf/use-callback
|
||||
(mf/deps team-id)
|
||||
(fn [event]
|
||||
(swap! filters assoc :term "")))
|
||||
|
||||
filtered-colors (fn [library-id]
|
||||
(as-> libraries $$
|
||||
(assoc $$ (:id file) file)
|
||||
(get-in $$ [library-id :colors])
|
||||
(filter #(or (matches-search (:name %) (:search-term @state))
|
||||
(matches-search (:content %) (:search-term @state))) $$)
|
||||
(sort-by #(str/lower (:name %)) $$)))
|
||||
|
||||
on-search-term-change (fn [event]
|
||||
(let [value (-> (dom/get-target event)
|
||||
(dom/get-value))]
|
||||
(swap! state assoc :search-term value)))
|
||||
|
||||
on-search-clear-click (fn [event]
|
||||
(swap! state assoc :search-term ""))
|
||||
|
||||
on-box-filter-change (fn [event]
|
||||
(let [value (-> (dom/get-target event)
|
||||
(dom/get-value)
|
||||
(d/read-string))]
|
||||
(swap! state assoc :box-filter value)))]
|
||||
on-box-filter-change
|
||||
(mf/use-callback
|
||||
(mf/deps team-id)
|
||||
(fn [event]
|
||||
(let [value (-> (dom/get-target event)
|
||||
(dom/get-value)
|
||||
(d/read-string))]
|
||||
(swap! filters assoc :box value))))]
|
||||
|
||||
[:div.assets-bar
|
||||
|
||||
[:div.tool-window
|
||||
[:div.tool-window-content
|
||||
[:div.assets-bar-title
|
||||
(tr "workspace.assets.assets")
|
||||
(t locale "workspace.assets.assets")
|
||||
[:div.libraries-button {:on-click #(modal/show! libraries-dialog {})}
|
||||
i/libraries
|
||||
(tr "workspace.assets.libraries")]]
|
||||
(t locale "workspace.assets.libraries")]]
|
||||
|
||||
[:div.search-block
|
||||
[:input.search-input
|
||||
{:placeholder (tr "workspace.assets.search")
|
||||
:type "text"
|
||||
:value (:search-term @state)
|
||||
:value (:term @filters)
|
||||
:on-change on-search-term-change}]
|
||||
(if (str/empty? (:search-term @state))
|
||||
(if (str/empty? (:term @filters))
|
||||
[:div.search-icon
|
||||
i/search]
|
||||
[:div.search-icon.close
|
||||
{:on-click on-search-clear-click}
|
||||
i/close])]
|
||||
|
||||
[:select.input-select {:value (:box-filter @state)
|
||||
[:select.input-select {:value (:box @filters)
|
||||
:on-change on-box-filter-change}
|
||||
[:option {:value ":all"} (tr "workspace.assets.box-filter-all")]
|
||||
[:option {:value ":graphics"} (tr "workspace.assets.box-filter-graphics")]
|
||||
[:option {:value ":colors"} (tr "workspace.assets.box-filter-colors")]]]]
|
||||
[:option {:value ":all"} (t locale "workspace.assets.box-filter-all")]
|
||||
[:option {:value ":graphics"} (t locale "workspace.assets.box-filter-graphics")]
|
||||
[:option {:value ":colors"} (t locale "workspace.assets.box-filter-colors")]]]]
|
||||
|
||||
[:& file-library-toolbox {:key (:id file)
|
||||
:library file
|
||||
:local-library? true
|
||||
:shared? (:is-shared file)
|
||||
:media-objects (filtered-media-objects (:id file))
|
||||
:colors (filtered-colors (:id file))
|
||||
:initial-open? true
|
||||
:search-term (:search-term @state)
|
||||
:box-filter (:box-filter @state)}]
|
||||
(for [library sorted-libraries]
|
||||
[:& file-library-toolbox {:key (:id library)
|
||||
:library library
|
||||
:local-library? false
|
||||
:shared? (:is-shared library)
|
||||
:media-objects (filtered-media-objects (:id library))
|
||||
:colors (filtered-colors (:id library))
|
||||
:initial-open? false
|
||||
:search-term (:search-term @state)
|
||||
:box-filter (:box-filter @state)}])]))
|
||||
[:& file-library
|
||||
{:file file
|
||||
:locale locale
|
||||
:local? true
|
||||
:open? true
|
||||
:filters @filters}]
|
||||
|
||||
(for [file (->> (vals libraries)
|
||||
(sort-by #(str/lower (:name %))))]
|
||||
[:& file-library
|
||||
{:key (:id file)
|
||||
:file file
|
||||
:local? false
|
||||
:open? false
|
||||
:filters @filters}])]))
|
||||
|
||||
|
|
|
@ -5,30 +5,29 @@
|
|||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
;; Copyright (c) 2015-2020 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.workspace.sidebar.layers
|
||||
(:require
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[beicon.core :as rx]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.common.data :as d]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.keyboard :as kbd]
|
||||
[app.main.ui.shapes.icon :as icon]
|
||||
[app.util.object :as obj]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.timers :as ts]
|
||||
[app.util.i18n :as i18n :refer [t]]
|
||||
[app.util.perf :as perf]))
|
||||
[app.util.object :as obj]
|
||||
[app.util.perf :as perf]
|
||||
[app.util.timers :as ts]
|
||||
[beicon.core :as rx]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
|
@ -305,15 +304,13 @@
|
|||
|
||||
(mf/defc layers-toolbox
|
||||
{:wrap [mf/memo]}
|
||||
[{:keys [page] :as props}]
|
||||
[]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
data (mf/deref refs/workspace-data)
|
||||
on-click #(st/emit! (dw/toggle-layout-flags :layers))]
|
||||
page (mf/deref refs/workspace-page)]
|
||||
[:div#layers.tool-window
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/layers]
|
||||
[:span (:name page)]
|
||||
#_[:div.tool-window-close {:on-click on-click} i/close]]
|
||||
[:span (:name page)]]
|
||||
[:div.tool-window-content
|
||||
[:& layers-tree-wrapper {:key (:id page)
|
||||
:objects (:objects data)}]]]))
|
||||
:objects (:objects page)}]]]))
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
(mf/defc shape-options
|
||||
{::mf/wrap [#(mf/throttle % 60)]}
|
||||
[{:keys [shape shapes-with-children page] :as props}]
|
||||
[{:keys [shape shapes-with-children page-id file-id]}]
|
||||
[:*
|
||||
(case (:type shape)
|
||||
:frame [:& frame/options {:shape shape}]
|
||||
|
@ -50,12 +50,15 @@
|
|||
:curve [:& path/options {:shape shape}]
|
||||
:image [:& image/options {:shape shape}]
|
||||
nil)
|
||||
[:& exports-menu {:shape shape :page page}]])
|
||||
[:& exports-menu
|
||||
{:shape shape
|
||||
:page-id page-id
|
||||
:file-id file-id}]])
|
||||
|
||||
|
||||
(mf/defc options-content
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [section shapes shapes-with-children page] :as props}]
|
||||
[{:keys [selected section shapes shapes-with-children page-id file-id]}]
|
||||
(let [locale (mf/deref i18n/locale)]
|
||||
[:div.tool-window
|
||||
[:div.tool-window-content
|
||||
|
@ -65,10 +68,11 @@
|
|||
:title (t locale "workspace.options.design")}
|
||||
[:div.element-options
|
||||
[:& align-options]
|
||||
(case (count shapes)
|
||||
0 [:& page/options {:page page}]
|
||||
(case (count selected)
|
||||
0 [:& page/options {:page-id page-id}]
|
||||
1 [:& shape-options {:shape (first shapes)
|
||||
:page page
|
||||
:page-id page-id
|
||||
:file-id file-id
|
||||
:shapes-with-children shapes-with-children}]
|
||||
[:& multiple/options {:shapes shapes-with-children}])]]
|
||||
|
||||
|
@ -78,14 +82,20 @@
|
|||
[:& interactions-menu {:shape (first shapes)}]]]]]]))
|
||||
|
||||
|
||||
;; TODO: this need optimizations, selected-objects and
|
||||
;; selected-objects-with-children are derefed always but they only
|
||||
;; need on multiple selection in majority of cases
|
||||
|
||||
(mf/defc options-toolbox
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [page local] :as props}]
|
||||
[{:keys [page-id file-id local] :as props}]
|
||||
(let [section (:options-mode local)
|
||||
shapes (mf/deref refs/selected-objects)
|
||||
shapes-with-children (mf/deref refs/selected-objects-with-children)]
|
||||
[:& options-content {:shapes shapes
|
||||
:selected (:selected local)
|
||||
:shapes-with-children shapes-with-children
|
||||
:page page
|
||||
:file-id file-id
|
||||
:page-id page-id
|
||||
:section section}]))
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
:response-type :blob
|
||||
:auth true
|
||||
:body {:page-id (:page-id shape)
|
||||
:file-id (:file-id shape)
|
||||
:object-id (:id shape)
|
||||
:name (:name shape)
|
||||
:exports exports}}))
|
||||
|
@ -45,7 +46,7 @@
|
|||
(.remove link)))
|
||||
|
||||
(mf/defc exports-menu
|
||||
[{:keys [shape page] :as props}]
|
||||
[{:keys [shape page-id file-id] :as props}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
exports (:exports shape [])
|
||||
loading? (mf/use-state false)
|
||||
|
@ -56,7 +57,7 @@
|
|||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(swap! loading? not)
|
||||
(->> (request-export (assoc shape :page-id (:id page)) exports)
|
||||
(->> (request-export (assoc shape :page-id page-id :file-id file-id) exports)
|
||||
(rx/subs
|
||||
(fn [{:keys [status body] :as response}]
|
||||
(js/console.log status body)
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
(mf/defc interactions-menu
|
||||
[{:keys [shape] :as props}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
objects (deref refs/workspace-objects)
|
||||
objects (deref refs/workspace-page-objects)
|
||||
interaction (first (:interactions shape)) ; TODO: in the
|
||||
; future we may
|
||||
; have several
|
||||
|
|
|
@ -21,18 +21,17 @@
|
|||
(def options-iref
|
||||
(l/derived :options refs/workspace-data))
|
||||
|
||||
(defn use-change-color [page]
|
||||
(defn use-change-color [page-id]
|
||||
(mf/use-callback
|
||||
(mf/deps page)
|
||||
(mf/deps page-id)
|
||||
(fn [value]
|
||||
(st/emit! (dw/change-canvas-color value)))))
|
||||
|
||||
(mf/defc options
|
||||
[{:keys [page] :as props}]
|
||||
|
||||
[{:keys [page-id] :as props}]
|
||||
(let [locale (i18n/use-locale)
|
||||
options (mf/deref refs/workspace-page-options)
|
||||
handle-change-color (use-change-color page)]
|
||||
handle-change-color (use-change-color page-id)]
|
||||
[:div.element-set
|
||||
[:div.element-set-title (t locale "workspace.options.canvas-background")]
|
||||
[:div.element-set-content
|
||||
|
|
|
@ -2,96 +2,104 @@
|
|||
;; 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-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
;; 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.workspace.sidebar.sitemap
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[app.common.data :as d]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.store :as st]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.confirm :refer [confirm-dialog]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.keyboard :as kbd]
|
||||
[app.main.ui.modal :as modal]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [t]]
|
||||
[app.util.router :as rt]))
|
||||
[app.util.router :as rt]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
;; --- Page Item
|
||||
|
||||
(mf/defc page-item
|
||||
[{:keys [page index deletable? selected?] :as props}]
|
||||
(let [local (mf/use-state {})
|
||||
edit-input-ref (mf/use-ref)
|
||||
(let [local (mf/use-state {})
|
||||
input-ref (mf/use-ref)
|
||||
id (:id page)
|
||||
|
||||
delete-fn (mf/use-callback (mf/deps id) #(st/emit! (dw/delete-page id)))
|
||||
on-delete (mf/use-callback (mf/deps id) #(modal/show! confirm-dialog {:on-accept delete-fn}))
|
||||
navigate-fn (mf/use-callback (mf/deps id) #(st/emit! (dw/go-to-page id)))
|
||||
|
||||
on-double-click
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(swap! local assoc :edition true))
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(swap! local assoc :edition true)))
|
||||
|
||||
on-blur
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(let [target (dom/event->target event)
|
||||
name (dom/get-value target)]
|
||||
(st/emit! (dw/rename-page id name))
|
||||
(swap! local assoc :edition false))))
|
||||
|
||||
on-key-down
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(let [target (dom/event->target event)
|
||||
name (dom/get-value target)]
|
||||
(st/emit! (dw/rename-page (:id page) name))
|
||||
(swap! local assoc :edition false)))
|
||||
(cond
|
||||
(kbd/enter? event)
|
||||
(on-blur event)
|
||||
|
||||
on-key-down (fn [event]
|
||||
(cond
|
||||
(kbd/enter? event)
|
||||
(on-blur event)
|
||||
|
||||
(kbd/esc? event)
|
||||
(swap! local assoc :edition false)))
|
||||
|
||||
delete-fn #(st/emit! (dw/delete-page (:id page)))
|
||||
on-delete #(do
|
||||
(dom/prevent-default %)
|
||||
(dom/stop-propagation %)
|
||||
(modal/show! confirm-dialog {:on-accept delete-fn}))
|
||||
|
||||
navigate-fn #(st/emit! (dw/go-to-page (:id page)))
|
||||
(kbd/esc? event)
|
||||
(swap! local assoc :edition false))))
|
||||
|
||||
on-drop
|
||||
(fn [side {:keys [id name] :as data}]
|
||||
(let [index (if (= :bot side) (inc index) index)]
|
||||
(st/emit! (dw/relocate-page id index))))
|
||||
(mf/use-callback
|
||||
(mf/deps id)
|
||||
(fn [side {:keys [id name] :as data}]
|
||||
(let [index (if (= :bot side) (inc index) index)]
|
||||
(st/emit! (dw/relocate-page id index)))))
|
||||
|
||||
[dprops dref] (hooks/use-sortable
|
||||
:data-type "app/page"
|
||||
:on-drop on-drop
|
||||
:data {:id (:id page)
|
||||
:index index
|
||||
:name (:name page)})]
|
||||
[dprops dref]
|
||||
(hooks/use-sortable
|
||||
:data-type "app/page"
|
||||
:on-drop on-drop
|
||||
:data {:id id
|
||||
:index index
|
||||
:name (:name page)})]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps (:edition @local))
|
||||
#(when (:edition @local)
|
||||
(let [edit-input (mf/ref-val edit-input-ref)]
|
||||
(mf/use-layout-effect
|
||||
(mf/deps (:edition @local))
|
||||
(fn []
|
||||
(when (:edition @local)
|
||||
(let [edit-input (mf/ref-val input-ref)]
|
||||
(dom/select-text! edit-input))
|
||||
nil))
|
||||
nil)))
|
||||
|
||||
[:li {:class (dom/classnames
|
||||
:selected selected?
|
||||
:dnd-over-top (= (:over dprops) :top)
|
||||
:dnd-over-bot (= (:over dprops) :bot))
|
||||
:ref dref}
|
||||
[:div.element-list-body {:class (dom/classnames
|
||||
:selected selected?)
|
||||
:on-click navigate-fn
|
||||
:on-double-click on-double-click}
|
||||
[:div.element-list-body
|
||||
{:class (dom/classnames
|
||||
:selected selected?)
|
||||
:on-click navigate-fn
|
||||
:on-double-click on-double-click}
|
||||
[:div.page-icon i/file-html]
|
||||
(if (:edition @local)
|
||||
[:*
|
||||
[:input.element-name {:type "text"
|
||||
:ref edit-input-ref
|
||||
:ref input-ref
|
||||
:on-blur on-blur
|
||||
:on-key-down on-key-down
|
||||
:auto-focus true
|
||||
|
@ -105,18 +113,17 @@
|
|||
|
||||
;; --- Page Item Wrapper
|
||||
|
||||
(defn- make-page-iref
|
||||
[id]
|
||||
#(l/derived (fn [state]
|
||||
(let [page (get-in state [:workspace-pages id])]
|
||||
(select-keys page [:id :name :ordering])))
|
||||
(defn- make-page-ref
|
||||
[page-id]
|
||||
(l/derived (fn [state]
|
||||
(let [page (get-in state [:workspace-file :data :pages-index page-id])]
|
||||
(select-keys page [:id :name])))
|
||||
st/state =))
|
||||
|
||||
(mf/defc page-item-wrapper
|
||||
[{:keys [page-id index deletable? selected?] :as props}]
|
||||
(let [page-iref (mf/use-memo (mf/deps page-id)
|
||||
(make-page-iref page-id))
|
||||
page (mf/deref page-iref)]
|
||||
[{:keys [file-id page-id index deletable? selected?] :as props}]
|
||||
(let [page-ref (mf/use-memo (mf/deps page-id) #(make-page-ref page-id))
|
||||
page (mf/deref page-ref)]
|
||||
[:& page-item {:page page
|
||||
:index index
|
||||
:deletable? deletable?
|
||||
|
@ -125,33 +132,35 @@
|
|||
;; --- Pages List
|
||||
|
||||
(mf/defc pages-list
|
||||
[{:keys [file current-page] :as props}]
|
||||
(let [pages (d/enumerate (:pages file))
|
||||
[{:keys [file current-page-id] :as props}]
|
||||
(let [pages (:pages file)
|
||||
deletable? (> (count pages) 1)]
|
||||
[:ul.element-list
|
||||
[:& hooks/sortable-container {}
|
||||
(for [[index page-id] pages]
|
||||
[:& page-item-wrapper
|
||||
{:page-id page-id
|
||||
:index index
|
||||
:deletable? deletable?
|
||||
:selected? (= page-id (:id current-page))
|
||||
:key page-id}])]]))
|
||||
(for [[index page-id] (d/enumerate pages)]
|
||||
[:& page-item-wrapper
|
||||
{:page-id page-id
|
||||
:index index
|
||||
:deletable? deletable?
|
||||
:selected? (= page-id current-page-id)
|
||||
:key page-id}])]]))
|
||||
|
||||
;; --- Sitemap Toolbox
|
||||
|
||||
(mf/defc sitemap-toolbox
|
||||
[{:keys [file page layout] :as props}]
|
||||
(let [on-create-click #(st/emit! dw/create-empty-page)
|
||||
toggle-layout #(st/emit! (dw/toggle-layout-flags %))
|
||||
locale (i18n/use-locale)]
|
||||
(mf/defc sitemap
|
||||
[{:keys [file page-id layout] :as props}]
|
||||
(let [create (mf/use-callback #(st/emit! dw/create-empty-page))
|
||||
collapse (mf/use-callback #(st/emit! (dw/toggle-layout-flags :sitemap-pages)))
|
||||
locale (mf/deref i18n/locale)]
|
||||
[:div.sitemap.tool-window
|
||||
[:div.tool-window-bar
|
||||
[:span (t locale "workspace.sidebar.sitemap")]
|
||||
[:div.add-page {:on-click on-create-click} i/close]
|
||||
[:div.collapse-pages {:on-click #(st/emit! (dw/toggle-layout-flags :sitemap-pages))}
|
||||
i/arrow-slide]]
|
||||
[:div.add-page {:on-click create} i/close]
|
||||
[:div.collapse-pages {:on-click collapse} i/arrow-slide]]
|
||||
|
||||
(when (contains? layout :sitemap-pages)
|
||||
[:div.tool-window-content
|
||||
[:& pages-list {:file file :current-page page}]])]))
|
||||
[:& pages-list
|
||||
{:file file
|
||||
:key (:id file)
|
||||
:current-page-id page-id}]])]))
|
||||
|
|
|
@ -132,7 +132,7 @@
|
|||
:frame-id (:id frame)
|
||||
:rect (gsh/pad-selrec (areas side))})
|
||||
(rx/map #(set/difference % selected))
|
||||
(rx/map #(->> % (map (partial get @refs/workspace-objects))))))]
|
||||
(rx/map #(->> % (map (partial get @refs/workspace-page-objects))))))]
|
||||
|
||||
(->> (query-side lt-side)
|
||||
(rx/combine-latest vector (query-side gt-side)))))
|
||||
|
@ -213,29 +213,25 @@
|
|||
:coord coord
|
||||
:zoom zoom}])))
|
||||
|
||||
(mf/defc snap-distances [{:keys [layout]}]
|
||||
(let [page-id (mf/deref refs/workspace-page-id)
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
shapes (->> (refs/objects-by-id selected)
|
||||
(mf/deref)
|
||||
(map gsh/transform-shape))
|
||||
selrect (gsh/selection-rect shapes)
|
||||
frame-id (-> shapes first :frame-id)
|
||||
frame (mf/deref (refs/object-by-id frame-id))
|
||||
zoom (mf/deref refs/selected-zoom)
|
||||
current-transform (mf/deref refs/current-transform)
|
||||
key (->> selected (map str) (str/join "-"))]
|
||||
|
||||
(when (and (contains? layout :dynamic-alignment)
|
||||
(= current-transform :move)
|
||||
(not (empty? selected)))
|
||||
[:g.distance
|
||||
(for [coord [:x :y]]
|
||||
[:& shape-distance
|
||||
{:key (str key (name coord))
|
||||
:selrect selrect
|
||||
:page-id page-id
|
||||
:frame frame
|
||||
:zoom zoom
|
||||
:coord coord
|
||||
:selected selected}])])))
|
||||
(mf/defc snap-distances
|
||||
[{:keys [layout page-id zoom selected transform]}]
|
||||
(when (and (contains? layout :dynamic-alignment)
|
||||
(= transform :move)
|
||||
(not (empty? selected)))
|
||||
(let [shapes (->> (refs/objects-by-id selected)
|
||||
(mf/deref)
|
||||
(map gsh/transform-shape))
|
||||
selrect (gsh/selection-rect shapes)
|
||||
frame-id (-> shapes first :frame-id)
|
||||
frame (mf/deref (refs/object-by-id frame-id))
|
||||
key (->> selected (map str) (str/join "-"))]
|
||||
[:g.distance
|
||||
(for [coord [:x :y]]
|
||||
[:& shape-distance
|
||||
{:key (str key (name coord))
|
||||
:selrect selrect
|
||||
:page-id page-id
|
||||
:frame frame
|
||||
:zoom zoom
|
||||
:coord coord
|
||||
:selected selected}])])))
|
||||
|
|
|
@ -116,6 +116,8 @@
|
|||
|
||||
(declare remote-user-cursors)
|
||||
|
||||
|
||||
;; TODO: revisit the refs usage (vs props)
|
||||
(mf/defc shape-outlines
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
|
@ -135,13 +137,13 @@
|
|||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [data (mf/deref refs/workspace-data)
|
||||
(let [data (mf/deref refs/workspace-page)
|
||||
hover (unchecked-get props "hover")
|
||||
selected (unchecked-get props "selected")
|
||||
objects (:objects data)
|
||||
root (get objects uuid/zero)
|
||||
shapes (->> (:shapes root)
|
||||
(map #(get objects %)))]
|
||||
objects (:objects data)
|
||||
root (get objects uuid/zero)
|
||||
shapes (->> (:shapes root)
|
||||
(map #(get objects %)))]
|
||||
[:*
|
||||
[:g.shapes
|
||||
(for [item shapes]
|
||||
|
@ -157,9 +159,8 @@
|
|||
:hover hover}]]))
|
||||
|
||||
(mf/defc viewport
|
||||
[{:keys [page local layout] :as props}]
|
||||
(let [{:keys [drawing-tool
|
||||
options-mode
|
||||
[{:keys [page-id page local layout] :as props}]
|
||||
(let [{:keys [options-mode
|
||||
zoom
|
||||
flags
|
||||
vport
|
||||
|
@ -169,9 +170,12 @@
|
|||
selected
|
||||
panning]} local
|
||||
|
||||
file (mf/deref refs/workspace-file)
|
||||
viewport-ref (mf/use-ref nil)
|
||||
file (mf/deref refs/workspace-file)
|
||||
viewport-ref (mf/use-ref nil)
|
||||
last-position (mf/use-var nil)
|
||||
drawing (mf/deref refs/workspace-drawing)
|
||||
drawing-tool (:tool drawing)
|
||||
drawing-obj (:object drawing)
|
||||
|
||||
zoom (or zoom 1)
|
||||
|
||||
|
@ -462,7 +466,7 @@
|
|||
:on-drop on-drop}
|
||||
|
||||
[:g
|
||||
[:& frames {:key (:id page)
|
||||
[:& frames {:key page-id
|
||||
:hover (:hover local)
|
||||
:selected (:selected selected)}]
|
||||
|
||||
|
@ -471,8 +475,8 @@
|
|||
:zoom zoom
|
||||
:edition edition}])
|
||||
|
||||
(when-let [drawing-shape (:drawing local)]
|
||||
[:& draw-area {:shape drawing-shape
|
||||
(when drawing-obj
|
||||
[:& draw-area {:shape drawing-obj
|
||||
:zoom zoom
|
||||
:modifiers (:modifiers local)}])
|
||||
|
||||
|
@ -481,17 +485,21 @@
|
|||
|
||||
[:& snap-points {:layout layout
|
||||
:transform (:transform local)
|
||||
:drawing (:drawing local)
|
||||
:drawing drawing-obj
|
||||
:zoom zoom
|
||||
:page-id (:id page)
|
||||
:page-id page-id
|
||||
:selected selected}]
|
||||
|
||||
[:& snap-distances {:layout layout}]
|
||||
[:& snap-distances {:layout layout
|
||||
:zoom zoom
|
||||
:transform (:transform local)
|
||||
:selected selected
|
||||
:page-id page-id}]
|
||||
|
||||
(when tooltip
|
||||
[:& cursor-tooltip {:zoom zoom :tooltip tooltip}])]
|
||||
|
||||
[:& presence/active-cursors {:page page}]
|
||||
[:& presence/active-cursors {:page-id page-id}]
|
||||
[:& selection-rect {:data (:selrect local)}]
|
||||
(when (= options-mode :prototype)
|
||||
[:& interactions {:selected selected}])]))
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
(when-not (nil? obj)
|
||||
(unchecked-get obj k)))
|
||||
([obj k default]
|
||||
(or (get obj k) default)))
|
||||
(let [result (get obj k)]
|
||||
(if (undefined? result) default result))))
|
||||
|
||||
(defn get-in
|
||||
[obj keys]
|
||||
|
|
|
@ -23,12 +23,12 @@
|
|||
[message]
|
||||
message)
|
||||
|
||||
(defmethod handler :create-page-indices
|
||||
(defmethod handler :initialize-indices
|
||||
[message]
|
||||
(handler (-> message
|
||||
(assoc :cmd :selection/create-index)))
|
||||
(assoc :cmd :selection/initialize-index)))
|
||||
(handler (-> message
|
||||
(assoc :cmd :snaps/create-index))))
|
||||
(assoc :cmd :snaps/initialize-index))))
|
||||
|
||||
(defmethod handler :update-page-indices
|
||||
[message]
|
||||
|
|
|
@ -26,16 +26,15 @@
|
|||
(declare index-object)
|
||||
(declare create-index)
|
||||
|
||||
(defmethod impl/handler :selection/create-index
|
||||
[{:keys [file-id pages] :as message}]
|
||||
(defmethod impl/handler :selection/initialize-index
|
||||
[{:keys [file-id data] :as message}]
|
||||
(letfn [(index-page [state page]
|
||||
(let [id (:id page)
|
||||
objects (get-in page [:data :objects])]
|
||||
(let [id (:id page)
|
||||
objects (:objects page)]
|
||||
(assoc state id (create-index objects))))
|
||||
|
||||
(update-state [state]
|
||||
(reduce index-page state pages))]
|
||||
|
||||
(reduce index-page state (vals (:pages-index data))))]
|
||||
(swap! state update-state)
|
||||
nil))
|
||||
|
||||
|
|
|
@ -65,19 +65,17 @@
|
|||
(assoc state page-id snap-data)))
|
||||
|
||||
;; Public API
|
||||
(defmethod impl/handler :snaps/create-index
|
||||
[{:keys [file-id pages] :as message}]
|
||||
|
||||
(defmethod impl/handler :snaps/initialize-index
|
||||
[{:keys [file-id data] :as message}]
|
||||
;; Create the index
|
||||
(letfn [(process-page [state page]
|
||||
(let [id (:id page)
|
||||
objects (get-in page [:data :objects])]
|
||||
(let [id (:id page)
|
||||
objects (:objects page)]
|
||||
(index-page state id objects)))]
|
||||
(swap! state #(reduce process-page % pages)))
|
||||
|
||||
;; (log-state)
|
||||
;; Return nil so the worker will not answer anything back
|
||||
nil)
|
||||
(swap! state #(reduce process-page % (vals (:pages-index data))))
|
||||
;; (log-state)
|
||||
;; Return nil so the worker will not answer anything back
|
||||
nil))
|
||||
|
||||
(defmethod impl/handler :snaps/update-index
|
||||
[{:keys [page-id objects] :as message}]
|
||||
|
|
|
@ -32,23 +32,23 @@
|
|||
:code (:error response)})))
|
||||
|
||||
(defn- request-page
|
||||
[id]
|
||||
[file-id page-id]
|
||||
(let [uri "/api/w/query/page"]
|
||||
(p/create
|
||||
(fn [resolve reject]
|
||||
(->> (http/send! {:uri uri
|
||||
:query {:id id}
|
||||
:query {:file-id file-id :id page-id}
|
||||
:method :get})
|
||||
(rx/mapcat handle-response)
|
||||
(rx/subs (fn [body]
|
||||
(resolve (:data body)))
|
||||
(resolve body))
|
||||
(fn [error]
|
||||
(reject error))))))))
|
||||
|
||||
(defmethod impl/handler :thumbnails/generate
|
||||
[{:keys [id] :as message}]
|
||||
[{:keys [file-id page-id] :as message}]
|
||||
(p/then
|
||||
(request-page id)
|
||||
(request-page file-id page-id)
|
||||
(fn [data]
|
||||
(let [elem (mf/element exports/page-svg #js {:data data
|
||||
:width "290"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue