🚧 More work on dashboard base model refactor (frontend).

This commit is contained in:
Andrey Antukh 2019-12-10 21:50:07 +01:00
parent 932d5bb004
commit 9bb5be306f
16 changed files with 563 additions and 522 deletions

View file

@ -49,7 +49,7 @@
(cond (cond
(and (= path "") (:auth storage)) (and (= path "") (:auth storage))
(st/emit! (rt/nav :dashboard/projects)) (st/emit! (rt/nav :dashboard-projects))
(and (= path "") (not (:auth storage))) (and (= path "") (not (:auth storage)))
(st/emit! (rt/nav :auth/login)) (st/emit! (rt/nav :auth/login))

View file

@ -5,6 +5,7 @@
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.pages (ns uxbox.main.data.pages
"Page related events (for workspace mainly)."
(:require (:require
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[beicon.core :as rx] [beicon.core :as rx]
@ -22,7 +23,7 @@
(s/def ::name ::us/string) (s/def ::name ::us/string)
(s/def ::inst ::us/inst) (s/def ::inst ::us/inst)
(s/def ::type ::us/keyword) (s/def ::type ::us/keyword)
(s/def ::project-id ::us/uuid) (s/def ::file-id ::us/uuid)
(s/def ::created-at ::us/inst) (s/def ::created-at ::us/inst)
(s/def ::modified-at ::us/inst) (s/def ::modified-at ::us/inst)
(s/def ::version ::us/number) (s/def ::version ::us/number)
@ -56,10 +57,10 @@
(s/def ::data (s/def ::data
(s/keys :req-un [::shapes ::canvas ::shapes-by-id])) (s/keys :req-un [::shapes ::canvas ::shapes-by-id]))
(s/def ::page-entity (s/def ::page
(s/keys :req-un [::id (s/keys :req-un [::id
::name ::name
::project-id ::file-id
::created-at ::created-at
::modified-at ::modified-at
::user-id ::user-id
@ -67,21 +68,19 @@
::metadata ::metadata
::data])) ::data]))
(s/def ::pages
(s/every ::page :kind vector?))
;; --- Protocols ;; --- Protocols
(defprotocol IPageUpdate (defprotocol IPageDataUpdate
"A marker protocol for mark events that alters the
page and is subject to perform a backend synchronization.")
(defprotocol IMetadataUpdate
"A marker protocol for mark events that alters the "A marker protocol for mark events that alters the
page and is subject to perform a backend synchronization.") page and is subject to perform a backend synchronization.")
(defn page-update? (defn page-update?
[o] [o]
(or (satisfies? IPageUpdate o) (or (satisfies? IPageDataUpdate o)
(satisfies? IMetadataUpdate o) (= ::page-data-update o)))
(= ::page-update o)))
;; --- Helpers ;; --- Helpers
@ -114,53 +113,39 @@
(update :pages-data dissoc id)) (update :pages-data dissoc id))
state)) state))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Generic Page Events (mostly Fetch & CRUD)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Fetch Pages (by File ID)
(declare pages-fetched)
(defn fetch-pages
[file-id]
(s/assert ::us/uuid file-id)
(reify
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query :project-pages {:file-id file-id})
(rx/map pages-fetched)))))
;; --- Pages Fetched ;; --- Pages Fetched
(defn pages-fetched (defn pages-fetched
[id pages] [pages]
(s/assert ::us/uuid id) (s/assert ::pages pages)
(s/assert ::us/coll pages)
(ptk/reify ::pages-fetched (ptk/reify ::pages-fetched
IDeref IDeref
(-deref [_] (list id pages)) (-deref [_] pages)
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(reduce unpack-page state pages)))) (reduce unpack-page state pages))))
(defn pages-fetched? ;; --- Fetch Page (By ID)
[v]
(= ::pages-fetched (ptk/type v)))
;; --- Fetch Pages (by project id) (declare page-fetched)
(defn fetch-pages
[id]
(s/assert ::us/uuid id)
(reify
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query :pages-by-project {:project-id id})
(rx/map #(pages-fetched id %))))))
;; --- Page Fetched
(defn page-fetched
[data]
(s/assert ::page-entity data)
(ptk/reify ::page-fetched
IDeref
(-deref [_] data)
ptk/UpdateEvent
(update [_ state]
(unpack-page state data))))
(defn page-fetched?
[v]
(= ::page-fetched (ptk/type v)))
;; --- Fetch Pages (by project id)
(defn fetch-page (defn fetch-page
"Fetch page by id." "Fetch page by id."
@ -169,35 +154,26 @@
(reify (reify
ptk/WatchEvent ptk/WatchEvent
(watch [_ state s] (watch [_ state s]
(->> (rp/query :page {:id id}) (->> (rp/query :project-page {:id id})
(rx/map page-fetched))))) (rx/map page-fetched)))))
;; --- Page Created ;; --- Page Fetched
(defn page-created (defn page-fetched
[{:keys [id project-id] :as page}] [data]
(s/assert ::page-entity page) (s/assert ::page data)
(ptk/reify ::page-created (ptk/reify ::page-fetched
cljs.core/IDeref IDeref
(-deref [_] page) (-deref [_] data)
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [data (:data page) (unpack-page state data))))
page (dissoc page :data)]
(-> state
(update-in [:projects project-id :pages] (fnil conj []) (:id page))
(update :pages assoc id page)
(update :pages-data assoc id data))))))
(defn page-created? ;; --- Create Page
[v]
(= ::page-created (ptk/type v)))
;; --- Create Page Form
(s/def ::create-page (s/def ::create-page
(s/keys :req-un [::name ::project-id])) (s/keys :req-un [::name ::file-id]))
(defn create-page (defn create-page
[{:keys [project-id name] :as data}] [{:keys [project-id name] :as data}]
@ -205,7 +181,7 @@
(ptk/reify ::create-page (ptk/reify ::create-page
ptk/WatchEvent ptk/WatchEvent
(watch [this state s] (watch [this state s]
(let [ordering (count (get-in state [:projects project-id :pages])) #_(let [ordering (count (get-in state [:projects project-id :pages]))
params {:name name params {:name name
:project-id project-id :project-id project-id
:ordering ordering :ordering ordering
@ -216,9 +192,28 @@
(->> (rp/mutation :create-page params) (->> (rp/mutation :create-page params)
(rx/map page-created)))))) (rx/map page-created))))))
;; --- Update Page Form ;; --- Page Created
(declare page-renamed) (defn page-created
[{:keys [id project-id] :as page}]
(s/assert ::page page)
(ptk/reify ::page-created
cljs.core/IDeref
(-deref [_] page)
ptk/UpdateEvent
(update [_ state]
(let [data (:data page)
page (dissoc page :data)]
(-> state
(update :pages assoc id page)
(update :pages-data assoc id data))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Workspace-Aware Page Events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Rename Page
(s/def ::rename-page (s/def ::rename-page
(s/keys :req-un [::id ::name])) (s/keys :req-un [::id ::name]))
@ -229,7 +224,10 @@
(ptk/reify ::rename-page (ptk/reify ::rename-page
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:pages id] assoc :name name)) (let [pid (get-in state [:workspace-page :id])
state (assoc-in state [:pages id :name] name)]
(cond-> state
(= pid id) (assoc-in [:workspace-page :name] name))))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
@ -237,59 +235,63 @@
(->> (rp/mutation :rename-page params) (->> (rp/mutation :rename-page params)
(rx/map #(ptk/data-event ::page-renamed data))))))) (rx/map #(ptk/data-event ::page-renamed data)))))))
;; --- Page Metadata Persisted ;; --- Persist Page
(s/def ::metadata-persisted-params (declare page-persisted)
(s/keys :req-un [::id ::version]))
(def persist-current-page
(ptk/reify ::persist-page
ptk/WatchEvent
(watch [this state s]
(let [local (:workspace-local state)
page (:workspace-page state)
data (:workspace-data state)]
(if (:history local)
(rx/empty)
(let [page (assoc page :data data)]
(->> (rp/mutation :update-page page)
(rx/map (fn [res] (merge page res)))
(rx/map page-persisted)
(rx/catch (fn [err] (rx/of ::page-persist-error))))))))))
;; --- Page Persisted
(defn page-persisted
[{:keys [id] :as page}]
(s/assert ::page page)
(ptk/reify ::page-persisted
cljs.core/IDeref
(-deref [_] page)
(defn metadata-persisted
[{:keys [id] :as data}]
(s/assert ::metadata-persisted-params data)
(ptk/reify ::metadata-persisted
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:pages id :version] (:version data))))) (let [data (:data page)
page (dissoc page :data)]
(defn metadata-persisted? (-> state
[v] (assoc :workspace-data data)
(= ::metadata-persisted (ptk/type v))) (assoc :workspace-page page)
(update :pages assoc id page)
;; --- Persist Page Metadata (update :pages-data assoc id data))))))
;; This is a simplified version of `PersistPage` event
;; that does not sends the heavyweiht `:data` attribute
;; and only serves for update other page data.
(defn persist-metadata
[id]
(s/assert ::us/uuid id)
(ptk/reify ::persist-metadata
ptk/WatchEvent
(watch [_ state stream]
#_(let [page (get-in state [:pages id])]
(->> (rp/req :update/page-metadata page)
(rx/map :payload)
(rx/map metadata-persisted))))))
;; --- Update Page ;; --- Update Page
;; TODO: deprecated, need refactor (this is used on page options)
(defn update-page-attrs (defn update-page-attrs
[{:keys [id] :as data}] [{:keys [id] :as data}]
(s/assert ::page-entity data) (s/assert ::page data)
(ptk/reify (ptk/reify ::update-page-attrs
IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:pages id] merge (dissoc data :id :version))))) (update state :workspace-page merge (dissoc data :id :version)))))
;; --- Update Page Metadata ;; --- Update Page Metadata
;; TODO: deprecated, need refactor (this is used on page options)
(defn update-metadata (defn update-metadata
[id metadata] [id metadata]
(s/assert ::id id) (s/assert ::id id)
(s/assert ::metadata metadata) (s/assert ::metadata metadata)
(reify (reify
IMetadataUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [this state] (update [this state]
(assoc-in state [:pages id :metadata] metadata)))) (assoc-in state [:pages id :metadata] metadata))))
@ -307,4 +309,4 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state s] (watch [_ state s]
(->> (rp/mutation :delete-page {:id id}) (->> (rp/mutation :delete-page {:id id})
(rx/map (constantly ::delete-completed)))))) (rx/map (ptk/data-event ::page-deleted {:id id}))))))

View file

@ -34,6 +34,9 @@
::created-at ::created-at
::modified-at])) ::modified-at]))
(declare fetch-projects)
(declare projects-fetched?)
;; --- Helpers ;; --- Helpers
(defn assoc-project (defn assoc-project
@ -49,21 +52,43 @@
;; --- Initialize ;; --- Initialize
(declare fetch-projects) (declare fetch-files)
(declare projects-fetched?) (declare initialized)
(defrecord Initialize []
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:dashboard :section] :dashboard/projects))
ptk/WatchEvent
(watch [_ state s]
(rx/of (fetch-projects))))
(defn initialize (defn initialize
[] [id]
(Initialize.)) (ptk/reify ::initialize
ptk/UpdateEvent
(update [_ state]
(update state :dashboard-projects assoc :id id))
ptk/WatchEvent
(watch [_ state stream]
(rx/merge
(rx/of (fetch-files id))
(->> stream
(rx/filter (ptk/type? ::files-fetched))
(rx/take 1)
(rx/map #(initialized id (deref %))))))))
(defn initialized
[id files]
(ptk/reify ::initialized
ptk/UpdateEvent
(update [_ state]
(let [files (into #{} (map :id) files)]
(update-in state [:dashboard-projects :files] assoc id files)))))
;; --- Update Opts (Filtering & Ordering)
(defn update-opts
[& {:keys [order filter] :as opts}]
(ptk/reify ::update-opts
ptk/UpdateEvent
(update [_ state]
(update state :dashboard-projects merge
(when order {:order order})
(when filter {:filter filter})))))
;; --- Projects Fetched ;; --- Projects Fetched
@ -81,14 +106,52 @@
;; --- Fetch Projects ;; --- Fetch Projects
(defn fetch-projects (def fetch-projects
[]
(ptk/reify ::fetch-projects (ptk/reify ::fetch-projects
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(->> (rp/query :projects) (->> (rp/query :projects)
(rx/map projects-fetched))))) (rx/map projects-fetched)))))
;; --- Fetch Files
(declare files-fetched)
(defn fetch-files
[project-id]
(ptk/reify ::fetch-files
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :project-files {:project-id project-id})
(rx/map files-fetched)))))
;; --- Fetch File (by ID)
(defn fetch-file
[id]
(s/assert ::us/uuid id)
(ptk/reify ::fetch-file
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :project-file {:id id})
(rx/map #(files-fetched [%]))))))
;; --- Files Fetched
(s/def ::files any?)
(defn files-fetched
[files]
(s/assert ::files files)
(ptk/reify ::files-fetched
cljs.core/IDeref
(-deref [_] files)
ptk/UpdateEvent
(update [_ state]
(let [assoc-file #(assoc-in %1 [:files (:id %2)] %2)]
(reduce assoc-file state files)))))
;; --- Project Persisted ;; --- Project Persisted
(defrecord ProjectPersisted [data] (defrecord ProjectPersisted [data]
@ -149,43 +212,38 @@
;; --- Create Project ;; --- Create Project
(s/def ::create-project-params (declare project-created)
(s/keys :req-un [::name ::width ::height]))
(s/def ::create-project
(s/keys :req-un [::name]))
(defn create-project (defn create-project
[{:keys [name] :as params}] [{:keys [name] :as params}]
(s/assert ::create-project-params params) (s/assert ::create-project params)
(reify (ptk/reify ::create-project
ptk/WatchEvent ptk/WatchEvent
(watch [this state stream] (watch [this state stream]
#_(->> (rp/req :create/project {:name name}) (->> (rp/mutation :create-project {:name name})
(rx/map :payload) (rx/map project-created)))))
(rx/mapcat (fn [{:keys [id] :as project}]
(rx/of #(assoc-project % project) ;; --- Project Created
(udp/form->create-page (assoc params :project id)))))))))
(defn project-created
[data]
(ptk/reify ::project-created
ptk/UpdateEvent
(update [_ state]
(assoc-project state data))))
;; --- Go To Project ;; --- Go To Project
(defn go-to (defn go-to
[id] [file-id]
(s/assert ::us/uuid id) (s/assert ::us/uuid file-id)
(ptk/reify ::go-to (ptk/reify ::go-to
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [page-ids (get-in state [:projects id :pages])] (let [page-ids (get-in state [:files file-id :pages])]
(let [params {:project id :page (first page-ids)}] (let [path-params {:file-id file-id}
(rx/of (rt/nav :workspace/page params))))))) query-params {:page-id (first page-ids)}]
(rx/of (rt/nav :workspace path-params query-params)))))))
;; --- Update Opts (Filtering & Ordering)
(defrecord UpdateOpts [order filter]
ptk/UpdateEvent
(update [_ state]
(update-in state [:dashboard :projects] merge
(when order {:order order})
(when filter {:filter filter}))))
(defn update-opts
[& {:keys [order filter] :as opts}]
(UpdateOpts. order filter))

View file

@ -106,12 +106,6 @@
(s/def ::set-of-uuid (s/def ::set-of-uuid
(s/every ::us/uuid :kind set?)) (s/every ::us/uuid :kind set?))
;; --- Protocols
(defprotocol IPageDataUpdate
"A marker protocol for mark events that alters the
page and is subject to perform a backend synchronization.")
;; --- Expose inner functions ;; --- Expose inner functions
(defn interrupt? [e] (= e :interrupt)) (defn interrupt? [e] (= e :interrupt))
@ -136,26 +130,29 @@
(declare initialized) (declare initialized)
(declare watch-page-changes) (declare watch-page-changes)
(declare watch-events) ;; (declare watch-events)
(defn initialize (defn initialize
"Initialize the workspace state." "Initialize the workspace state."
[project-id page-id] [file-id page-id]
(s/assert ::us/uuid project-id) (s/assert ::us/uuid file-id)
(s/assert ::us/uuid page-id) (s/assert ::us/uuid page-id)
(ptk/reify ::initialize (ptk/reify ::initialize
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [local (assoc workspace-default
:file-id file-id
:page-id page-id)]
(-> state (-> state
(assoc :workspace-layout default-layout) (assoc :workspace-layout default-layout)
;; (update :workspace-layout ;; (update :workspace-layout
;; (fn [data] ;; (fn [data]
;; (if (nil? data) default-layout data))) ;; (if (nil? data) default-layout data)))
(assoc :workspace-local (assoc :workspace-local local))))
(assoc workspace-default :id page-id))))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(prn "initialize" file-id page-id)
#_(when-not (get-in state [:pages page-id]) #_(when-not (get-in state [:pages page-id])
(reset! st/loader true)) (reset! st/loader true))
@ -164,35 +161,41 @@
;; and all project related pages. ;; and all project related pages.
(rx/of ::stop-watcher (rx/of ::stop-watcher
(udp/fetch-page page-id) (udp/fetch-page page-id)
(udp/fetch-pages project-id)) (dp/fetch-file file-id)
(udp/fetch-pages file-id))
;; When main page is fetched, schedule the main initialization. ;; When main page is fetched, schedule the main initialization.
(->> (rx/filter udp/page-fetched? stream) (->> (rx/zip (rx/filter (ptk/type? ::udp/page-fetched) stream)
(rx/filter (ptk/type? ::dp/files-fetched) stream))
(rx/take 1) (rx/take 1)
(rx/do #(reset! st/loader false)) (rx/do #(reset! st/loader false))
(rx/mapcat #(rx/of (initialized page-id) (rx/mapcat #(rx/of (initialized file-id page-id)
#_(initialize-alignment page-id)))) #_(initialize-alignment page-id))))
;; When workspace is initialized, run the event watchers. ;; When workspace is initialized, run the event watchers.
(->> (rx/filter (ptk/type? ::initialized) stream) (->> (rx/filter (ptk/type? ::initialized) stream)
(rx/take 1) (rx/take 1)
(rx/mapcat #(rx/of watch-page-changes (rx/mapcat #(rx/of watch-page-changes)))))
watch-events)))))
ptk/EffectEvent ptk/EffectEvent
(effect [_ state stream] (effect [_ state stream]
;; Optimistic prefetch of projects if them are not already fetched ;; Optimistic prefetch of projects if them are not already fetched
(when-not (seq (:projects state)) #_(when-not (seq (:projects state))
(st/emit! (dp/fetch-projects)))))) (st/emit! (dp/fetch-projects))))))
(defn- initialized (defn- initialized
[page-id] [file-id page-id]
(s/assert ::us/uuid file-id)
(s/assert ::us/uuid page-id) (s/assert ::us/uuid page-id)
(ptk/reify ::initialized (ptk/reify ::initialized
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [page (get-in state [:pages page-id]) (let [file (get-in state [:files file-id])
page (get-in state [:pages page-id])
data (get-in state [:pages-data page-id])] data (get-in state [:pages-data page-id])]
(prn "initialized" file)
(assoc state (assoc state
:workspace-file file
:workspace-data data :workspace-data data
:workspace-page page))))) :workspace-page page)))))
@ -335,7 +338,6 @@
(CopyToClipboard.)) (CopyToClipboard.))
(defrecord PasteFromClipboard [id] (defrecord PasteFromClipboard [id]
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
state state
@ -442,7 +444,7 @@
(defn add-shape (defn add-shape
[data] [data]
(ptk/reify ::add-shape (ptk/reify ::add-shape
IPageDataUpdate udp/IPageDataUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
;; TODO: revisit the `setup-proportions` seems unnecesary ;; TODO: revisit the `setup-proportions` seems unnecesary
@ -460,7 +462,7 @@
(def duplicate-selected (def duplicate-selected
(ptk/reify ::duplicate-selected (ptk/reify ::duplicate-selected
IPageDataUpdate udp/IPageDataUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [selected (get-in state [:workspace-local :selected]) (let [selected (get-in state [:workspace-local :selected])
@ -485,7 +487,7 @@
[id] [id]
(s/assert ::us/uuid id) (s/assert ::us/uuid id)
(ptk/reify ::delete-shape (ptk/reify ::delete-shape
IPageDataUpdate udp/IPageDataUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [shape (get-in state [:workspace-data :shapes-by-id id])] (let [shape (get-in state [:workspace-data :shapes-by-id id])]
@ -495,7 +497,7 @@
[ids] [ids]
(s/assert ::us/set ids) (s/assert ::us/set ids)
(ptk/reify ::delete-many-shapes (ptk/reify ::delete-many-shapes
IPageDataUpdate udp/IPageDataUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(reduce impl-dissoc-shape state (reduce impl-dissoc-shape state
@ -656,7 +658,6 @@
;; --- Update Shape Position ;; --- Update Shape Position
(deftype UpdateShapePosition [id point] (deftype UpdateShapePosition [id point]
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:shapes id] geom/absolute-move point))) (update-in state [:shapes id] geom/absolute-move point)))
@ -683,7 +684,6 @@
[id name] [id name]
{:pre [(uuid? id) (string? name)]} {:pre [(uuid? id) (string? name)]}
(ptk/reify ::rename-shape (ptk/reify ::rename-shape
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:shapes id :name] name)))) (assoc-in state [:shapes id :name] name))))
@ -722,7 +722,7 @@
[loc] [loc]
(s/assert ::direction loc) (s/assert ::direction loc)
(ptk/reify ::move-selected-layer (ptk/reify ::move-selected-layer
IPageDataUpdate udp/IPageDataUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [id (first (get-in state [:workspace-local :selected])) (let [id (first (get-in state [:workspace-local :selected]))
@ -804,7 +804,6 @@
(update-in [:workspace-data :shapes-by-id id] dissoc :modifier-mtx)) (update-in [:workspace-data :shapes-by-id id] dissoc :modifier-mtx))
state)))] state)))]
(ptk/reify ::materialize-current-modifier-in-bulk (ptk/reify ::materialize-current-modifier-in-bulk
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(reduce process-shape state ids))))) (reduce process-shape state ids)))))
@ -890,7 +889,6 @@
;; TODO: revisit ;; TODO: revisit
(deftype UnlockShapeProportions [id] (deftype UnlockShapeProportions [id]
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:shapes id :proportion-lock] false))) (assoc-in state [:shapes id :proportion-lock] false)))
@ -918,7 +916,6 @@
(s/assert ::us/uuid id) (s/assert ::us/uuid id)
(s/assert ::update-dimensions dimensions) (s/assert ::update-dimensions dimensions)
(ptk/reify ::update-dimensions (ptk/reify ::update-dimensions
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:shapes id] geom/resize-dim dimensions)))) (update-in state [:shapes id] geom/resize-dim dimensions))))
@ -927,7 +924,6 @@
;; TODO: revisit ;; TODO: revisit
(deftype UpdateInteraction [shape interaction] (deftype UpdateInteraction [shape interaction]
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [id (or (:id interaction) (let [id (or (:id interaction)
@ -943,7 +939,6 @@
;; TODO: revisit ;; TODO: revisit
(deftype DeleteInteracton [shape id] (deftype DeleteInteracton [shape id]
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:shapes shape :interactions] dissoc id))) (update-in state [:shapes shape :interactions] dissoc id)))
@ -1006,7 +1001,6 @@
(reduce impl-set-hidden $ (sequence xform shapes))) (reduce impl-set-hidden $ (sequence xform shapes)))
$))))] $))))]
(ptk/reify ::set-hidden-attr (ptk/reify ::set-hidden-attr
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(impl-set-hidden state id))))) (impl-set-hidden state id)))))
@ -1030,7 +1024,6 @@
(reduce impl-set-blocked $ (sequence xform shapes))) (reduce impl-set-blocked $ (sequence xform shapes)))
$))))] $))))]
(ptk/reify ::set-blocked-attr (ptk/reify ::set-blocked-attr
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(impl-set-blocked state id))))) (impl-set-blocked state id)))))
@ -1039,7 +1032,6 @@
;; TODO: revisit ;; TODO: revisit
(deftype LockShape [id] (deftype LockShape [id]
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(letfn [(mark-locked [state id] (letfn [(mark-locked [state id]
@ -1058,7 +1050,6 @@
(LockShape. id)) (LockShape. id))
(deftype UnlockShape [id] (deftype UnlockShape [id]
udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(letfn [(mark-unlocked [state id] (letfn [(mark-unlocked [state id]
@ -1146,20 +1137,6 @@
#_(rx/of (udp/update-metadata id metadata) #_(rx/of (udp/update-metadata id metadata)
(initialize-alignment id))))) (initialize-alignment id)))))
;; (defrecord OpenView [page-id]
;; ptk/WatchEvent
;; (watch [_ state s]
;; (let [page-id (get-in state [:workspace :page])]
;; (rx/of (udp/persist-page page-id))))
;; ptk/EffectEvent
;; (effect [_ state s]
;; (let [rval (rand-int 1000000)
;; page (get-in state [:pages page-id])
;; project (get-in state [:projects (:project page)])
;; url (str cfg/viewurl "?v=" rval "#/preview/" (:share-token project) "/" page-id)]
;; (js/open url "new tab" ""))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Navigation ;; Navigation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1173,53 +1150,21 @@
params {:project project-id :page (first page-ids)}] params {:project project-id :page (first page-ids)}]
(rx/of (rt/nav :workspace/page params)))))) (rx/of (rt/nav :workspace/page params))))))
(defn go-to-page
[page-id]
(s/assert ::us/uuid page-id)
(ptk/reify ::go-to
ptk/WatchEvent
(watch [_ state stream]
(let [file-id (get-in state [:workspace-local :file-id])
path-params {:file-id file-id}
query-params {:page-id page-id}]
(rx/of (rt/nav :workspace path-params query-params))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Page Changes Reactions ;; Page Changes Reactions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn page-update?
[o]
(or (satisfies? IPageDataUpdate o)
(= ::page-data-update o)))
(defn page-persisted
[{:keys [id] :as page}]
(s/assert ::page-entity page)
(ptk/reify ::page-persisted
cljs.core/IDeref
(-deref [_] page)
ptk/UpdateEvent
(update [_ state]
(let [data (:data page)
page (dissoc page :data)]
(-> state
(assoc :workspace-data data)
(assoc :workspace-page page)
(update :pages assoc id page)
(update :pages-data assoc id data))))))
(defn page-persisted?
[v]
(= ::page-persisted (ptk/type v)))
;; --- Persist Page
(def persist-page
(ptk/reify ::persist-page
ptk/WatchEvent
(watch [this state s]
(let [local (:workspace-local state)
page (:workspace-page state)
data (:workspace-data state)]
(if (:history local)
(rx/empty)
(let [page (assoc page :data data)]
(->> (rp/mutation :update-page page)
(rx/map (fn [res] (merge page res)))
(rx/map page-persisted)
(rx/catch (fn [err] (rx/of ::page-persist-error))))))))))
;; --- Change Page Order (D&D Ordering) ;; --- Change Page Order (D&D Ordering)
(defn change-page-order (defn change-page-order
@ -1236,7 +1181,7 @@
(assoc-in state [:projects (:project-id page) :pages] pages))))) (assoc-in state [:projects (:project-id page) :pages] pages)))))
;; --- Delete Page ;; --- Delete Page
;; TODO: join with udp/delete-page
(defn delete-page (defn delete-page
[id] [id]
(s/assert ::us/uuid id) (s/assert ::us/uuid id)
@ -1259,30 +1204,15 @@
(watch [_ state stream] (watch [_ state stream]
(let [stopper (rx/filter #(= % ::stop-watcher) stream)] (let [stopper (rx/filter #(= % ::stop-watcher) stream)]
(->> stream (->> stream
(rx/filter page-update?) (rx/filter udp/page-update?)
(rx/debounce 500) (rx/debounce 500)
(rx/mapcat #(rx/merge (rx/of rehash-shapes-relationships persist-page) (rx/mapcat #(rx/merge (rx/of rehash-shapes-relationships udp/persist-current-page)
(->> (rx/filter page-persisted? stream) (->> (rx/filter (ptk/type? ::udp/page-persisted) stream)
(rx/timeout 1000 (rx/empty)) (rx/timeout 1000 (rx/empty))
(rx/take 1) (rx/take 1)
(rx/ignore)))) (rx/ignore))))
(rx/take-until stopper)))))) (rx/take-until stopper))))))
(def watch-events
(letfn [(on-page-renamed [event]
(rx/of #(update % :workspace-page assoc :name (:name @event))))]
(ptk/reify ::watch-events
ptk/WatchEvent
(watch [_ state stream]
(let [stopper (rx/filter #(= % ::stop-watcher) stream)
page-id (get-in state [:workspace-page :id])]
(->> stream
(rx/filter (ptk/type? ::udp/page-renamed))
(rx/filter #(= (:id (deref %)) page-id))
(rx/map on-page-renamed)
(rx/filter ptk/event?)
(rx/take-until stopper)))))))
;; (def watch-shapes-changes ;; (def watch-shapes-changes
;; (letfn [(look-for-changes [[old new]] ;; (letfn [(look-for-changes [[old new]]
;; (reduce-kv (fn [acc k v] ;; (reduce-kv (fn [acc k v]
@ -1299,7 +1229,7 @@
;; (watch [_ state stream] ;; (watch [_ state stream]
;; (let [stopper (rx/filter #(= % ::stop-page-watcher) stream)] ;; (let [stopper (rx/filter #(= % ::stop-page-watcher) stream)]
;; (->> stream ;; (->> stream
;; (rx/filter page-update?) ;; (rx/filter udp/page-update?)
;; (rx/debounce 1000) ;; (rx/debounce 1000)
;; (rx/mapcat #(rx/merge (rx/of persist-page ;; (rx/mapcat #(rx/merge (rx/of persist-page
;; (->> (rx/filter page-persisted? stream) ;; (->> (rx/filter page-persisted? stream)

View file

@ -12,6 +12,9 @@
"ds.num-projects" ["No projects" "ds.num-projects" ["No projects"
"%s project" "%s project"
"%s projects"] "%s projects"]
"ds.num-files" ["No files"
"%s file"
"%s files"]
"ds.project-title" "Your projects" "ds.project-title" "Your projects"
"ds.project-new" "+ New project" "ds.project-new" "+ New project"
"ds.project-thumbnail.alt" "Project title" "ds.project-thumbnail.alt" "Project title"

View file

@ -27,6 +27,10 @@
(-> (l/key :workspace-page) (-> (l/key :workspace-page)
(l/derive st/state))) (l/derive st/state)))
(def workspace-file
(-> (l/key :workspace-file)
(l/derive st/state)))
(def workspace-data (def workspace-data
(-> (l/key :workspace-data) (-> (l/key :workspace-data)
(l/derive st/state))) (l/derive st/state)))

View file

@ -53,11 +53,7 @@
nil)) nil))
(def initial-state (def initial-state
{:dashboard {:project-order :name {:route nil
:project-filter ""
:images-order :name
:images-filter ""}
:route nil
:router nil :router nil
:auth (:auth storage) :auth (:auth storage)
:profile (:profile storage) :profile (:profile storage)

View file

@ -9,10 +9,10 @@
(:require (:require
[beicon.core :as rx] [beicon.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[expound.alpha :as expound]
[lentes.core :as l] [lentes.core :as l]
[potok.core :as ptk] [potok.core :as ptk]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[expound.alpha :as expound]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.data.auth :refer [logout]] [uxbox.main.data.auth :refer [logout]]
[uxbox.main.data.projects :as dp] [uxbox.main.data.projects :as dp]
@ -21,7 +21,7 @@
[uxbox.main.ui.dashboard :as dashboard] [uxbox.main.ui.dashboard :as dashboard]
[uxbox.main.ui.settings :as settings] [uxbox.main.ui.settings :as settings]
[uxbox.main.ui.shapes] [uxbox.main.ui.shapes]
[uxbox.main.ui.workspace :refer [workspace-page]] [uxbox.main.ui.workspace :as workspace]
[uxbox.util.data :refer [parse-int uuid-str?]] [uxbox.util.data :refer [parse-int uuid-str?]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.html.history :as html-history] [uxbox.util.html.history :as html-history]
@ -38,17 +38,19 @@
["/register" :auth/register] ["/register" :auth/register]
["/recovery/request" :auth/recovery-request] ["/recovery/request" :auth/recovery-request]
["/recovery/token/:token" :auth/recovery]] ["/recovery/token/:token" :auth/recovery]]
["/settings" ["/settings"
["/profile" :settings/profile] ["/profile" :settings/profile]
["/password" :settings/password] ["/password" :settings/password]
["/notifications" :settings/notifications]] ["/notifications" :settings/notifications]]
["/dashboard" ["/dashboard"
["/projects" :dashboard/projects] ["/projects" :dashboard-projects]
["/elements" :dashboard/elements] ["/icons" :dashboard-icons]
["/icons" :dashboard/icons] ["/images" :dashboard-images]
["/images" :dashboard/images] ["/colors" :dashboard-colors]]
["/colors" :dashboard/colors]]
["/workspace/:project/:page" :workspace/page]]) ["/workspace/:file-id" :workspace]])
;; --- Error Handling ;; --- Error Handling
@ -100,8 +102,8 @@
(case (get-in route [:data :name]) (case (get-in route [:data :name])
:auth/login (mf/element auth/login-page) :auth/login (mf/element auth/login-page)
:auth/register (mf/element auth/register-page) :auth/register (mf/element auth/register-page)
;; :auth/recovery-request (auth/recovery-request-page)
;; :auth/recovery-request (auth/recovery-request-page)
;; :auth/recovery ;; :auth/recovery
;; (let [token (get-in route [:params :path :token])] ;; (let [token (get-in route [:params :path :token])]
;; (auth/recovery-page token)) ;; (auth/recovery-page token))
@ -111,18 +113,19 @@
:settings/notifications) :settings/notifications)
(mf/element settings/settings #js {:route route}) (mf/element settings/settings #js {:route route})
(:dashboard/projects :dashboard-projects
:dashboard/icons (mf/element dashboard/dashboard-projects #js {:route route})
:dashboard/images
:dashboard/colors)
(mf/element dashboard/dashboard #js {:route route})
:workspace/page (:dashboard-icons
(let [project-id (uuid (get-in route [:params :path :project])) :dashboard-images
page-id (uuid (get-in route [:params :path :page]))] :dashboard-colors)
[:& workspace-page {:project-id project-id (mf/element dashboard/dashboard-assets #js {:route route})
:workspace
(let [file-id (uuid (get-in route [:params :path :file-id]))
page-id (uuid (get-in route [:params :query :page-id]))]
[:& workspace/workspace {:file-id file-id
:page-id page-id :page-id page-id
:key page-id}]) :key file-id}])
nil))) nil)))

View file

@ -11,19 +11,25 @@
[uxbox.main.ui.dashboard.colors :as colors] [uxbox.main.ui.dashboard.colors :as colors]
[uxbox.main.ui.messages :refer [messages-widget]])) [uxbox.main.ui.messages :refer [messages-widget]]))
(defn- parse-route (mf/defc dashboard-projects
[{:keys [params data] :as route}]
(let [{:keys [id type]} (:query params)
id (cond
(str/digits? id) (parse-int id)
(uuid-str? id) (uuid id)
:else nil)
type (when (str/alpha? type) (keyword type))]
[(:name data) type id]))
(mf/defc dashboard
[{:keys [route] :as props}] [{:keys [route] :as props}]
(let [[section type id] (parse-route route)] (let [id (get-in route [:params :query :project-id])
id (when (uuid-str? id) (uuid id))]
[:main.dashboard-main
[:& messages-widget]
[:& header {:section :dashboard/projects}]
[:& projects/projects-page {:id id}]]))
(mf/defc dashboard-assets
[{:keys [route] :as props}]
(let [section (:name route)
{:keys [id type]} (get-in route [:params :query])
id (cond
;; (str/digits? id) (parse-int id)
(uuid-str? id) (uuid id)
(str/empty-or-nil? id) nil
:else id)
type (when (str/alpha? type) (keyword type))]
[:main.dashboard-main [:main.dashboard-main
[:& messages-widget] [:& messages-widget]
[:& header {:section section}] [:& header {:section section}]
@ -34,8 +40,5 @@
:dashboard/images :dashboard/images
[:& images/images-page {:type type :id id}] [:& images/images-page {:type type :id id}]
:dashboard/projects
[:& projects/projects-page]
:dashboard/colors :dashboard/colors
[:& colors/colors-page {:type type :id id}])])) [:& colors/colors-page {:type type :id id}])]))

View file

@ -61,7 +61,7 @@
(-> (l/in [:dashboard :icons]) (-> (l/in [:dashboard :icons])
(l/derive st/state))) (l/derive st/state)))
;; --- Page Title ;; --- Component: Grid Header
(mf/defc grid-header (mf/defc grid-header
[{:keys [coll] :as props}] [{:keys [coll] :as props}]

View file

@ -6,6 +6,7 @@
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.dashboard.projects (ns uxbox.main.ui.dashboard.projects
(:refer-clojure :exclude [sort-by])
(:require (:require
[cuerdas.core :as str] [cuerdas.core :as str]
[lentes.core :as l] [lentes.core :as l]
@ -18,10 +19,11 @@
[uxbox.main.ui.keyboard :as kbd] [uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.confirm :refer [confirm-dialog]] [uxbox.main.ui.confirm :refer [confirm-dialog]]
[uxbox.main.ui.dashboard.projects-forms :refer [create-project-dialog]] [uxbox.main.ui.dashboard.projects-forms :refer [create-project-dialog]]
[uxbox.main.ui.dashboard.common :as common]
[uxbox.util.data :refer [read-string]] [uxbox.util.data :refer [read-string]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.i18n :as t :refer [tr]] [uxbox.util.i18n :as t :refer [tr]]
[uxbox.util.router :as r] [uxbox.util.router :as rt]
[uxbox.util.time :as dt])) [uxbox.util.time :as dt]))
;; --- Helpers & Constants ;; --- Helpers & Constants
@ -33,7 +35,7 @@
;; --- Refs ;; --- Refs
(def opts-iref (def opts-iref
(-> (l/in [:dashboard :projects]) (-> (l/key :dashboard-projects)
(l/derive st/state))) (l/derive st/state)))
(def projects-iref (def projects-iref
@ -42,64 +44,65 @@
;; --- Helpers ;; --- Helpers
(defn sort-projects-by (defn sort-by
[ordering projs] [ordering files]
(case ordering (case ordering
:name (sort-by :name projs) :name (cljs.core/sort-by :name files)
:created (reverse (sort-by :created-at projs)) :created (reverse (cljs.core/sort-by :created-at files))
projs)) files))
(defn contains-term? (defn contains-term?
[phrase term] [phrase term]
(let [term (name term)] (let [term (name term)]
(str/includes? (str/lower phrase) (str/trim (str/lower term))))) (str/includes? (str/lower phrase) (str/trim (str/lower term)))))
(defn filter-projects-by (defn filter-by
[term projs] [term files]
(if (str/blank? term) (if (str/blank? term)
projs files
(filter #(contains-term? (:name %) term) projs))) (filter #(contains-term? (:name %) term) files)))
;; --- Menu (Filter & Sort) ;; --- Menu (Filter & Sort)
(mf/def menu (mf/defc menu
:mixins #{mf/memo mf/reactive} [{:keys [opts files] :as props}]
:init
(fn [own props]
(assoc own ::num-projects (-> (comp (l/key :projects)
(l/lens #(-> % vals count)))
(l/derive st/state))))
:render
(fn [own {:keys [opts] :as props}]
(let [ordering (:order opts :created) (let [ordering (:order opts :created)
filtering (:filter opts "") filtering (:filter opts "")
num-projects (mf/react (::num-projects own))]
(letfn [(on-term-change [event] on-term-change
(fn [event]
(let [term (-> (dom/get-target event) (let [term (-> (dom/get-target event)
(dom/get-value))] (dom/get-value))]
(st/emit! (udp/update-opts :filter term)))) (st/emit! (udp/update-opts :filter term))))
(on-ordering-change [event]
on-order-change
(fn [event]
(let [value (dom/event->value event) (let [value (dom/event->value event)
value (read-string value)] value (read-string value)]
(st/emit! (udp/update-opts :order value)))) (st/emit! (udp/update-opts :order value))))
(on-clear [event]
on-clear
(fn [event]
(st/emit! (udp/update-opts :filter "")))] (st/emit! (udp/update-opts :filter "")))]
[:section.dashboard-bar
[:section.dashboard-bar.library-gap
[:div.dashboard-info [:div.dashboard-info
;; Counter ;; Counter
[:span.dashboard-images (tr "ds.num-projects" (t/c num-projects))] [:span.dashboard-images (tr "ds.num-files" (t/c (count files)))]
;; Sorting
[:div [:div
;; Sorting
;; TODO: convert to separate component?
[:span (tr "ds.ordering")] [:span (tr "ds.ordering")]
[:select.input-select [:select.input-select {:on-change on-order-change
{:on-change on-ordering-change
:value (pr-str ordering)} :value (pr-str ordering)}
(for [[key value] (seq +ordering-options+)] (for [[key value] (seq +ordering-options+)]
(let [key (pr-str key)] (let [key (pr-str key)]
[:option {:key key :value key} (tr value)]))]] [:option {:key key :value key} (tr value)]))]]
;; Search ;; Search
;; TODO: convert to separate component?
[:form.dashboard-search [:form.dashboard-search
[:input.input-text [:input.input-text
{:key :images-search-box {:key :images-search-box
@ -108,94 +111,179 @@
:auto-focus true :auto-focus true
:placeholder (tr "ds.search.placeholder") :placeholder (tr "ds.search.placeholder")
:value (or filtering "")}] :value (or filtering "")}]
[:div.clear-search {:on-click on-clear} i/close]]]])))) [:div.clear-search {:on-click on-clear} i/close]]]]))
;; --- Grid Item Thumbnail ;; --- Grid Item Thumbnail
(mf/defc grid-item-thumbnail (mf/defc grid-item-thumbnail
[{:keys [project] :as props}] [{:keys [project] :as props}]
[:div.grid-item-th [:div.grid-item-th
[:img.img-th {:src "/images/project-placeholder.svg" [:img.img-th {:src "/images/project-placeholder.svg"}]])
:alt (tr "ds.project-thumbnail.alt")}]])
;; --- Grid Item ;; --- Grid Item
(mf/defc grid-item (mf/defc grid-item
{:wrap [mf/wrap-memo]} {:wrap [mf/wrap-memo]}
[{:keys [project] :as props}] [{:keys [file] :as props}]
(let [local (mf/use-state {}) (let [local (mf/use-state {})
on-navigate #(st/emit! (udp/go-to (:id project))) on-navigate #(st/emit! (udp/go-to (:id file)))
delete #(st/emit! (udp/delete-project project)) ;; delete #(st/emit! (udp/delete-project project))
on-delete #(do ;; on-delete #(do
(dom/stop-propagation %) ;; (dom/stop-propagation %)
(modal/show! confirm-dialog {:on-accept delete})) ;; (modal/show! confirm-dialog {:on-accept delete}))
on-blur #(let [target (dom/event->target %) ;; on-blur #(let [target (dom/event->target %)
name (dom/get-value target) ;; name (dom/get-value target)
id (:id project)] ;; id (:id project)]
(swap! local assoc :edition false) ;; (swap! local assoc :edition false)
(st/emit! (udp/rename-project id name))) ;; (st/emit! (udp/rename-project id name)))
on-key-down #(when (kbd/enter? %) (on-blur %)) ;; on-key-down #(when (kbd/enter? %) (on-blur %))
on-edit #(do ;; on-edit #(do
(dom/stop-propagation %) ;; (dom/stop-propagation %)
(dom/prevent-default %) ;; (dom/prevent-default %)
(swap! local assoc :edition true))] ;; (swap! local assoc :edition true))
]
[:div.grid-item.project-th {:on-click on-navigate} [:div.grid-item.project-th {:on-click on-navigate}
[:& grid-item-thumbnail {:project project [:& grid-item-thumbnail {:file file}]
:key (select-keys project [:id :page-id])}]
[:div.item-info [:div.item-info
(if (:edition @local) (if (:edition @local)
[:input.element-name {:type "text" [:input.element-name {:type "text"
:auto-focus true :auto-focus true
:on-key-down on-key-down ;; :on-key-down on-key-down
:on-blur on-blur ;; :on-blur on-blur
:on-click on-edit ;; :on-click on-edit
:default-value (:name project)}] :default-value (:name file)}]
[:h3 (:name project)]) [:h3 (:name file)])
[:span.date [:span.date
(str (tr "ds.updated-at" (dt/timeago (:modified-at project))))]] (str (tr "ds.updated-at" (dt/timeago (:modified-at file))))]]
[:div.project-th-actions [:div.project-th-actions
[:div.project-th-icon.pages [:div.project-th-icon.pages
i/page i/page
[:span (:total-pages project)]] #_[:span (:total-pages project)]]
#_[:div.project-th-icon.comments #_[:div.project-th-icon.comments
i/chat i/chat
[:span "0"]] [:span "0"]]
[:div.project-th-icon.edit [:div.project-th-icon.edit
{:on-click on-edit} #_{:on-click on-edit}
i/pencil] i/pencil]
[:div.project-th-icon.delete [:div.project-th-icon.delete
{:on-click on-delete} #_{:on-click on-delete}
i/trash]]])) i/trash]]]))
;; --- Grid ;; --- Grid
(mf/defc grid (mf/defc grid
[{:keys [opts] :as props}] [{:keys [opts files] :as props}]
(let [order (:order opts :created) (let [order (:order opts :created)
filter (:filter opts "") filter (:filter opts "")
projects (mf/deref projects-iref) files (->> files
projects (->> (vals projects) (filter-by filter)
(filter-projects-by filter) (sort-by order))
(sort-projects-by order))
on-click #(do on-click #(do
(dom/prevent-default %) (dom/prevent-default %)
(modal/show! create-project-dialog {}) #_(modal/show! create-project-dialog {})
#_(udl/open! :create-project))] #_(udl/open! :create-project))
]
[:section.dashboard-grid [:section.dashboard-grid
[:h2 (tr "ds.project-title")] [:h2 (tr "ds.projects.file-name")]
[:div.dashboard-grid-content [:div.dashboard-grid-content
[:div.dashboard-grid-row [:div.dashboard-grid-row
[:div.grid-item.add-project {:on-click on-click} [:div.grid-item.add-project #_{:on-click on-click}
[:span (tr "ds.project-new")]] [:span (tr "ds.project-file")]]
(for [item files]
[:& grid-item {:file item :key (:id item)}])]]]))
;; --- Component: Nav
;; (letfn [(on-click [event]
;; #_(let [type (or type :own)]
;; (st/emit! (rt/nav :dashboard/icons {} {:type type :id id}))))
;; (on-input-change [event]
;; #_(-> (dom/get-target event)
;; (dom/get-value)
;; (swap! local assoc :name)))
;; (on-cancel [event]
;; #_(swap! local dissoc :name :edit))
;; (on-double-click [event]
;; #_(when editable?
;; (swap! local assoc :edit true)))
;; (on-input-keyup [event]
;; #_(when (kbd/enter? event)
;; (let [value (-> (dom/get-target event) (dom/get-value))]
;; (st/emit! (di/rename-collection id (str/trim (:name @local))))
;; (swap! local assoc :edit false))))]
(mf/defc nav-item
[{:keys [id name selected?] :as props}]
(let [local (mf/use-state {})
editable? (not (nil? id))
on-click (fn [event]
(st/emit! (rt/nav :dashboard-projects {} {:project-id (str id)})))]
[:li {:on-click on-click
;; :on-double-click on-double-click
:class-name (when selected? "current")}
(if (:edit @local)
[:div
[:input.element-title #_{:value (if (:name @local)
(:name @local)
(if id name "Storage"))
:on-change on-input-change
:on-key-down on-input-keyup}]
[:span.close #_{:on-click on-cancel} i/close]]
[:span.element-title (if id name "Recent")])
#_[:span.element-subtitle (tr "ds.num-elements" (t/c num-icons))]
]))
(mf/defc nav
[{:keys [id] :as props}]
(let [projects (->> (mf/deref projects-iref)
(vals)
(sort-by :created-at))]
[:div.library-bar
[:div.library-bar-inside
[:ul.library-elements
[:li
[:a.btn-primary #_{:on-click #(st/emit! di/create-collection)}
"new project +"]]
[:li {:style {:marginBottom "20px"}
:on-click #(st/emit! (rt/nav :dashboard/projects {} {}))
:class-name (when (nil? id) "current")}
[:span.element-title "Recent"]]
(for [item projects] (for [item projects]
[:& grid-item {:project item :key (:id item)}])]]])) [:& nav-item {:id (:id item)
:key (:id item)
:name (:name item)
:selected? (= (:id item) id)}])]]]))
;; --- Component: Content
(def files-ref
(letfn [(selector [state]
(let [id (get-in state [:dashboard-projects :id])
ids (get-in state [:dashboard-projects :files id])]
(mapv #(get-in state [:files %]) ids)))]
(-> (l/lens selector)
(l/derive st/state))))
(mf/defc content
[{:keys [id] :as props}]
(let [opts (mf/deref opts-iref)
files (mf/deref files-ref)]
[:*
[:& menu {:opts opts :files files}]
[:section.dashboard-grid.library
[:& grid {:id id :opts opts :files files}]]]))
;; --- Projects Page ;; --- Projects Page
(mf/defc projects-page (mf/defc projects-page
[_] [{:keys [id] :as props}]
(mf/use-effect #(st/emit! (udp/initialize))) (mf/use-effect #(st/emit! udp/fetch-projects))
(let [opts (mf/deref opts-iref)] (mf/use-effect {:fn #(st/emit! (udp/initialize id))
:deps #js [id]})
[:section.dashboard-content [:section.dashboard-content
[:& menu {:opts opts}] [:& nav {:id id}]
[:& grid {:opts opts}]])) [:& content {:id id}]])

View file

@ -22,12 +22,9 @@
(s/def ::height ::fm/number-str) (s/def ::height ::fm/number-str)
(s/def ::project-form (s/def ::project-form
(s/keys :req-un [::name ::width ::height])) (s/keys :req-un [::name]))
(def defaults (def defaults {:name ""})
{:name ""
:width "1366"
:height "768"})
;; --- Create Project Form ;; --- Create Project Form
@ -57,33 +54,6 @@
:on-blur (fm/on-input-blur form :name) :on-blur (fm/on-input-blur form :name)
:on-change (fm/on-input-change form :name) :on-change (fm/on-input-change form :name)
:auto-focus true}] :auto-focus true}]
[:div.project-size
[:div.input-element.pixels
[:span (tr "ds.width")]
[:input#project-witdh.input-text
{:placeholder (tr "ds.width")
:name "width"
:type "number"
:min 0
:max 5000
:class (fm/error-class form :width)
:on-blur (fm/on-input-blur form :width)
:on-change (fm/on-input-change form :width)
:value (:width data)}]]
[:a.toggle-layout {:on-click #(swap-size % form)} i/toggle]
[:div.input-element.pixels
[:span (tr "ds.height")]
[:input#project-height.input-text
{:placeholder (tr "ds.height")
:type "number"
:name "height"
:min 0
:max 5000
:class (fm/error-class form :height)
:on-blur (fm/on-input-blur form :height)
:on-change (fm/on-input-change form :height)
:value (:height data)}]]]
;; Submit ;; Submit
[:input#project-btn.btn-primary [:input#project-btn.btn-primary
{:value (tr "ds.go") {:value (tr "ds.go")

View file

@ -60,7 +60,7 @@
(scroll/scroll-to-point dom mouse-point scroll-position)))) (scroll/scroll-to-point dom mouse-point scroll-position))))
(mf/defc workspace-content (mf/defc workspace-content
[{:keys [layout page] :as params}] [{:keys [layout page file] :as params}]
(let [canvas (mf/use-ref nil) (let [canvas (mf/use-ref nil)
left-sidebar? (not (empty? (keep layout [:layers :sitemap left-sidebar? (not (empty? (keep layout [:layers :sitemap
:document-history]))) :document-history])))
@ -89,17 +89,26 @@
;; Aside ;; Aside
(when left-sidebar? (when left-sidebar?
[:& left-sidebar {:page page :layout layout}]) [:& left-sidebar {:file file :page page :layout layout}])
(when right-sidebar? (when right-sidebar?
[:& right-sidebar {:page page :layout layout}])])) [:& right-sidebar {:page page :layout layout}])]))
(mf/defc workspace (mf/defc workspace
[{:keys [page-id] :as props}] [{:keys [file-id page-id] :as props}]
(let [layout (mf/deref refs/workspace-layout)
flags (mf/deref refs/selected-flags)
page (mf/deref refs/workspace-page)]
[:* (mf/use-effect
{:deps #js [file-id page-id]
:fn (fn []
(let [sub (shortcuts/init)]
(st/emit! (udw/initialize file-id page-id))
#(rx/cancel! sub)))})
(let [layout (mf/deref refs/workspace-layout)
file (mf/deref refs/workspace-file)
page (mf/deref refs/workspace-page)
flags (mf/deref refs/selected-flags)]
[:> rdnd/provider {:backend rdnd/html5}
[:& messages-widget] [:& messages-widget]
[:& header {:page page :flags flags}] [:& header {:page page :flags flags}]
@ -107,17 +116,6 @@
[:& colorpalette]) [:& colorpalette])
(when (and layout page) (when (and layout page)
[:& workspace-content {:layout layout :page page}])])) [:& workspace-content {:layout layout
:file file
(mf/defc workspace-page :page page}])]))
[{:keys [project-id page-id] :as props}]
(mf/use-effect
{:deps #js [page-id]
:fn (fn []
(let [sub (shortcuts/init)]
(st/emit! (udw/initialize project-id page-id))
#(rx/cancel! sub)))})
[:> rdnd/provider {:backend rdnd/html5}
[:& workspace {:page-id page-id :key page-id}]])

View file

@ -52,7 +52,7 @@
] ]
[:header#workspace-bar.workspace-bar [:header#workspace-bar.workspace-bar
[:div.main-icon [:div.main-icon
[:a {:on-click #(st/emit! (rt/nav :dashboard/projects))} i/logo-icon]] [:a {:on-click #(st/emit! (rt/nav :dashboard-projects))} i/logo-icon]]
[:div.project-tree-btn [:div.project-tree-btn
{:alt (tr "header.sitemap") {:alt (tr "header.sitemap")
:class (when (contains? flags :sitemap) "selected") :class (when (contains? flags :sitemap) "selected")

View file

@ -20,13 +20,11 @@
(mf/defc left-sidebar (mf/defc left-sidebar
{:wrap [mf/wrap-memo]} {:wrap [mf/wrap-memo]}
[{:keys [layout page] :as props}] [{:keys [layout page file] :as props}]
[:aside.settings-bar.settings-bar-left [:aside.settings-bar.settings-bar-left
[:div.settings-bar-inside [:div.settings-bar-inside
(when (contains? layout :sitemap) (when (contains? layout :sitemap)
[:& sitemap-toolbox {:project-id (:project-id page) [:& sitemap-toolbox {:file file :page page}])
:current-page-id (:id page)
:page page}])
(when (contains? layout :document-history) (when (contains? layout :document-history)
[:& history-toolbox]) [:& history-toolbox])
(when (contains? layout :layers) (when (contains? layout :layers)

View file

@ -29,20 +29,18 @@
(mf/defc page-item (mf/defc page-item
[{:keys [page index deletable? selected?] :as props}] [{:keys [page index deletable? selected?] :as props}]
(letfn [(on-edit [event] (let [on-edit #(modal/show! page-form-dialog {:page page})
(modal/show! page-form-dialog {:page page})) delete-fn #(st/emit! (dw/delete-page (:id page)))
(delete [] on-delete #(do
(st/emit! (dw/delete-page (:id page)))) (dom/prevent-default %)
(on-delete [event] (dom/stop-propagation %)
(dom/prevent-default event) (modal/show! confirm-dialog {:on-accept delete-fn}))
(dom/stop-propagation event) on-drop #(do (prn "TODO"))
(modal/show! confirm-dialog {:on-accept delete})) on-hover #(st/emit! (dw/change-page-order {:id (:id page)
(on-drop [item monitor] :index index}))
(prn "TODO"))
(on-hover [item monitor] navigate-fn #(st/emit! (dw/go-to-page (:id page)))
(st/emit! (dw/change-page-order {:id (:id item) [dprops ref] (use-sortable {:type "page-item"
:index index})))]
(let [[dprops ref] (use-sortable {:type "page-item"
:data {:id (:id page) :data {:id (:id page)
:index index} :index index}
:on-hover on-hover :on-hover on-hover
@ -51,8 +49,7 @@
[:div.element-list-body [:div.element-list-body
{:class (classnames :selected selected? {:class (classnames :selected selected?
:dragging (:dragging? dprops)) :dragging (:dragging? dprops))
:on-click #(st/emit! (rt/nav :workspace/page {:project (:project-id page) :on-click navigate-fn
:page (:id page)}))
:on-double-click #(dom/stop-propagation %) :on-double-click #(dom/stop-propagation %)
:draggable true} :draggable true}
@ -61,7 +58,7 @@
[:div.page-actions {} [:div.page-actions {}
[:a {:on-click on-edit} i/pencil] [:a {:on-click on-edit} i/pencil]
(when deletable? (when deletable?
[:a {:on-click on-delete} i/trash])]]]))) [:a {:on-click on-delete} i/trash])]]]))
;; --- Page Item Wrapper ;; --- Page Item Wrapper
@ -84,8 +81,8 @@
;; --- Pages List ;; --- Pages List
(mf/defc pages-list (mf/defc pages-list
[{:keys [project current-page-id] :as props}] [{:keys [file current-page] :as props}]
(let [pages (enumerate (:pages project)) (let [pages (enumerate (:pages file))
deletable? (> (count pages) 1)] deletable? (> (count pages) 1)]
[:ul.element-list [:ul.element-list
(for [[index page-id] pages] (for [[index page-id] pages]
@ -93,31 +90,22 @@
{:page-id page-id {:page-id page-id
:index index :index index
:deletable? deletable? :deletable? deletable?
:selected? (= page-id current-page-id) :selected? (= page-id (:id current-page))
:key page-id}])])) :key page-id}])]))
;; --- Sitemap Toolbox ;; --- Sitemap Toolbox
(def ^:private workspace-project
(letfn [(selector [state]
(let [project-id (get-in state [:workspace-page :project-id])]
(get-in state [:projects project-id])))]
(-> (l/lens selector)
(l/derive st/state))))
(mf/defc sitemap-toolbox (mf/defc sitemap-toolbox
[{:keys [project-id current-page-id] :as props}] [{:keys [file page] :as props}]
(let [project (mf/deref workspace-project) (let [create-fn #(modal/show! page-form-dialog {:page {:file-id (:file-id page)}})
create #(modal/show! page-form-dialog {:page {:project-id project-id}}) close-fn #(st/emit! (dw/toggle-flag :sitemap))]
close #(st/emit! (dw/toggle-flag :sitemap))]
[:div.sitemap.tool-window [:div.sitemap.tool-window
[:div.tool-window-bar [:div.tool-window-bar
[:div.tool-window-icon i/project-tree] [:div.tool-window-icon i/project-tree]
[:span (tr "ds.settings.sitemap")] [:span (tr "ds.settings.sitemap")]
[:div.tool-window-close {:on-click close} i/close]] [:div.tool-window-close {:on-click close-fn} i/close]]
[:div.tool-window-content [:div.tool-window-content
[:div.project-title [:div.project-title
[:span (:name project)] #_[:span (:name project)]
[:div.add-page {:on-click create} i/close]] [:div.add-page {:on-click create-fn} i/close]]
[:& pages-list {:project project [:& pages-list {:file file :current-page page}]]]))
:current-page-id current-page-id}]]]))