diff --git a/backend/src/uxbox/services/mutations/files.clj b/backend/src/uxbox/services/mutations/files.clj index 69ac5eac6..726410925 100644 --- a/backend/src/uxbox/services/mutations/files.clj +++ b/backend/src/uxbox/services/mutations/files.clj @@ -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 diff --git a/backend/src/uxbox/services/queries/projects.clj b/backend/src/uxbox/services/queries/projects.clj index bae7426ac..ffd1ffb4d 100644 --- a/backend/src/uxbox/services/queries/projects.clj +++ b/backend/src/uxbox/services/queries/projects.clj @@ -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) diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index 4d34445dc..3a8f9b54a 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -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." diff --git a/frontend/resources/styles/main/partials/dashboard-grid.scss b/frontend/resources/styles/main/partials/dashboard-grid.scss index fc6c97e7e..7f274c71b 100644 --- a/frontend/resources/styles/main/partials/dashboard-grid.scss +++ b/frontend/resources/styles/main/partials/dashboard-grid.scss @@ -272,6 +272,10 @@ } + .project-th-actions.force-display { + display: flex; + } + } // IMAGES SECTION diff --git a/frontend/resources/styles/main/partials/main-bar.scss b/frontend/resources/styles/main/partials/main-bar.scss index 794526b6b..f0cc8775d 100644 --- a/frontend/resources/styles/main/partials/main-bar.scss +++ b/frontend/resources/styles/main/partials/main-bar.scss @@ -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; diff --git a/frontend/src/uxbox/main/data/dashboard.cljs b/frontend/src/uxbox/main/data/dashboard.cljs index c17ee0851..0dafbe6c5 100644 --- a/frontend/src/uxbox/main/data/dashboard.cljs +++ b/frontend/src/uxbox/main/data/dashboard.cljs @@ -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)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/uxbox/main/ui/dashboard/grid.cljs b/frontend/src/uxbox/main/ui/dashboard/grid.cljs index f619dd354..b933c3b7e 100644 --- a/frontend/src/uxbox/main/ui/dashboard/grid.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/grid.cljs @@ -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)]] diff --git a/frontend/src/uxbox/main/ui/dashboard/project.cljs b/frontend/src/uxbox/main/ui/dashboard/project.cljs index a759a8645..60aa4fbda 100644 --- a/frontend/src/uxbox/main/ui/dashboard/project.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/project.cljs @@ -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}]]])) diff --git a/frontend/src/uxbox/main/ui/dashboard/recent_files.cljs b/frontend/src/uxbox/main/ui/dashboard/recent_files.cljs index b781330ba..cf2b309dc 100644 --- a/frontend/src/uxbox/main/ui/dashboard/recent_files.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/recent_files.cljs @@ -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))}])]])))