mirror of
https://github.com/penpot/penpot.git
synced 2025-02-09 16:48:16 -05:00
Project and files edition and deletion
This commit is contained in:
parent
8b0eab5e90
commit
b210e84dd3
9 changed files with 143 additions and 63 deletions
|
@ -103,12 +103,12 @@
|
|||
"update file
|
||||
set name = $2
|
||||
where id = $1
|
||||
and deleted_at is null")
|
||||
and deleted_at is null
|
||||
returning *")
|
||||
|
||||
(defn- rename-file
|
||||
[conn {:keys [id name] :as params}]
|
||||
(-> (db/query-one conn [sql:rename-file id name])
|
||||
(p/then' su/constantly-nil)))
|
||||
(db/query-one conn [sql:rename-file id name]))
|
||||
|
||||
|
||||
;; --- Mutation: Delete Project File
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
from project as p
|
||||
inner join team_profile_rel as tpr on (tpr.team_id = p.team_id)
|
||||
where tpr.profile_id = $1
|
||||
and p.deleted_at is null
|
||||
and (tpr.is_admin = true or
|
||||
tpr.is_owner = true or
|
||||
tpr.can_edit = true)
|
||||
|
@ -36,6 +37,7 @@
|
|||
from project as p
|
||||
inner join project_profile_rel as ppr on (ppr.project_id = p.id)
|
||||
where ppr.profile_id = $1
|
||||
and p.deleted_at is null
|
||||
and (ppr.is_admin = true or
|
||||
ppr.is_owner = true or
|
||||
ppr.can_edit = true)
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
{
|
||||
"dashboard.grid.delete" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:88" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:73", "src/uxbox/main/ui/dashboard/grid.cljs:91" ],
|
||||
"translations" : {
|
||||
"en" : "Delete"
|
||||
}
|
||||
},
|
||||
"dashboard.grid.edit" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:87" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:72", "src/uxbox/main/ui/dashboard/grid.cljs:90" ],
|
||||
"translations" : {
|
||||
"en" : "Edit"
|
||||
}
|
||||
},
|
||||
"dashboard.grid.empty-files" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:110" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:113" ],
|
||||
"translations" : {
|
||||
"en" : "You still have no files here"
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
|||
"unused" : true
|
||||
},
|
||||
"dashboard.header.draft" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:36" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:58" ],
|
||||
"translations" : {
|
||||
"en" : "Draft"
|
||||
}
|
||||
|
@ -45,11 +45,17 @@
|
|||
"unused" : true
|
||||
},
|
||||
"dashboard.header.new-file" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:41" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:78" ],
|
||||
"translations" : {
|
||||
"en" : "+ New file"
|
||||
}
|
||||
},
|
||||
"dashboard.header.new-project" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/recent_files.cljs:54" ],
|
||||
"translations" : {
|
||||
"en" : "+ New project"
|
||||
}
|
||||
},
|
||||
"dashboard.header.profile-menu.logout" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/profile.cljs:59", "src/uxbox/main/ui/workspace/header.cljs:93" ],
|
||||
"translations" : {
|
||||
|
@ -72,7 +78,7 @@
|
|||
}
|
||||
},
|
||||
"dashboard.header.project" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:37" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:74" ],
|
||||
"translations" : {
|
||||
"en" : "Project %s"
|
||||
}
|
||||
|
@ -374,7 +380,7 @@
|
|||
"unused" : true
|
||||
},
|
||||
"ds.new-file" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:106", "src/uxbox/main/ui/dashboard/grid.cljs:112" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:109", "src/uxbox/main/ui/dashboard/grid.cljs:115" ],
|
||||
"translations" : {
|
||||
"en" : "+ New File",
|
||||
"fr" : null
|
||||
|
@ -451,7 +457,7 @@
|
|||
}
|
||||
},
|
||||
"ds.updated-at" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:34" ],
|
||||
"used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:35" ],
|
||||
"translations" : {
|
||||
"en" : "Updated: %s",
|
||||
"fr" : "Mis à jour: %s"
|
||||
|
@ -528,14 +534,14 @@
|
|||
}
|
||||
},
|
||||
"errors.generic" : {
|
||||
"used-in" : [ "src/uxbox/main/ui.cljs:154" ],
|
||||
"used-in" : [ "src/uxbox/main/ui.cljs:160" ],
|
||||
"translations" : {
|
||||
"en" : "Something wrong has happened.",
|
||||
"fr" : "Quelque chose c'est mal passé."
|
||||
}
|
||||
},
|
||||
"errors.network" : {
|
||||
"used-in" : [ "src/uxbox/main/ui.cljs:148" ],
|
||||
"used-in" : [ "src/uxbox/main/ui.cljs:154" ],
|
||||
"translations" : {
|
||||
"en" : "Unable to connect to backend server.",
|
||||
"fr" : "Impossible de se connecter au serveur principal."
|
||||
|
|
|
@ -272,6 +272,10 @@
|
|||
|
||||
}
|
||||
|
||||
.project-th-actions.force-display {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// IMAGES SECTION
|
||||
|
|
|
@ -15,11 +15,22 @@
|
|||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
.element-name {
|
||||
margin-right: $small;
|
||||
}
|
||||
|
||||
.btn-dashboard {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: $color-black;
|
||||
height: 14px;
|
||||
margin-right: $x-small;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.main-logo {
|
||||
|
@ -70,6 +81,7 @@
|
|||
|
||||
.dashboard-title {
|
||||
color: $color-black;
|
||||
display: flex;
|
||||
font-size: $fs15;
|
||||
font-weight: normal;
|
||||
margin: 0 $x-big;
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
(watch [_ state stream]
|
||||
(let [local (:dashboard-local state)]
|
||||
(rx/of (fetch-files (:project-id local))
|
||||
(fetch-projects (:team-id local) (:project-id local)))))))
|
||||
(fetch-projects (:team-id local)))))))
|
||||
|
||||
|
||||
(defn initialize-recent
|
||||
|
@ -104,7 +104,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [local (:dashboard-local state)]
|
||||
(rx/of (fetch-projects (:team-id local) (:project-id nil))
|
||||
(rx/of (fetch-projects (:team-id local))
|
||||
(fetch-recent-files (:team-id local)))))))
|
||||
|
||||
|
||||
|
@ -122,7 +122,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [local (:dashboard-local state)]
|
||||
(rx/of (fetch-projects (:team-id local) (:project-id local))
|
||||
(rx/of (fetch-projects (:team-id local))
|
||||
(fetch-files (:project-id local)))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -141,21 +141,15 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/query :projects-by-team {:team-id team-id})
|
||||
(rx/map (projects-fetched project-id))))))
|
||||
(rx/map projects-fetched)))))
|
||||
|
||||
(defn projects-fetched
|
||||
[project-id]
|
||||
(us/assert (s/nilable ::us/uuid) project-id)
|
||||
(fn [projects]
|
||||
(us/verify (s/every ::project) projects)
|
||||
(ptk/reify ::projects-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [find-project #(first (filter (fn [p] (= (:id p) %1)) projects))
|
||||
set-project #(assoc %1 :project (find-project project-id))
|
||||
assoc-project #(assoc-in %1 [:projects (:id %2)] %2)
|
||||
reduce-projects #(reduce assoc-project %1 projects)]
|
||||
(-> state set-project reduce-projects))))))
|
||||
[projects]
|
||||
(us/verify (s/every ::project) projects)
|
||||
(ptk/reify ::projects-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :projects (d/index-by :id projects)))))
|
||||
|
||||
;; --- Search Files
|
||||
|
||||
|
@ -221,7 +215,17 @@
|
|||
(ptk/reify ::recent-files-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :recent-files recent-files))))
|
||||
(let [flatten-files #(reduce (fn [acc [project-id files]]
|
||||
(merge acc (d/index-by :id files)))
|
||||
{}
|
||||
%1)
|
||||
extract-ids #(reduce (fn [acc [project-id files]]
|
||||
(assoc acc project-id (map :id files)))
|
||||
{}
|
||||
%1)]
|
||||
(assoc state
|
||||
:files (flatten-files recent-files)
|
||||
:recent-file-ids (extract-ids recent-files))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Modification
|
||||
|
@ -229,6 +233,8 @@
|
|||
|
||||
;; --- Create Project
|
||||
|
||||
(declare project-created)
|
||||
|
||||
(def create-project
|
||||
(ptk/reify ::create-project
|
||||
ptk/WatchEvent
|
||||
|
@ -236,8 +242,15 @@
|
|||
(let [name (str "New Project " (gensym "p"))
|
||||
team-id (get-in state [:dashboard-local :team-id])]
|
||||
(->> (rp/mutation! :create-project {:name name :team-id team-id})
|
||||
(rx/map (fn [data]
|
||||
(projects-fetched [data]))))))))
|
||||
(rx/map project-created))))))
|
||||
|
||||
(defn project-created
|
||||
[data]
|
||||
(us/verify ::project data)
|
||||
(ptk/reify ::project-created
|
||||
ptk/UpdateEvent
|
||||
(update [this state]
|
||||
(update state :projects assoc (:id data) data))))
|
||||
|
||||
;; --- Rename Project
|
||||
|
||||
|
@ -285,7 +298,7 @@
|
|||
(->> (rp/mutation :delete-file {:id id})
|
||||
(rx/ignore)))))
|
||||
|
||||
;; --- Rename Project
|
||||
;; --- Rename File
|
||||
|
||||
(defn rename-file
|
||||
[id name]
|
||||
|
@ -299,11 +312,8 @@
|
|||
(watch [_ state stream]
|
||||
(let [local (:dashboard-local state)
|
||||
params {:id id :name name}]
|
||||
;; NOTE: this is a temporal (quick & dirty) solution for
|
||||
;; refreshing the results; we need to think in a better way to
|
||||
;; do it instead of a simple and complete data refresh.
|
||||
(->> (rp/mutation :rename-file params)
|
||||
(rx/map (fn [_] (initialize-recent (:team-id local)))))))))
|
||||
(rx/ignore))))))
|
||||
|
||||
|
||||
;; --- Create File
|
||||
|
@ -312,7 +322,7 @@
|
|||
|
||||
(defn create-file
|
||||
[project-id]
|
||||
(ptk/reify ::create-draft-file
|
||||
(ptk/reify ::create-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [name (str "New File " (gensym "p"))
|
||||
|
@ -323,12 +333,10 @@
|
|||
(defn file-created
|
||||
[data]
|
||||
(us/verify ::file data)
|
||||
(ptk/reify ::create-draft-file
|
||||
(ptk/reify ::file-created
|
||||
ptk/UpdateEvent
|
||||
(update [this state]
|
||||
(-> state
|
||||
(update :files assoc (:id data) data)
|
||||
(update-in [:recent-files (:project-id data)] conj data)))))
|
||||
(update state :files assoc (:id data) data))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
||||
[uxbox.main.ui.components.context-menu :refer [context-menu]]
|
||||
[uxbox.util.data :refer [classnames]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :as i18n :refer [t tr]]
|
||||
[uxbox.util.router :as rt]
|
||||
|
@ -51,7 +52,9 @@
|
|||
(st/emit! (dsh/rename-file (:id file) name))
|
||||
(swap! local assoc :edition false))
|
||||
|
||||
on-key-down #(when (kbd/enter? %) (on-blur %))
|
||||
on-key-down #(cond
|
||||
(kbd/enter? %) (on-blur %)
|
||||
(kbd/esc? %) (swap! local assoc :edition false))
|
||||
on-menu-click #(do
|
||||
(dom/stop-propagation %)
|
||||
(swap! local assoc :menu-open true))
|
||||
|
@ -68,11 +71,11 @@
|
|||
:auto-focus true
|
||||
:on-key-down on-key-down
|
||||
:on-blur on-blur
|
||||
;; :on-click on-edit
|
||||
:default-value (:name file)}]
|
||||
[:h3 (:name file)])
|
||||
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
|
||||
[:div.project-th-actions
|
||||
[:div.project-th-actions {:class (classnames
|
||||
:force-display (:menu-open @local))}
|
||||
;; [:div.project-th-icon.pages
|
||||
;; i/page
|
||||
;; #_[:span (:total-pages project)]]
|
||||
|
|
|
@ -12,14 +12,20 @@
|
|||
(:require
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.util.i18n :as i18n :refer [t]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.main.data.dashboard :as dsh]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
||||
[uxbox.main.ui.components.context-menu :refer [context-menu]]
|
||||
[uxbox.main.ui.dashboard.grid :refer [grid]]))
|
||||
|
||||
(def project-ref
|
||||
(-> (l/key :project)
|
||||
(def projects-ref
|
||||
(-> (l/key :projects)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def files-ref
|
||||
|
@ -28,16 +34,47 @@
|
|||
(l/derive st/state)))
|
||||
|
||||
(mf/defc project-header
|
||||
[{:keys [profile] :as props}]
|
||||
(let [project (mf/deref project-ref)
|
||||
locale (i18n/use-locale)]
|
||||
[{:keys [team-id project-id] :as props}]
|
||||
(let [local (mf/use-state {:menu-open false
|
||||
:edition false})
|
||||
projects (mf/deref projects-ref)
|
||||
project (get projects project-id)
|
||||
locale (i18n/use-locale)
|
||||
on-menu-click #(swap! local assoc :menu-open true)
|
||||
on-menu-close #(swap! local assoc :menu-open false)
|
||||
on-edit #(swap! local assoc :edition true :menu-open false)
|
||||
on-blur #(let [name (-> % dom/get-target dom/get-value)]
|
||||
(st/emit! (dsh/rename-project project-id name))
|
||||
(swap! local assoc :edition false))
|
||||
on-key-down #(cond
|
||||
(kbd/enter? %) (on-blur %)
|
||||
(kbd/esc? %) (swap! local assoc :edition false))
|
||||
delete-fn #(do
|
||||
(st/emit! (dsh/delete-project project-id))
|
||||
(st/emit! (rt/nav :dashboard-team {:team-id team-id})))
|
||||
on-delete #(modal/show! confirm-dialog {:on-accept delete-fn})]
|
||||
[:header#main-bar.main-bar
|
||||
(if (:is-default project)
|
||||
[:h1.dashboard-title (t locale "dashboard.header.draft")]
|
||||
[:h1.dashboard-title (t locale "dashboard.header.project" (:name project))])
|
||||
[:*
|
||||
(if (:edition @local)
|
||||
[:input.element-name {:type "text"
|
||||
:auto-focus true
|
||||
:on-key-down on-key-down
|
||||
:on-blur on-blur
|
||||
:default-value (:name project)}]
|
||||
[:h1.dashboard-title
|
||||
[:div.main-bar-icon
|
||||
{:on-click on-menu-click}
|
||||
i/actions]
|
||||
[:& context-menu {:on-close on-menu-close
|
||||
:show (:menu-open @local)
|
||||
:options [[(t locale "dashboard.grid.edit") on-edit]
|
||||
[(t locale "dashboard.grid.delete") on-delete]]}]
|
||||
(t locale "dashboard.header.project" (:name project))])])
|
||||
[:a.btn-dashboard {:on-click #(do
|
||||
(dom/prevent-default %)
|
||||
(st/emit! (dsh/create-file (:id project))))}
|
||||
(st/emit! (dsh/create-file project-id)))}
|
||||
(t locale "dashboard.header.new-file")]]))
|
||||
|
||||
(mf/defc project-page
|
||||
|
@ -47,10 +84,10 @@
|
|||
(reverse))]
|
||||
(mf/use-effect
|
||||
{:fn #(st/emit! (dsh/initialize-project team-id project-id))
|
||||
:deps (mf/deps team-id project-id)})
|
||||
:deps (mf/deps section team-id project-id)})
|
||||
|
||||
[:*
|
||||
[:& project-header]
|
||||
[:& project-header {:team-id team-id :project-id project-id}]
|
||||
[:section.projects-page
|
||||
[:& grid { :id project-id :files files :hide-new? true}]]]))
|
||||
|
||||
|
|
|
@ -35,8 +35,12 @@
|
|||
(-> (l/key :projects)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def recent-files-ref
|
||||
(-> (l/key :recent-files)
|
||||
(def recent-file-ids-ref
|
||||
(-> (l/key :recent-file-ids)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def files-ref
|
||||
(-> (l/key :files)
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Component: Recent files
|
||||
|
@ -46,7 +50,8 @@
|
|||
(let [locale (i18n/use-locale)]
|
||||
[:header#main-bar.main-bar
|
||||
[:h1.dashboard-title "Recent"]
|
||||
[:a.btn-dashboard "+ New project"]]))
|
||||
[:a.btn-dashboard {:on-click #(st/emit! dsh/create-project)}
|
||||
(t locale "dashboard.header.new-project")]]))
|
||||
|
||||
(mf/defc recent-project
|
||||
[{:keys [project files first? locale] :as props}]
|
||||
|
@ -60,12 +65,12 @@
|
|||
(dt/timeago {:locale locale}))]
|
||||
[:span.recent-files-row-title-info (str ", " time)])]
|
||||
[:& grid {:id (:id project)
|
||||
:files (or files [])
|
||||
:files files
|
||||
:hide-new? true}]]))
|
||||
|
||||
|
||||
(mf/defc recent-files-page
|
||||
[{:keys [section team-id] :as props}]
|
||||
[{:keys [team-id] :as props}]
|
||||
(mf/use-effect
|
||||
{:fn #(st/emit! (dsh/initialize-recent team-id))
|
||||
:deps (mf/deps team-id)})
|
||||
|
@ -73,10 +78,11 @@
|
|||
(vals)
|
||||
(sort-by :modified-at)
|
||||
(reverse))
|
||||
|
||||
recent-files (mf/deref recent-files-ref)
|
||||
files (mf/deref files-ref)
|
||||
recent-file-ids (mf/deref recent-file-ids-ref)
|
||||
locale (i18n/use-locale)]
|
||||
(when (and projects recent-files)
|
||||
|
||||
(when (and projects recent-file-ids)
|
||||
[:*
|
||||
[:& recent-files-header]
|
||||
[:section.recent-files-page
|
||||
|
@ -84,6 +90,8 @@
|
|||
[:& recent-project {:project project
|
||||
:locale locale
|
||||
:key (:id project)
|
||||
:files (get recent-files (:id project))
|
||||
:files (->> (get recent-file-ids (:id project))
|
||||
(map #(get files %))
|
||||
(filter identity)) ;; avoid failure if a "project only" files list is in global state
|
||||
:first? (= project (first projects))}])]])))
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue