diff --git a/frontend/src/uxbox/main.cljs b/frontend/src/uxbox/main.cljs index 9ce3a2057..2aa685b92 100644 --- a/frontend/src/uxbox/main.cljs +++ b/frontend/src/uxbox/main.cljs @@ -49,7 +49,7 @@ (cond (and (= path "") (:auth storage)) - (st/emit! (rt/nav :dashboard/projects)) + (st/emit! (rt/nav :dashboard-projects)) (and (= path "") (not (:auth storage))) (st/emit! (rt/nav :auth/login)) diff --git a/frontend/src/uxbox/main/data/pages.cljs b/frontend/src/uxbox/main/data/pages.cljs index ff3dc4ab9..878c6ca76 100644 --- a/frontend/src/uxbox/main/data/pages.cljs +++ b/frontend/src/uxbox/main/data/pages.cljs @@ -5,6 +5,7 @@ ;; Copyright (c) 2015-2017 Andrey Antukh (ns uxbox.main.data.pages + "Page related events (for workspace mainly)." (:require [cljs.spec.alpha :as s] [beicon.core :as rx] @@ -22,7 +23,7 @@ (s/def ::name ::us/string) (s/def ::inst ::us/inst) (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 ::modified-at ::us/inst) (s/def ::version ::us/number) @@ -56,10 +57,10 @@ (s/def ::data (s/keys :req-un [::shapes ::canvas ::shapes-by-id])) -(s/def ::page-entity +(s/def ::page (s/keys :req-un [::id ::name - ::project-id + ::file-id ::created-at ::modified-at ::user-id @@ -67,21 +68,19 @@ ::metadata ::data])) +(s/def ::pages + (s/every ::page :kind vector?)) + ;; --- Protocols -(defprotocol IPageUpdate - "A marker protocol for mark events that alters the - page and is subject to perform a backend synchronization.") - -(defprotocol IMetadataUpdate +(defprotocol IPageDataUpdate "A marker protocol for mark events that alters the page and is subject to perform a backend synchronization.") (defn page-update? [o] - (or (satisfies? IPageUpdate o) - (satisfies? IMetadataUpdate o) - (= ::page-update o))) + (or (satisfies? IPageDataUpdate o) + (= ::page-data-update o))) ;; --- Helpers @@ -114,53 +113,39 @@ (update :pages-data dissoc id)) 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 (defn pages-fetched - [id pages] - (s/assert ::us/uuid id) - (s/assert ::us/coll pages) + [pages] + (s/assert ::pages pages) (ptk/reify ::pages-fetched IDeref - (-deref [_] (list id pages)) + (-deref [_] pages) ptk/UpdateEvent (update [_ state] (reduce unpack-page state pages)))) -(defn pages-fetched? - [v] - (= ::pages-fetched (ptk/type v))) +;; --- Fetch Page (By ID) -;; --- Fetch Pages (by project id) - -(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) +(declare page-fetched) (defn fetch-page "Fetch page by id." @@ -169,35 +154,26 @@ (reify ptk/WatchEvent (watch [_ state s] - (->> (rp/query :page {:id id}) + (->> (rp/query :project-page {:id id}) (rx/map page-fetched))))) -;; --- Page Created +;; --- Page Fetched -(defn page-created - [{:keys [id project-id] :as page}] - (s/assert ::page-entity page) - (ptk/reify ::page-created - cljs.core/IDeref - (-deref [_] page) +(defn page-fetched + [data] + (s/assert ::page data) + (ptk/reify ::page-fetched + IDeref + (-deref [_] data) ptk/UpdateEvent (update [_ state] - (let [data (:data page) - 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)))))) + (unpack-page state data)))) -(defn page-created? - [v] - (= ::page-created (ptk/type v))) - -;; --- Create Page Form +;; --- Create Page (s/def ::create-page - (s/keys :req-un [::name ::project-id])) + (s/keys :req-un [::name ::file-id])) (defn create-page [{:keys [project-id name] :as data}] @@ -205,7 +181,7 @@ (ptk/reify ::create-page ptk/WatchEvent (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 :project-id project-id :ordering ordering @@ -216,9 +192,28 @@ (->> (rp/mutation :create-page params) (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/keys :req-un [::id ::name])) @@ -229,7 +224,10 @@ (ptk/reify ::rename-page ptk/UpdateEvent (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 (watch [_ state stream] @@ -237,59 +235,63 @@ (->> (rp/mutation :rename-page params) (rx/map #(ptk/data-event ::page-renamed data))))))) -;; --- Page Metadata Persisted +;; --- Persist Page -(s/def ::metadata-persisted-params - (s/keys :req-un [::id ::version])) +(declare page-persisted) + +(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 (update [_ state] - (assoc-in state [:pages id :version] (:version data))))) - -(defn metadata-persisted? - [v] - (= ::metadata-persisted (ptk/type v))) - -;; --- Persist Page Metadata - -;; 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)))))) + (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)))))) ;; --- Update Page +;; TODO: deprecated, need refactor (this is used on page options) (defn update-page-attrs [{:keys [id] :as data}] - (s/assert ::page-entity data) - (ptk/reify - IPageUpdate + (s/assert ::page data) + (ptk/reify ::update-page-attrs ptk/UpdateEvent (update [_ state] - (update-in state [:pages id] merge (dissoc data :id :version))))) + (update state :workspace-page merge (dissoc data :id :version))))) ;; --- Update Page Metadata +;; TODO: deprecated, need refactor (this is used on page options) (defn update-metadata [id metadata] (s/assert ::id id) (s/assert ::metadata metadata) (reify - IMetadataUpdate ptk/UpdateEvent (update [this state] (assoc-in state [:pages id :metadata] metadata)))) @@ -307,4 +309,4 @@ ptk/WatchEvent (watch [_ state s] (->> (rp/mutation :delete-page {:id id}) - (rx/map (constantly ::delete-completed)))))) + (rx/map (ptk/data-event ::page-deleted {:id id})))))) diff --git a/frontend/src/uxbox/main/data/projects.cljs b/frontend/src/uxbox/main/data/projects.cljs index 68edb036f..093b6d3f8 100644 --- a/frontend/src/uxbox/main/data/projects.cljs +++ b/frontend/src/uxbox/main/data/projects.cljs @@ -34,6 +34,9 @@ ::created-at ::modified-at])) +(declare fetch-projects) +(declare projects-fetched?) + ;; --- Helpers (defn assoc-project @@ -49,21 +52,43 @@ ;; --- Initialize -(declare fetch-projects) -(declare projects-fetched?) - -(defrecord Initialize [] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:dashboard :section] :dashboard/projects)) - - ptk/WatchEvent - (watch [_ state s] - (rx/of (fetch-projects)))) +(declare fetch-files) +(declare initialized) (defn initialize - [] - (Initialize.)) + [id] + (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 @@ -81,14 +106,52 @@ ;; --- Fetch Projects -(defn fetch-projects - [] +(def fetch-projects (ptk/reify ::fetch-projects ptk/WatchEvent (watch [_ state stream] (->> (rp/query :projects) (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 (defrecord ProjectPersisted [data] @@ -149,43 +212,38 @@ ;; --- Create Project -(s/def ::create-project-params - (s/keys :req-un [::name ::width ::height])) +(declare project-created) + +(s/def ::create-project + (s/keys :req-un [::name])) (defn create-project [{:keys [name] :as params}] - (s/assert ::create-project-params params) - (reify + (s/assert ::create-project params) + (ptk/reify ::create-project ptk/WatchEvent (watch [this state stream] - #_(->> (rp/req :create/project {:name name}) - (rx/map :payload) - (rx/mapcat (fn [{:keys [id] :as project}] - (rx/of #(assoc-project % project) - (udp/form->create-page (assoc params :project id))))))))) + (->> (rp/mutation :create-project {:name name}) + (rx/map project-created))))) + +;; --- Project Created + +(defn project-created + [data] + (ptk/reify ::project-created + ptk/UpdateEvent + (update [_ state] + (assoc-project state data)))) ;; --- Go To Project (defn go-to - [id] - (s/assert ::us/uuid id) + [file-id] + (s/assert ::us/uuid file-id) (ptk/reify ::go-to ptk/WatchEvent (watch [_ state stream] - (let [page-ids (get-in state [:projects id :pages])] - (let [params {:project id :page (first page-ids)}] - (rx/of (rt/nav :workspace/page 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)) + (let [page-ids (get-in state [:files file-id :pages])] + (let [path-params {:file-id file-id} + query-params {:page-id (first page-ids)}] + (rx/of (rt/nav :workspace path-params query-params))))))) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 3e1d5f85f..6e54390c5 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -106,12 +106,6 @@ (s/def ::set-of-uuid (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 (defn interrupt? [e] (= e :interrupt)) @@ -136,70 +130,79 @@ (declare initialized) (declare watch-page-changes) -(declare watch-events) +;; (declare watch-events) (defn initialize "Initialize the workspace state." - [project-id page-id] - (s/assert ::us/uuid project-id) + [file-id page-id] + (s/assert ::us/uuid file-id) (s/assert ::us/uuid page-id) (ptk/reify ::initialize ptk/UpdateEvent (update [_ state] - (-> state - (assoc :workspace-layout default-layout) - ;; (update :workspace-layout - ;; (fn [data] - ;; (if (nil? data) default-layout data))) - (assoc :workspace-local - (assoc workspace-default :id page-id)))) + (let [local (assoc workspace-default + :file-id file-id + :page-id page-id)] + (-> state + (assoc :workspace-layout default-layout) + ;; (update :workspace-layout + ;; (fn [data] + ;; (if (nil? data) default-layout data))) + (assoc :workspace-local local)))) ptk/WatchEvent (watch [_ state stream] + (prn "initialize" file-id page-id) #_(when-not (get-in state [:pages page-id]) - (reset! st/loader true)) + (reset! st/loader true)) (rx/merge ;; Stop possible previous watchers and re-fetch the main page ;; and all project related pages. (rx/of ::stop-watcher (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. - (->> (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/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)))) + ;; When workspace is initialized, run the event watchers. (->> (rx/filter (ptk/type? ::initialized) stream) (rx/take 1) - (rx/mapcat #(rx/of watch-page-changes - watch-events))))) + (rx/mapcat #(rx/of watch-page-changes))))) ptk/EffectEvent (effect [_ state stream] ;; 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)))))) (defn- initialized - [page-id] + [file-id page-id] + (s/assert ::us/uuid file-id) (s/assert ::us/uuid page-id) (ptk/reify ::initialized ptk/UpdateEvent (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])] + (prn "initialized" file) (assoc state + :workspace-file file :workspace-data data :workspace-page page))))) ;; --- Workspace Flags (defn activate-flag - [flag] + [flag] (s/assert keyword? flag) (ptk/reify ::activate-flag ptk/UpdateEvent @@ -335,7 +338,6 @@ (CopyToClipboard.)) (defrecord PasteFromClipboard [id] - udp/IPageUpdate ptk/UpdateEvent (update [_ state] state @@ -442,7 +444,7 @@ (defn add-shape [data] (ptk/reify ::add-shape - IPageDataUpdate + udp/IPageDataUpdate ptk/UpdateEvent (update [_ state] ;; TODO: revisit the `setup-proportions` seems unnecesary @@ -460,7 +462,7 @@ (def duplicate-selected (ptk/reify ::duplicate-selected - IPageDataUpdate + udp/IPageDataUpdate ptk/UpdateEvent (update [_ state] (let [selected (get-in state [:workspace-local :selected]) @@ -485,7 +487,7 @@ [id] (s/assert ::us/uuid id) (ptk/reify ::delete-shape - IPageDataUpdate + udp/IPageDataUpdate ptk/UpdateEvent (update [_ state] (let [shape (get-in state [:workspace-data :shapes-by-id id])] @@ -495,7 +497,7 @@ [ids] (s/assert ::us/set ids) (ptk/reify ::delete-many-shapes - IPageDataUpdate + udp/IPageDataUpdate ptk/UpdateEvent (update [_ state] (reduce impl-dissoc-shape state @@ -656,7 +658,6 @@ ;; --- Update Shape Position (deftype UpdateShapePosition [id point] - udp/IPageUpdate ptk/UpdateEvent (update [_ state] (update-in state [:shapes id] geom/absolute-move point))) @@ -683,7 +684,6 @@ [id name] {:pre [(uuid? id) (string? name)]} (ptk/reify ::rename-shape - udp/IPageUpdate ptk/UpdateEvent (update [_ state] (assoc-in state [:shapes id :name] name)))) @@ -722,7 +722,7 @@ [loc] (s/assert ::direction loc) (ptk/reify ::move-selected-layer - IPageDataUpdate + udp/IPageDataUpdate ptk/UpdateEvent (update [_ state] (let [id (first (get-in state [:workspace-local :selected])) @@ -804,7 +804,6 @@ (update-in [:workspace-data :shapes-by-id id] dissoc :modifier-mtx)) state)))] (ptk/reify ::materialize-current-modifier-in-bulk - udp/IPageUpdate ptk/UpdateEvent (update [_ state] (reduce process-shape state ids))))) @@ -890,7 +889,6 @@ ;; TODO: revisit (deftype UnlockShapeProportions [id] - udp/IPageUpdate ptk/UpdateEvent (update [_ state] (assoc-in state [:shapes id :proportion-lock] false))) @@ -918,7 +916,6 @@ (s/assert ::us/uuid id) (s/assert ::update-dimensions dimensions) (ptk/reify ::update-dimensions - udp/IPageUpdate ptk/UpdateEvent (update [_ state] (update-in state [:shapes id] geom/resize-dim dimensions)))) @@ -927,7 +924,6 @@ ;; TODO: revisit (deftype UpdateInteraction [shape interaction] - udp/IPageUpdate ptk/UpdateEvent (update [_ state] (let [id (or (:id interaction) @@ -943,7 +939,6 @@ ;; TODO: revisit (deftype DeleteInteracton [shape id] - udp/IPageUpdate ptk/UpdateEvent (update [_ state] (update-in state [:shapes shape :interactions] dissoc id))) @@ -1006,7 +1001,6 @@ (reduce impl-set-hidden $ (sequence xform shapes))) $))))] (ptk/reify ::set-hidden-attr - udp/IPageUpdate ptk/UpdateEvent (update [_ state] (impl-set-hidden state id))))) @@ -1030,7 +1024,6 @@ (reduce impl-set-blocked $ (sequence xform shapes))) $))))] (ptk/reify ::set-blocked-attr - udp/IPageUpdate ptk/UpdateEvent (update [_ state] (impl-set-blocked state id))))) @@ -1039,7 +1032,6 @@ ;; TODO: revisit (deftype LockShape [id] - udp/IPageUpdate ptk/UpdateEvent (update [_ state] (letfn [(mark-locked [state id] @@ -1058,7 +1050,6 @@ (LockShape. id)) (deftype UnlockShape [id] - udp/IPageUpdate ptk/UpdateEvent (update [_ state] (letfn [(mark-unlocked [state id] @@ -1146,20 +1137,6 @@ #_(rx/of (udp/update-metadata id metadata) (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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1173,53 +1150,21 @@ params {:project project-id :page (first page-ids)}] (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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(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) (defn change-page-order @@ -1236,7 +1181,7 @@ (assoc-in state [:projects (:project-id page) :pages] pages))))) ;; --- Delete Page - +;; TODO: join with udp/delete-page (defn delete-page [id] (s/assert ::us/uuid id) @@ -1259,30 +1204,15 @@ (watch [_ state stream] (let [stopper (rx/filter #(= % ::stop-watcher) stream)] (->> stream - (rx/filter page-update?) + (rx/filter udp/page-update?) (rx/debounce 500) - (rx/mapcat #(rx/merge (rx/of rehash-shapes-relationships persist-page) - (->> (rx/filter page-persisted? stream) + (rx/mapcat #(rx/merge (rx/of rehash-shapes-relationships udp/persist-current-page) + (->> (rx/filter (ptk/type? ::udp/page-persisted) stream) (rx/timeout 1000 (rx/empty)) (rx/take 1) (rx/ignore)))) (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 ;; (letfn [(look-for-changes [[old new]] ;; (reduce-kv (fn [acc k v] @@ -1299,7 +1229,7 @@ ;; (watch [_ state stream] ;; (let [stopper (rx/filter #(= % ::stop-page-watcher) stream)] ;; (->> stream -;; (rx/filter page-update?) +;; (rx/filter udp/page-update?) ;; (rx/debounce 1000) ;; (rx/mapcat #(rx/merge (rx/of persist-page ;; (->> (rx/filter page-persisted? stream) diff --git a/frontend/src/uxbox/main/locales/en.cljs b/frontend/src/uxbox/main/locales/en.cljs index 7968c7eb9..3095db29f 100644 --- a/frontend/src/uxbox/main/locales/en.cljs +++ b/frontend/src/uxbox/main/locales/en.cljs @@ -12,6 +12,9 @@ "ds.num-projects" ["No projects" "%s project" "%s projects"] + "ds.num-files" ["No files" + "%s file" + "%s files"] "ds.project-title" "Your projects" "ds.project-new" "+ New project" "ds.project-thumbnail.alt" "Project title" diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index 2d14e0493..95c49a127 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -27,6 +27,10 @@ (-> (l/key :workspace-page) (l/derive st/state))) +(def workspace-file + (-> (l/key :workspace-file) + (l/derive st/state))) + (def workspace-data (-> (l/key :workspace-data) (l/derive st/state))) diff --git a/frontend/src/uxbox/main/store.cljs b/frontend/src/uxbox/main/store.cljs index 361f373b3..96d6efa05 100644 --- a/frontend/src/uxbox/main/store.cljs +++ b/frontend/src/uxbox/main/store.cljs @@ -53,11 +53,7 @@ nil)) (def initial-state - {:dashboard {:project-order :name - :project-filter "" - :images-order :name - :images-filter ""} - :route nil + {:route nil :router nil :auth (:auth storage) :profile (:profile storage) diff --git a/frontend/src/uxbox/main/ui.cljs b/frontend/src/uxbox/main/ui.cljs index 5d928bae2..07e77dabf 100644 --- a/frontend/src/uxbox/main/ui.cljs +++ b/frontend/src/uxbox/main/ui.cljs @@ -9,10 +9,10 @@ (:require [beicon.core :as rx] [cuerdas.core :as str] + [expound.alpha :as expound] [lentes.core :as l] [potok.core :as ptk] [rumext.alpha :as mf] - [expound.alpha :as expound] [uxbox.builtins.icons :as i] [uxbox.main.data.auth :refer [logout]] [uxbox.main.data.projects :as dp] @@ -21,7 +21,7 @@ [uxbox.main.ui.dashboard :as dashboard] [uxbox.main.ui.settings :as settings] [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.dom :as dom] [uxbox.util.html.history :as html-history] @@ -38,17 +38,19 @@ ["/register" :auth/register] ["/recovery/request" :auth/recovery-request] ["/recovery/token/:token" :auth/recovery]] + ["/settings" ["/profile" :settings/profile] ["/password" :settings/password] ["/notifications" :settings/notifications]] + ["/dashboard" - ["/projects" :dashboard/projects] - ["/elements" :dashboard/elements] - ["/icons" :dashboard/icons] - ["/images" :dashboard/images] - ["/colors" :dashboard/colors]] - ["/workspace/:project/:page" :workspace/page]]) + ["/projects" :dashboard-projects] + ["/icons" :dashboard-icons] + ["/images" :dashboard-images] + ["/colors" :dashboard-colors]] + + ["/workspace/:file-id" :workspace]]) ;; --- Error Handling @@ -100,8 +102,8 @@ (case (get-in route [:data :name]) :auth/login (mf/element auth/login-page) :auth/register (mf/element auth/register-page) - ;; :auth/recovery-request (auth/recovery-request-page) + ;; :auth/recovery-request (auth/recovery-request-page) ;; :auth/recovery ;; (let [token (get-in route [:params :path :token])] ;; (auth/recovery-page token)) @@ -111,18 +113,19 @@ :settings/notifications) (mf/element settings/settings #js {:route route}) - (:dashboard/projects - :dashboard/icons - :dashboard/images - :dashboard/colors) - (mf/element dashboard/dashboard #js {:route route}) + :dashboard-projects + (mf/element dashboard/dashboard-projects #js {:route route}) - :workspace/page - (let [project-id (uuid (get-in route [:params :path :project])) - page-id (uuid (get-in route [:params :path :page]))] - [:& workspace-page {:project-id project-id - :page-id page-id - :key page-id}]) + (:dashboard-icons + :dashboard-images + :dashboard-colors) + (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 + :key file-id}]) nil))) diff --git a/frontend/src/uxbox/main/ui/dashboard.cljs b/frontend/src/uxbox/main/ui/dashboard.cljs index a48a82194..5d0524bed 100644 --- a/frontend/src/uxbox/main/ui/dashboard.cljs +++ b/frontend/src/uxbox/main/ui/dashboard.cljs @@ -11,19 +11,25 @@ [uxbox.main.ui.dashboard.colors :as colors] [uxbox.main.ui.messages :refer [messages-widget]])) -(defn- parse-route - [{: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 +(mf/defc dashboard-projects [{: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 [:& messages-widget] [:& header {:section section}] @@ -34,8 +40,5 @@ :dashboard/images [:& images/images-page {:type type :id id}] - :dashboard/projects - [:& projects/projects-page] - :dashboard/colors [:& colors/colors-page {:type type :id id}])])) diff --git a/frontend/src/uxbox/main/ui/dashboard/icons.cljs b/frontend/src/uxbox/main/ui/dashboard/icons.cljs index 2e8ed2a9d..c0a9091c4 100644 --- a/frontend/src/uxbox/main/ui/dashboard/icons.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/icons.cljs @@ -61,7 +61,7 @@ (-> (l/in [:dashboard :icons]) (l/derive st/state))) -;; --- Page Title +;; --- Component: Grid Header (mf/defc grid-header [{:keys [coll] :as props}] diff --git a/frontend/src/uxbox/main/ui/dashboard/projects.cljs b/frontend/src/uxbox/main/ui/dashboard/projects.cljs index 4d5d99c49..23b9ad6b0 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects.cljs @@ -6,6 +6,7 @@ ;; Copyright (c) 2015-2017 Juan de la Cruz (ns uxbox.main.ui.dashboard.projects + (:refer-clojure :exclude [sort-by]) (:require [cuerdas.core :as str] [lentes.core :as l] @@ -18,10 +19,11 @@ [uxbox.main.ui.keyboard :as kbd] [uxbox.main.ui.confirm :refer [confirm-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.dom :as dom] [uxbox.util.i18n :as t :refer [tr]] - [uxbox.util.router :as r] + [uxbox.util.router :as rt] [uxbox.util.time :as dt])) ;; --- Helpers & Constants @@ -33,7 +35,7 @@ ;; --- Refs (def opts-iref - (-> (l/in [:dashboard :projects]) + (-> (l/key :dashboard-projects) (l/derive st/state))) (def projects-iref @@ -42,160 +44,246 @@ ;; --- Helpers -(defn sort-projects-by - [ordering projs] +(defn sort-by + [ordering files] (case ordering - :name (sort-by :name projs) - :created (reverse (sort-by :created-at projs)) - projs)) + :name (cljs.core/sort-by :name files) + :created (reverse (cljs.core/sort-by :created-at files)) + files)) (defn contains-term? [phrase term] (let [term (name term)] (str/includes? (str/lower phrase) (str/trim (str/lower term))))) -(defn filter-projects-by - [term projs] +(defn filter-by + [term files] (if (str/blank? term) - projs - (filter #(contains-term? (:name %) term) projs))) + files + (filter #(contains-term? (:name %) term) files))) ;; --- Menu (Filter & Sort) -(mf/def menu - :mixins #{mf/memo mf/reactive} - :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) - filtering (:filter opts "") - num-projects (mf/react (::num-projects own))] - (letfn [(on-term-change [event] - (let [term (-> (dom/get-target event) - (dom/get-value))] - (st/emit! (udp/update-opts :filter term)))) - (on-ordering-change [event] - (let [value (dom/event->value event) - value (read-string value)] - (st/emit! (udp/update-opts :order value)))) - (on-clear [event] - (st/emit! (udp/update-opts :filter "")))] - [:section.dashboard-bar - [:div.dashboard-info +(mf/defc menu + [{:keys [opts files] :as props}] + (let [ordering (:order opts :created) + filtering (:filter opts "") - ;; Counter - [:span.dashboard-images (tr "ds.num-projects" (t/c num-projects))] + on-term-change + (fn [event] + (let [term (-> (dom/get-target event) + (dom/get-value))] + (st/emit! (udp/update-opts :filter term)))) - ;; Sorting - [:div - [:span (tr "ds.ordering")] - [:select.input-select - {:on-change on-ordering-change - :value (pr-str ordering)} - (for [[key value] (seq +ordering-options+)] - (let [key (pr-str key)] - [:option {:key key :value key} (tr value)]))]] - ;; Search - [:form.dashboard-search - [:input.input-text - {:key :images-search-box - :type "text" - :on-change on-term-change - :auto-focus true - :placeholder (tr "ds.search.placeholder") - :value (or filtering "")}] - [:div.clear-search {:on-click on-clear} i/close]]]])))) + on-order-change + (fn [event] + (let [value (dom/event->value event) + value (read-string value)] + (st/emit! (udp/update-opts :order value)))) + + on-clear + (fn [event] + (st/emit! (udp/update-opts :filter "")))] + + [:section.dashboard-bar.library-gap + [:div.dashboard-info + + ;; Counter + [:span.dashboard-images (tr "ds.num-files" (t/c (count files)))] + + [:div + ;; Sorting + ;; TODO: convert to separate component? + [:span (tr "ds.ordering")] + [:select.input-select {:on-change on-order-change + :value (pr-str ordering)} + (for [[key value] (seq +ordering-options+)] + (let [key (pr-str key)] + [:option {:key key :value key} (tr value)]))]] + + ;; Search + ;; TODO: convert to separate component? + [:form.dashboard-search + [:input.input-text + {:key :images-search-box + :type "text" + :on-change on-term-change + :auto-focus true + :placeholder (tr "ds.search.placeholder") + :value (or filtering "")}] + [:div.clear-search {:on-click on-clear} i/close]]]])) ;; --- Grid Item Thumbnail (mf/defc grid-item-thumbnail [{:keys [project] :as props}] [:div.grid-item-th - [:img.img-th {:src "/images/project-placeholder.svg" - :alt (tr "ds.project-thumbnail.alt")}]]) + [:img.img-th {:src "/images/project-placeholder.svg"}]]) ;; --- Grid Item (mf/defc grid-item {:wrap [mf/wrap-memo]} - [{:keys [project] :as props}] + [{:keys [file] :as props}] (let [local (mf/use-state {}) - on-navigate #(st/emit! (udp/go-to (:id project))) - delete #(st/emit! (udp/delete-project project)) - on-delete #(do - (dom/stop-propagation %) - (modal/show! confirm-dialog {:on-accept delete})) - on-blur #(let [target (dom/event->target %) - name (dom/get-value target) - id (:id project)] - (swap! local assoc :edition false) - (st/emit! (udp/rename-project id name))) - on-key-down #(when (kbd/enter? %) (on-blur %)) - on-edit #(do - (dom/stop-propagation %) - (dom/prevent-default %) - (swap! local assoc :edition true))] + on-navigate #(st/emit! (udp/go-to (:id file))) + ;; delete #(st/emit! (udp/delete-project project)) + ;; on-delete #(do + ;; (dom/stop-propagation %) + ;; (modal/show! confirm-dialog {:on-accept delete})) + ;; on-blur #(let [target (dom/event->target %) + ;; name (dom/get-value target) + ;; id (:id project)] + ;; (swap! local assoc :edition false) + ;; (st/emit! (udp/rename-project id name))) + ;; on-key-down #(when (kbd/enter? %) (on-blur %)) + ;; on-edit #(do + ;; (dom/stop-propagation %) + ;; (dom/prevent-default %) + ;; (swap! local assoc :edition true)) + ] [:div.grid-item.project-th {:on-click on-navigate} - [:& grid-item-thumbnail {:project project - :key (select-keys project [:id :page-id])}] + [:& grid-item-thumbnail {:file file}] [:div.item-info (if (:edition @local) [:input.element-name {:type "text" :auto-focus true - :on-key-down on-key-down - :on-blur on-blur - :on-click on-edit - :default-value (:name project)}] - [:h3 (:name project)]) + ;; :on-key-down on-key-down + ;; :on-blur on-blur + ;; :on-click on-edit + :default-value (:name file)}] + [:h3 (:name file)]) [: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-icon.pages i/page - [:span (:total-pages project)]] + #_[:span (:total-pages project)]] #_[:div.project-th-icon.comments i/chat [:span "0"]] [:div.project-th-icon.edit - {:on-click on-edit} + #_{:on-click on-edit} i/pencil] [:div.project-th-icon.delete - {:on-click on-delete} + #_{:on-click on-delete} i/trash]]])) ;; --- Grid (mf/defc grid - [{:keys [opts] :as props}] + [{:keys [opts files] :as props}] (let [order (:order opts :created) filter (:filter opts "") - projects (mf/deref projects-iref) - projects (->> (vals projects) - (filter-projects-by filter) - (sort-projects-by order)) + files (->> files + (filter-by filter) + (sort-by order)) on-click #(do (dom/prevent-default %) - (modal/show! create-project-dialog {}) - #_(udl/open! :create-project))] + #_(modal/show! create-project-dialog {}) + #_(udl/open! :create-project)) + ] [:section.dashboard-grid - [:h2 (tr "ds.project-title")] + [:h2 (tr "ds.projects.file-name")] [:div.dashboard-grid-content [:div.dashboard-grid-row - [:div.grid-item.add-project {:on-click on-click} - [:span (tr "ds.project-new")]] + [:div.grid-item.add-project #_{:on-click on-click} + [: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] - [:& 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 (mf/defc projects-page - [_] - (mf/use-effect #(st/emit! (udp/initialize))) - (let [opts (mf/deref opts-iref)] - [:section.dashboard-content - [:& menu {:opts opts}] - [:& grid {:opts opts}]])) + [{:keys [id] :as props}] + (mf/use-effect #(st/emit! udp/fetch-projects)) + (mf/use-effect {:fn #(st/emit! (udp/initialize id)) + :deps #js [id]}) + [:section.dashboard-content + [:& nav {:id id}] + [:& content {:id id}]]) diff --git a/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs b/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs index 9cd2b8540..dda2af45d 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs @@ -22,12 +22,9 @@ (s/def ::height ::fm/number-str) (s/def ::project-form - (s/keys :req-un [::name ::width ::height])) + (s/keys :req-un [::name])) -(def defaults - {:name "" - :width "1366" - :height "768"}) +(def defaults {:name ""}) ;; --- Create Project Form @@ -57,33 +54,6 @@ :on-blur (fm/on-input-blur form :name) :on-change (fm/on-input-change form :name) :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 [:input#project-btn.btn-primary {:value (tr "ds.go") diff --git a/frontend/src/uxbox/main/ui/workspace.cljs b/frontend/src/uxbox/main/ui/workspace.cljs index d217441b8..958c555e1 100644 --- a/frontend/src/uxbox/main/ui/workspace.cljs +++ b/frontend/src/uxbox/main/ui/workspace.cljs @@ -60,7 +60,7 @@ (scroll/scroll-to-point dom mouse-point scroll-position)))) (mf/defc workspace-content - [{:keys [layout page] :as params}] + [{:keys [layout page file] :as params}] (let [canvas (mf/use-ref nil) left-sidebar? (not (empty? (keep layout [:layers :sitemap :document-history]))) @@ -89,17 +89,26 @@ ;; Aside (when left-sidebar? - [:& left-sidebar {:page page :layout layout}]) + [:& left-sidebar {:file file :page page :layout layout}]) (when right-sidebar? [:& right-sidebar {:page page :layout layout}])])) (mf/defc workspace - [{:keys [page-id] :as props}] - (let [layout (mf/deref refs/workspace-layout) - flags (mf/deref refs/selected-flags) - page (mf/deref refs/workspace-page)] + [{:keys [file-id page-id] :as props}] - [:* + (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] [:& header {:page page :flags flags}] @@ -107,17 +116,6 @@ [:& colorpalette]) (when (and layout page) - [:& workspace-content {:layout layout :page page}])])) - -(mf/defc workspace-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}]]) + [:& workspace-content {:layout layout + :file file + :page page}])])) diff --git a/frontend/src/uxbox/main/ui/workspace/header.cljs b/frontend/src/uxbox/main/ui/workspace/header.cljs index a00af580b..6c402e917 100644 --- a/frontend/src/uxbox/main/ui/workspace/header.cljs +++ b/frontend/src/uxbox/main/ui/workspace/header.cljs @@ -52,7 +52,7 @@ ] [:header#workspace-bar.workspace-bar [: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 {:alt (tr "header.sitemap") :class (when (contains? flags :sitemap) "selected") diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs index 831b7a038..bd8446f4c 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs @@ -20,13 +20,11 @@ (mf/defc left-sidebar {:wrap [mf/wrap-memo]} - [{:keys [layout page] :as props}] + [{:keys [layout page file] :as props}] [:aside.settings-bar.settings-bar-left [:div.settings-bar-inside (when (contains? layout :sitemap) - [:& sitemap-toolbox {:project-id (:project-id page) - :current-page-id (:id page) - :page page}]) + [:& sitemap-toolbox {:file file :page page}]) (when (contains? layout :document-history) [:& history-toolbox]) (when (contains? layout :layers) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs index b8ebafe2a..5af0c0725 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs @@ -29,39 +29,36 @@ (mf/defc page-item [{:keys [page index deletable? selected?] :as props}] - (letfn [(on-edit [event] - (modal/show! page-form-dialog {:page page})) - (delete [] - (st/emit! (dw/delete-page (:id page)))) - (on-delete [event] - (dom/prevent-default event) - (dom/stop-propagation event) - (modal/show! confirm-dialog {:on-accept delete})) - (on-drop [item monitor] - (prn "TODO")) - (on-hover [item monitor] - (st/emit! (dw/change-page-order {:id (:id item) - :index index})))] - (let [[dprops ref] (use-sortable {:type "page-item" - :data {:id (:id page) - :index index} - :on-hover on-hover - :on-drop on-drop})] - [:li {:ref ref :class (classnames :selected selected?)} - [:div.element-list-body - {:class (classnames :selected selected? - :dragging (:dragging? dprops)) - :on-click #(st/emit! (rt/nav :workspace/page {:project (:project-id page) - :page (:id page)})) - :on-double-click #(dom/stop-propagation %) - :draggable true} + (let [on-edit #(modal/show! page-form-dialog {:page page}) + 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})) + on-drop #(do (prn "TODO")) + on-hover #(st/emit! (dw/change-page-order {:id (:id page) + :index index})) - [:div.page-icon i/page] - [:span (:name page)] - [:div.page-actions {} - [:a {:on-click on-edit} i/pencil] - (when deletable? - [:a {:on-click on-delete} i/trash])]]]))) + navigate-fn #(st/emit! (dw/go-to-page (:id page))) + [dprops ref] (use-sortable {:type "page-item" + :data {:id (:id page) + :index index} + :on-hover on-hover + :on-drop on-drop})] + [:li {:ref ref :class (classnames :selected selected?)} + [:div.element-list-body + {:class (classnames :selected selected? + :dragging (:dragging? dprops)) + :on-click navigate-fn + :on-double-click #(dom/stop-propagation %) + :draggable true} + + [:div.page-icon i/page] + [:span (:name page)] + [:div.page-actions {} + [:a {:on-click on-edit} i/pencil] + (when deletable? + [:a {:on-click on-delete} i/trash])]]])) ;; --- Page Item Wrapper @@ -84,8 +81,8 @@ ;; --- Pages List (mf/defc pages-list - [{:keys [project current-page-id] :as props}] - (let [pages (enumerate (:pages project)) + [{:keys [file current-page] :as props}] + (let [pages (enumerate (:pages file)) deletable? (> (count pages) 1)] [:ul.element-list (for [[index page-id] pages] @@ -93,31 +90,22 @@ {:page-id page-id :index index :deletable? deletable? - :selected? (= page-id current-page-id) + :selected? (= page-id (:id current-page)) :key page-id}])])) ;; --- 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 - [{:keys [project-id current-page-id] :as props}] - (let [project (mf/deref workspace-project) - create #(modal/show! page-form-dialog {:page {:project-id project-id}}) - close #(st/emit! (dw/toggle-flag :sitemap))] + [{:keys [file page] :as props}] + (let [create-fn #(modal/show! page-form-dialog {:page {:file-id (:file-id page)}}) + close-fn #(st/emit! (dw/toggle-flag :sitemap))] [:div.sitemap.tool-window [:div.tool-window-bar [:div.tool-window-icon i/project-tree] [: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.project-title - [:span (:name project)] - [:div.add-page {:on-click create} i/close]] - [:& pages-list {:project project - :current-page-id current-page-id}]]])) + #_[:span (:name project)] + [:div.add-page {:on-click create-fn} i/close]] + [:& pages-list {:file file :current-page page}]]]))