mirror of
https://github.com/penpot/penpot.git
synced 2025-04-14 07:51:35 -05:00
♻️ Refactor dashboard state management.
Mainly for performance, also affects backend endpoints.
This commit is contained in:
parent
e7b3f12b71
commit
c70bc5baff
23 changed files with 1110 additions and 1007 deletions
|
@ -12,6 +12,7 @@
|
|||
[app.db :as db]
|
||||
[app.rpc.permissions :as perms]
|
||||
[app.rpc.queries.projects :as projects]
|
||||
[app.rpc.queries.teams :as teams]
|
||||
[app.util.blob :as blob]
|
||||
[app.util.services :as sv]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
@ -97,7 +98,13 @@
|
|||
ppr.is_owner = true or
|
||||
ppr.can_edit = true)
|
||||
)
|
||||
select distinct f.*
|
||||
select distinct
|
||||
f.id,
|
||||
f.project_id,
|
||||
f.created_at,
|
||||
f.modified_at,
|
||||
f.name,
|
||||
f.is_shared
|
||||
from file as f
|
||||
inner join projects as pr on (f.project_id = pr.id)
|
||||
where f.name ilike ('%' || ? || '%')
|
||||
|
@ -109,14 +116,14 @@
|
|||
|
||||
(sv/defmethod ::search-files
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id search-term] :as params}]
|
||||
(let [rows (db/exec! pool [sql:search-files
|
||||
profile-id team-id
|
||||
profile-id team-id
|
||||
search-term])]
|
||||
(into [] decode-row-xf rows)))
|
||||
(db/exec! pool [sql:search-files
|
||||
profile-id team-id
|
||||
profile-id team-id
|
||||
search-term]))
|
||||
|
||||
;; --- Query: Files
|
||||
|
||||
;; --- Query: Project Files
|
||||
;; DEPRECATED: should be removed probably on 1.6.x
|
||||
|
||||
(def ^:private sql:files
|
||||
"select f.*
|
||||
|
@ -136,6 +143,29 @@
|
|||
(into [] decode-row-xf (db/exec! conn [sql:files project-id]))))
|
||||
|
||||
|
||||
;; --- Query: Project Files
|
||||
|
||||
(def ^:private sql:project-files
|
||||
"select f.id,
|
||||
f.project_id,
|
||||
f.created_at,
|
||||
f.modified_at,
|
||||
f.name,
|
||||
f.is_shared
|
||||
from file as f
|
||||
where f.project_id = ?
|
||||
and f.deleted_at is null
|
||||
order by f.modified_at desc")
|
||||
|
||||
(s/def ::project-files
|
||||
(s/keys :req-un [::profile-id ::project-id]))
|
||||
|
||||
(sv/defmethod ::project-files
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
|
||||
(with-open [conn (db/open pool)]
|
||||
(projects/check-read-permissions! conn profile-id project-id)
|
||||
(db/exec! conn [sql:project-files project-id])))
|
||||
|
||||
;; --- Query: File (By ID)
|
||||
|
||||
(defn retrieve-file
|
||||
|
@ -154,17 +184,20 @@
|
|||
(retrieve-file conn id)))
|
||||
|
||||
(s/def ::page
|
||||
(s/keys :req-un [::profile-id ::id ::file-id]))
|
||||
(s/keys :req-un [::profile-id ::file-id]))
|
||||
|
||||
(sv/defmethod ::page
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id id]}]
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id]}]
|
||||
(db/with-atomic [conn pool]
|
||||
(check-edition-permissions! conn profile-id file-id)
|
||||
(let [file (retrieve-file conn file-id)]
|
||||
(get-in file [:data :pages-index id]))))
|
||||
(let [file (retrieve-file conn file-id)
|
||||
page-id (get-in file [:data :pages 0])]
|
||||
(get-in file [:data :pages-index page-id]))))
|
||||
|
||||
;; --- Query: Shared Library Files
|
||||
|
||||
;; DEPRECATED: and will be removed on 1.6.x
|
||||
|
||||
(def ^:private sql:shared-files
|
||||
"select f.*
|
||||
from file as f
|
||||
|
@ -183,11 +216,35 @@
|
|||
(into [] decode-row-xf (db/exec! pool [sql:shared-files team-id])))
|
||||
|
||||
|
||||
;; --- Query: Shared Library Files
|
||||
|
||||
(def ^:private sql:team-shared-files
|
||||
"select f.id,
|
||||
f.project_id,
|
||||
f.created_at,
|
||||
f.modified_at,
|
||||
f.name,
|
||||
f.is_shared
|
||||
from file as f
|
||||
inner join project as p on (p.id = f.project_id)
|
||||
where f.is_shared = true
|
||||
and f.deleted_at is null
|
||||
and p.deleted_at is null
|
||||
and p.team_id = ?
|
||||
order by f.modified_at desc")
|
||||
|
||||
(s/def ::team-shared-files
|
||||
(s/keys :req-un [::profile-id ::team-id]))
|
||||
|
||||
(sv/defmethod ::team-shared-files
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}]
|
||||
(db/exec! pool [sql:team-shared-files team-id]))
|
||||
|
||||
|
||||
;; --- Query: File Libraries used by a File
|
||||
|
||||
(def ^:private sql:file-libraries
|
||||
"select fl.*,
|
||||
? as is_indirect,
|
||||
flr.synced_at as synced_at
|
||||
from file as fl
|
||||
inner join file_library_rel as flr on (flr.library_file_id = fl.id)
|
||||
|
@ -196,22 +253,12 @@
|
|||
|
||||
(defn retrieve-file-libraries
|
||||
[conn is-indirect file-id]
|
||||
(let [direct-libraries
|
||||
(into [] decode-row-xf (db/exec! conn [sql:file-libraries is-indirect file-id]))
|
||||
|
||||
select-distinct
|
||||
(fn [used-libraries new-libraries]
|
||||
(remove (fn [new-library]
|
||||
(some #(= (:id %) (:id new-library)) used-libraries))
|
||||
new-libraries))]
|
||||
|
||||
(reduce (fn [used-libraries library]
|
||||
(concat used-libraries
|
||||
(select-distinct
|
||||
used-libraries
|
||||
(retrieve-file-libraries conn true (:id library)))))
|
||||
direct-libraries
|
||||
direct-libraries)))
|
||||
(let [libraries (->> (db/exec! conn [sql:file-libraries file-id])
|
||||
(map #(assoc :is-indirect is-indirect))
|
||||
(into #{} decode-row-xf))]
|
||||
(reduce #(into %1 (retrieve-file-libraries conn true %2))
|
||||
libraries
|
||||
(map :id libraries))))
|
||||
|
||||
(s/def ::file-libraries
|
||||
(s/keys :req-un [::profile-id ::file-id]))
|
||||
|
@ -222,31 +269,35 @@
|
|||
(check-edition-permissions! conn profile-id file-id)
|
||||
(retrieve-file-libraries conn false file-id)))
|
||||
|
||||
;; --- QUERY: team-recent-files
|
||||
|
||||
;; --- Query: Single File Library
|
||||
(def sql:team-recent-files
|
||||
"with recent_files as (
|
||||
select f.id,
|
||||
f.project_id,
|
||||
f.created_at,
|
||||
f.modified_at,
|
||||
f.name,
|
||||
f.is_shared,
|
||||
row_number() over w as row_num
|
||||
from file as f
|
||||
join project as p on (p.id = f.project_id)
|
||||
where p.team_id = ?
|
||||
and p.deleted_at is null
|
||||
and f.deleted_at is null
|
||||
window w as (partition by f.project_id order by f.modified_at desc)
|
||||
order by f.modified_at desc
|
||||
)
|
||||
select * from recent_files where row_num <= 10;")
|
||||
|
||||
;; TODO: this looks like is duplicate of `::file`
|
||||
(s/def ::team-recent-files
|
||||
(s/keys :req-un [::profile-id ::team-id]))
|
||||
|
||||
(def ^:private sql:file-library
|
||||
"select fl.*
|
||||
from file as fl
|
||||
where fl.id = ?")
|
||||
|
||||
(defn retrieve-file-library
|
||||
[conn file-id]
|
||||
(let [rows (db/exec! conn [sql:file-library file-id])]
|
||||
(when-not (seq rows)
|
||||
(ex/raise :type :not-found))
|
||||
(first (sequence decode-row-xf rows))))
|
||||
|
||||
(s/def ::file-library
|
||||
(s/keys :req-un [::profile-id ::file-id]))
|
||||
|
||||
(sv/defmethod ::file-library
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(check-edition-permissions! conn profile-id file-id) ;; TODO: this should check read permissions
|
||||
(retrieve-file-library conn file-id)))
|
||||
(sv/defmethod ::team-recent-files
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
|
||||
(with-open [conn (db/open pool)]
|
||||
(teams/check-read-permissions! conn profile-id team-id)
|
||||
(db/exec! conn [sql:team-recent-files team-id])))
|
||||
|
||||
|
||||
;; --- Helpers
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
[app.util.services :as sv]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
;; DEPRECATED: should be removed on 1.6.x
|
||||
|
||||
(def sql:recent-files
|
||||
"with recent_files as (
|
||||
select f.*, row_number() over w as row_num
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
(t/is (= (:id data) (:id result)))
|
||||
(t/is (= (:name data) (:name result))))))
|
||||
|
||||
(t/testing "query files"
|
||||
(t/testing "query files (deprecated)"
|
||||
(let [data {::th/type :files
|
||||
:project-id proj-id
|
||||
:profile-id (:id prof)}
|
||||
|
@ -67,6 +67,20 @@
|
|||
(t/is (= "new name" (get-in result [0 :name])))
|
||||
(t/is (= 1 (count (get-in result [0 :data :pages])))))))
|
||||
|
||||
(t/testing "query files"
|
||||
(let [data {::th/type :project-files
|
||||
:project-id proj-id
|
||||
:profile-id (:id prof)}
|
||||
out (th/query! data)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
|
||||
(let [result (:result out)]
|
||||
(t/is (= 1 (count result)))
|
||||
(t/is (= file-id (get-in result [0 :id])))
|
||||
(t/is (= "new name" (get-in result [0 :name]))))))
|
||||
|
||||
(t/testing "query single file without users"
|
||||
(let [data {::th/type :file
|
||||
:profile-id (:id prof)
|
||||
|
|
|
@ -54,146 +54,194 @@
|
|||
(s/def ::file
|
||||
(s/keys :req-un [::id
|
||||
::name
|
||||
::created-at
|
||||
::modified-at
|
||||
::project-id]))
|
||||
::project-id]
|
||||
:opt-un [::created-at
|
||||
::modified-at]))
|
||||
|
||||
(s/def ::set-of-uuid
|
||||
(s/every ::us/uuid :kind set?))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Fetching
|
||||
;; Initialization
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn fetch-team
|
||||
(declare fetch-projects)
|
||||
|
||||
(defn initialize
|
||||
[{:keys [id] :as params}]
|
||||
(letfn [(fetched [team state]
|
||||
(update state :teams assoc id team))]
|
||||
(ptk/reify ::fetch-team
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [profile (:profile state)]
|
||||
(->> (rp/query :team params)
|
||||
(rx/map #(partial fetched %))))))))
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::initialize
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [prev-team-id (:current-team-id state)]
|
||||
(cond-> state
|
||||
(not= prev-team-id id)
|
||||
(-> (assoc :current-team-id id)
|
||||
(dissoc :dashboard-files)
|
||||
(dissoc :dashboard-projects)
|
||||
(dissoc :dashboard-recent-files)
|
||||
(dissoc :dashboard-team-members)
|
||||
(dissoc :dashboard-team-stats)))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/merge
|
||||
(ptk/watch (fetch-projects) state stream)
|
||||
(ptk/watch (du/fetch-teams) state stream)
|
||||
(ptk/watch (du/fetch-users {:team-id id}) state stream)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Fetching (context aware: current team)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; --- EVENT: fetch-team-members
|
||||
|
||||
(defn team-members-fetched
|
||||
[members]
|
||||
(ptk/reify ::team-members-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :dashboard-team-members (d/index-by :id members)))))
|
||||
|
||||
(defn fetch-team-members
|
||||
[{:keys [id] :as params}]
|
||||
(us/assert ::us/uuid id)
|
||||
(letfn [(fetched [members state]
|
||||
(->> members
|
||||
(d/index-by :id)
|
||||
(assoc-in state [:team-members id])))]
|
||||
(ptk/reify ::fetch-team-members
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/query :team-members {:team-id id})
|
||||
(rx/map #(partial fetched %)))))))
|
||||
|
||||
(defn fetch-team-stats
|
||||
[{:keys [id] :as team}]
|
||||
(us/assert ::us/uuid id)
|
||||
[]
|
||||
(ptk/reify ::fetch-team-members
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [fetched #(assoc-in %2 [:team-stats id] %1)]
|
||||
(->> (rp/query :team-stats {:team-id id})
|
||||
(rx/map #(partial fetched %)))))))
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/query :team-members {:team-id team-id})
|
||||
(rx/map team-members-fetched))))))
|
||||
|
||||
(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 %)))))))
|
||||
;; --- EVENT: fetch-team-stats
|
||||
|
||||
(defn fetch-bundle
|
||||
[{:keys [id] :as params}]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::fetch-bundle
|
||||
(defn team-stats-fetched
|
||||
[stats]
|
||||
(ptk/reify ::team-stats-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :dashboard-team-stats stats))))
|
||||
|
||||
(defn fetch-team-stats
|
||||
[]
|
||||
(ptk/reify ::fetch-team-stats
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [profile (:profile state)]
|
||||
(rx/merge (ptk/watch (fetch-team params) state stream)
|
||||
(ptk/watch (fetch-projects {:team-id id}) state stream)
|
||||
(ptk/watch (du/fetch-users {:team-id id}) state stream))))))
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/query :team-stats {:team-id team-id})
|
||||
(rx/map team-stats-fetched))))))
|
||||
|
||||
(s/def :internal.event.search-files/team-id ::us/uuid)
|
||||
(s/def :internal.event.search-files/search-term (s/nilable ::us/string))
|
||||
;; --- EVENT: fetch-projects
|
||||
|
||||
(s/def :internal.event/search-files
|
||||
(s/keys :req-un [:internal.event.search-files/search-term
|
||||
:internal.event.search-files/team-id]))
|
||||
(defn projects-fetched
|
||||
[projects]
|
||||
(ptk/reify ::projects-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [projects (d/index-by :id projects)]
|
||||
(assoc state :dashboard-projects projects)))))
|
||||
|
||||
(defn search-files
|
||||
(defn fetch-projects
|
||||
[]
|
||||
(ptk/reify ::fetch-projects
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/query :projects {:team-id team-id})
|
||||
(rx/map projects-fetched))))))
|
||||
|
||||
;; --- EVENT: search
|
||||
|
||||
(defn search-result-fetched
|
||||
[result]
|
||||
(ptk/reify ::search-result-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :dashboard-search-result result))))
|
||||
|
||||
(s/def ::search-term (s/nilable ::us/string))
|
||||
(s/def ::search
|
||||
(s/keys :req-un [::search-term ]))
|
||||
|
||||
(defn search
|
||||
[params]
|
||||
(us/assert :internal.event/search-files params)
|
||||
(letfn [(fetched [result state]
|
||||
(update state :dashboard-local
|
||||
assoc :search-result result))]
|
||||
(ptk/reify ::search-files
|
||||
(us/assert ::search params)
|
||||
(ptk/reify ::search
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(dissoc state :dashboard-search-result))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)]
|
||||
(->> (rp/query :search-files params)
|
||||
(rx/map search-result-fetched))))))
|
||||
|
||||
;; --- EVENT: files
|
||||
|
||||
(defn files-fetched
|
||||
[project-id files]
|
||||
(letfn [(remove-project-files [files]
|
||||
(reduce-kv (fn [result id file]
|
||||
(cond-> result
|
||||
(= (:project-id file) project-id) (dissoc id)))
|
||||
files
|
||||
files))]
|
||||
(ptk/reify ::files-fetched
|
||||
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 %)))))))
|
||||
(update state :dashboard-files
|
||||
(fn [state]
|
||||
(let [state (remove-project-files state)]
|
||||
(reduce #(assoc %1 (:id %2) %2) state 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 %)))))))
|
||||
|
||||
(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 %)))))))
|
||||
|
||||
(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/reify ::fetch-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [params {:team-id team-id}]
|
||||
(->> (rp/query :recent-files params)
|
||||
(rx/map #(recent-files-fetched team-id %)))))))
|
||||
(->> (rp/query :project-files {:project-id project-id})
|
||||
(rx/map #(files-fetched project-id %))))))
|
||||
|
||||
;; --- EVENT: shared-files
|
||||
|
||||
(defn shared-files-fetched
|
||||
[files]
|
||||
(ptk/reify ::shared-files-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :dashboard-shared-files (d/index-by :id files)))))
|
||||
|
||||
(defn fetch-shared-files
|
||||
[]
|
||||
(ptk/reify ::fetch-shared-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/query :team-shared-files {:team-id team-id})
|
||||
(rx/map shared-files-fetched))))))
|
||||
|
||||
;; --- EVENT: recent-files
|
||||
|
||||
(defn recent-files-fetched
|
||||
[team-id files]
|
||||
[files]
|
||||
(ptk/reify ::recent-files-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [projects (keys (get-in state [:projects team-id]))]
|
||||
(reduce (fn [state project-id]
|
||||
(let [files (filter #(= project-id (: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
|
||||
projects)))))
|
||||
(let [files (d/index-by :id files)]
|
||||
(-> state
|
||||
(assoc :dashboard-recent-files files)
|
||||
(update :dashboard-files d/merge files))))))
|
||||
|
||||
(defn fetch-recent-files
|
||||
[]
|
||||
(ptk/reify ::fetch-recent-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/query :team-recent-files {:team-id team-id})
|
||||
(rx/map recent-files-fetched))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Selection
|
||||
|
@ -209,31 +257,34 @@
|
|||
:selected-project nil))))
|
||||
|
||||
(defn toggle-file-select
|
||||
[{:keys [file] :as params}]
|
||||
[{:keys [id project-id] :as file}]
|
||||
(us/assert ::file file)
|
||||
(ptk/reify ::toggle-file-select
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [file-id (:id file)
|
||||
selected-project (get-in state [:dashboard-local
|
||||
:selected-project])]
|
||||
(if (or (nil? selected-project)
|
||||
(= selected-project (:project-id file)))
|
||||
(let [selected-project-id (get-in state [:dashboard-local :selected-project])]
|
||||
(if (or (nil? selected-project-id)
|
||||
(= selected-project-id project-id))
|
||||
(update state :dashboard-local
|
||||
(fn [local]
|
||||
(-> local
|
||||
(update :selected-files
|
||||
#(if (contains? % file-id)
|
||||
(disj % file-id)
|
||||
(conj % file-id)))
|
||||
(assoc :selected-project
|
||||
(:project-id file)))))
|
||||
(update :selected-files #(if (contains? % id)
|
||||
(disj % id)
|
||||
(conj % id)))
|
||||
(assoc :selected-project project-id))))
|
||||
state)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Modification
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; --- Create Project
|
||||
;; --- EVENT: create-team
|
||||
|
||||
(defn team-created
|
||||
[team]
|
||||
(ptk/reify ::team-created
|
||||
IDeref
|
||||
(-deref [_] team)))
|
||||
|
||||
(defn create-team
|
||||
[{:keys [name] :as params}]
|
||||
|
@ -246,9 +297,10 @@
|
|||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/mutation! :create-team {:name name})
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error)
|
||||
(rx/map (fn [team]
|
||||
(ptk/event ::ev/event {::ev/name "create-team" :id (:id team)}))))))))
|
||||
(rx/map team-created)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: update-team
|
||||
|
||||
(defn update-team
|
||||
[{:keys [id name] :as params}]
|
||||
|
@ -264,75 +316,75 @@
|
|||
(rx/ignore)))))
|
||||
|
||||
(defn update-team-photo
|
||||
[{:keys [file team-id] :as params}]
|
||||
[{:keys [file] :as params}]
|
||||
(us/assert ::di/file file)
|
||||
(us/assert ::us/uuid team-id)
|
||||
(ptk/reify ::update-team-photo
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [on-success di/notify-finished-loading
|
||||
|
||||
on-error #(do (di/notify-finished-loading)
|
||||
(di/process-error %))
|
||||
|
||||
prepare #(hash-map :file % :team-id team-id)]
|
||||
on-error #(do (di/notify-finished-loading)
|
||||
(di/process-error %))
|
||||
team-id (:current-team-id state)
|
||||
prepare #(hash-map :file % :team-id team-id)]
|
||||
|
||||
(di/notify-start-loading)
|
||||
|
||||
(->> (rx/of file)
|
||||
(rx/map di/validate-file)
|
||||
(rx/map prepare)
|
||||
(rx/mapcat #(rp/mutation :update-team-photo %))
|
||||
(rx/do on-success)
|
||||
(rx/map #(fetch-team %))
|
||||
(rx/map du/fetch-teams)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn update-team-member-role
|
||||
[{:keys [team-id role member-id] :as params}]
|
||||
(us/assert ::us/uuid team-id)
|
||||
[{:keys [role member-id] :as params}]
|
||||
(us/assert ::us/uuid member-id)
|
||||
(us/assert ::us/keyword role)
|
||||
(ptk/reify ::update-team-member-role
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/mutation! :update-team-member-role params)
|
||||
(rx/mapcat #(rx/of (fetch-team-members {:id team-id})
|
||||
(fetch-team {:id team-id})))))))
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)]
|
||||
(->> (rp/mutation! :update-team-member-role params)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (fetch-team-members)
|
||||
(du/fetch-teams)))))))))
|
||||
|
||||
(defn delete-team-member
|
||||
[{:keys [team-id member-id] :as params}]
|
||||
(us/assert ::us/uuid team-id)
|
||||
[{:keys [member-id] :as params}]
|
||||
(us/assert ::us/uuid member-id)
|
||||
(ptk/reify ::delete-team-member
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/mutation! :delete-team-member params)
|
||||
(rx/mapcat #(rx/of (fetch-team-members {:id team-id})
|
||||
(fetch-team {:id team-id})))))))
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)]
|
||||
(->> (rp/mutation! :delete-team-member params)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (fetch-team-members)
|
||||
(du/fetch-teams)))))))))
|
||||
|
||||
(defn leave-team
|
||||
[{:keys [id reassign-to] :as params}]
|
||||
(us/assert ::team params)
|
||||
[{:keys [reassign-to] :as params}]
|
||||
(us/assert (s/nilable ::us/uuid) reassign-to)
|
||||
(ptk/reify ::leave-team
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
on-error rx/throw}} (meta params)
|
||||
team-id (:current-team-id state)]
|
||||
(rx/concat
|
||||
(when (uuid? reassign-to)
|
||||
(->> (rp/mutation! :update-team-member-role {:team-id id
|
||||
(->> (rp/mutation! :update-team-member-role {:team-id team-id
|
||||
:role :owner
|
||||
:member-id reassign-to})
|
||||
(rx/ignore)))
|
||||
(->> (rp/mutation! :leave-team {:id id})
|
||||
(->> (rp/mutation! :leave-team {:id team-id})
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error)))))))
|
||||
|
||||
(defn invite-team-member
|
||||
[{:keys [team-id email role] :as params}]
|
||||
(us/assert ::us/uuid team-id)
|
||||
[{:keys [email role] :as params}]
|
||||
(us/assert ::us/email email)
|
||||
(us/assert ::us/keyword role)
|
||||
(ptk/reify ::invite-team-member
|
||||
|
@ -340,11 +392,15 @@
|
|||
(watch [_ state stream]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
on-error rx/throw}} (meta params)
|
||||
team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)]
|
||||
(->> (rp/mutation! :invite-team-member params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: delete-team
|
||||
|
||||
(defn delete-team
|
||||
[{:keys [id] :as params}]
|
||||
(us/assert ::team params)
|
||||
|
@ -358,9 +414,10 @@
|
|||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: create-project
|
||||
|
||||
(defn- project-created
|
||||
[{:keys [id team-id] :as project}]
|
||||
[{:keys [id] :as project}]
|
||||
(ptk/reify ::project-created
|
||||
IDeref
|
||||
(-deref [_] project)
|
||||
|
@ -368,44 +425,52 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:projects team-id id] project)
|
||||
(assoc-in [:dashboard-projects id] project)
|
||||
(assoc-in [:dashboard-local :project-for-edit] id)))))
|
||||
|
||||
(defn create-project
|
||||
[{:keys [team-id] :as params}]
|
||||
(us/assert ::us/uuid team-id)
|
||||
[]
|
||||
(ptk/reify ::create-project
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [name (name (gensym "New Project "))
|
||||
(let [name (name (gensym "New Project "))
|
||||
team-id (:current-team-id state)
|
||||
params {:name name
|
||||
:team-id team-id}
|
||||
{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/mutation! :create-project {:name name :team-id team-id})
|
||||
(->> (rp/mutation! :create-project params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error)
|
||||
(rx/map project-created))))))
|
||||
(rx/map project-created)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: duplicate-project
|
||||
|
||||
(defn project-duplicated
|
||||
[{:keys [id] :as project}]
|
||||
(ptk/reify ::project-duplicated
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:dashboard-projects id] project))))
|
||||
|
||||
(defn duplicate-project
|
||||
[{:keys [id name] :as params}]
|
||||
(us/assert ::us/uuid id)
|
||||
(letfn [(duplicated [project state]
|
||||
(-> state
|
||||
(assoc-in [:projects (:team-id project) (:id project)] project)))]
|
||||
(ptk/reify ::duplicate-project
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error identity}} (meta params)
|
||||
(ptk/reify ::duplicate-project
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
|
||||
new-name (str name " " (tr "dashboard.copy-suffix"))]
|
||||
new-name (str name " " (tr "dashboard.copy-suffix"))]
|
||||
|
||||
(->> (rp/mutation! :duplicate-project {:project-id id
|
||||
:name new-name})
|
||||
(rx/tap on-success)
|
||||
(rx/map #(partial duplicated %))
|
||||
(rx/catch on-error)))))))
|
||||
(->> (rp/mutation! :duplicate-project {:project-id id
|
||||
:name new-name})
|
||||
(rx/tap on-success)
|
||||
(rx/map project-duplicated)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn move-project
|
||||
[{:keys [id team-id] :as params}]
|
||||
|
@ -416,7 +481,7 @@
|
|||
(watch [_ state stream]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error identity}} (meta params)]
|
||||
on-error rx/throw}} (meta params)]
|
||||
|
||||
(->> (rp/mutation! :move-project {:project-id id
|
||||
:team-id team-id})
|
||||
|
@ -424,21 +489,21 @@
|
|||
(rx/catch on-error))))))
|
||||
|
||||
(defn toggle-project-pin
|
||||
[{:keys [id is-pinned team-id] :as params}]
|
||||
(us/assert ::project params)
|
||||
[{:keys [id is-pinned team-id] :as project}]
|
||||
(us/assert ::project project)
|
||||
(ptk/reify ::toggle-project-pin
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:projects team-id id :is-pinned] (not is-pinned)))
|
||||
(assoc-in state [:dashboard-projects id :is-pinned] (not is-pinned)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [project (get-in state [:projects team-id id])
|
||||
(let [project (get-in state [:dashboard-projects id])
|
||||
params (select-keys project [:id :is-pinned :team-id])]
|
||||
(->> (rp/mutation :update-project-pin params)
|
||||
(rx/ignore))))))
|
||||
|
||||
;; --- Rename Project
|
||||
;; --- EVENT: rename-project
|
||||
|
||||
(defn rename-project
|
||||
[{:keys [id name team-id] :as params}]
|
||||
|
@ -447,7 +512,7 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:projects team-id id :name] name)
|
||||
(update-in [:dashboard-projects id :name] (constantly name))
|
||||
(update :dashboard-local dissoc :project-for-edit)))
|
||||
|
||||
ptk/WatchEvent
|
||||
|
@ -456,7 +521,7 @@
|
|||
(->> (rp/mutation :rename-project params)
|
||||
(rx/ignore))))))
|
||||
|
||||
;; --- Delete Project (by id)
|
||||
;; --- EVENT: delete-project
|
||||
|
||||
(defn delete-project
|
||||
[{:keys [id team-id] :as params}]
|
||||
|
@ -464,16 +529,21 @@
|
|||
(ptk/reify ::delete-project
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:projects team-id] dissoc id))
|
||||
(update state :dashboard-projects dissoc id))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/mutation :delete-project {:id id})
|
||||
(rx/ignore)))))
|
||||
|
||||
;; --- Delete File (by id)
|
||||
;; --- EVENT: delete-file
|
||||
|
||||
(declare delete-file-result)
|
||||
(defn file-deleted
|
||||
[team-id project-id]
|
||||
(ptk/reify ::file-deleted
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard-projects project-id :count] dec))))
|
||||
|
||||
(defn delete-file
|
||||
[{:keys [id project-id] :as params}]
|
||||
|
@ -482,33 +552,26 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update-in [:files project-id] dissoc id)
|
||||
(update-in [:recent-files project-id] (fnil disj #{}) id)))
|
||||
(d/update-when :dashboard-files dissoc id)
|
||||
(d/update-when :dashboard-recent-files dissoc id)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [team-id (uuid/uuid (get-in state [:route :path-params :team-id]))]
|
||||
(->> (rp/mutation :delete-file {:id id})
|
||||
(rx/map #(delete-file-result team-id project-id)))))))
|
||||
|
||||
(defn delete-file-result
|
||||
[team-id project-id]
|
||||
|
||||
(ptk/reify ::delete-file
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update-in [:projects team-id project-id :count] dec)))))
|
||||
(rx/map #(file-deleted team-id project-id)))))))
|
||||
|
||||
;; --- Rename File
|
||||
|
||||
(defn rename-file
|
||||
[{:keys [id name project-id] :as params}]
|
||||
[{:keys [id name] :as params}]
|
||||
(us/assert ::file params)
|
||||
(ptk/reify ::rename-file
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:files project-id id :name] name))
|
||||
(-> state
|
||||
(d/update-in-when [:dashboard-files id :name] (constantly name))
|
||||
(d/update-in-when [:dashboard-recent-files id :name] (constantly name))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -519,12 +582,14 @@
|
|||
;; --- Set File shared
|
||||
|
||||
(defn set-file-shared
|
||||
[{:keys [id project-id is-shared] :as params}]
|
||||
[{:keys [id is-shared] :as params}]
|
||||
(us/assert ::file params)
|
||||
(ptk/reify ::set-file-shared
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:files project-id id :is-shared] is-shared))
|
||||
(-> state
|
||||
(d/update-in-when [:dashboard-files id :is-shared] (constantly is-shared))
|
||||
(d/update-in-when [:dashboard-recent-files id :is-shared] (constantly is-shared))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -532,10 +597,23 @@
|
|||
(->> (rp/mutation :set-file-shared params)
|
||||
(rx/ignore))))))
|
||||
|
||||
;; --- Create File
|
||||
;; --- EVENT: create-file
|
||||
|
||||
(declare file-created)
|
||||
|
||||
(defn file-created
|
||||
[{:keys [id] :as file}]
|
||||
(us/verify ::file file)
|
||||
(ptk/reify ::file-created
|
||||
IDeref
|
||||
(-deref [_] file)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:dashboard-files id] file)
|
||||
(assoc-in [:dashboard-recent-files id] file)))))
|
||||
|
||||
(defn create-file
|
||||
[{:keys [project-id] :as params}]
|
||||
(us/assert ::us/uuid project-id)
|
||||
|
@ -551,23 +629,10 @@
|
|||
|
||||
(->> (rp/mutation! :create-file params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error)
|
||||
(rx/map file-created))))))
|
||||
(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
|
||||
IDeref
|
||||
(-deref [_] file)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:files project-id id] file)
|
||||
(update-in [:recent-files project-id] (fnil conj #{}) id)))))
|
||||
|
||||
;; --- Duplicate File
|
||||
;; --- EVENT: duplicate-file
|
||||
|
||||
(defn duplicate-file
|
||||
[{:keys [id name] :as params}]
|
||||
|
@ -578,7 +643,7 @@
|
|||
(watch [_ state stream]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error identity}} (meta params)
|
||||
on-error rx/throw}} (meta params)
|
||||
|
||||
new-name (str name " " (tr "dashboard.copy-suffix"))]
|
||||
|
||||
|
@ -588,7 +653,7 @@
|
|||
(rx/map file-created)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- Move File
|
||||
;; --- EVENT: move-files
|
||||
|
||||
(defn move-files
|
||||
[{:keys [ids project-id] :as params}]
|
||||
|
@ -599,10 +664,76 @@
|
|||
(watch [_ state stream]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error identity}} (meta params)]
|
||||
on-error rx/throw}} (meta params)]
|
||||
|
||||
(->> (rp/mutation! :move-files {:ids ids
|
||||
:project-id project-id})
|
||||
(->> (rp/mutation! :move-files {:ids ids :project-id project-id})
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Navigation
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn go-to-workspace
|
||||
[{:keys [id project-id] :as file}]
|
||||
(us/assert ::file file)
|
||||
(ptk/reify ::go-to-workspace
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [pparams {:project-id project-id :file-id id}]
|
||||
(rx/of (rt/nav :workspace pparams))))))
|
||||
|
||||
|
||||
(defn go-to-files
|
||||
[project-id]
|
||||
(ptk/reify ::go-to-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (rt/nav :dashboard-files {:team-id team-id
|
||||
:project-id project-id}))))))
|
||||
|
||||
(defn go-to-search
|
||||
([] (go-to-search nil))
|
||||
([term]
|
||||
(ptk/reify ::go-to-search
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(if (empty? term)
|
||||
(rx/of (rt/nav :dashboard-search
|
||||
{:team-id team-id}))
|
||||
(rx/of (rt/nav :dashboard-search
|
||||
{:team-id team-id}
|
||||
{:search-term term}))))))))
|
||||
|
||||
(defn go-to-projects
|
||||
([]
|
||||
(ptk/reify ::go-to-projects
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (rt/nav :dashboard-projects {:team-id team-id}))))))
|
||||
([team-id]
|
||||
(ptk/reify ::go-to-projects
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(du/set-current-team! team-id)
|
||||
(rx/of (rt/nav :dashboard-projects {:team-id team-id}))))))
|
||||
|
||||
(defn go-to-team-members
|
||||
[]
|
||||
(ptk/reify ::go-to-team-members
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (rt/nav :dashboard-team-members {:team-id team-id}))))))
|
||||
|
||||
(defn go-to-team-settings
|
||||
[]
|
||||
(ptk/reify ::go-to-team-settings
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (rt/nav :dashboard-team-settings {:team-id team-id}))))))
|
||||
|
|
|
@ -58,19 +58,28 @@
|
|||
[team-id]
|
||||
(swap! storage assoc ::current-team-id team-id))
|
||||
|
||||
|
||||
;; --- EVENT: fetch-teams
|
||||
|
||||
(defn teams-fetched
|
||||
[teams]
|
||||
(let [teams (d/index-by :id teams)]
|
||||
(ptk/reify ::teams-fetched
|
||||
IDeref
|
||||
(-deref [_] teams)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :teams teams)))))
|
||||
|
||||
(defn fetch-teams
|
||||
[]
|
||||
(letfn [(on-fetched [state data]
|
||||
(let [teams (d/index-by :id data)]
|
||||
(assoc state :teams teams)))]
|
||||
(ptk/reify ::fetch-teams
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/query! :teams)
|
||||
(rx/map (fn [data] #(on-fetched % data))))))))
|
||||
(ptk/reify ::fetch-teams
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/query! :teams)
|
||||
(rx/map teams-fetched)))))
|
||||
|
||||
;; --- EVENT: fetch-profile
|
||||
|
||||
(defn profile-fetched
|
||||
[{:keys [id] :as profile}]
|
||||
|
@ -94,8 +103,6 @@
|
|||
(some-> (:theme profile)
|
||||
(theme/set-current-theme!)))))))
|
||||
|
||||
;; --- Fetch Profile
|
||||
|
||||
(defn fetch-profile
|
||||
[]
|
||||
(ptk/reify ::fetch-profile
|
||||
|
|
|
@ -160,16 +160,12 @@
|
|||
;; Initialize notifications (websocket connection) and the file persistence
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dwp/bundle-fetched))
|
||||
(rx/first)
|
||||
(rx/mapcat #(rx/of (dwn/initialize file-id)
|
||||
(dwp/initialize-file-persistence file-id))))
|
||||
|
||||
;; Initialize Indexes (webworker)
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dwp/bundle-fetched))
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/map dwc/initialize-indices)
|
||||
(rx/first))
|
||||
(rx/mapcat (fn [bundle]
|
||||
(rx/of (dwn/initialize file-id)
|
||||
(dwp/initialize-file-persistence file-id)
|
||||
(dwc/initialize-indices bundle)))))
|
||||
|
||||
;; Mark file initialized when indexes are ready
|
||||
(->> stream
|
||||
|
@ -217,35 +213,39 @@
|
|||
(rx/of (dwn/finalize file-id)
|
||||
::dwp/finalize))))
|
||||
|
||||
(declare go-to-page)
|
||||
|
||||
(defn initialize-page
|
||||
[page-id]
|
||||
(us/assert ::us/uuid page-id)
|
||||
(ptk/reify ::initialize-page
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [;; we maintain a cache of page state for user convenience
|
||||
;; with the exception of the selection; when user abandon
|
||||
;; the current page, the selection is lost
|
||||
local (-> state
|
||||
(get-in [:workspace-cache page-id] workspace-local-default)
|
||||
(assoc :selected (d/ordered-set)))
|
||||
page (-> (get-in state [:workspace-data :pages-index page-id])
|
||||
(select-keys [:id :name]))]
|
||||
(assoc state
|
||||
:current-page-id page-id ; mainly used by events
|
||||
:trimmed-page page
|
||||
:workspace-local local)))))
|
||||
|
||||
page (get-in state [:workspace-data :pages-index page-id])
|
||||
local (-> state
|
||||
(get-in [:workspace-cache page-id] workspace-local-default)
|
||||
(assoc :selected (d/ordered-set)))]
|
||||
(-> state
|
||||
(assoc :current-page-id page-id)
|
||||
(assoc :trimmed-page (select-keys page [:id :name]))
|
||||
(assoc :workspace-local local)
|
||||
(update-in [:route :params :query] assoc :page-id (str page-id)))))))
|
||||
|
||||
(defn finalize-page
|
||||
[page-id]
|
||||
(us/verify ::us/uuid page-id)
|
||||
(us/assert ::us/uuid page-id)
|
||||
(ptk/reify ::finalize-page
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [local (-> (:workspace-local state)
|
||||
(dissoc :edition)
|
||||
(dissoc :edit-path)
|
||||
(dissoc :selected))]
|
||||
(let [page-id (or page-id (get-in state [:workspace-data :pages 0]))
|
||||
local (-> (:workspace-local state)
|
||||
(dissoc :edition)
|
||||
(dissoc :edit-path)
|
||||
(dissoc :selected))]
|
||||
(-> state
|
||||
(assoc-in [:workspace-cache page-id] local)
|
||||
(dissoc :current-page-id :workspace-local :trimmed-page :workspace-drawing))))))
|
||||
|
@ -1043,7 +1043,7 @@
|
|||
uchg {:type :mov-page
|
||||
:id id
|
||||
:index cidx}]
|
||||
(rx/of (dch/commit-changes [rchg] [uchg] {:commit-local? true}))))))
|
||||
(rx/of (dch/commit-changes [rchg] [uchg]))))))
|
||||
|
||||
;; --- Shape / Selection Alignment and Distribution
|
||||
|
||||
|
@ -1169,16 +1169,27 @@
|
|||
(rx/of (rt/nav :workspace/page params))))))
|
||||
|
||||
(defn go-to-page
|
||||
[page-id]
|
||||
(us/verify ::us/uuid page-id)
|
||||
(ptk/reify ::go-to-page
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [project-id (get-in state [:workspace-project :id])
|
||||
file-id (get-in state [:workspace-file :id])
|
||||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id}]
|
||||
(rx/of (rt/nav :workspace pparams qparams))))))
|
||||
([]
|
||||
(ptk/reify ::go-to-page
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [project-id (:current-project-id state)
|
||||
file-id (:current-file-id state)
|
||||
page-id (get-in state [:workspace-data :pages 0])
|
||||
|
||||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id}]
|
||||
(rx/of (rt/nav :workspace pparams qparams))))))
|
||||
([page-id]
|
||||
(us/verify ::us/uuid page-id)
|
||||
(ptk/reify ::go-to-page
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [project-id (:current-project-id state)
|
||||
file-id (:current-file-id state)
|
||||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id}]
|
||||
(rx/of (rt/nav :workspace pparams qparams)))))))
|
||||
|
||||
(defn go-to-layout
|
||||
[layout]
|
||||
|
|
|
@ -162,8 +162,7 @@
|
|||
file-id]
|
||||
:or {save-undo? true}
|
||||
:as opts}]
|
||||
(us/assert ::cp/changes changes)
|
||||
(us/assert ::cp/changes undo-changes)
|
||||
|
||||
(log/debug :msg "commit-changes"
|
||||
:js/changes changes
|
||||
:js/undo-changes undo-changes)
|
||||
|
@ -178,12 +177,13 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [current-file-id (get state :current-file-id)
|
||||
file-id (or file-id current-file-id)
|
||||
path (if (= file-id current-file-id)
|
||||
[:workspace-data]
|
||||
[:workspace-libraries file-id :data])]
|
||||
file-id (or file-id current-file-id)
|
||||
path (if (= file-id current-file-id)
|
||||
[:workspace-data]
|
||||
[:workspace-libraries file-id :data])]
|
||||
(try
|
||||
(us/assert ::spec/changes changes)
|
||||
(us/assert ::spec/changes undo-changes)
|
||||
(update-in state path cp/process-changes changes false)
|
||||
(catch :default e
|
||||
(vreset! error e)
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
[objects page-id shape old-content new-content]
|
||||
(us/verify ::spec/content old-content)
|
||||
(us/verify ::spec/content new-content)
|
||||
(let [shape-id (:id shape)
|
||||
frame-id (:frame-id shape)
|
||||
parent-id (:parent-id shape)
|
||||
(let [shape-id (:id shape)
|
||||
frame-id (:frame-id shape)
|
||||
parent-id (:parent-id shape)
|
||||
parent-index (cp/position-on-parent shape-id objects)
|
||||
|
||||
[old-points old-selrect] (helpers/content->points+selrect shape old-content)
|
||||
|
@ -72,7 +72,6 @@
|
|||
(defn save-path-content
|
||||
([]
|
||||
(save-path-content {}))
|
||||
|
||||
([{:keys [preserve-move-to] :or {preserve-move-to false}}]
|
||||
(ptk/reify ::save-path-content
|
||||
ptk/UpdateEvent
|
||||
|
@ -86,14 +85,14 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
id (get-in state [:workspace-local :edition])
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
page-id (:current-page-id state)
|
||||
id (get-in state [:workspace-local :edition])
|
||||
old-content (get-in state [:workspace-local :edit-path id :old-content])]
|
||||
(if (some? old-content)
|
||||
(let [shape (get-in state (st/get-path state))
|
||||
page-id (:current-page-id state)
|
||||
(let [shape (get-in state (st/get-path state))
|
||||
[rch uch] (generate-path-changes objects page-id shape old-content (:content shape))]
|
||||
(rx/of (dch/commit-changes rch uch {:commit-local? true})))
|
||||
(rx/of (dch/commit-changes rch uch)))
|
||||
(rx/empty)))))))
|
||||
|
||||
|
||||
|
|
|
@ -310,8 +310,8 @@
|
|||
(us/assert ::us/uuid team-id)
|
||||
(ptk/reify ::fetch-shared-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/query :shared-files params)
|
||||
(watch [it state stream]
|
||||
(->> (rp/query :team-shared-files {:team-id team-id})
|
||||
(rx/map shared-files-fetched)))))
|
||||
|
||||
(defn shared-files-fetched
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
;; Undo / Redo
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(s/def ::undo-changes ::cp/changes)
|
||||
(s/def ::redo-changes ::cp/changes)
|
||||
(s/def ::undo-changes ::spec/changes)
|
||||
(s/def ::redo-changes ::spec/changes)
|
||||
(s/def ::undo-entry
|
||||
(s/keys :req-un [::undo-changes ::redo-changes]))
|
||||
|
||||
|
|
|
@ -47,6 +47,30 @@
|
|||
(def dashboard-fonts
|
||||
(l/derived :dashboard-fonts st/state))
|
||||
|
||||
(def dashboard-projects
|
||||
(l/derived :dashboard-projects st/state))
|
||||
|
||||
(def dashboard-files
|
||||
(l/derived :dashboard-files st/state))
|
||||
|
||||
(def dashboard-shared-files
|
||||
(l/derived :dashboard-shared-files st/state))
|
||||
|
||||
(def dashboard-search-result
|
||||
(l/derived :dashboard-search-result st/state))
|
||||
|
||||
(def dashboard-team
|
||||
(l/derived (fn [state]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(get-in state [:teams team-id])))
|
||||
st/state))
|
||||
|
||||
(def dashboard-team-stats
|
||||
(l/derived :dashboard-team-stats st/state))
|
||||
|
||||
(def dashboard-team-members
|
||||
(l/derived :dashboard-team-members st/state))
|
||||
|
||||
(def dashboard-selected-project
|
||||
(l/derived (fn [state]
|
||||
(get-in state [:dashboard-local :selected-project]))
|
||||
|
@ -54,17 +78,14 @@
|
|||
|
||||
(def dashboard-selected-files
|
||||
(l/derived (fn [state]
|
||||
(get-in state [:dashboard-local :selected-files] #{}))
|
||||
st/state))
|
||||
|
||||
(def dashboard-selected-file-objs
|
||||
(l/derived (fn [state]
|
||||
(let [dashboard-local (get state :dashboard-local)
|
||||
selected-project (get dashboard-local :selected-project)
|
||||
selected-files (get dashboard-local :selected-files #{})]
|
||||
(map #(get-in state [:files selected-project %])
|
||||
selected-files)))
|
||||
st/state))
|
||||
(let [get-file #(get-in state [:dashboard-files %])
|
||||
sim-file #(select-keys % [:id :name :project-id])
|
||||
selected (get-in state [:dashboard-local :selected-files])
|
||||
xform (comp (map get-file)
|
||||
(map sim-file))]
|
||||
(->> (into #{} xform selected)
|
||||
(d/index-by :id))))
|
||||
st/state =))
|
||||
|
||||
;; ---- Workspace refs
|
||||
|
||||
|
@ -130,10 +151,11 @@
|
|||
|
||||
(def workspace-file
|
||||
(l/derived (fn [state]
|
||||
(when-let [file (:workspace-file state)]
|
||||
(let [file (:workspace-file state)
|
||||
data (:workspace-data state)]
|
||||
(-> file
|
||||
(dissoc :data)
|
||||
(assoc :pages (get-in file [:data :pages])))))
|
||||
(assoc :pages (:pages data)))))
|
||||
st/state =))
|
||||
|
||||
(def workspace-file-colors
|
||||
|
|
|
@ -107,85 +107,85 @@
|
|||
(mf/defc main-page
|
||||
{::mf/wrap [#(mf/catch % {:fallback on-main-error})]}
|
||||
[{:keys [route] :as props}]
|
||||
(let [{:keys [data params]} route]
|
||||
[:& (mf/provider ctx/current-route) {:value route}
|
||||
(case (:name data)
|
||||
(:auth-login
|
||||
:auth-register
|
||||
:auth-register-success
|
||||
:auth-recovery-request
|
||||
:auth-recovery)
|
||||
[:& auth {:route route}]
|
||||
|
||||
[:& (mf/provider ctx/current-route) {:value route}
|
||||
(case (get-in route [:data :name])
|
||||
(:auth-login
|
||||
:auth-register
|
||||
:auth-register-success
|
||||
:auth-recovery-request
|
||||
:auth-recovery)
|
||||
[:& auth {:route route}]
|
||||
:auth-verify-token
|
||||
[:& verify-token {:route route}]
|
||||
|
||||
:auth-verify-token
|
||||
[:& verify-token {:route route}]
|
||||
(:settings-profile
|
||||
:settings-password
|
||||
:settings-options
|
||||
:settings-feedback)
|
||||
[:& settings/settings {:route route}]
|
||||
|
||||
(:settings-profile
|
||||
:settings-password
|
||||
:settings-options
|
||||
:settings-feedback)
|
||||
[:& settings/settings {:route route}]
|
||||
:debug-icons-preview
|
||||
(when *assert*
|
||||
[:div.debug-preview
|
||||
[:h1 "Cursors"]
|
||||
[:& c/debug-preview]
|
||||
[:h1 "Icons"]
|
||||
[:& i/debug-icons-preview]
|
||||
])
|
||||
|
||||
:debug-icons-preview
|
||||
(when *assert*
|
||||
[:div.debug-preview
|
||||
[:h1 "Cursors"]
|
||||
[:& c/debug-preview]
|
||||
[:h1 "Icons"]
|
||||
[:& i/debug-icons-preview]
|
||||
])
|
||||
(:dashboard-search
|
||||
:dashboard-projects
|
||||
:dashboard-files
|
||||
:dashboard-libraries
|
||||
:dashboard-fonts
|
||||
:dashboard-font-providers
|
||||
:dashboard-team-members
|
||||
:dashboard-team-settings)
|
||||
[:*
|
||||
#_[:div.modal-wrapper
|
||||
[:& app.main.ui.onboarding/release-notes-modal {:version "1.5"}]]
|
||||
[:& dashboard {:route route}]]
|
||||
|
||||
(:dashboard-search
|
||||
:dashboard-projects
|
||||
:dashboard-files
|
||||
:dashboard-libraries
|
||||
:dashboard-fonts
|
||||
:dashboard-font-providers
|
||||
:dashboard-team-members
|
||||
:dashboard-team-settings)
|
||||
[:*
|
||||
#_[:div.modal-wrapper
|
||||
[:& app.main.ui.onboarding/release-notes-modal {:version "1.5"}]]
|
||||
[:& dashboard {:route route}]]
|
||||
:viewer
|
||||
(let [index (get-in route [:query-params :index])
|
||||
token (get-in route [:query-params :token])
|
||||
section (get-in route [:query-params :section] :interactions)
|
||||
file-id (get-in route [:path-params :file-id])
|
||||
page-id (get-in route [:path-params :page-id])]
|
||||
[:& fs/fullscreen-wrapper {}
|
||||
(if (= section :handoff)
|
||||
[:& handoff {:page-id page-id
|
||||
:file-id file-id
|
||||
:index index
|
||||
:token token}]
|
||||
[:& viewer-page {:page-id page-id
|
||||
:file-id file-id
|
||||
:section section
|
||||
:index index
|
||||
:token token}])])
|
||||
|
||||
:viewer
|
||||
(let [index (get-in route [:query-params :index])
|
||||
token (get-in route [:query-params :token])
|
||||
section (get-in route [:query-params :section] :interactions)
|
||||
file-id (get-in route [:path-params :file-id])
|
||||
page-id (get-in route [:path-params :page-id])]
|
||||
[:& fs/fullscreen-wrapper {}
|
||||
(if (= section :handoff)
|
||||
[:& handoff {:page-id page-id
|
||||
:file-id file-id
|
||||
:index index
|
||||
:token token}]
|
||||
[:& viewer-page {:page-id page-id
|
||||
:file-id file-id
|
||||
:section section
|
||||
:index index
|
||||
:token token}])])
|
||||
:render-object
|
||||
(do
|
||||
(let [file-id (uuid (get-in route [:path-params :file-id]))
|
||||
page-id (uuid (get-in route [:path-params :page-id]))
|
||||
object-id (uuid (get-in route [:path-params :object-id]))]
|
||||
[:& render/render-object {:file-id file-id
|
||||
:page-id page-id
|
||||
:object-id object-id}]))
|
||||
|
||||
:render-object
|
||||
(do
|
||||
(let [file-id (uuid (get-in route [:path-params :file-id]))
|
||||
page-id (uuid (get-in route [:path-params :page-id]))
|
||||
object-id (uuid (get-in route [:path-params :object-id]))]
|
||||
[:& render/render-object {:file-id file-id
|
||||
:page-id page-id
|
||||
:object-id object-id}]))
|
||||
|
||||
:workspace
|
||||
(let [project-id (uuid (get-in route [:params :path :project-id]))
|
||||
file-id (uuid (get-in route [:params :path :file-id]))
|
||||
page-id (uuid (get-in route [:params :query :page-id]))
|
||||
layout-name (get-in route [:params :query :layout])]
|
||||
[:& workspace/workspace {:project-id project-id
|
||||
:file-id file-id
|
||||
:page-id page-id
|
||||
:layout-name (keyword layout-name)
|
||||
:key file-id}])
|
||||
nil)])
|
||||
:workspace
|
||||
(let [project-id (some-> params :path :project-id uuid)
|
||||
file-id (some-> params :path :file-id uuid)
|
||||
page-id (some-> params :query :page-id uuid)
|
||||
layout (some-> params :query :layout keyword)]
|
||||
[:& workspace/workspace {:project-id project-id
|
||||
:file-id file-id
|
||||
:page-id page-id
|
||||
:layout-name layout
|
||||
:key file-id}])
|
||||
nil)]))
|
||||
|
||||
(mf/defc app
|
||||
[]
|
||||
|
@ -249,7 +249,7 @@
|
|||
context (str/fmt "ns: '%s'\nname: '%s'\nfile: '%s:%s'"
|
||||
(:ns context)
|
||||
(:name context)
|
||||
(str cfg/public-uri "/js/cljs-runtime/" (:file context))
|
||||
(str cfg/public-uri "js/cljs-runtime/" (:file context))
|
||||
(:line context))]
|
||||
(ts/schedule
|
||||
(st/emitf
|
||||
|
|
|
@ -51,14 +51,6 @@
|
|||
(uuid-str? project-id)
|
||||
(assoc :project-id (uuid project-id)))))
|
||||
|
||||
(defn- team-ref
|
||||
[id]
|
||||
(l/derived (l/in [:teams id]) st/state))
|
||||
|
||||
(defn- projects-ref
|
||||
[team-id]
|
||||
(l/derived (l/in [:projects team-id]) st/state))
|
||||
|
||||
(mf/defc dashboard-content
|
||||
[{:keys [team projects project section search-term profile] :as props}]
|
||||
[:div.dashboard-content {:on-click (st/emitf (dd/clear-selected-files))}
|
||||
|
@ -101,16 +93,15 @@
|
|||
team-id (:team-id params)
|
||||
search-term (:search-term params)
|
||||
|
||||
projects-ref (mf/use-memo (mf/deps team-id) #(projects-ref team-id))
|
||||
team-ref (mf/use-memo (mf/deps team-id) #(team-ref team-id))
|
||||
teams (mf/deref refs/teams)
|
||||
team (get teams team-id)
|
||||
|
||||
team (mf/deref team-ref)
|
||||
projects (mf/deref projects-ref)
|
||||
projects (mf/deref refs/dashboard-projects)
|
||||
project (get projects project-id)]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps team-id)
|
||||
(st/emitf (dd/fetch-bundle {:id team-id})))
|
||||
(st/emitf (dd/initialize {:id team-id})))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps)
|
||||
|
@ -122,12 +113,18 @@
|
|||
(not= "0.0" (:main @cf/version)))
|
||||
(tm/schedule 1000 #(st/emit! (modal/show {:type :release-notes :version (:main @cf/version)})))))))
|
||||
|
||||
[:& (mf/provider ctx/current-file-id) {:value nil}
|
||||
[:& (mf/provider ctx/current-team-id) {:value team-id}
|
||||
[:& (mf/provider ctx/current-project-id) {:value project-id}
|
||||
[:& (mf/provider ctx/current-page-id) {:value nil}
|
||||
[:& (mf/provider ctx/current-team-id) {:value team-id}
|
||||
[:& (mf/provider ctx/current-project-id) {:value project-id}
|
||||
|
||||
[:section.dashboard-layout
|
||||
;; NOTE: dashboard events and other related functions assumes
|
||||
;; that the team is a implicit context variable that is
|
||||
;; available using react context or accessing
|
||||
;; the :current-team-id on the state. We set the key to the
|
||||
;; team-id becase we want to completly refresh all the
|
||||
;; components on team change. Many components assumess that the
|
||||
;; team is already set so don't put the team into mf/deps.
|
||||
(when team
|
||||
[:section.dashboard-layout {:key (:id team)}
|
||||
[:& sidebar
|
||||
{:team team
|
||||
:projects projects
|
||||
|
@ -142,5 +139,5 @@
|
|||
:project project
|
||||
:section section
|
||||
:search-term search-term
|
||||
:team team}])]]]]]))
|
||||
:team team}])])]]))
|
||||
|
||||
|
|
|
@ -19,6 +19,33 @@
|
|||
[beicon.core :as rx]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn get-project-name
|
||||
[project]
|
||||
(if (:is-default project)
|
||||
(tr "labels.drafts")
|
||||
(:name project)))
|
||||
|
||||
(defn get-team-name
|
||||
[team]
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team)))
|
||||
|
||||
(defn group-by-team
|
||||
"Group projects by team."
|
||||
[projects]
|
||||
(reduce (fn [teams project]
|
||||
(update teams
|
||||
(:team-id project)
|
||||
#(if (nil? %)
|
||||
{:id (:team-id project)
|
||||
:name (:team-name project)
|
||||
:is-default (:is-default-team project)
|
||||
:projects [project]}
|
||||
(update % :projects conj project))))
|
||||
{}
|
||||
projects))
|
||||
|
||||
(mf/defc file-menu
|
||||
[{:keys [files show? on-edit on-menu-close top left navigate?] :as props}]
|
||||
(assert (seq files) "missing `files` prop")
|
||||
|
@ -26,8 +53,8 @@
|
|||
(assert (fn? on-edit) "missing `on-edit` prop")
|
||||
(assert (fn? on-menu-close) "missing `on-menu-close` prop")
|
||||
(assert (boolean? navigate?) "missing `navigate?` prop")
|
||||
(let [top (or top 0)
|
||||
left (or left 0)
|
||||
(let [top (or top 0)
|
||||
left (or left 0)
|
||||
|
||||
file (first files)
|
||||
file-count (count files)
|
||||
|
@ -35,157 +62,113 @@
|
|||
|
||||
current-team-id (mf/use-ctx ctx/current-team-id)
|
||||
teams (mf/use-state nil)
|
||||
|
||||
current-team (get @teams current-team-id)
|
||||
other-teams (remove #(= (:id %) current-team-id)
|
||||
(vals @teams))
|
||||
other-teams (remove #(= (:id %) current-team-id) (vals @teams))
|
||||
|
||||
current-projects (remove #(= (:id %) (:project-id file))
|
||||
(:projects current-team))
|
||||
|
||||
project-name (fn [project]
|
||||
(if (:is-default project)
|
||||
(tr "labels.drafts")
|
||||
(:name project)))
|
||||
|
||||
team-name (fn [team]
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team)))
|
||||
|
||||
on-new-tab
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(fn [event]
|
||||
(let [pparams {:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
qparams {:page-id (first (get-in file [:data :pages]))}]
|
||||
(st/emit! (rt/nav-new-window :workspace pparams qparams)))))
|
||||
(fn [event]
|
||||
(let [pparams {:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
qparams {:page-id (first (get-in file [:data :pages]))}]
|
||||
(st/emit! (rt/nav-new-window :workspace pparams qparams))))
|
||||
|
||||
on-duplicate
|
||||
(mf/use-callback
|
||||
(mf/deps files)
|
||||
(fn [event]
|
||||
(apply st/emit! (map dd/duplicate-file files))
|
||||
(st/emit! (dm/success (tr "dashboard.success-duplicate-file")))))
|
||||
(fn [event]
|
||||
(apply st/emit! (map dd/duplicate-file files))
|
||||
(st/emit! (dm/success (tr "dashboard.success-duplicate-file"))))
|
||||
|
||||
delete-fn
|
||||
(mf/use-callback
|
||||
(mf/deps files)
|
||||
(fn [event]
|
||||
(apply st/emit! (map dd/delete-file files))
|
||||
(st/emit! (dm/success (tr "dashboard.success-delete-file")))))
|
||||
(fn [event]
|
||||
(apply st/emit! (map dd/delete-file files))
|
||||
(st/emit! (dm/success (tr "dashboard.success-delete-file"))))
|
||||
|
||||
on-delete
|
||||
(mf/use-callback
|
||||
(mf/deps files)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(if multi?
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-file-multi-confirm.title" file-count)
|
||||
:message (tr "modals.delete-file-multi-confirm.message" file-count)
|
||||
:accept-label (tr "modals.delete-file-multi-confirm.accept" file-count)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(if multi?
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-file-multi-confirm.title" file-count)
|
||||
:message (tr "modals.delete-file-multi-confirm.message" file-count)
|
||||
:accept-label (tr "modals.delete-file-multi-confirm.accept" file-count)
|
||||
:on-accept delete-fn}))
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-file-confirm.title")
|
||||
:message (tr "modals.delete-file-confirm.message")
|
||||
:accept-label (tr "modals.delete-file-confirm.accept")
|
||||
:on-accept delete-fn})))))
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-file-confirm.title")
|
||||
:message (tr "modals.delete-file-confirm.message")
|
||||
:accept-label (tr "modals.delete-file-confirm.accept")
|
||||
:on-accept delete-fn}))))
|
||||
|
||||
on-move-success
|
||||
(fn [team-id project-id]
|
||||
(if multi?
|
||||
(st/emit! (dm/success (tr "dashboard.success-move-files")))
|
||||
(st/emit! (dm/success (tr "dashboard.success-move-file"))))
|
||||
(if (or navigate? (not= team-id current-team-id))
|
||||
(st/emit! (dd/go-to-files project-id))
|
||||
(st/emit! (dd/fetch-recent-files)
|
||||
(dd/clear-selected-files))))
|
||||
|
||||
on-move
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(fn [team-id project-id]
|
||||
(let [data {:ids (set (map :id files))
|
||||
:project-id project-id}
|
||||
|
||||
mdata {:on-success
|
||||
#(do
|
||||
(if multi?
|
||||
(st/emit! (dm/success (tr "dashboard.success-move-files")))
|
||||
(st/emit! (dm/success (tr "dashboard.success-move-file"))))
|
||||
(if (or navigate? (not= team-id current-team-id))
|
||||
(st/emit! (rt/nav :dashboard-files
|
||||
{:team-id team-id
|
||||
:project-id project-id}))
|
||||
(st/emit! (dd/fetch-recent-files {:team-id team-id})
|
||||
(dd/clear-selected-files))))}]
|
||||
|
||||
(st/emitf (dd/move-files (with-meta data mdata))))))
|
||||
(fn [team-id project-id]
|
||||
(let [data {:ids (set (map :id files))
|
||||
:project-id project-id}
|
||||
mdata {:on-success #(on-move-success team-id project-id)}]
|
||||
(st/emitf (dd/move-files (with-meta data mdata)))))
|
||||
|
||||
add-shared
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(st/emitf (dd/set-file-shared (assoc file :is-shared true))))
|
||||
(st/emitf (dd/set-file-shared (assoc file :is-shared true)))
|
||||
|
||||
del-shared
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(st/emitf (dd/set-file-shared (assoc file :is-shared false))))
|
||||
(st/emitf (dd/set-file-shared (assoc file :is-shared false)))
|
||||
|
||||
on-add-shared
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:message ""
|
||||
:title (tr "modals.add-shared-confirm.message" (:name file))
|
||||
:hint (tr "modals.add-shared-confirm.hint")
|
||||
:cancel-label :omit
|
||||
:accept-label (tr "modals.add-shared-confirm.accept")
|
||||
:accept-style :primary
|
||||
:on-accept add-shared}))))
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:message ""
|
||||
:title (tr "modals.add-shared-confirm.message" (:name file))
|
||||
:hint (tr "modals.add-shared-confirm.hint")
|
||||
:cancel-label :omit
|
||||
:accept-label (tr "modals.add-shared-confirm.accept")
|
||||
:accept-style :primary
|
||||
:on-accept add-shared})))
|
||||
|
||||
on-del-shared
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:message ""
|
||||
:title (tr "modals.remove-shared-confirm.message" (:name file))
|
||||
:hint (tr "modals.remove-shared-confirm.hint")
|
||||
:cancel-label :omit
|
||||
:accept-label (tr "modals.remove-shared-confirm.accept")
|
||||
:on-accept del-shared}))))]
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:message ""
|
||||
:title (tr "modals.remove-shared-confirm.message" (:name file))
|
||||
:hint (tr "modals.remove-shared-confirm.hint")
|
||||
:cancel-label :omit
|
||||
:accept-label (tr "modals.remove-shared-confirm.accept")
|
||||
:on-accept del-shared})))]
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps show?)
|
||||
(fn []
|
||||
(let [group-by-team (fn [projects]
|
||||
(reduce
|
||||
(fn [teams project]
|
||||
(update teams (:team-id project)
|
||||
#(if (nil? %)
|
||||
{:id (:team-id project)
|
||||
:name (:team-name project)
|
||||
:is-default (:is-default-team project)
|
||||
:projects [project]}
|
||||
(update % :projects conj project))))
|
||||
{}
|
||||
projects))]
|
||||
(if show?
|
||||
(->> (rp/query! :all-projects)
|
||||
(rx/map group-by-team)
|
||||
(rx/subs #(reset! teams %)))
|
||||
(reset! teams [])))))
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(->> (rp/query! :all-projects)
|
||||
(rx/map group-by-team)
|
||||
(rx/subs #(reset! teams %)))))
|
||||
|
||||
(when current-team
|
||||
(let [sub-options (conj (vec (for [project current-projects]
|
||||
[(project-name project)
|
||||
[(get-project-name project)
|
||||
(on-move (:id current-team)
|
||||
(:id project))]))
|
||||
(when (seq other-teams)
|
||||
[(tr "dashboard.move-to-other-team") nil
|
||||
(for [team other-teams]
|
||||
[(team-name team) nil
|
||||
[(get-team-name team) nil
|
||||
(for [sub-project (:projects team)]
|
||||
[(project-name sub-project)
|
||||
[(get-project-name sub-project)
|
||||
(on-move (:id team)
|
||||
(:id sub-project))])])]))
|
||||
|
||||
|
@ -214,4 +197,3 @@
|
|||
:top top
|
||||
:left left
|
||||
:options options}]))))
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
(:require
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.dashboard.grid :refer [grid]]
|
||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||
|
@ -77,15 +78,11 @@
|
|||
[:a.btn-secondary.btn-small {:on-click on-create-clicked}
|
||||
(tr "dashboard.new-file")]]))
|
||||
|
||||
(defn files-ref
|
||||
[project-id]
|
||||
(l/derived (l/in [:files project-id]) st/state))
|
||||
|
||||
(mf/defc files-section
|
||||
[{:keys [project team] :as props}]
|
||||
(let [files-ref (mf/use-memo (mf/deps (:id project)) #(files-ref (:id project)))
|
||||
files-map (mf/deref files-ref)
|
||||
(let [files-map (mf/deref refs/dashboard-files)
|
||||
files (->> (vals files-map)
|
||||
(filter #(= (:id project) (:project-id %)))
|
||||
(sort-by :modified-at)
|
||||
(reverse))]
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
[app.main.worker :as wrk]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.dom.dnd :as dnd]
|
||||
[app.util.i18n :as i18n :refer [t tr]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.router :as rt]
|
||||
[app.util.time :as dt]
|
||||
|
@ -55,76 +55,66 @@
|
|||
[{:keys [modified-at]}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
time (dt/timeago modified-at {:locale locale})]
|
||||
(str (t locale "ds.updated-at" time))))
|
||||
(str (tr "ds.updated-at" time))))
|
||||
|
||||
(defn create-counter-element
|
||||
[element file-count]
|
||||
(let [counter-el (dom/create-element "div")]
|
||||
(dom/set-property! counter-el "class" "drag-counter")
|
||||
(dom/set-text! counter-el (str file-count))
|
||||
counter-el))
|
||||
|
||||
(mf/defc grid-item
|
||||
{:wrap [mf/memo]}
|
||||
[{:keys [id file selected-files navigate?] :as props}]
|
||||
(let [local (mf/use-state {:menu-open false
|
||||
:menu-pos nil
|
||||
:edition false})
|
||||
locale (mf/deref i18n/locale)
|
||||
item-ref (mf/use-ref)
|
||||
menu-ref (mf/use-ref)
|
||||
selected? (contains? selected-files id)
|
||||
|
||||
selected-file-objs
|
||||
(deref refs/dashboard-selected-file-objs)
|
||||
;; not needed to subscribe and repaint if changed
|
||||
[{:keys [file navigate?] :as props}]
|
||||
(let [file-id (:id file)
|
||||
local (mf/use-state {:menu-open false
|
||||
:menu-pos nil
|
||||
:edition false})
|
||||
selected-files (mf/deref refs/dashboard-selected-files)
|
||||
item-ref (mf/use-ref)
|
||||
menu-ref (mf/use-ref)
|
||||
selected? (contains? selected-files file-id)
|
||||
|
||||
on-menu-close
|
||||
(mf/use-callback
|
||||
#(swap! local assoc :menu-open false))
|
||||
|
||||
on-select
|
||||
(mf/use-callback
|
||||
(mf/deps id selected? selected-files @local)
|
||||
(fn [event]
|
||||
(when (and (or (not selected?) (> (count selected-files) 1))
|
||||
(not (:menu-open @local)))
|
||||
(dom/stop-propagation event)
|
||||
(let [shift? (kbd/shift? event)]
|
||||
(when-not shift?
|
||||
(st/emit! (dd/clear-selected-files)))
|
||||
(st/emit! (dd/toggle-file-select {:file file}))))))
|
||||
(fn [event]
|
||||
(when (and (or (not selected?) (> (count selected-files) 1))
|
||||
(not (:menu-open @local)))
|
||||
(dom/stop-propagation event)
|
||||
(let [shift? (kbd/shift? event)]
|
||||
(when-not shift?
|
||||
(st/emit! (dd/clear-selected-files)))
|
||||
(st/emit! (dd/toggle-file-select file)))))
|
||||
|
||||
on-navigate
|
||||
(mf/use-callback
|
||||
(mf/deps id)
|
||||
(mf/deps file)
|
||||
(fn [event]
|
||||
(let [menu-icon (mf/ref-val menu-ref)
|
||||
target (dom/get-target event)]
|
||||
(when-not (dom/child? target menu-icon)
|
||||
(let [pparams {:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
qparams {:page-id (first (get-in file [:data :pages]))}]
|
||||
(st/emit! (rt/nav :workspace pparams qparams)))))))
|
||||
|
||||
create-counter
|
||||
(mf/use-callback
|
||||
(fn [element file-count]
|
||||
(let [counter-el (dom/create-element "div")]
|
||||
(dom/set-property! counter-el "class" "drag-counter")
|
||||
(dom/set-text! counter-el (str file-count))
|
||||
counter-el)))
|
||||
(st/emit! (dd/go-to-workspace file))))))
|
||||
|
||||
on-drag-start
|
||||
(mf/use-callback
|
||||
(mf/deps selected-files)
|
||||
(fn [event]
|
||||
(let [offset (dom/get-offset-position (.-nativeEvent event))
|
||||
(let [offset (dom/get-offset-position (.-nativeEvent event))
|
||||
|
||||
select-current? (not (contains? selected-files (:id file)))
|
||||
|
||||
item-el (mf/ref-val item-ref)
|
||||
counter-el (create-counter item-el
|
||||
(if select-current?
|
||||
1
|
||||
(count selected-files)))]
|
||||
|
||||
item-el (mf/ref-val item-ref)
|
||||
counter-el (create-counter-element item-el
|
||||
(if select-current?
|
||||
1
|
||||
(count selected-files)))]
|
||||
(when select-current?
|
||||
(st/emit! (dd/clear-selected-files))
|
||||
(st/emit! (dd/toggle-file-select {:file file})))
|
||||
(st/emit! (dd/toggle-file-select file)))
|
||||
|
||||
(dnd/set-data! event "penpot/files" "dummy")
|
||||
(dnd/set-allowed-effect! event "move")
|
||||
|
@ -135,7 +125,7 @@
|
|||
;; afterwards, in the next render cycle.
|
||||
(dom/append-child! item-el counter-el)
|
||||
(dnd/set-drag-image! event item-el (:x offset) (:y offset))
|
||||
(ts/raf #(.removeChild item-el counter-el)))))
|
||||
(ts/raf #(.removeChild ^js item-el counter-el)))))
|
||||
|
||||
on-menu-click
|
||||
(mf/use-callback
|
||||
|
@ -146,10 +136,11 @@
|
|||
(let [shift? (kbd/shift? event)]
|
||||
(when-not shift?
|
||||
(st/emit! (dd/clear-selected-files)))
|
||||
(st/emit! (dd/toggle-file-select {:file file}))))
|
||||
(st/emit! (dd/toggle-file-select file))))
|
||||
(let [position (dom/get-client-position event)]
|
||||
(swap! local assoc :menu-open true
|
||||
:menu-pos position))))
|
||||
(swap! local assoc
|
||||
:menu-open true
|
||||
:menu-pos position))))
|
||||
|
||||
edit
|
||||
(mf/use-callback
|
||||
|
@ -168,19 +159,20 @@
|
|||
:menu-open false)))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps selected? local)
|
||||
(fn []
|
||||
(when (and (not selected?) (:menu-open @local))
|
||||
(swap! local assoc :menu-open false))))
|
||||
(mf/deps selected? local)
|
||||
(fn []
|
||||
(when (and (not selected?) (:menu-open @local))
|
||||
(swap! local assoc :menu-open false))))
|
||||
|
||||
[:div.grid-item.project-th
|
||||
{:class (dom/classnames :selected selected?)
|
||||
:ref item-ref
|
||||
:draggable true
|
||||
:on-click on-select
|
||||
:on-double-click on-navigate
|
||||
:on-drag-start on-drag-start
|
||||
:on-context-menu on-menu-click}
|
||||
|
||||
[:div.grid-item.project-th {:class (dom/classnames
|
||||
:selected selected?)
|
||||
:ref item-ref
|
||||
:draggable true
|
||||
:on-click on-select
|
||||
:on-double-click on-navigate
|
||||
:on-drag-start on-drag-start
|
||||
:on-context-menu on-menu-click}
|
||||
[:div.overlay]
|
||||
[:& grid-item-thumbnail {:file file}]
|
||||
(when (:is-shared file)
|
||||
|
@ -198,7 +190,7 @@
|
|||
:on-click on-menu-click}
|
||||
i/actions
|
||||
(when selected?
|
||||
[:& file-menu {:files selected-file-objs
|
||||
[:& file-menu {:files (vals selected-files)
|
||||
:show? (:menu-open @local)
|
||||
:left (+ 24 (:x (:menu-pos @local)))
|
||||
:top (:y (:menu-pos @local))
|
||||
|
@ -223,28 +215,24 @@
|
|||
|
||||
(mf/defc grid
|
||||
[{:keys [id opts files] :as props}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
selected-files (mf/deref refs/dashboard-selected-files)]
|
||||
[:section.dashboard-grid
|
||||
(cond
|
||||
(nil? files)
|
||||
[:& loading-placeholder]
|
||||
[:section.dashboard-grid
|
||||
(cond
|
||||
(nil? files)
|
||||
[:& loading-placeholder]
|
||||
|
||||
(seq files)
|
||||
[:div.grid-row
|
||||
(for [item files]
|
||||
[:& grid-item
|
||||
{:id (:id item)
|
||||
:file item
|
||||
:selected-files selected-files
|
||||
:key (:id item)
|
||||
:navigate? true}])]
|
||||
(seq files)
|
||||
[:div.grid-row
|
||||
(for [item files]
|
||||
[:& grid-item
|
||||
{:file item
|
||||
:key (:id item)
|
||||
:navigate? true}])]
|
||||
|
||||
:else
|
||||
[:& empty-placeholder])]))
|
||||
:else
|
||||
[:& empty-placeholder])])
|
||||
|
||||
(mf/defc line-grid-row
|
||||
[{:keys [locale files team-id selected-files on-load-more dragging?] :as props}]
|
||||
[{:keys [files team-id selected-files on-load-more dragging?] :as props}]
|
||||
(let [rowref (mf/use-ref)
|
||||
|
||||
width (mf/use-state nil)
|
||||
|
@ -294,12 +282,11 @@
|
|||
[:div.grid-item.placeholder {:on-click on-load-more}
|
||||
[:div.placeholder-icon i/arrow-down]
|
||||
[:div.placeholder-label
|
||||
(t locale "dashboard.show-all-files")]])]))
|
||||
(tr "dashboard.show-all-files")]])]))
|
||||
|
||||
(mf/defc line-grid
|
||||
[{:keys [project-id team-id opts files on-load-more] :as props}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
dragging? (mf/use-state false)
|
||||
(let [dragging? (mf/use-state false)
|
||||
|
||||
selected-files (mf/deref refs/dashboard-selected-files)
|
||||
selected-project (mf/deref refs/dashboard-selected-project)
|
||||
|
@ -327,6 +314,12 @@
|
|||
(when-not (dnd/from-child? e)
|
||||
(reset! dragging? false))))
|
||||
|
||||
on-drop-success
|
||||
(fn []
|
||||
(st/emit! (dm/success (tr "dashboard.success-move-file"))
|
||||
(dd/fetch-recent-files)
|
||||
(dd/clear-selected-files)))
|
||||
|
||||
on-drop
|
||||
(mf/use-callback
|
||||
(mf/deps files selected-files)
|
||||
|
@ -335,11 +328,7 @@
|
|||
(when (not= selected-project project-id)
|
||||
(let [data {:ids selected-files
|
||||
:project-id project-id}
|
||||
|
||||
mdata {:on-success
|
||||
(st/emitf (dm/success (tr "dashboard.success-move-file"))
|
||||
(dd/fetch-recent-files {:team-id team-id})
|
||||
(dd/clear-selected-files))}]
|
||||
mdata {:on-success on-drop-success}]
|
||||
(st/emit! (dd/move-files (with-meta data mdata)))))))]
|
||||
|
||||
[:section.dashboard-grid {:on-drag-enter on-drag-enter
|
||||
|
@ -355,8 +344,7 @@
|
|||
:team-id team-id
|
||||
:selected-files selected-files
|
||||
:on-load-more on-load-more
|
||||
:dragging? @dragging?
|
||||
:locale locale}]
|
||||
:dragging? @dragging?}]
|
||||
|
||||
:else
|
||||
[:& empty-placeholder {:dragging? @dragging?}])]))
|
||||
|
|
|
@ -10,20 +10,16 @@
|
|||
[app.main.store :as st]
|
||||
[app.main.ui.dashboard.grid :refer [grid]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.refs :as refs]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn files-ref
|
||||
[team-id]
|
||||
(l/derived (l/in [:shared-files team-id]) st/state))
|
||||
|
||||
(mf/defc libraries-page
|
||||
[{:keys [team] :as props}]
|
||||
(let [files-ref (mf/use-memo (mf/deps (:id team)) #(files-ref (:id team)))
|
||||
files-map (mf/deref files-ref)
|
||||
(let [files-map (mf/deref refs/dashboard-shared-files)
|
||||
files (->> (vals files-map)
|
||||
(sort-by :modified-at)
|
||||
(reverse))]
|
||||
|
@ -33,9 +29,11 @@
|
|||
(dom/set-html-title (tr "title.dashboard.shared-libraries"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))))
|
||||
(st/emit! (dd/fetch-shared-files {:team-id (:id team)})
|
||||
(dd/clear-selected-files))))
|
||||
(:name team))))))
|
||||
|
||||
(mf/use-effect
|
||||
(st/emitf (dd/fetch-shared-files)
|
||||
(dd/clear-selected-files)))
|
||||
|
||||
[:*
|
||||
[:header.dashboard-header
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
[app.main.data.modal :as modal]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.components.context-menu :refer [context-menu]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[beicon.core :as rx]
|
||||
|
@ -30,64 +30,53 @@
|
|||
current-team-id (mf/use-ctx ctx/current-team-id)
|
||||
teams (mf/use-state nil)
|
||||
|
||||
on-duplicate-success
|
||||
(fn [new-project]
|
||||
(st/emit! (dm/success (tr "dashboard.success-duplicate-project"))
|
||||
(rt/nav :dashboard-files
|
||||
{:team-id (:team-id new-project)
|
||||
:project-id (:id new-project)})))
|
||||
|
||||
on-duplicate
|
||||
(mf/use-callback
|
||||
(mf/deps project)
|
||||
#(let [on-success
|
||||
(fn [new-project]
|
||||
(st/emit! (dm/success (tr "dashboard.success-duplicate-project"))
|
||||
(rt/nav :dashboard-files
|
||||
{:team-id (:team-id new-project)
|
||||
:project-id (:id new-project)})))]
|
||||
(st/emit! (dd/duplicate-project
|
||||
(with-meta project {:on-success on-success})))))
|
||||
(fn []
|
||||
(st/emit! (dd/duplicate-project
|
||||
(with-meta project {:on-success on-duplicate-success}))))
|
||||
|
||||
toggle-pin
|
||||
(mf/use-callback
|
||||
(mf/deps project)
|
||||
(st/emitf (dd/toggle-project-pin project)))
|
||||
(st/emitf (dd/toggle-project-pin project))
|
||||
|
||||
on-move-success
|
||||
(fn [team-id]
|
||||
(st/emit! (dd/go-to-projects team-id)))
|
||||
|
||||
on-move
|
||||
(mf/use-callback
|
||||
(mf/deps project)
|
||||
(fn [team-id]
|
||||
(let [data {:id (:id project)
|
||||
:team-id team-id}
|
||||
|
||||
mdata {:on-success
|
||||
(st/emitf (rt/nav :dashboard-projects
|
||||
{:team-id team-id}))}]
|
||||
|
||||
(fn [team-id]
|
||||
(let [data {:id (:id project) :team-id team-id}
|
||||
mdata {:on-success #(on-move-success team-id)}]
|
||||
(st/emitf (dm/success (tr "dashboard.success-move-project"))
|
||||
(dd/move-project (with-meta data mdata))))))
|
||||
(dd/move-project (with-meta data mdata)))))
|
||||
|
||||
delete-fn
|
||||
(mf/use-callback
|
||||
(mf/deps project)
|
||||
(fn [event]
|
||||
(st/emit! (dm/success (tr "dashboard.success-delete-project"))
|
||||
(dd/delete-project project)
|
||||
(rt/nav :dashboard-projects {:team-id (:team-id project)}))))
|
||||
(fn [event]
|
||||
(st/emit! (dm/success (tr "dashboard.success-delete-project"))
|
||||
(dd/delete-project project)
|
||||
(dd/go-to-projects (:team-id project))))
|
||||
|
||||
on-delete
|
||||
(mf/use-callback
|
||||
(mf/deps project)
|
||||
(st/emitf (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-project-confirm.title")
|
||||
:message (tr "modals.delete-project-confirm.message")
|
||||
:accept-label (tr "modals.delete-project-confirm.accept")
|
||||
:on-accept delete-fn})))]
|
||||
(st/emitf
|
||||
(modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-project-confirm.title")
|
||||
:message (tr "modals.delete-project-confirm.message")
|
||||
:accept-label (tr "modals.delete-project-confirm.accept")
|
||||
:on-accept delete-fn}))]
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps show?)
|
||||
(fn []
|
||||
(if show?
|
||||
(->> (rp/query! :teams)
|
||||
(rx/map (fn [teams]
|
||||
(remove #(= (:id %) current-team-id) teams)))
|
||||
(rx/subs #(reset! teams %)))
|
||||
(reset! teams []))))
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(->> (rp/query! :teams)
|
||||
(rx/map (fn [teams]
|
||||
(into [] (remove #(= (:id %) current-team-id)) teams)))
|
||||
(rx/subs #(reset! teams %)))))
|
||||
|
||||
(when @teams
|
||||
[:& context-menu {:on-close on-menu-close
|
||||
|
|
|
@ -25,45 +25,29 @@
|
|||
|
||||
(mf/defc header
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [locale team] :as props}]
|
||||
(let [create #(st/emit! (dd/create-project {:team-id (:id team)}))]
|
||||
[]
|
||||
(let [create (st/emitf (dd/create-project))]
|
||||
[:header.dashboard-header
|
||||
[:div.dashboard-title
|
||||
[:h1 (t locale "dashboard.projects-title")]]
|
||||
[:h1 (tr "dashboard.projects-title")]]
|
||||
[:a.btn-secondary.btn-small {:on-click create}
|
||||
(t locale "dashboard.new-project")]]))
|
||||
|
||||
(defn files-ref
|
||||
[project-id]
|
||||
(l/derived (l/in [:files project-id]) st/state))
|
||||
|
||||
(defn recent-ref
|
||||
[project-id]
|
||||
(l/derived (l/in [:recent-files project-id]) st/state))
|
||||
(tr "dashboard.new-project")]]))
|
||||
|
||||
(mf/defc project-item
|
||||
[{:keys [project first? locale] :as props}]
|
||||
(let [files-ref (mf/use-memo (mf/deps project) #(files-ref (:id project)))
|
||||
recent-ref (mf/use-memo (mf/deps project) #(recent-ref (:id project)))
|
||||
|
||||
files-map (mf/deref files-ref)
|
||||
recent-ids (mf/deref recent-ref)
|
||||
|
||||
files (some->> recent-ids
|
||||
(map #(get files-map %))
|
||||
(sort-by :modified-at)
|
||||
(filter some?)
|
||||
(reverse))
|
||||
[{:keys [project first? files] :as props}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
|
||||
project-id (:id project)
|
||||
team-id (:team-id project)
|
||||
file-count (or (:count project) 0)
|
||||
|
||||
dstate (mf/deref refs/dashboard-local)
|
||||
edit-id (:project-for-edit dstate)
|
||||
local (mf/use-state {:menu-open false
|
||||
:menu-pos nil
|
||||
:edition? (= (:id project) edit-id)})
|
||||
dstate (mf/deref refs/dashboard-local)
|
||||
edit-id (:project-for-edit dstate)
|
||||
|
||||
local
|
||||
(mf/use-state {:menu-open false
|
||||
:menu-pos nil
|
||||
:edition? (= (:id project) edit-id)})
|
||||
|
||||
on-nav
|
||||
(mf/use-callback
|
||||
|
@ -145,20 +129,26 @@
|
|||
|
||||
[:a.btn-secondary.btn-small
|
||||
{:on-click create-file}
|
||||
(t locale "dashboard.new-file")]]
|
||||
(tr "dashboard.new-file")]]
|
||||
|
||||
[:& line-grid
|
||||
{:project-id (:id project)
|
||||
:project project
|
||||
:team-id team-id
|
||||
:on-load-more on-nav
|
||||
:files files}]]))
|
||||
|
||||
|
||||
(def recent-files-ref
|
||||
(l/derived :dashboard-recent-files st/state))
|
||||
|
||||
(mf/defc projects-section
|
||||
[{:keys [team projects] :as props}]
|
||||
(let [projects (->> (vals projects)
|
||||
(sort-by :modified-at)
|
||||
(reverse))
|
||||
locale (mf/deref i18n/locale)]
|
||||
(let [projects (->> (vals projects)
|
||||
(sort-by :modified-at)
|
||||
(reverse))
|
||||
recent-map (mf/deref recent-files-ref)
|
||||
files (vals recent-map)]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps team)
|
||||
|
@ -166,18 +156,20 @@
|
|||
(dom/set-html-title (tr "title.dashboard.projects"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))))
|
||||
(st/emit! (dd/fetch-recent-files {:team-id (:id team)})
|
||||
(dd/clear-selected-files))))
|
||||
(:name team))))))
|
||||
|
||||
(mf/use-effect
|
||||
(st/emitf (dd/fetch-recent-files)
|
||||
(dd/clear-selected-files)))
|
||||
|
||||
(when (seq projects)
|
||||
[:*
|
||||
[:& header {:locale locale
|
||||
:team team}]
|
||||
[:& header]
|
||||
[:section.dashboard-container
|
||||
(for [project projects]
|
||||
[:& project-item {:project project
|
||||
:locale locale
|
||||
:first? (= project (first projects))
|
||||
:key (:id project)}])]])))
|
||||
(for [{:keys [id] :as project} projects]
|
||||
(let [files (some->> files (filterv #(= id (:project-id %))))]
|
||||
[:& project-item {:project project
|
||||
:files files
|
||||
:first? (= project (first projects))
|
||||
:key (:id project)}]))]])))
|
||||
|
||||
|
|
|
@ -7,56 +7,52 @@
|
|||
(ns app.main.ui.dashboard.search
|
||||
(:require
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.dashboard.grid :refer [grid]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [t]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
|
||||
(def result-ref
|
||||
(l/derived (l/in [:dashboard-local :search-result]) st/state))
|
||||
|
||||
(mf/defc search-page
|
||||
[{:keys [team search-term] :as props}]
|
||||
(let [result (mf/deref result-ref)
|
||||
locale (mf/deref i18n/locale)]
|
||||
|
||||
(let [result (mf/deref refs/dashboard-search-result)]
|
||||
(mf/use-effect
|
||||
(mf/deps team search-term)
|
||||
(mf/deps team)
|
||||
(fn []
|
||||
(dom/set-html-title (t locale "title.dashboard.search"
|
||||
(dom/set-html-title (tr "title.dashboard.search"
|
||||
(if (:is-default team)
|
||||
(t locale "dashboard.your-penpot")
|
||||
(:name team))))
|
||||
(when search-term
|
||||
(st/emit! (dd/search-files {:team-id (:id team)
|
||||
:search-term search-term})
|
||||
(dd/clear-selected-files)))))
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))))))
|
||||
(mf/use-effect
|
||||
(mf/deps search-term)
|
||||
(fn []
|
||||
(st/emit! (dd/search {:search-term search-term})
|
||||
(dd/clear-selected-files))))
|
||||
|
||||
[:*
|
||||
[:header.dashboard-header
|
||||
[:div.dashboard-title
|
||||
[:h1 (t locale "dashboard.title-search")]]]
|
||||
[:h1 (tr "dashboard.title-search")]]]
|
||||
|
||||
[:section.dashboard-container.search
|
||||
(cond
|
||||
(empty? search-term)
|
||||
[:div.grid-empty-placeholder
|
||||
[:div.icon i/search]
|
||||
[:div.text (t locale "dashboard.type-something")]]
|
||||
[:div.text (tr "dashboard.type-something")]]
|
||||
|
||||
(nil? result)
|
||||
[:div.grid-empty-placeholder
|
||||
[:div.icon i/search]
|
||||
[:div.text (t locale "dashboard.searching-for" search-term)]]
|
||||
[:div.text (tr "dashboard.searching-for" search-term)]]
|
||||
|
||||
(empty? result)
|
||||
[:div.grid-empty-placeholder
|
||||
[:div.icon i/search]
|
||||
[:div.text (t locale "dashboard.no-matches-for" search-term)]]
|
||||
[:div.text (tr "dashboard.no-matches-for" search-term)]]
|
||||
|
||||
:else
|
||||
[:& grid {:files result
|
||||
|
|
|
@ -56,8 +56,7 @@
|
|||
(mf/use-callback
|
||||
(mf/deps item)
|
||||
(fn []
|
||||
(st/emit! (rt/nav :dashboard-files {:team-id (:team-id item)
|
||||
:project-id (:id item)}))))
|
||||
(st/emit! (dd/go-to-files (:id item)))))
|
||||
|
||||
on-menu-click
|
||||
(mf/use-callback
|
||||
|
@ -103,21 +102,22 @@
|
|||
(when-not (dnd/from-child? e)
|
||||
(swap! local assoc :dragging? false))))
|
||||
|
||||
on-drop-success
|
||||
(mf/use-callback
|
||||
(mf/deps (:id item))
|
||||
(st/emitf (dm/success (tr "dashboard.success-move-file"))
|
||||
(dd/go-to-files (:id item))))
|
||||
|
||||
on-drop
|
||||
(mf/use-callback
|
||||
(mf/deps item selected-files)
|
||||
(fn [e]
|
||||
(swap! local assoc :dragging? false)
|
||||
(when (not= selected-project (:id item))
|
||||
(let [data {:ids selected-files
|
||||
:project-id (:id item)}
|
||||
|
||||
mdata {:on-success
|
||||
(st/emitf (dm/success (tr "dashboard.success-move-file"))
|
||||
(rt/nav :dashboard-files
|
||||
{:team-id team-id
|
||||
:project-id (:id item)}))}]
|
||||
(st/emit! (dd/move-files (with-meta data mdata)))))))]
|
||||
(mf/deps item selected-files)
|
||||
(fn [e]
|
||||
(swap! local assoc :dragging? false)
|
||||
(when (not= selected-project (:id item))
|
||||
(let [data {:ids selected-files
|
||||
:project-id (:id item)}
|
||||
mdata {:on-success on-drop-success}]
|
||||
(st/emit! (dd/move-files (with-meta data mdata)))))))]
|
||||
|
||||
[:*
|
||||
[:li {:class (if selected? "current"
|
||||
|
@ -151,12 +151,9 @@
|
|||
(mf/deps team-id)
|
||||
(fn [event]
|
||||
(reset! focused? true)
|
||||
(let [target (dom/get-target event)
|
||||
value (dom/get-value target)]
|
||||
(dom/select-text! target)
|
||||
(if (empty? value)
|
||||
(emit! (rt/nav :dashboard-search {:team-id team-id} {}))
|
||||
(emit! (rt/nav :dashboard-search {:team-id team-id} {:search-term value}))))))
|
||||
(let [value (dom/get-target-val event)]
|
||||
(dom/select-text! (dom/get-target event))
|
||||
(emit! (dd/go-to-search value)))))
|
||||
|
||||
on-search-blur
|
||||
(mf/use-callback
|
||||
|
@ -167,9 +164,8 @@
|
|||
(mf/use-callback
|
||||
(mf/deps team-id)
|
||||
(fn [event]
|
||||
(let [value (-> (dom/get-target event)
|
||||
(dom/get-value))]
|
||||
(emit! (rt/nav :dashboard-search {:team-id team-id} {:search-term value})))))
|
||||
(let [value (dom/get-target-val event)]
|
||||
(emit! (dd/go-to-search value)))))
|
||||
|
||||
on-clear-click
|
||||
(mf/use-callback
|
||||
|
@ -178,7 +174,7 @@
|
|||
(let [search-input (dom/get-element "search-input")]
|
||||
(dom/clean-value! search-input)
|
||||
(dom/focus! search-input)
|
||||
(emit! (rt/nav :dashboard-search {:team-id team-id} {})))))]
|
||||
(emit! (dd/go-to-search)))))]
|
||||
|
||||
[:form.sidebar-search
|
||||
[:input.input-text
|
||||
|
@ -213,9 +209,8 @@
|
|||
|
||||
team-selected
|
||||
(mf/use-callback
|
||||
(fn [team-id]
|
||||
(du/set-current-team! team-id)
|
||||
(st/emit! (rt/nav :dashboard-projects {:team-id team-id}))))]
|
||||
(fn [team-id]
|
||||
(st/emit! (dd/go-to-projects team-id))))]
|
||||
|
||||
[:ul.dropdown.teams-dropdown
|
||||
[:li.title (tr "dashboard.switch-team")]
|
||||
|
@ -243,21 +238,17 @@
|
|||
{::mf/register modal/components
|
||||
::mf/register-as ::leave-and-reassign}
|
||||
[{:keys [members profile team accept]}]
|
||||
(let [form (fm/use-form :spec ::leave-modal-form :initial {})
|
||||
not-current-user? (fn [{:keys [id]}] (not= id (:id profile)))
|
||||
members (->> members (filterv not-current-user?))
|
||||
options (into [{:value "" :label (tr "modals.leave-and-reassign.select-memeber-to-promote")}]
|
||||
(map #(hash-map :label (:name %) :value (str (:id %))) members))
|
||||
|
||||
on-cancel
|
||||
(mf/use-callback (st/emitf (modal/hide)))
|
||||
(let [form (fm/use-form :spec ::leave-modal-form :initial {})
|
||||
members (some->> members (filterv #(not= (:id %) (:id profile))))
|
||||
options (into [{:value ""
|
||||
:label (tr "modals.leave-and-reassign.select-memeber-to-promote")}]
|
||||
(map #(hash-map :label (:name %) :value (str (:id %))) members))
|
||||
|
||||
on-cancel (st/emitf (modal/hide))
|
||||
on-accept
|
||||
(mf/use-callback
|
||||
(mf/deps form)
|
||||
(fn [event]
|
||||
(let [member-id (get-in @form [:clean-data :member-id])]
|
||||
(accept member-id))))]
|
||||
(fn [event]
|
||||
(let [member-id (get-in @form [:clean-data :member-id])]
|
||||
(accept member-id)))]
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.confirm-dialog
|
||||
|
@ -292,92 +283,60 @@
|
|||
:value (tr "modals.leave-and-reassign.promote-and-leave")
|
||||
:on-click on-accept}]]]]]))
|
||||
|
||||
|
||||
(mf/defc team-options-dropdown
|
||||
[{:keys [team profile] :as props}]
|
||||
(let [members (mf/use-state [])
|
||||
(let [go-members (st/emitf (dd/go-to-team-members))
|
||||
go-settings (st/emitf (dd/go-to-team-settings))
|
||||
|
||||
go-members
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (rt/nav :dashboard-team-members {:team-id (:id team)})))
|
||||
|
||||
go-settings
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (rt/nav :dashboard-team-settings {:team-id (:id team)})))
|
||||
members-map (mf/deref refs/dashboard-team-members)
|
||||
members (vals members-map)
|
||||
|
||||
on-create-clicked
|
||||
(mf/use-callback
|
||||
(st/emitf (modal/show :team-form {})))
|
||||
(st/emitf (modal/show :team-form {}))
|
||||
|
||||
on-rename-clicked
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (modal/show :team-form {:team team})))
|
||||
(st/emitf (modal/show :team-form {:team team}))
|
||||
|
||||
on-leaved-success
|
||||
(mf/use-callback
|
||||
(mf/deps team profile)
|
||||
(fn []
|
||||
(let [team-id (:default-team-id profile)]
|
||||
(du/set-current-team! team-id)
|
||||
(st/emit! (modal/hide)
|
||||
(du/fetch-teams)
|
||||
(rt/nav :dashboard-projects {:team-id team-id})))))
|
||||
(fn []
|
||||
(st/emit! (modal/hide)
|
||||
(dd/go-to-projects (:default-team-id profile))))
|
||||
|
||||
leave-fn
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (dd/leave-team (with-meta team {:on-success on-leaved-success}))))
|
||||
(st/emitf (dd/leave-team (with-meta {} {:on-success on-leaved-success})))
|
||||
|
||||
leave-and-reassign-fn
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(fn [member-id]
|
||||
(let [team (assoc team :reassign-to member-id)]
|
||||
(st/emit! (dd/leave-team (with-meta team {:on-success on-leaved-success}))))))
|
||||
(fn [member-id]
|
||||
(let [params {:reassign-to member-id}]
|
||||
(st/emit! (dd/leave-team (with-meta params {:on-success on-leaved-success})))))
|
||||
|
||||
on-leave-clicked
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.leave-confirm.title")
|
||||
:message (tr "modals.leave-confirm.message")
|
||||
:accept-label (tr "modals.leave-confirm.accept")
|
||||
:on-accept leave-fn})))
|
||||
(st/emitf (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.leave-confirm.title")
|
||||
:message (tr "modals.leave-confirm.message")
|
||||
:accept-label (tr "modals.leave-confirm.accept")
|
||||
:on-accept leave-fn}))
|
||||
|
||||
on-leave-as-owner-clicked
|
||||
(mf/use-callback
|
||||
(mf/deps team @members)
|
||||
(st/emitf (modal/show
|
||||
{:type ::leave-and-reassign
|
||||
:profile profile
|
||||
:team team
|
||||
:accept leave-and-reassign-fn
|
||||
:members @members})))
|
||||
(st/emitf (modal/show
|
||||
{:type ::leave-and-reassign
|
||||
:profile profile
|
||||
:team team
|
||||
:members members
|
||||
:accept leave-and-reassign-fn}))
|
||||
|
||||
delete-fn
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (dd/delete-team (with-meta team {:on-success on-leaved-success}))))
|
||||
(st/emitf (dd/delete-team (with-meta team {:on-success on-leaved-success})))
|
||||
|
||||
on-delete-clicked
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-team-confirm.title")
|
||||
:message (tr "modals.delete-team-confirm.message")
|
||||
:accept-label (tr "modals.delete-team-confirm.accept")
|
||||
:on-accept delete-fn})))]
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps (:id team))
|
||||
(fn []
|
||||
(->> (rp/query! :team-members {:team-id (:id team)})
|
||||
(rx/subs #(reset! members %)))))
|
||||
(st/emitf
|
||||
(modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-team-confirm.title")
|
||||
:message (tr "modals.delete-team-confirm.message")
|
||||
:accept-label (tr "modals.delete-team-confirm.accept")
|
||||
:on-accept delete-fn}))]
|
||||
|
||||
[:ul.dropdown.options-dropdown
|
||||
[:li {:on-click go-members} (tr "labels.members")]
|
||||
|
@ -389,7 +348,7 @@
|
|||
(:is-owner team)
|
||||
[:li {:on-click on-leave-as-owner-clicked} (tr "dashboard.leave-team")]
|
||||
|
||||
(> (count @members) 1)
|
||||
(> (count members) 1)
|
||||
[:li {:on-click on-leave-clicked} (tr "dashboard.leave-team")])
|
||||
|
||||
|
||||
|
|
|
@ -32,22 +32,9 @@
|
|||
(mf/defc header
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [section team] :as props}]
|
||||
(let [go-members
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (rt/nav :dashboard-team-members {:team-id (:id team)})))
|
||||
|
||||
go-settings
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (rt/nav :dashboard-team-settings {:team-id (:id team)})))
|
||||
|
||||
invite-member
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (modal/show {:type ::invite-member
|
||||
:team team})))
|
||||
|
||||
(let [go-members (st/emitf (dd/go-to-team-members))
|
||||
go-settings (st/emitf (dd/go-to-team-settings))
|
||||
invite-member (st/emitf (modal/show {:type ::invite-member}))
|
||||
members-section? (= section :dashboard-team-members)
|
||||
settings-section? (= section :dashboard-team-settings)]
|
||||
|
||||
|
@ -69,6 +56,16 @@
|
|||
(tr "dashboard.invite-profile")]
|
||||
[:div])]))
|
||||
|
||||
(defn get-available-roles
|
||||
[]
|
||||
[{:value "" :label (tr "labels.role")}
|
||||
{:value "admin" :label (tr "labels.admin")}
|
||||
{:value "editor" :label (tr "labels.editor")}
|
||||
;; Temporarily disabled viewer role
|
||||
;; https://tree.taiga.io/project/uxboxproject/issue/1083
|
||||
;; {:value "viewer" :label (tr "labels.viewer")}
|
||||
])
|
||||
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::role ::us/keyword)
|
||||
(s/def ::invite-member-form
|
||||
|
@ -77,53 +74,40 @@
|
|||
(mf/defc invite-member-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as ::invite-member}
|
||||
[{:keys [team] :as props}]
|
||||
(let [roles [{:value "" :label (tr "labels.role")}
|
||||
{:value "admin" :label (tr "labels.admin")}
|
||||
{:value "editor" :label (tr "labels.editor")}]
|
||||
;; Temporarily disabled viewer role
|
||||
;; https://tree.taiga.io/project/uxboxproject/issue/1083
|
||||
;; {:value "viewer" :label (tr "labels.viewer")}]
|
||||
|
||||
initial (mf/use-memo (mf/deps team) (constantly {:team-id (:id team)
|
||||
:role "editor"}))
|
||||
[]
|
||||
(let [roles (mf/use-memo get-available-roles)
|
||||
initial (mf/use-memo (constantly {:role "editor"}))
|
||||
form (fm/use-form :spec ::invite-member-form
|
||||
:initial initial)
|
||||
on-success
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(st/emitf (dm/success (tr "notifications.invitation-email-sent"))
|
||||
(modal/hide)))
|
||||
(st/emitf (dm/success (tr "notifications.invitation-email-sent"))
|
||||
(modal/hide))
|
||||
|
||||
on-error
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(fn [form {:keys [type code] :as error}]
|
||||
(let [email (get @form [:data :email])]
|
||||
(cond
|
||||
(and (= :validation type)
|
||||
(= :profile-is-muted code))
|
||||
(dm/error (tr "errors.profile-is-muted"))
|
||||
(fn [form {:keys [type code] :as error}]
|
||||
(let [email (get @form [:data :email])]
|
||||
(cond
|
||||
(and (= :validation type)
|
||||
(= :profile-is-muted code))
|
||||
(dm/error (tr "errors.profile-is-muted"))
|
||||
|
||||
(and (= :validation type)
|
||||
(= :member-is-muted code))
|
||||
(dm/error (tr "errors.member-is-muted"))
|
||||
(and (= :validation type)
|
||||
(= :member-is-muted code))
|
||||
(dm/error (tr "errors.member-is-muted"))
|
||||
|
||||
(and (= :validation type)
|
||||
(= :email-has-permanent-bounces))
|
||||
(dm/error (tr "errors.email-has-permanent-bounces" email))
|
||||
(and (= :validation type)
|
||||
(= :email-has-permanent-bounces))
|
||||
(dm/error (tr "errors.email-has-permanent-bounces" email))
|
||||
|
||||
:else
|
||||
(dm/error (tr "errors.generic"))))))
|
||||
:else
|
||||
(dm/error (tr "errors.generic")))))
|
||||
|
||||
on-submit
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(fn [form]
|
||||
(let [params (:clean-data @form)
|
||||
mdata {:on-success (partial on-success form)
|
||||
:on-error (partial on-error form)}]
|
||||
(st/emit! (dd/invite-team-member (with-meta params mdata))))))]
|
||||
(fn [form]
|
||||
(let [params (:clean-data @form)
|
||||
mdata {:on-success (partial on-success form)
|
||||
:on-error (partial on-error form)}]
|
||||
(st/emit! (dd/invite-team-member (with-meta params mdata)))))]
|
||||
|
||||
[:div.modal.dashboard-invite-modal.form-container
|
||||
[:& fm/form {:on-submit on-submit :form form}
|
||||
|
@ -139,50 +123,39 @@
|
|||
[:div.action-buttons
|
||||
[:& fm/submit-button {:label (tr "modals.invite-member-confirm.accept")}]]]]))
|
||||
|
||||
|
||||
(mf/defc team-member
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [team member profile] :as props}]
|
||||
(let [show? (mf/use-state false)
|
||||
|
||||
set-role
|
||||
#(st/emit! (dd/update-team-member-role {:team-id (:id team)
|
||||
:member-id (:id member)
|
||||
:role %}))
|
||||
set-owner-fn
|
||||
(partial set-role :owner)
|
||||
(fn [role]
|
||||
(let [params {:member-id (:id member) :role role}]
|
||||
(st/emit! (dd/update-team-member-role params))))
|
||||
|
||||
set-admin
|
||||
(mf/use-callback (mf/deps team member) (partial set-role :admin))
|
||||
|
||||
set-editor
|
||||
(mf/use-callback (mf/deps team member) (partial set-role :editor))
|
||||
|
||||
set-viewer
|
||||
(mf/use-callback (mf/deps team member) (partial set-role :viewer))
|
||||
set-owner-fn (partial set-role :owner)
|
||||
set-admin (partial set-role :admin)
|
||||
set-editor (partial set-role :editor)
|
||||
set-viewer (partial set-role :viewer)
|
||||
|
||||
set-owner
|
||||
(mf/use-callback
|
||||
(mf/deps team member)
|
||||
(st/emitf (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.promote-owner-confirm.title")
|
||||
:message (tr "modals.promote-owner-confirm.message")
|
||||
:accept-label (tr "modals.promote-owner-confirm.accept")
|
||||
:on-accept set-owner-fn})))
|
||||
(st/emitf (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.promote-owner-confirm.title")
|
||||
:message (tr "modals.promote-owner-confirm.message")
|
||||
:accept-label (tr "modals.promote-owner-confirm.accept")
|
||||
:on-accept set-owner-fn}))
|
||||
|
||||
delete-fn
|
||||
(st/emitf (dd/delete-team-member {:team-id (:id team) :member-id (:id member)}))
|
||||
(st/emitf (dd/delete-team-member {:member-id (:id member)}))
|
||||
|
||||
delete
|
||||
(mf/use-callback
|
||||
(mf/deps team member)
|
||||
(st/emitf (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-team-member-confirm.title")
|
||||
:message (tr "modals.delete-team-member-confirm.message")
|
||||
:accept-label (tr "modals.delete-team-member-confirm.accept")
|
||||
:on-accept delete-fn})))]
|
||||
|
||||
(st/emitf (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-team-member-confirm.title")
|
||||
:message (tr "modals.delete-team-member-confirm.message")
|
||||
:accept-label (tr "modals.delete-team-member-confirm.accept")
|
||||
:on-accept delete-fn}))]
|
||||
|
||||
[:div.table-row
|
||||
[:div.table-field.name (:name member)]
|
||||
|
@ -244,23 +217,21 @@
|
|||
(for [item members]
|
||||
[:& team-member {:member item :team team :profile profile :key (:id item)}])]]))
|
||||
|
||||
(defn- members-ref
|
||||
[{:keys [id] :as team}]
|
||||
(l/derived (l/in [:team-members id]) st/state))
|
||||
|
||||
(mf/defc team-members-page
|
||||
[{:keys [team profile] :as props}]
|
||||
(let [members-ref (mf/use-memo (mf/deps team) #(members-ref team))
|
||||
members-map (mf/deref members-ref)]
|
||||
(let [members-map (mf/deref refs/dashboard-team-members)]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps team)
|
||||
(fn []
|
||||
(dom/set-html-title (tr "title.team-members"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))))
|
||||
(st/emit! (dd/fetch-team-members team))))
|
||||
(dom/set-html-title
|
||||
(tr "title.team-members"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))))))
|
||||
|
||||
(mf/use-effect
|
||||
(st/emitf (dd/fetch-team-members)))
|
||||
|
||||
[:*
|
||||
[:& header {:section :dashboard-team-members
|
||||
|
@ -270,42 +241,35 @@
|
|||
:team team
|
||||
:members-map members-map}]]]))
|
||||
|
||||
(defn- stats-ref
|
||||
[{:keys [id] :as team}]
|
||||
(l/derived (l/in [:team-stats id]) st/state))
|
||||
|
||||
(mf/defc team-settings-page
|
||||
[{:keys [team profile] :as props}]
|
||||
(let [finput (mf/use-ref)
|
||||
|
||||
members-ref (mf/use-memo (mf/deps team) #(members-ref team))
|
||||
members-map (mf/deref members-ref)
|
||||
|
||||
members-map (mf/deref refs/dashboard-team-members)
|
||||
owner (->> (vals members-map)
|
||||
(d/seek :is-owner))
|
||||
|
||||
stats-ref (mf/use-memo (mf/deps team) #(stats-ref team))
|
||||
stats (mf/deref stats-ref)
|
||||
stats (mf/deref refs/dashboard-team-stats)
|
||||
|
||||
on-image-click
|
||||
(mf/use-callback #(dom/click (mf/ref-val finput)))
|
||||
|
||||
on-file-selected
|
||||
(mf/use-callback
|
||||
(mf/deps team)
|
||||
(fn [file]
|
||||
(st/emit! (dd/update-team-photo {:file file
|
||||
:team-id (:id team)}))))]
|
||||
(fn [file]
|
||||
(st/emit! (dd/update-team-photo {:file file})))]
|
||||
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps team)
|
||||
(fn []
|
||||
(dom/set-html-title (tr "title.team-settings"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))))
|
||||
(st/emit! (dd/fetch-team-members team)
|
||||
(dd/fetch-team-stats team))))
|
||||
(mf/deps team)
|
||||
(fn []
|
||||
(dom/set-html-title (tr "title.team-settings"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))))))
|
||||
|
||||
(mf/use-effect
|
||||
(st/emitf (dd/fetch-team-members)
|
||||
(dd/fetch-team-stats)))
|
||||
|
||||
[:*
|
||||
[:& header {:section :dashboard-team-settings
|
||||
|
|
|
@ -100,8 +100,13 @@
|
|||
(mf/use-layout-effect
|
||||
(mf/deps page-id)
|
||||
(fn []
|
||||
(st/emit! (dw/initialize-page page-id))
|
||||
(st/emitf (dw/finalize-page page-id))))
|
||||
(if (nil? page-id)
|
||||
(st/emit! (dw/go-to-page))
|
||||
(st/emit! (dw/initialize-page page-id)))
|
||||
|
||||
(fn []
|
||||
(when page-id
|
||||
(st/emitf (dw/finalize-page page-id))))))
|
||||
|
||||
(when page
|
||||
[:& workspace-content {:key page-id
|
||||
|
@ -116,7 +121,6 @@
|
|||
(mf/defc workspace
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [project-id file-id page-id layout-name] :as props}]
|
||||
|
||||
(let [file (mf/deref refs/workspace-file)
|
||||
project (mf/deref refs/workspace-project)
|
||||
layout (mf/deref refs/workspace-layout)]
|
||||
|
|
Loading…
Add table
Reference in a new issue