mirror of
https://github.com/penpot/penpot.git
synced 2025-01-25 07:58:49 -05:00
✨ Implement recent files (back and front).
This commit is contained in:
parent
4dad6bef40
commit
44e120d382
6 changed files with 72 additions and 42 deletions
|
@ -7,9 +7,11 @@
|
||||||
(ns uxbox.services.queries.project-files
|
(ns uxbox.services.queries.project-files
|
||||||
(:require
|
(:require
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[uxbox.db :as db]
|
[uxbox.db :as db]
|
||||||
[uxbox.services.queries :as sq]
|
[uxbox.services.queries :as sq]
|
||||||
|
[uxbox.services.util :as su]
|
||||||
[uxbox.util.blob :as blob]
|
[uxbox.util.blob :as blob]
|
||||||
[uxbox.util.spec :as us]))
|
[uxbox.util.spec :as us]))
|
||||||
|
|
||||||
|
@ -22,7 +24,7 @@
|
||||||
(s/def ::project-id ::us/uuid)
|
(s/def ::project-id ::us/uuid)
|
||||||
(s/def ::user ::us/uuid)
|
(s/def ::user ::us/uuid)
|
||||||
|
|
||||||
(def ^:private sql:generic-project-files
|
(su/defsql sql:generic-project-files
|
||||||
"select pf.*,
|
"select pf.*,
|
||||||
array_agg(pp.id) as pages
|
array_agg(pp.id) as pages
|
||||||
from project_files as pf
|
from project_files as pf
|
||||||
|
@ -31,21 +33,42 @@
|
||||||
left join project_pages as pp on (pf.id = pp.file_id)
|
left join project_pages as pp on (pf.id = pp.file_id)
|
||||||
where pu.user_id = $1
|
where pu.user_id = $1
|
||||||
and pu.can_edit = true
|
and pu.can_edit = true
|
||||||
group by pf.id
|
group by pf.id")
|
||||||
order by pf.created_at asc")
|
|
||||||
|
|
||||||
;; --- Query: Project Files
|
;; --- Query: Project Files
|
||||||
|
|
||||||
(def ^:private sql:project-files
|
(declare retrieve-recent-files)
|
||||||
(str "with files as (" sql:generic-project-files ")"
|
(declare retrieve-project-files)
|
||||||
" select * from files where project_id = $2"))
|
|
||||||
|
|
||||||
(s/def ::project-files
|
(s/def ::project-files
|
||||||
(s/keys :req-un [::user ::project-id]))
|
(s/keys :req-un [::user]
|
||||||
|
:opt-un [::project-id]))
|
||||||
|
|
||||||
(sq/defquery ::project-files
|
(sq/defquery ::project-files
|
||||||
[{:keys [user project-id] :as params}]
|
[{:keys [project-id] :as params}]
|
||||||
(-> (db/query db/pool [sql:project-files user project-id])
|
(if (nil? project-id)
|
||||||
|
(retrieve-recent-files db/pool params)
|
||||||
|
(retrieve-project-files db/pool params)))
|
||||||
|
|
||||||
|
(def ^:private sql:project-files
|
||||||
|
(str "with files as (" sql:generic-project-files ")"
|
||||||
|
" select * from files where project_id = $2"
|
||||||
|
" order by created_at asc"))
|
||||||
|
|
||||||
|
(defn retrieve-project-files
|
||||||
|
[conn {:keys [user project-id]}]
|
||||||
|
(-> (db/query conn [sql:project-files user project-id])
|
||||||
|
(p/then' (partial mapv decode-row))))
|
||||||
|
|
||||||
|
(su/defsql sql:recent-files
|
||||||
|
"with files as (~{sql:generic-project-files})
|
||||||
|
select * from files
|
||||||
|
order by modified_at desc
|
||||||
|
limit $2")
|
||||||
|
|
||||||
|
(defn retrieve-recent-files
|
||||||
|
[conn {:keys [user]}]
|
||||||
|
(-> (db/query conn [sql:recent-files user 20])
|
||||||
(p/then' (partial mapv decode-row))))
|
(p/then' (partial mapv decode-row))))
|
||||||
|
|
||||||
;; --- Query: Project File (By ID)
|
;; --- Query: Project File (By ID)
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[uxbox.db :as db]
|
[uxbox.db :as db]
|
||||||
[uxbox.services.queries :as sq]
|
[uxbox.services.queries :as sq]
|
||||||
|
[uxbox.services.util :as su]
|
||||||
[uxbox.util.blob :as blob]
|
[uxbox.util.blob :as blob]
|
||||||
[uxbox.util.spec :as us]))
|
[uxbox.util.spec :as us]))
|
||||||
|
|
||||||
|
@ -24,33 +25,20 @@
|
||||||
|
|
||||||
;; --- Query: Projects
|
;; --- Query: Projects
|
||||||
|
|
||||||
;; (def ^:private projects-sql
|
(su/defsql sql:projects
|
||||||
;; "select distinct on (p.id, p.created_at)
|
|
||||||
;; p.*,
|
|
||||||
;; array_agg(pg.id) over (
|
|
||||||
;; partition by p.id
|
|
||||||
;; order by pg.created_at
|
|
||||||
;; range between unbounded preceding and unbounded following
|
|
||||||
;; ) as pages
|
|
||||||
;; from projects as p
|
|
||||||
;; left join pages as pg
|
|
||||||
;; on (pg.project_id = p.id)
|
|
||||||
;; where p.user_id = $1
|
|
||||||
;; order by p.created_at asc")
|
|
||||||
|
|
||||||
(def ^:private projects-sql
|
|
||||||
"select p.*
|
"select p.*
|
||||||
from project_users as pu
|
from project_users as pu
|
||||||
inner join projects as p on (p.id = pu.project_id)
|
inner join projects as p on (p.id = pu.project_id)
|
||||||
where pu.can_edit = true
|
where pu.can_edit = true
|
||||||
and pu.user_id = $1;")
|
and pu.user_id = $1
|
||||||
|
order by p.created_at asc")
|
||||||
|
|
||||||
(s/def ::projects
|
(s/def ::projects
|
||||||
(s/keys :req-un [::user]))
|
(s/keys :req-un [::user]))
|
||||||
|
|
||||||
(sq/defquery ::projects
|
(sq/defquery ::projects
|
||||||
[{:keys [user] :as params}]
|
[{:keys [user] :as params}]
|
||||||
(-> (db/query db/pool [projects-sql user])
|
(-> (db/query db/pool [sql:projects user])
|
||||||
(p/then' (partial mapv decode-row))))
|
(p/then' (partial mapv decode-row))))
|
||||||
|
|
||||||
;; --- Helpers
|
;; --- Helpers
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
(ns uxbox.services.util
|
(ns uxbox.services.util
|
||||||
(:require
|
(:require
|
||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[vertx.core :as vc]
|
[vertx.core :as vc]
|
||||||
[uxbox.core :refer [system]]
|
[uxbox.core :refer [system]]
|
||||||
[uxbox.util.uuid :as uuid]
|
[uxbox.util.uuid :as uuid]
|
||||||
|
@ -24,6 +25,10 @@
|
||||||
;; (log/info "service" type "processed in" elapsed)
|
;; (log/info "service" type "processed in" elapsed)
|
||||||
;; data))})
|
;; data))})
|
||||||
|
|
||||||
|
(defmacro defsql
|
||||||
|
[sym str]
|
||||||
|
`(def ~sym (str/istr ~str)))
|
||||||
|
|
||||||
(defn raise-not-found-if-nil
|
(defn raise-not-found-if-nil
|
||||||
[v]
|
[v]
|
||||||
(if (nil? v)
|
(if (nil? v)
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
(watch [this state s]
|
(watch [this state s]
|
||||||
(swap! storage assoc :auth data)
|
(swap! storage assoc :auth data)
|
||||||
(rx/of du/fetch-profile
|
(rx/of du/fetch-profile
|
||||||
(rt/navigate :dashboard/projects)))))
|
(rt/navigate :dashboard-projects)))))
|
||||||
|
|
||||||
(defn logged-in?
|
(defn logged-in?
|
||||||
[v]
|
[v]
|
||||||
|
|
|
@ -122,8 +122,9 @@
|
||||||
(ptk/reify ::fetch-files
|
(ptk/reify ::fetch-files
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(->> (rp/query :project-files {:project-id project-id})
|
(let [params (if (nil? project-id) {} {:project-id project-id})]
|
||||||
(rx/map files-fetched)))))
|
(->> (rp/query :project-files params)
|
||||||
|
(rx/map files-fetched))))))
|
||||||
|
|
||||||
;; --- Fetch File (by ID)
|
;; --- Fetch File (by ID)
|
||||||
|
|
||||||
|
@ -247,3 +248,13 @@
|
||||||
(let [path-params {:file-id file-id}
|
(let [path-params {:file-id file-id}
|
||||||
query-params {:page-id (first page-ids)}]
|
query-params {:page-id (first page-ids)}]
|
||||||
(rx/of (rt/nav :workspace path-params query-params)))))))
|
(rx/of (rt/nav :workspace path-params query-params)))))))
|
||||||
|
|
||||||
|
(defn go-to-project
|
||||||
|
[id]
|
||||||
|
(s/assert (s/nilable ::us/uuid) id)
|
||||||
|
(ptk/reify ::go-to-project
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(if (nil? id)
|
||||||
|
(rx/of (rt/nav :dashboard-projects {} {}))
|
||||||
|
(rx/of (rt/nav :dashboard-projects {} {:project-id (str id)}))))))
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
(def +ordering-options+
|
(def +ordering-options+
|
||||||
{:name "ds.ordering.by-name"
|
{:name "ds.ordering.by-name"
|
||||||
|
:modified "ds.ordering.by-last-update"
|
||||||
:created "ds.ordering.by-creation-date"})
|
:created "ds.ordering.by-creation-date"})
|
||||||
|
|
||||||
;; --- Refs
|
;; --- Refs
|
||||||
|
@ -49,6 +50,7 @@
|
||||||
(case ordering
|
(case ordering
|
||||||
:name (cljs.core/sort-by :name files)
|
:name (cljs.core/sort-by :name files)
|
||||||
:created (reverse (cljs.core/sort-by :created-at files))
|
:created (reverse (cljs.core/sort-by :created-at files))
|
||||||
|
:modified (reverse (cljs.core/sort-by :modified-at files))
|
||||||
files))
|
files))
|
||||||
|
|
||||||
(defn contains-term?
|
(defn contains-term?
|
||||||
|
@ -65,8 +67,8 @@
|
||||||
;; --- Menu (Filter & Sort)
|
;; --- Menu (Filter & Sort)
|
||||||
|
|
||||||
(mf/defc menu
|
(mf/defc menu
|
||||||
[{:keys [opts files] :as props}]
|
[{:keys [id opts files] :as props}]
|
||||||
(let [ordering (:order opts :created)
|
(let [ordering (:order opts :modified)
|
||||||
filtering (:filter opts "")
|
filtering (:filter opts "")
|
||||||
|
|
||||||
on-term-change
|
on-term-change
|
||||||
|
@ -94,12 +96,14 @@
|
||||||
[:div
|
[:div
|
||||||
;; Sorting
|
;; Sorting
|
||||||
;; TODO: convert to separate component?
|
;; TODO: convert to separate component?
|
||||||
[:span (tr "ds.ordering")]
|
(when id
|
||||||
[:select.input-select {:on-change on-order-change
|
[:*
|
||||||
:value (pr-str ordering)}
|
[:span (tr "ds.ordering")]
|
||||||
(for [[key value] (seq +ordering-options+)]
|
[:select.input-select {:on-change on-order-change
|
||||||
(let [key (pr-str key)]
|
:value (pr-str ordering)}
|
||||||
[:option {:key key :value key} (tr value)]))]]
|
(for [[key value] (seq +ordering-options+)]
|
||||||
|
(let [key (pr-str key)]
|
||||||
|
[:option {:key key :value key} (tr value)]))]])]
|
||||||
|
|
||||||
;; Search
|
;; Search
|
||||||
;; TODO: convert to separate component?
|
;; TODO: convert to separate component?
|
||||||
|
@ -174,7 +178,7 @@
|
||||||
|
|
||||||
(mf/defc grid
|
(mf/defc grid
|
||||||
[{:keys [opts files] :as props}]
|
[{:keys [opts files] :as props}]
|
||||||
(let [order (:order opts :created)
|
(let [order (:order opts :modified)
|
||||||
filter (:filter opts "")
|
filter (:filter opts "")
|
||||||
files (->> files
|
files (->> files
|
||||||
(filter-by filter)
|
(filter-by filter)
|
||||||
|
@ -217,8 +221,7 @@
|
||||||
[{:keys [id name selected?] :as props}]
|
[{:keys [id name selected?] :as props}]
|
||||||
(let [local (mf/use-state {})
|
(let [local (mf/use-state {})
|
||||||
editable? (not (nil? id))
|
editable? (not (nil? id))
|
||||||
on-click (fn [event]
|
on-click #(st/emit! (udp/go-to-project id))]
|
||||||
(st/emit! (rt/nav :dashboard-projects {} {:project-id (str id)})))]
|
|
||||||
[:li {:on-click on-click
|
[:li {:on-click on-click
|
||||||
;; :on-double-click on-double-click
|
;; :on-double-click on-double-click
|
||||||
:class-name (when selected? "current")}
|
:class-name (when selected? "current")}
|
||||||
|
@ -248,7 +251,7 @@
|
||||||
"new project +"]]
|
"new project +"]]
|
||||||
|
|
||||||
[:li {:style {:marginBottom "20px"}
|
[:li {:style {:marginBottom "20px"}
|
||||||
:on-click #(st/emit! (rt/nav :dashboard/projects {} {}))
|
:on-click #(st/emit! (udp/go-to-project nil))
|
||||||
:class-name (when (nil? id) "current")}
|
:class-name (when (nil? id) "current")}
|
||||||
[:span.element-title "Recent"]]
|
[:span.element-title "Recent"]]
|
||||||
|
|
||||||
|
@ -273,7 +276,7 @@
|
||||||
(let [opts (mf/deref opts-iref)
|
(let [opts (mf/deref opts-iref)
|
||||||
files (mf/deref files-ref)]
|
files (mf/deref files-ref)]
|
||||||
[:*
|
[:*
|
||||||
[:& menu {:opts opts :files files}]
|
[:& menu {:id id :opts opts :files files}]
|
||||||
[:section.dashboard-grid.library
|
[:section.dashboard-grid.library
|
||||||
[:& grid {:id id :opts opts :files files}]]]))
|
[:& grid {:id id :opts opts :files files}]]]))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue