♻️ Big refactor of the default data model.

Introduce teams.
This commit is contained in:
Andrey Antukh 2020-02-17 09:49:04 +01:00
parent 6379c62e37
commit 7a5145fa37
65 changed files with 4529 additions and 3005 deletions

View file

@ -58,7 +58,7 @@
(st/emit! #(assoc % :router router))
(add-watch html-history/path ::main #(on-navigate router %4))
(when (:auth storage)
(when (:profile storage)
(st/emit! udu/fetch-profile))
(mf/mount (mf/element ui/app) (dom/get-element "app"))

View file

@ -30,14 +30,9 @@
(defn logged-in
[data]
(ptk/reify ::logged-in
ptk/UpdateEvent
(update [this state]
(assoc state :auth data))
ptk/WatchEvent
(watch [this state s]
(swap! storage assoc :auth data)
(rx/of du/fetch-profile
(watch [this state stream]
(rx/of (du/profile-fetched data)
(rt/navigate :dashboard-projects)))))
;; --- Login

View file

@ -5,49 +5,222 @@
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.dashboard
(:require [beicon.core :as rx]
[uxbox.util.uuid :as uuid]
[potok.core :as ptk]
[uxbox.util.router :as r]
[uxbox.main.store :as st]
[uxbox.main.repo :as rp]
[uxbox.main.data.projects :as dp]
[uxbox.main.data.colors :as dc]
[uxbox.main.data.images :as di]
[uxbox.util.data :refer (deep-merge)]))
(:require
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[potok.core :as ptk]
[uxbox.common.data :as d]
[uxbox.common.pages :as cp]
[uxbox.common.spec :as us]
[uxbox.main.repo :as rp]
[uxbox.util.router :as rt]
[uxbox.util.time :as dt]
[uxbox.util.timers :as ts]
[uxbox.util.uuid :as uuid]))
;; --- Events
;; --- Specs
(defrecord InitializeDashboard [section]
ptk/UpdateEvent
(update [_ state]
(update state :dashboard assoc
:section section
:collection-type :builtin
:collection-id 1)))
(s/def ::id ::us/uuid)
(s/def ::name string?)
(s/def ::team-id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::project-id ::us/uuid)
(s/def ::created-at ::us/inst)
(s/def ::modified-at ::us/inst)
(defn initialize
[section]
(InitializeDashboard. section))
(s/def ::team
(s/keys :req-un [::id
::name
::created-at
::modified-at]))
(defn set-collection-type
[type]
{:pre [(contains? #{:builtin :own} type)]}
(letfn [(select-first [state]
(if (= type :builtin)
(assoc-in state [:dashboard :collection-id] 1)
(let [colls (sort-by :id (vals (:colors-by-id state)))]
(assoc-in state [:dashboard :collection-id] (:id (first colls))))))]
(reify
ptk/UpdateEvent
(update [_ state]
(as-> state $
(assoc-in $ [:dashboard :collection-type] type)
(select-first $))))))
(s/def ::project
(s/keys ::req-un [::id
::name
::team-id
::version
::profile-id
::created-at
::modified-at]))
(defn set-collection
[id]
(reify
(s/def ::file
(s/keys :req-un [::id
::name
::created-at
::modified-at
::project-id]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Initialization
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare fetch-files)
(def initialize-drafts
(ptk/reify ::initialize
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:dashboard :collection-id] id))))
(let [profile (:profile state)]
(update state :dashboard-local assoc
:team-id (:default-team-id profile)
:project-id (:default-project-id profile))))
ptk/WatchEvent
(watch [_ state stream]
(let [local (:dashboard-local state)]
(rx/of (fetch-files (:project-id local)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Fetching
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Fetch Projects
(declare projects-fetched)
(def fetch-projects
(ptk/reify ::fetch-projects
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :projects)
(rx/map projects-fetched)))))
(defn projects-fetched
[projects]
(us/verify (s/every ::project) projects)
(ptk/reify ::projects-fetched
ptk/UpdateEvent
(update [_ state]
(let [assoc-project #(update-in %1 [:projects (:id %2)] merge %2)]
(reduce assoc-project state projects)))))
;; --- Fetch Files
(declare files-fetched)
(defn fetch-files
[project-id]
(ptk/reify ::fetch-files
ptk/WatchEvent
(watch [_ state stream]
(let [params {:project-id project-id}]
(->> (rp/query :files params)
(rx/map files-fetched))))))
(defn files-fetched
[files]
(us/verify (s/every ::file) files)
(ptk/reify ::files-fetched
ptk/UpdateEvent
(update [_ state]
(let [state (dissoc state :files)
files (d/index-by :id files)]
(assoc state :files files)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Modification
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Rename Project
(defn rename-project
[id name]
{:pre [(uuid? id) (string? name)]}
(ptk/reify ::rename-project
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:projects id :name] name))
ptk/WatchEvent
(watch [_ state stream]
(let [params {:id id :name name}]
(->> (rp/mutation :rename-project params)
(rx/ignore))))))
;; --- Delete Project (by id)
(defn delete-project
[id]
(us/verify ::us/uuid id)
(ptk/reify ::delete-project
ptk/UpdateEvent
(update [_ state]
(update state :projects dissoc id))
ptk/WatchEvent
(watch [_ state s]
(->> (rp/mutation :delete-project {:id id})
(rx/ignore)))))
;; --- Delete File (by id)
(defn delete-file
[id]
(us/verify ::us/uuid id)
(ptk/reify ::delete-file
ptk/UpdateEvent
(update [_ state]
(update state :files dissoc id))
ptk/WatchEvent
(watch [_ state s]
(->> (rp/mutation :delete-file {:id id})
(rx/ignore)))))
;; --- Rename Project
(defn rename-file
[id name]
{:pre [(uuid? id) (string? name)]}
(ptk/reify ::rename-file
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:files id :name] name))
ptk/WatchEvent
(watch [_ state stream]
(let [params {:id id :name name}]
(->> (rp/mutation :rename-file params)
(rx/ignore))))))
;; --- Create File
(declare file-created)
(def create-file
(ptk/reify ::create-draft-file
ptk/WatchEvent
(watch [_ state stream]
(let [name (str "New File " (gensym "p"))
project-id (get-in state [:dashboard-local :project-id])
params {:name name :project-id project-id}]
(->> (rp/mutation! :create-file params)
(rx/map file-created))))))
(defn file-created
[data]
(us/verify ::file data)
(ptk/reify ::create-draft-file
ptk/UpdateEvent
(update [this state]
(update state :files assoc (:id data) data))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; UI State Handling
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Update Opts (Filtering & Ordering)
(defn update-opts
[& {:keys [order filter] :as opts}]
(ptk/reify ::update-opts
ptk/UpdateEvent
(update [_ state]
(update state :dashboard-local merge
(when order {:order order})
(when filter {:filter filter})))))

View file

@ -79,7 +79,7 @@
(ptk/reify ::fetch-collections
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query! :images-collections)
(->> (rp/query! :image-collections)
(rx/map collections-fetched)))))
@ -108,7 +108,7 @@
ptk/WatchEvent
(watch [_ state s]
(let [data {:name (tr "ds.default-library-title" (gensym "c"))}]
(->> (rp/mutation! :create-images-collection data)
(->> (rp/mutation! :create-image-collection data)
(rx/map collection-created))))))
;; --- Collection Created
@ -134,7 +134,7 @@
ptk/WatchEvent
(watch [_ state s]
(let [params {:id id :name name}]
(->> (rp/mutation! :rename-images-collection params)
(->> (rp/mutation! :rename-image-collection params)
(rx/ignore))))))
;; --- Delete Collection
@ -148,7 +148,7 @@
ptk/WatchEvent
(watch [_ state s]
(->> (rp/mutation! :delete-images-collection {:id id})
(->> (rp/mutation! :delete-image-collection {:id id})
(rx/tap on-success)
(rx/ignore)))))

View file

@ -4,6 +4,11 @@
;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; NOTE: this namespace is deprecated and will be removed when new
;; dashboard is implemented. Is just maintained as a temporal solution
;; for have the old dashboard code "working".
(ns uxbox.main.data.projects
(:require
[beicon.core :as rx]
@ -22,22 +27,16 @@
(s/def ::id ::us/uuid)
(s/def ::name string?)
(s/def ::user ::us/uuid)
(s/def ::type keyword?)
(s/def ::file-id ::us/uuid)
(s/def ::project-id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::project-id (s/nilable ::us/uuid))
(s/def ::created-at ::us/inst)
(s/def ::modified-at ::us/inst)
(s/def ::version ::us/number)
(s/def ::ordering ::us/number)
(s/def ::metadata (s/nilable ::cp/metadata))
(s/def ::data ::cp/data)
(s/def ::project
(s/keys ::req-un [::id
::name
::version
::user-id
::profile-id
::created-at
::modified-at]))
@ -48,43 +47,17 @@
::modified-at
::project-id]))
(s/def ::page
(s/keys :req-un [::id
::name
::file-id
::version
::created-at
::modified-at
::user-id
::ordering
::data]))
;; --- Helpers
(defn unpack-page
[state {:keys [id data] :as page}]
(-> state
(update :pages assoc id (dissoc page :data))
(update :pages-data assoc id data)))
(defn purge-page
"Remove page and all related stuff from the state."
[state id]
(if-let [file-id (get-in state [:pages id :file-id])]
(-> state
(update-in [:files file-id :pages] #(filterv (partial not= id) %))
(update-in [:workspace-file :pages] #(filterv (partial not= id) %))
(update :pages dissoc id)
(update :pages-data dissoc id))
state))
;; --- Initialize Dashboard
(declare fetch-projects)
(declare fetch-files)
(declare fetch-draft-files)
(declare initialized)
;; NOTE/WARN: this need to be refactored completly when new UI is
;; prototyped.
(defn initialize
[id]
(ptk/reify ::initialize
@ -95,7 +68,9 @@
ptk/WatchEvent
(watch [_ state stream]
(rx/merge
(rx/of (fetch-files id))
(if (nil? id)
(rx/of fetch-draft-files)
(rx/of (fetch-files id)))
(->> stream
(rx/filter (ptk/type? ::files-fetched))
(rx/take 1)
@ -121,6 +96,7 @@
(when filter {:filter filter})))))
;; --- Fetch Projects
(declare projects-fetched)
(def fetch-projects
@ -151,9 +127,16 @@
ptk/WatchEvent
(watch [_ state stream]
(let [params (if (nil? project-id) {} {:project-id project-id})]
(->> (rp/query :project-files params)
(->> (rp/query :files params)
(rx/map files-fetched))))))
(def fetch-draft-files
(ptk/reify ::fetch-draft-files
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :draft-files {})
(rx/map files-fetched)))))
;; --- Fetch File (by ID)
(defn fetch-file
@ -162,23 +145,11 @@
(ptk/reify ::fetch-file
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :project-file {:id id})
(->> (rp/query :file {:id id})
(rx/map #(files-fetched [%]))))))
;; --- Files Fetched
(defn files-fetched
[files]
(us/verify (s/every ::file) 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)))))
;; --- Create Project
(declare project-created)
@ -201,12 +172,31 @@
(watch [this state stream]
(let [name (str "New File " (gensym "p"))
params {:name name :project-id project-id}]
(->> (rp/mutation! :create-project-file params)
(->> (rp/mutation! :create-file params)
(rx/mapcat
(fn [data]
(rx/of (files-fetched [data])
#(update-in % [:dashboard-projects :files project-id] conj (:id data))))))))))
(declare file-created)
(def create-draft-file
(ptk/reify ::create-draft-file
ptk/WatchEvent
(watch [this state stream]
(let [name (str "New File " (gensym "p"))
params {:name name}]
(->> (rp/mutation! :create-draft-file params)
(rx/map file-created))))))
(defn file-created
[data]
(us/verify ::file data)
(ptk/reify ::create-draft-file
ptk/UpdateEvent
(update [this state]
(update state :files assoc (:id data) data))))
;; --- Rename Project
(defn rename-project
@ -250,7 +240,7 @@
ptk/WatchEvent
(watch [_ state s]
(->> (rp/mutation :delete-project-file {:id id})
(->> (rp/mutation :delete-file {:id id})
(rx/ignore)))))
;; --- Rename Project
@ -266,7 +256,7 @@
ptk/WatchEvent
(watch [_ state stream]
(let [params {:id id :name name}]
(->> (rp/mutation :rename-project-file params)
(->> (rp/mutation :rename-file params)
(rx/ignore))))))
;; --- Go To Project
@ -291,178 +281,3 @@
(if (nil? id)
(rx/of (rt/nav :dashboard-projects {} {}))
(rx/of (rt/nav :dashboard-projects {} {:project-id (str id)}))))))
;; --- Fetch Pages (by File ID)
(declare pages-fetched)
(defn fetch-pages
[file-id]
(us/verify ::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
[pages]
(us/verify (s/every ::page) pages)
(ptk/reify ::pages-fetched
IDeref
(-deref [_] pages)
ptk/UpdateEvent
(update [_ state]
(reduce unpack-page state pages))))
;; --- Fetch Page (By ID)
(declare page-fetched)
(defn fetch-page
"Fetch page by id."
[id]
(us/verify ::us/uuid id)
(reify
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query :project-page {:id id})
(rx/map page-fetched)))))
;; --- Page Fetched
(defn page-fetched
[data]
(us/verify ::page data)
(ptk/reify ::page-fetched
IDeref
(-deref [_] data)
ptk/UpdateEvent
(update [_ state]
(unpack-page state data))))
;; --- Create Page
(declare page-created)
(def create-empty-page
(ptk/reify ::create-empty-page
ptk/WatchEvent
(watch [this state stream]
(let [file-id (get-in state [:workspace-page :file-id])
name (str "Page " (gensym "p"))
ordering (count (get-in state [:files file-id :pages]))
params {:name name
:file-id file-id
:ordering ordering
:data cp/default-page-data}]
(->> (rp/mutation :create-project-page params)
(rx/map page-created))))))
;; --- Page Created
(defn page-created
[{:keys [id file-id] :as page}]
(us/verify ::page page)
(ptk/reify ::page-created
cljs.core/IDeref
(-deref [_] page)
ptk/UpdateEvent
(update [_ state]
(let [data (:data page)
page (dissoc page :data)]
(-> state
(update-in [:workspace-file :pages] (fnil conj []) id)
(update :pages assoc id page)
(update :pages-data assoc id data))))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (fetch-file file-id)))))
;; --- Rename Page
(s/def ::rename-page
(s/keys :req-un [::id ::name]))
(defn rename-page
[id name]
(us/verify ::us/uuid id)
(us/verify string? name)
(ptk/reify ::rename-page
ptk/UpdateEvent
(update [_ state]
(let [pid (get-in state [:workspace-page :id])
state (assoc-in state [:pages id :name] name)]
(cond-> state
(= pid id) (assoc-in [:workspace-page :name] name))))
ptk/WatchEvent
(watch [_ state stream]
(let [params {:id id :name name}]
(->> (rp/mutation :rename-project-page params)
(rx/map #(ptk/data-event ::page-renamed params)))))))
;; --- Delete Page (by ID)
(defn delete-page
[id]
{:pre [(uuid? id)]}
(reify
ptk/UpdateEvent
(update [_ state]
(purge-page state id))
ptk/WatchEvent
(watch [_ state s]
(let [page (:workspace-page state)]
(rx/merge
(->> (rp/mutation :delete-project-page {:id id})
(rx/flat-map (fn [_]
(if (= id (:id page))
(rx/of (go-to (:file-id page)))
(rx/empty))))))))))
;; --- Persist Page
(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-project-page-data 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}]
(us/verify ::page 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))))))

View file

@ -58,12 +58,12 @@
(declare handle-who)
(declare handle-pointer-update)
(declare handle-pointer-send)
(declare handle-page-snapshot)
(declare handle-page-change)
(declare shapes-changes-commited)
(declare commit-changes)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Websockets Events
;; Workspace WebSocket
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Initialize WebSocket
@ -93,7 +93,7 @@
(case type
:who (handle-who msg)
:pointer-update (handle-pointer-update msg)
:page-snapshot (handle-page-snapshot msg)
:page-change (handle-page-change msg)
::unknown))))
(->> stream
@ -160,11 +160,12 @@
:y (:y point)}]
(ws/-send ws (t/encode msg))))))
(defn handle-page-snapshot
[{:keys [user-id page-id version operations] :as msg}]
(ptk/reify ::handle-page-snapshot
(defn handle-page-change
[{:keys [profile-id page-id revn operations] :as msg}]
(ptk/reify ::handle-page-change
ptk/WatchEvent
(watch [_ state stream]
(prn "handle-page-change")
(let [page-id' (get-in state [:workspace-page :id])]
(when (= page-id page-id')
(rx/of (shapes-changes-commited msg)))))))
@ -254,9 +255,9 @@
;; (update [_ state]
;; (update :workspace-local dissoc :undo-index))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; General workspace events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Workspace Initialization
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Initialize Workspace
@ -275,6 +276,9 @@
(declare initialize-layout)
(declare initialize-page)
(declare initialize-file)
(declare fetch-file-with-users)
(declare fetch-pages)
(declare fetch-page)
(defn initialize
"Initialize the workspace state."
@ -286,21 +290,31 @@
(watch [_ state stream]
(let [file (:workspace-file state)]
(if (not= (:id file) file-id)
(do
;; (reset! st/loader true)
(rx/merge
(rx/of (fetch-file-with-users file-id)
(fetch-pages file-id)
(initialize-layout file-id)
(fetch-images file-id))
(->> (rx/zip (rx/filter (ptk/type? ::pages-fetched) stream)
(rx/filter (ptk/type? ::file-fetched) stream))
(rx/take 1)
(rx/do (fn [_]
(uxbox.util.timers/schedule 500 #(reset! st/loader false))))
(rx/mapcat (fn [_]
(rx/of (initialize-file file-id)
(initialize-page page-id)
#_(initialize-alignment page-id)))))))
(rx/merge
(rx/of (dp/fetch-file file-id)
(dp/fetch-pages file-id)
(initialize-layout file-id)
(fetch-users file-id)
(fetch-images file-id))
(->> (rx/zip (rx/filter (ptk/type? ::dp/pages-fetched) stream)
(rx/filter (ptk/type? ::dp/files-fetched) stream))
(rx/of (fetch-page page-id))
(->> stream
(rx/filter (ptk/type? ::pages-fetched))
(rx/take 1)
(rx/do #(reset! st/loader false))
(rx/mapcat #(rx/of (initialize-file file-id)
(initialize-page page-id)
#_(initialize-alignment page-id)))))
(rx/of (initialize-file file-id)
(initialize-page page-id)))))))
(rx/merge-map (fn [_]
(rx/of (initialize-file file-id)
(initialize-page page-id)))))))))))
(defn- initialize-layout
[file-id]
@ -351,9 +365,10 @@
(ptk/reify ::finalize
ptk/UpdateEvent
(update [_ state]
(dissoc state
:workspace-page
:workspace-data))))
state
#_(dissoc state
:workspace-page
:workspace-data))))
(def diff-and-commit-changes
(ptk/reify ::diff-and-commit-changes
@ -379,17 +394,69 @@
(when-not (empty? changes)
(rx/of (commit-changes changes)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Fetching & Uploading
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Specs
(s/def ::id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::name string?)
(s/def ::type keyword?)
(s/def ::file-id ::us/uuid)
(s/def ::created-at ::us/inst)
(s/def ::modified-at ::us/inst)
(s/def ::version ::us/integer)
(s/def ::revn ::us/integer)
(s/def ::ordering ::us/integer)
(s/def ::metadata (s/nilable ::cp/metadata))
(s/def ::data ::cp/data)
(s/def ::file ::dp/file)
(s/def ::page
(s/keys :req-un [::id
::name
::file-id
::version
::revn
::created-at
::modified-at
::ordering
::data]))
;; --- Fetch Workspace Users
(declare users-fetched)
(declare file-fetched)
(defn fetch-users
[file-id]
(ptk/reify ::fetch-users
(defn fetch-file-with-users
[id]
(us/verify ::us/uuid id)
(ptk/reify ::fetch-file-with-users
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :project-file-users {:file-id file-id})
(rx/map users-fetched)))))
(->> (rp/query :file-with-users {:id id})
(rx/merge-map (fn [result]
(rx/of (file-fetched (dissoc result :users))
(users-fetched (:users result)))))))))
(defn fetch-file
[id]
(us/verify ::us/uuid id)
(ptk/reify ::fetch-file
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :file {:id id})
(rx/map file-fetched)))))
(defn file-fetched
[{:keys [id] :as file}]
(us/verify ::file file)
(ptk/reify ::file-fetched
ptk/UpdateEvent
(update [_ state]
(update state :files assoc id file))))
(defn users-fetched
[users]
@ -402,6 +469,226 @@
users))))
;; --- Fetch Pages
(declare pages-fetched)
(declare unpack-page)
(defn fetch-pages
[file-id]
(us/verify ::us/uuid file-id)
(ptk/reify ::fetch-pages
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query :pages {:file-id file-id})
(rx/map pages-fetched)))))
(defn fetch-page
[page-id]
(us/verify ::us/uuid page-id)
(ptk/reify ::fetch-pages
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query :page {:id page-id})
(rx/map #(pages-fetched [%]))))))
(defn pages-fetched
[pages]
(us/verify (s/every ::page) pages)
(ptk/reify ::pages-fetched
IDeref
(-deref [_] pages)
ptk/UpdateEvent
(update [_ state]
(reduce unpack-page state pages))))
;; --- Page Crud
(declare page-created)
(def create-empty-page
(ptk/reify ::create-empty-page
ptk/WatchEvent
(watch [this state stream]
(let [file-id (get-in state [:workspace-page :file-id])
name (str "Page " (gensym "p"))
ordering (count (get-in state [:files file-id :pages]))
params {:name name
:file-id file-id
:ordering ordering
:data cp/default-page-data}]
(->> (rp/mutation :create-page params)
(rx/map page-created))))))
(defn page-created
[{:keys [id file-id] :as page}]
(us/verify ::page page)
(ptk/reify ::page-created
cljs.core/IDeref
(-deref [_] page)
ptk/UpdateEvent
(update [_ state]
(-> state
(update-in [:workspace-file :pages] (fnil conj []) id)
(unpack-page page)))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (fetch-file file-id)))))
(s/def ::rename-page
(s/keys :req-un [::id ::name]))
(defn rename-page
[id name]
(us/verify ::us/uuid id)
(us/verify string? name)
(ptk/reify ::rename-page
ptk/UpdateEvent
(update [_ state]
(let [pid (get-in state [:workspac-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]
(let [params {:id id :name name}]
(->> (rp/mutation :rename-page params)
(rx/map #(ptk/data-event ::page-renamed params)))))))
(declare purge-page)
(declare go-to-file)
(defn delete-page
[id]
{:pre [(uuid? id)]}
(reify
ptk/UpdateEvent
(update [_ state]
(purge-page state id))
ptk/WatchEvent
(watch [_ state s]
(let [page (:workspace-page state)]
(rx/merge
(->> (rp/mutation :delete-page {:id id})
(rx/flat-map (fn [_]
(if (= id (:id page))
(rx/of (go-to-file (:file-id page)))
(rx/empty))))))))))
;; --- Fetch Workspace Images
(declare images-fetched)
(defn fetch-images
[file-id]
(ptk/reify ::fetch-images
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :file-images {:file-id file-id})
(rx/map images-fetched)))))
(defn images-fetched
[images]
(ptk/reify ::images-fetched
ptk/UpdateEvent
(update [_ state]
(let [images (d/index-by :id images)]
(assoc state :workspace-images images)))))
;; --- Upload Image
(declare image-uploaded)
(def allowed-file-types #{"image/jpeg" "image/png"})
(defn upload-image
([file] (upload-image file identity))
([file on-uploaded]
(us/verify fn? on-uploaded)
(ptk/reify ::upload-image
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :uploading] true))
ptk/WatchEvent
(watch [_ state stream]
(let [allowed-file? #(contains? allowed-file-types (.-type %))
finalize-upload #(assoc-in % [:workspace-local :uploading] false)
file-id (get-in state [:workspace-page :file-id])
on-success #(do (st/emit! finalize-upload)
(on-uploaded %))
on-error #(do (st/emit! finalize-upload)
(rx/throw %))
prepare
(fn [file]
{:name (.-name file)
:file-id file-id
:content file})]
(->> (rx/of file)
(rx/filter allowed-file?)
(rx/map prepare)
(rx/mapcat #(rp/mutation! :upload-file-image %))
(rx/do on-success)
(rx/map image-uploaded)
(rx/catch on-error)))))))
(s/def ::id ::us/uuid)
(s/def ::name ::us/string)
(s/def ::width ::us/number)
(s/def ::height ::us/number)
(s/def ::mtype ::us/string)
(s/def ::uri ::us/string)
(s/def ::thumb-uri ::us/string)
(s/def ::image
(s/keys :req-un [::id
::name
::width
::height
::uri
::thumb-uri]))
(defn image-uploaded
[item]
(us/verify ::image item)
(ptk/reify ::image-created
ptk/UpdateEvent
(update [_ state]
(update state :workspace-images assoc (:id item) item))))
;; --- Helpers
(defn unpack-page
[state {:keys [id data] :as page}]
(-> state
(update :pages assoc id (dissoc page :data))
(update :pages-data assoc id data)))
(defn purge-page
"Remove page and all related stuff from the state."
[state id]
(if-let [file-id (get-in state [:pages id :file-id])]
(-> state
(update-in [:files file-id :pages] #(filterv (partial not= id) %))
(update-in [:workspace-file :pages] #(filterv (partial not= id) %))
(update :pages dissoc id)
(update :pages-data dissoc id))
state))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Workspace State Manipulation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Toggle layout flag
(defn toggle-layout-flag
@ -1052,23 +1339,23 @@
(watch [_ state stream]
(let [page (:workspace-page state)
params {:id (:id page)
:version (:version page)
:revn (:revn page)
:changes (vec changes)}]
(->> (rp/mutation :update-project-page params)
(->> (rp/mutation :update-page params)
(rx/map shapes-changes-commited))))))
(s/def ::shapes-changes-commited
(s/keys :req-un [::page-id ::version ::cp/changes]))
(s/keys :req-un [::page-id ::revn ::cp/changes]))
(defn shapes-changes-commited
[{:keys [page-id version changes] :as params}]
[{:keys [page-id revn changes] :as params}]
(us/verify ::shapes-changes-commited params)
(ptk/reify ::shapes-changes-commited
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-page :version] version)
(assoc-in [:pages page-id :version] version)
(assoc-in [:workspace-page :revn] revn)
(assoc-in [:pages page-id :revn] revn)
(update-in [:pages-data page-id] cp/process-changes changes)
(update :workspace-data cp/process-changes changes)))))
@ -1298,7 +1585,7 @@
(defn go-to-page
[page-id]
(us/verify ::us/uuid page-id)
(ptk/reify ::go-to
(ptk/reify ::go-to-page
ptk/WatchEvent
(watch [_ state stream]
(let [file-id (get-in state [:workspace-page :file-id])
@ -1306,93 +1593,16 @@
query-params {:page-id page-id}]
(rx/of (rt/nav :workspace path-params query-params))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Workspace Images
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Fetch Workspace Images
(declare images-fetched)
(defn fetch-images
(defn go-to-file
[file-id]
(ptk/reify ::fetch-images
(us/verify ::us/uuid file-id)
(ptk/reify ::go-to-file
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :project-file-images {:file-id file-id})
(rx/map images-fetched)))))
(defn images-fetched
[images]
(ptk/reify ::images-fetched
ptk/UpdateEvent
(update [_ state]
(let [images (d/index-by :id images)]
(assoc state :workspace-images images)))))
;; --- Upload Image
(declare image-uploaded)
(def allowed-file-types #{"image/jpeg" "image/png"})
(defn upload-image
([file] (upload-image file identity))
([file on-uploaded]
(us/verify fn? on-uploaded)
(ptk/reify ::upload-image
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :uploading] true))
ptk/WatchEvent
(watch [_ state stream]
(let [allowed-file? #(contains? allowed-file-types (.-type %))
finalize-upload #(assoc-in % [:workspace-local :uploading] false)
file-id (get-in state [:workspace-page :file-id])
on-success #(do (st/emit! finalize-upload)
(on-uploaded %))
on-error #(do (st/emit! finalize-upload)
(rx/throw %))
prepare
(fn [file]
{:name (.-name file)
:file-id file-id
:content file})]
(->> (rx/of file)
(rx/filter allowed-file?)
(rx/map prepare)
(rx/mapcat #(rp/mutation! :upload-project-file-image %))
(rx/do on-success)
(rx/map image-uploaded)
(rx/catch on-error)))))))
(s/def ::id ::us/uuid)
(s/def ::name ::us/string)
(s/def ::width ::us/number)
(s/def ::height ::us/number)
(s/def ::mtype ::us/string)
(s/def ::uri ::us/string)
(s/def ::thumb-uri ::us/string)
(s/def ::image
(s/keys :req-un [::id
::name
::width
::height
::uri
::thumb-uri]))
(defn image-uploaded
[item]
(us/verify ::image item)
(ptk/reify ::image-created
ptk/UpdateEvent
(update [_ state]
(update state :workspace-images assoc (:id item) item))))
(let [page-ids (get-in state [:files file-id :pages])
path-params {:file-id file-id}
query-params {:page-id (first page-ids)}]
(rx/of (rt/nav :workspace path-params query-params))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Page Changes Reactions

View file

@ -120,7 +120,7 @@
(seq params))
(send-mutation! id form)))
(defmethod mutation :upload-project-file-image
(defmethod mutation :upload-file-image
[id params]
(let [form (js/FormData.)]
(run! (fn [[key val]]

View file

@ -5,7 +5,6 @@
[uxbox.util.data :refer [parse-int uuid-str?]]
[uxbox.main.ui.dashboard.header :refer [header]]
[uxbox.main.ui.dashboard.projects :as projects]
;; [uxbox.main.ui.dashboard.elements :as elements]
[uxbox.main.ui.dashboard.icons :as icons]
[uxbox.main.ui.dashboard.images :as images]
[uxbox.main.ui.dashboard.colors :as colors]

View file

@ -2,6 +2,9 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
@ -14,6 +17,7 @@
[uxbox.builtins.icons :as i]
[uxbox.main.constants :as c]
[uxbox.main.data.projects :as udp]
[uxbox.main.data.dashboard :as dsh]
[uxbox.main.store :as st]
[uxbox.main.exports :as exports]
[uxbox.main.ui.modal :as modal]
@ -137,13 +141,12 @@
(sort-by order))
on-click #(do
(dom/prevent-default %)
(st/emit! (udp/create-file {:project-id id})))]
(st/emit! dsh/create-file))]
[:section.dashboard-grid
[:div.dashboard-grid-content
[:div.dashboard-grid-row
(when id
[:div.grid-item.add-project {:on-click on-click}
[:span (tr "ds.new-file")]])
[:div.grid-item.add-project {:on-click on-click}
[:span (tr "ds.new-file")]]
(for [item files]
[:& grid-item {:file item :key (:id item)}])]]]))
@ -195,16 +198,20 @@
:placeholder (tr "ds.search.placeholder")}]
[:div.clear-search i/close]]
[:ul.library-elements
[:li.recent-projects {:on-click #(st/emit! (udp/go-to-project nil))
:class-name (when (nil? id) "current")}
[:li.recent-projects #_{:on-click #(st/emit! (udp/go-to-project nil))
:class-name (when (nil? id) "current")}
[:span.element-title "Recent"]]
[:li.recent-projects {:on-click #(st/emit! (udp/go-to-project nil))
:class-name (when (nil? id) "current")}
[:span.element-title "Drafts"]]
[:div.projects-row
[:span "PROJECTS"]
[:a.add-project {:on-click #(st/emit! udp/create-project)}
[:span "PROJECTS/TEAMS TODO"]
#_[:a.add-project {:on-click #(st/emit! udp/create-project)}
i/close]]
(for [item projects]
#_(for [item projects]
[:& nav-item {:id (:id item)
:key (:id item)
:name (:name item)
@ -213,14 +220,9 @@
;; --- Component: Content
(def files-ref
(letfn [(selector [state]
(let [id (get-in state [:dashboard-projects :id])
ids (get-in state [:dashboard-projects :files id])
xf (comp (map #(get-in state [:files %]))
(remove nil?))]
(into [] xf ids)))]
(-> (l/lens selector)
(l/derive st/state))))
(-> (comp (l/key :files)
(l/lens vals))
(l/derive st/state)))
(mf/defc content
[{:keys [id] :as props}]
@ -233,8 +235,7 @@
(mf/defc projects-page
[{:keys [id] :as props}]
(mf/use-effect #(st/emit! udp/fetch-projects))
(mf/use-effect {:fn #(st/emit! (udp/initialize id))
(mf/use-effect {:fn #(st/emit! dsh/initialize-drafts)
:deps #js [id]})
[:section.dashboard-content
[:& nav {:id id}]

View file

@ -87,7 +87,7 @@
{:alt (tr "header.sitemap")
:class (when (contains? layout :sitemap) "selected")
:on-click #(st/emit! (dw/toggle-layout-flag :sitemap))}
[:span (:project-name file) " / " (:name file)]]
[:span (:name file)]]
[:& active-users]

View file

@ -11,7 +11,6 @@
[lentes.core :as l]
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.main.data.projects :as dp]
[uxbox.main.data.workspace :as dw]
[uxbox.main.store :as st]
[uxbox.main.refs :as refs]
@ -45,7 +44,7 @@
;; parent (.-parentNode parent)
name (dom/get-value target)]
;; (set! (.-draggable parent) true)
(st/emit! (dp/rename-page (:id page) name))
(st/emit! (dw/rename-page (:id page) name))
(swap! local assoc :edition false)))
on-key-down (fn [event]
@ -56,7 +55,7 @@
(kbd/esc? event)
(swap! local assoc :edition false)))
delete-fn #(st/emit! (dp/delete-page (:id page)))
delete-fn #(st/emit! (dw/delete-page (:id page)))
on-delete #(do
(dom/prevent-default %)
(dom/stop-propagation %)
@ -128,7 +127,7 @@
(mf/defc sitemap-toolbox
[{:keys [file page] :as props}]
(let [on-create-click #(st/emit! dp/create-empty-page)
(let [on-create-click #(st/emit! dw/create-empty-page)
locale (i18n/use-locale)]
[:div.sitemap.tool-window
[:div.tool-window-bar