penpot/frontend/src/uxbox/main/data/dashboard.cljs
2020-03-18 09:31:03 +01:00

344 lines
8.9 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.dashboard
(: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]))
;; --- Specs
(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)
(s/def ::team
(s/keys :req-un [::id
::name
::created-at
::modified-at]))
(s/def ::project
(s/keys ::req-un [::id
::name
::team-id
::version
::profile-id
::created-at
::modified-at]))
(s/def ::file
(s/keys :req-un [::id
::name
::created-at
::modified-at
::project-id]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Initialization
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare search-files)
(defn initialize-search
[team-id search-term]
(ptk/reify ::initialize-search
ptk/UpdateEvent
(update [_ state]
(update state :dashboard-local assoc
:search-result nil))
ptk/WatchEvent
(watch [_ state stream]
(let [local (:dashboard-local state)]
(when-not (empty? search-term)
(rx/of (search-files team-id search-term)))))))
(declare fetch-files)
(declare fetch-projects)
(declare fetch-recent-files)
(def initialize-drafts
(ptk/reify ::initialize-drafts
ptk/UpdateEvent
(update [_ state]
(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))
(fetch-projects (:team-id local)))))))
(defn initialize-recent
[team-id]
(us/verify ::us/uuid team-id)
(ptk/reify ::initialize-team
ptk/UpdateEvent
(update [_ state]
(update state :dashboard-local assoc
:project-id nil
:team-id team-id))
ptk/WatchEvent
(watch [_ state stream]
(let [local (:dashboard-local state)]
(rx/of (fetch-projects (:team-id local))
(fetch-recent-files (:team-id local)))))))
(defn initialize-project
[team-id project-id]
(us/verify ::us/uuid team-id)
(us/verify ::us/uuid project-id)
(ptk/reify ::initialize-project
ptk/UpdateEvent
(update [_ state]
(update state :dashboard-local assoc
:team-id team-id
:project-id project-id))
ptk/WatchEvent
(watch [_ state stream]
(let [local (:dashboard-local state)]
(rx/of (fetch-files (:project-id local))
(fetch-projects (:team-id local)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Fetching
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Fetch Projects
(declare projects-fetched)
(defn fetch-projects
[team-id]
(us/assert ::us/uuid team-id)
(ptk/reify ::fetch-projects
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :projects-by-team {:team-id team-id})
(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 #(assoc-in %1 [:projects (:id %2)] %2)]
(reduce assoc-project state projects)))))
;; --- Search Files
(declare files-searched)
(defn search-files
[team-id search-term]
(us/assert ::us/uuid team-id)
(us/assert ::us/string search-term)
(ptk/reify ::search-files
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :search-files {:team-id team-id :search-term search-term})
(rx/map files-searched)))))
(defn files-searched
[files]
(us/verify (s/every ::file) files)
(ptk/reify ::files-searched
ptk/UpdateEvent
(update [_ state]
(update state :dashboard-local assoc
:search-result files))))
;; --- 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)))))
;; --- Fetch recent files
(declare recent-files-fetched)
(defn fetch-recent-files
[team-id]
(ptk/reify ::fetch-recent-files
ptk/WatchEvent
(watch [_ state stream]
(let [params {:team-id team-id}]
(->> (rp/query :recent-files params)
(rx/map recent-files-fetched))))))
(defn recent-files-fetched
[recent-files]
(ptk/reify ::recent-files-fetched
ptk/UpdateEvent
(update [_ state]
(assoc state :recent-files recent-files))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Modification
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Create Project
(def create-project
(ptk/reify ::create-project
ptk/WatchEvent
(watch [this state stream]
(let [name (str "New Project " (gensym "p"))
team-id (get-in state [:dashboard-local :team-id])]
(->> (rp/mutation! :create-project {:name name :team-id team-id})
(rx/map (fn [data]
(projects-fetched [data]))))))))
;; --- 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 [local (:dashboard-local state)
params {:id id :name name}]
;; NOTE: this is a temporal (quick & dirty) solution for
;; refreshing the results; we need to think in a better way to
;; do it instead of a simple and complete data refresh.
(->> (rp/mutation :rename-file params)
(rx/map (fn [_] (initialize-recent (:team-id local)))))))))
;; --- Create File
(declare file-created)
(defn create-file
[project-id]
(ptk/reify ::create-draft-file
ptk/WatchEvent
(watch [_ state stream]
(let [name (str "New File " (gensym "p"))
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]
(-> state
(update :files assoc (:id data) data)
(update-in [:recent-files (:project-id data)] conj 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})))))