penpot/frontend/src/app/main/data/dashboard.cljs
2020-09-28 12:28:29 +02:00

336 lines
9.5 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 app.main.data.dashboard
(:require
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.main.repo :as rp]
[app.util.router :as rt]
[app.util.time :as dt]
[app.util.timers :as ts]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[potok.core :as ptk]))
;; --- 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 ::is-pinned ::us/boolean)
(s/def ::team
(s/keys :req-un [::id
::name
::created-at
::modified-at]))
(s/def ::project
(s/keys ::req-un [::id
::name
::team-id
::profile-id
::created-at
::modified-at
::is-pinned]))
(s/def ::file
(s/keys :req-un [::id
::name
::created-at
::modified-at
::project-id]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Fetching
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Fetch Team
(defn fetch-team
[{:keys [id] :as params}]
(letfn [(fetched [team state]
(update state :teams assoc id team))]
(ptk/reify ::fetch-team
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :team params)
(rx/map #(partial fetched %)))))))
;; --- Fetch Projects
(defn fetch-projects
[{:keys [team-id] :as params}]
(us/assert ::us/uuid team-id)
(letfn [(fetched [projects state]
(assoc-in state [:projects team-id] (d/index-by :id projects)))]
(ptk/reify ::fetch-projects
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :projects {:team-id team-id})
(rx/map #(partial fetched %)))))))
;; --- Search Files
(s/def :internal.event.search-files/team-id ::us/uuid)
(s/def :internal.event.search-files/search-term (s/nilable ::us/string))
(s/def :internal.event/search-files
(s/keys :req-un [:internal.event.search-files/search-term
:internal.event.search-files/team-id]))
(defn search-files
[params]
(us/assert :internal.event/search-files params)
(letfn [(fetched [result state]
(update state :dashboard-local
assoc :search-result result))]
(ptk/reify ::search-files
ptk/UpdateEvent
(update [_ state]
(update state :dashboard-local
assoc :search-result nil))
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :search-files params)
(rx/map #(partial fetched %)))))))
;; --- Fetch Files
(defn fetch-files
[{:keys [project-id] :as params}]
(us/assert ::us/uuid project-id)
(letfn [(fetched [files state]
(update state :files assoc project-id (d/index-by :id files)))]
(ptk/reify ::fetch-files
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :files params)
(rx/map #(partial fetched %)))))))
;; --- Fetch Shared Files
(defn fetch-shared-files
[{:keys [team-id] :as params}]
(us/assert ::us/uuid team-id)
(letfn [(fetched [files state]
(update state :shared-files assoc team-id (d/index-by :id files)))]
(ptk/reify ::fetch-shared-files
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :shared-files {:team-id team-id})
(rx/map #(partial fetched %)))))))
;; --- Fetch recent files
(declare recent-files-fetched)
(defn fetch-recent-files
[{:keys [team-id] :as params}]
(us/assert ::us/uuid team-id)
(ptk/reify ::fetch-recent-files
ptk/WatchEvent
(watch [_ state stream]
(let [params {:team-id team-id}]
(->> (rp/query :recent-files params)
(rx/map recent-files-fetched))))))
(defn recent-files-fetched
[files]
(ptk/reify ::recent-files-fetched
ptk/UpdateEvent
(update [_ state]
(reduce-kv (fn [state project-id files]
(-> state
(update-in [:files project-id] merge (d/index-by :id files))
(assoc-in [:recent-files project-id] (into #{} (map :id) files))))
state
(group-by :project-id files)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Modification
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Create Project
(defn create-team
[{:keys [name] :as params}]
(us/assert string? name)
(ptk/reify ::create-team
ptk/WatchEvent
(watch [_ state stream]
(let [{:keys [on-success on-error]
:or {on-success identity
on-error identity}} (meta params)]
(->> (rp/mutation! :create-team {:name name})
(rx/tap on-success)
(rx/catch on-error))))))
(defn create-project
[{:keys [team-id] :as params}]
(us/assert ::us/uuid team-id)
(letfn [(created [project state]
(-> state
(assoc-in [:projects team-id (:id project)] project)
(assoc-in [:dashboard-local :project-for-edit] (:id project))))]
(ptk/reify ::create-project
ptk/WatchEvent
(watch [_ state stream]
(let [name (name (gensym "New Project "))
{:keys [on-success on-error]
:or {on-success identity
on-error identity}} (meta params)]
(->> (rp/mutation! :create-project {:name name :team-id team-id})
(rx/tap on-success)
(rx/map #(partial created %))
(rx/catch on-error)))))))
(def clear-project-for-edit
(ptk/reify ::clear-project-for-edit
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:dashboard-local :project-for-edit] nil))))
(defn toggle-project-pin
[{:keys [id is-pinned team-id] :as params}]
(us/assert ::project params)
(ptk/reify ::toggle-project-pin
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:projects team-id id :is-pinned] (not is-pinned)))
ptk/WatchEvent
(watch [_ state stream]
(let [project (get-in state [:projects team-id id])
params (select-keys project [:id :is-pinned :team-id])]
(->> (rp/mutation :update-project-pin params)
(rx/ignore))))))
;; --- Rename Project
(defn rename-project
[{:keys [id name team-id] :as params}]
(us/assert ::project params)
(ptk/reify ::rename-project
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:projects team-id 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
[{:keys [id team-id] :as params}]
(us/assert ::project params)
(ptk/reify ::delete-project
ptk/UpdateEvent
(update [_ state]
(update-in state [:projects team-id] dissoc id))
ptk/WatchEvent
(watch [_ state s]
(->> (rp/mutation :delete-project {:id id})
(rx/ignore)))))
;; --- Delete File (by id)
(defn delete-file
[{:keys [id project-id] :as params}]
(us/assert ::file params)
(ptk/reify ::delete-file
ptk/UpdateEvent
(update [_ state]
(-> state
(update-in [:files project-id] dissoc id)
(update-in [:recent-files project-id] (fnil disj #{}) id)))
ptk/WatchEvent
(watch [_ state s]
(->> (rp/mutation :delete-file {:id id})
(rx/ignore)))))
;; --- Rename File
(defn rename-file
[{:keys [id name project-id] :as params}]
(us/assert ::file params)
(ptk/reify ::rename-file
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:files project-id id :name] name))
ptk/WatchEvent
(watch [_ state stream]
(let [params (select-keys params [:id :name])]
(->> (rp/mutation :rename-file params)
(rx/ignore))))))
;; --- Set File shared
(defn set-file-shared
[id is-shared]
{:pre [(uuid? id) (boolean? is-shared)]}
(ptk/reify ::set-file-shared
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:files id :is-shared] is-shared))
ptk/WatchEvent
(watch [_ state stream]
(let [params {:id id :is-shared is-shared}]
(->> (rp/mutation :set-file-shared params)
(rx/ignore))))))
;; --- Create File
(declare file-created)
(defn create-file
[{:keys [project-id] :as params}]
(us/assert ::us/uuid project-id)
(ptk/reify ::create-file
ptk/WatchEvent
(watch [_ state stream]
(let [{:keys [on-success on-error]
:or {on-success identity
on-error identity}} (meta params)
name (name (gensym "New File "))
params (assoc params :name name)]
(->> (rp/mutation! :create-file params)
(rx/tap on-success)
(rx/map file-created)
(rx/catch on-error))))))
(defn file-created
[{:keys [project-id id] :as file}]
(us/verify ::file file)
(ptk/reify ::file-created
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:files project-id id] file)
(update-in [:recent-files project-id] (fnil conj #{}) id)))))