diff --git a/backend/src/uxbox/services/queries/colors.clj b/backend/src/uxbox/services/queries/colors.clj index b1deb3dfb7..afe78209d5 100644 --- a/backend/src/uxbox/services/queries/colors.clj +++ b/backend/src/uxbox/services/queries/colors.clj @@ -50,7 +50,7 @@ (sq/defquery ::color-libraries [{:keys [profile-id team-id]}] (db/with-atomic [conn db/pool] - (teams/check-edition-permissions! conn profile-id team-id) + (teams/check-read-permissions! conn profile-id team-id) (db/query conn [sql:libraries team-id]))) @@ -66,7 +66,7 @@ [{:keys [profile-id id]}] (db/with-atomic [conn db/pool] (p/let [lib (retrieve-library conn id)] - (teams/check-edition-permissions! conn profile-id (:team-id lib)) + (teams/check-read-permissions! conn profile-id (:team-id lib)) lib))) (def ^:private sql:single-library @@ -94,7 +94,7 @@ [{:keys [profile-id library-id] :as params}] (db/with-atomic [conn db/pool] (p/let [lib (retrieve-library conn library-id)] - (teams/check-edition-permissions! conn profile-id (:team-id lib)) + (teams/check-read-permissions! conn profile-id (:team-id lib)) (retrieve-colors conn library-id)))) (def ^:private sql:colors @@ -123,7 +123,7 @@ [{:keys [profile-id id] :as params}] (db/with-atomic [conn db/pool] (p/let [color (retrieve-color conn id)] - (teams/check-edition-permissions! conn profile-id (:team-id color)) + (teams/check-read-permissions! conn profile-id (:team-id color)) color))) (def ^:private sql:single-color diff --git a/backend/src/uxbox/services/queries/icons.clj b/backend/src/uxbox/services/queries/icons.clj index b1922f37fe..deddf82b6e 100644 --- a/backend/src/uxbox/services/queries/icons.clj +++ b/backend/src/uxbox/services/queries/icons.clj @@ -56,8 +56,10 @@ (sq/defquery ::icon-libraries [{:keys [profile-id team-id]}] + (println profile-id) + (println team-id) (db/with-atomic [conn db/pool] - (teams/check-edition-permissions! conn profile-id team-id) + (teams/check-read-permissions! conn profile-id team-id) (db/query conn [sql:libraries team-id]))) @@ -73,7 +75,7 @@ [{:keys [profile-id id]}] (db/with-atomic [conn db/pool] (p/let [lib (retrieve-library conn id)] - (teams/check-edition-permissions! conn profile-id (:team-id lib)) + (teams/check-read-permissions! conn profile-id (:team-id lib)) lib))) (def ^:private sql:single-library @@ -101,7 +103,7 @@ [{:keys [profile-id library-id] :as params}] (db/with-atomic [conn db/pool] (p/let [lib (retrieve-library conn library-id)] - (teams/check-edition-permissions! conn profile-id (:team-id lib)) + (teams/check-read-permissions! conn profile-id (:team-id lib)) (-> (retrieve-icons conn library-id) (p/then' (fn [rows] (mapv decode-row rows))))))) @@ -131,7 +133,7 @@ [{:keys [profile-id id] :as params}] (db/with-atomic [conn db/pool] (p/let [icon (retrieve-icon conn id)] - (teams/check-edition-permissions! conn profile-id (:team-id icon)) + (teams/check-read-permissions! conn profile-id (:team-id icon)) (decode-row icon)))) (def ^:private sql:single-icon diff --git a/backend/src/uxbox/services/queries/images.clj b/backend/src/uxbox/services/queries/images.clj index a690a5cc1d..c6cd106a8f 100644 --- a/backend/src/uxbox/services/queries/images.clj +++ b/backend/src/uxbox/services/queries/images.clj @@ -40,7 +40,7 @@ (sq/defquery ::image-libraries [{:keys [profile-id team-id]}] (db/with-atomic [conn db/pool] - (teams/check-edition-permissions! conn profile-id team-id) + (teams/check-read-permissions! conn profile-id team-id) (db/query conn [sql:libraries team-id]))) @@ -55,7 +55,7 @@ [{:keys [profile-id id]}] (db/with-atomic [conn db/pool] (p/let [lib (retrieve-library conn id)] - (teams/check-edition-permissions! conn profile-id (:team-id lib)) + (teams/check-read-permissions! conn profile-id (:team-id lib)) lib))) (def ^:private sql:single-library @@ -86,7 +86,7 @@ [{:keys [profile-id library-id] :as params}] (db/with-atomic [conn db/pool] (p/let [lib (retrieve-library conn library-id)] - (teams/check-edition-permissions! conn profile-id (:team-id lib)) + (teams/check-read-permissions! conn profile-id (:team-id lib)) (-> (retrieve-images conn library-id) (p/then' (fn [rows] (->> rows @@ -120,7 +120,7 @@ [{:keys [profile-id id] :as params}] (db/with-atomic [conn db/pool] (p/let [img (retrieve-image conn id)] - (teams/check-edition-permissions! conn profile-id (:team-id img)) + (teams/check-read-permissions! conn profile-id (:team-id img)) (-> img (images/resolve-urls :path :uri) (images/resolve-urls :thumb-path :thumb-uri))))) diff --git a/backend/src/uxbox/services/queries/projects.clj b/backend/src/uxbox/services/queries/projects.clj index ffd1ffb4dd..45d495b4cc 100644 --- a/backend/src/uxbox/services/queries/projects.clj +++ b/backend/src/uxbox/services/queries/projects.clj @@ -47,16 +47,37 @@ where team_id = $2 order by modified_at desc") +(def ^:private sql:project-by-id + "select p.* + from project as p + inner join project_profile_rel as ppr on (ppr.project_id = p.id) + where ppr.profile_id = $1 + and p.id = $2 + and p.deleted_at is null + and (ppr.is_admin = true or + ppr.is_owner = true or + ppr.can_edit = true)") + (s/def ::team-id ::us/uuid) (s/def ::profile-id ::us/uuid) +(s/def ::project-id ::us/uuid) (s/def ::projects-by-team (s/keys :req-un [::profile-id ::team-id])) +(s/def ::project-by-id + (s/keys :req-un [::profile-id ::project-id])) + (defn projects-by-team [profile-id team-id] (db/query db/pool [sql:projects profile-id team-id])) +(defn project-by-id [profile-id project-id] + (db/query-one db/pool [sql:project-by-id profile-id project-id])) + (sq/defquery ::projects-by-team [{:keys [profile-id team-id]}] (projects-by-team profile-id team-id)) +(sq/defquery ::project-by-id + [{:keys [profile-id project-id]}] + (project-by-id profile-id project-id)) diff --git a/backend/src/uxbox/services/queries/teams.clj b/backend/src/uxbox/services/queries/teams.clj index efce8203a3..b3b32d58d3 100644 --- a/backend/src/uxbox/services/queries/teams.clj +++ b/backend/src/uxbox/services/queries/teams.clj @@ -40,5 +40,14 @@ (ex/raise :type :validation :code :not-authorized)))))) - - +(defn check-read-permissions! + [conn profile-id team-id] + (-> (db/query-one conn [sql:team-permissions profile-id team-id]) + (p/then' (fn [row] + (when-not (or (:can-edit row) + (:is-admin row) + (:is-owner row) + ;; We can read global-project owned items + (= team-id #uuid "00000000-0000-0000-0000-000000000000")) + (ex/raise :type :validation + :code :not-authorized)))))) diff --git a/common/uxbox/common/spec.cljc b/common/uxbox/common/spec.cljc index 10af336cf7..179d3bac6a 100644 --- a/common/uxbox/common/spec.cljc +++ b/common/uxbox/common/spec.cljc @@ -24,7 +24,7 @@ #"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") (def uuid-rx - #"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$") + #"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$") ;; --- Conformers diff --git a/frontend/resources/images/icons/tick.svg b/frontend/resources/images/icons/tick.svg new file mode 100644 index 0000000000..88d3be0aa3 --- /dev/null +++ b/frontend/resources/images/icons/tick.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index 155778fed6..3f9f493395 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/project.cljs:73", "src/uxbox/main/ui/dashboard/grid.cljs:91" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:72", "src/uxbox/main/ui/dashboard/grid.cljs:92" ], "translations" : { "en" : "Delete" } }, "dashboard.grid.edit" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:72", "src/uxbox/main/ui/dashboard/grid.cljs:90" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:71", "src/uxbox/main/ui/dashboard/grid.cljs:91" ], "translations" : { "en" : "Edit" } }, "dashboard.grid.empty-files" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:113" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:114" ], "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:58" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:57" ], "translations" : { "en" : "Draft" } @@ -45,40 +45,40 @@ "unused" : true }, "dashboard.header.new-file" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:78" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:77" ], "translations" : { "en" : "+ New file" } }, "dashboard.header.new-project" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/recent_files.cljs:54" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/recent_files.cljs:53" ], "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" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/profile.cljs:59" ], "translations" : { "en" : "Exit", "fr" : "Quitter" } }, "dashboard.header.profile-menu.password" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/profile.cljs:56", "src/uxbox/main/ui/workspace/header.cljs:92" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/profile.cljs:56" ], "translations" : { "en" : "Password", "fr" : "Mot de passe" } }, "dashboard.header.profile-menu.profile" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/profile.cljs:53", "src/uxbox/main/ui/workspace/header.cljs:91" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/profile.cljs:53" ], "translations" : { "en" : "Profile", "fr" : "Profil" } }, "dashboard.header.project" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:74" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:73" ], "translations" : { "en" : "Project %s" } @@ -127,25 +127,25 @@ "unused" : true }, "dashboard.library.menu.icons" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:103" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:96" ], "translations" : { "en" : "Icons" } }, "dashboard.library.menu.images" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:107" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:100" ], "translations" : { "en" : "Images" } }, "dashboard.library.menu.palettes" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:111" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:104" ], "translations" : { "en" : "Palettes" } }, "dashboard.search.no-matches-for" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/search.cljs:47" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/search.cljs:48" ], "translations" : { "en" : "No matches found for \"%s\"" } @@ -157,13 +157,13 @@ "unused" : true }, "dashboard.search.searching-for" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/search.cljs:43" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/search.cljs:44" ], "translations" : { "en" : "Searching for %s..." } }, "dashboard.search.type-something" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/search.cljs:39" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/search.cljs:40" ], "translations" : { "en" : "Type to search results" } @@ -200,19 +200,19 @@ } }, "ds.button.delete" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:147", "src/uxbox/main/ui/dashboard/library.cljs:192", "src/uxbox/main/ui/dashboard/library.cljs:216", "src/uxbox/main/ui/dashboard/library.cljs:243" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:152", "src/uxbox/main/ui/dashboard/library.cljs:220", "src/uxbox/main/ui/dashboard/library.cljs:257", "src/uxbox/main/ui/dashboard/library.cljs:296" ], "translations" : { "en" : "Delete" } }, "ds.button.rename" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:146" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:149" ], "translations" : { "en" : "Rename" } }, "ds.button.save" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:51" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:54" ], "translations" : { "en" : "Save" } @@ -253,21 +253,21 @@ "unused" : true }, "ds.confirm-cancel" : { - "used-in" : [ "src/uxbox/main/ui/confirm.cljs:38" ], + "used-in" : [ "src/uxbox/main/ui/confirm.cljs:19" ], "translations" : { "en" : "Cancel", "fr" : "Annuler" } }, "ds.confirm-ok" : { - "used-in" : [ "src/uxbox/main/ui/confirm.cljs:34" ], + "used-in" : [ "src/uxbox/main/ui/confirm.cljs:20" ], "translations" : { "en" : "Ok", "fr" : "Ok" } }, "ds.confirm-title" : { - "used-in" : [ "src/uxbox/main/ui/confirm.cljs:28" ], + "used-in" : [ "src/uxbox/main/ui/confirm.cljs:18" ], "translations" : { "en" : "Are you sure?", "fr" : "Êtes-vous sûr ?" @@ -380,7 +380,7 @@ "unused" : true }, "ds.new-file" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:109", "src/uxbox/main/ui/dashboard/grid.cljs:115" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:110", "src/uxbox/main/ui/dashboard/grid.cljs:116" ], "translations" : { "en" : "+ New File", "fr" : null @@ -450,7 +450,7 @@ "unused" : true }, "ds.store-images-title" : { - "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:181" ], + "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:180" ], "translations" : { "en" : "IMAGES STORE", "fr" : "BOUTIQUE" @@ -499,7 +499,7 @@ "unused" : true }, "ds.your-images-title" : { - "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:178" ], + "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:177" ], "translations" : { "en" : "YOUR IMAGES", "fr" : "VOS IMAGES" @@ -534,21 +534,21 @@ } }, "errors.generic" : { - "used-in" : [ "src/uxbox/main/ui.cljs:160" ], + "used-in" : [ "src/uxbox/main/ui.cljs:162" ], "translations" : { "en" : "Something wrong has happened.", "fr" : "Quelque chose c'est mal passé." } }, "errors.network" : { - "used-in" : [ "src/uxbox/main/ui.cljs:154" ], + "used-in" : [ "src/uxbox/main/ui.cljs:156" ], "translations" : { "en" : "Unable to connect to backend server.", "fr" : "Impossible de se connecter au serveur principal." } }, "header.sitemap" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:96" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:154" ], "translations" : { "en" : null, "fr" : null @@ -562,28 +562,28 @@ } }, "image.import-library" : { - "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:170" ], + "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:169" ], "translations" : { "en" : "Import image from library", "fr" : "Importer une image depuis une librairie" } }, "image.new" : { - "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:84" ], + "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:83" ], "translations" : { "en" : "New image", "fr" : "Nouvelle image" } }, "image.select" : { - "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:90", "src/uxbox/main/ui/workspace/images.cljs:95" ], + "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:89", "src/uxbox/main/ui/workspace/images.cljs:94" ], "translations" : { "en" : "Select from library", "fr" : "Choisir depuis une librairie" } }, "image.upload" : { - "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:102" ], + "used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:101" ], "translations" : { "en" : "Upload file", "fr" : "Envoyer un fichier" @@ -645,7 +645,7 @@ "unused" : true }, "modal.create-color.new-color" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:45" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:48" ], "translations" : { "en" : "New Color" } @@ -658,7 +658,7 @@ } }, "profile.recovery.go-to-login" : { - "used-in" : [ "src/uxbox/main/ui/profile/recovery_request.cljs:65", "src/uxbox/main/ui/profile/recovery.cljs:81" ], + "used-in" : [ "src/uxbox/main/ui/profile/recovery.cljs:81", "src/uxbox/main/ui/profile/recovery_request.cljs:65" ], "translations" : { "en" : "Go back!", "fr" : "Retour!" @@ -798,23 +798,12 @@ } }, "settings.password" : { - "used-in" : [ "src/uxbox/main/ui/settings/header.cljs:37" ], + "used-in" : [ "src/uxbox/main/ui/settings/header.cljs:34" ], "translations" : { "en" : "PASSWORD", "fr" : "MOT DE PASSE" } }, - "workspace.header.menu.hide-rules": "Hide rules", - "workspace.header.menu.show-rules": "Show rules", - "workspace.header.menu.hide-grid": "Hide grid", - "workspace.header.menu.show-grid": "Show grid", - "workspace.header.menu.hide-layers": "Hide layers", - "workspace.header.menu.show-layers": "Show layers", - "workspace.header.menu.hide-palette": "Hide color palette", - "workspace.header.menu.show-palette": "Show color palette", - "workspace.header.menu.hide-libraries": "Hide libraries", - "workspace.header.menu.show-libraries": "Show libraries", - "settings.password.change-password" : { "used-in" : [ "src/uxbox/main/ui/settings/password.cljs:64" ], "translations" : { @@ -851,14 +840,14 @@ } }, "settings.profile" : { - "used-in" : [ "src/uxbox/main/ui/settings/header.cljs:34" ], + "used-in" : [ "src/uxbox/main/ui/settings/header.cljs:30" ], "translations" : { "en" : "PROFILE", "fr" : "PROFIL" } }, "settings.profile.lang" : { - "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:91" ], + "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:92" ], "translations" : { "en" : "Default language", "fr" : "Langue par défaut" @@ -872,28 +861,28 @@ } }, "settings.profile.section-basic-data" : { - "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:64" ], + "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:66" ], "translations" : { "en" : "Name, username and email", "fr" : "Nom, nom d'utilisateur et adresse email" } }, "settings.profile.your-avatar" : { - "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:135" ], + "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:138" ], "translations" : { "en" : "Your avatar", "fr" : "Votre avatar" } }, "settings.profile.your-email" : { - "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:85" ], + "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:87" ], "translations" : { "en" : null, "fr" : null } }, "settings.profile.your-name" : { - "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:72" ], + "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:75" ], "translations" : { "en" : "Your name", "fr" : "Votre nom complet" @@ -907,7 +896,7 @@ "unused" : true }, "settings.update-settings" : { - "used-in" : [ "src/uxbox/main/ui/settings/notifications.cljs:42", "src/uxbox/main/ui/settings/password.cljs:102", "src/uxbox/main/ui/settings/profile.cljs:104" ], + "used-in" : [ "src/uxbox/main/ui/settings/notifications.cljs:42", "src/uxbox/main/ui/settings/password.cljs:102", "src/uxbox/main/ui/settings/profile.cljs:105" ], "translations" : { "en" : "Update settings", "fr" : "Mettre à jour les paramètres" @@ -990,6 +979,66 @@ }, "unused" : true }, + "workspace.header.menu.hide-grid" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:104" ], + "translations" : { + "en" : "Hide grid" + } + }, + "workspace.header.menu.hide-layers" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:111" ], + "translations" : { + "en" : "Hide layers" + } + }, + "workspace.header.menu.hide-libraries" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:125" ], + "translations" : { + "en" : "Hide libraries" + } + }, + "workspace.header.menu.hide-palette" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:118" ], + "translations" : { + "en" : "Hide color palette" + } + }, + "workspace.header.menu.hide-rules" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:97" ], + "translations" : { + "en" : "Hide rules" + } + }, + "workspace.header.menu.show-grid" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:105" ], + "translations" : { + "en" : "Show grid" + } + }, + "workspace.header.menu.show-layers" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:112" ], + "translations" : { + "en" : "Show layers" + } + }, + "workspace.header.menu.show-libraries" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:126" ], + "translations" : { + "en" : "Show libraries" + } + }, + "workspace.header.menu.show-palette" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:119" ], + "translations" : { + "en" : "Show color palette" + } + }, + "workspace.header.menu.show-rules" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:98" ], + "translations" : { + "en" : "Show rules" + } + }, "workspace.header.path" : { "translations" : { "en" : "Path", @@ -1025,29 +1074,65 @@ }, "unused" : true }, + "workspace.library.all" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:106" ], + "translations" : { + "en" : "All libraries" + } + }, + "workspace.library.icons" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:156" ], + "translations" : { + "en" : "Icons" + } + }, + "workspace.library.images" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:161" ], + "translations" : { + "en" : "Images" + } + }, + "workspace.library.libraries" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:138" ], + "translations" : { + "en" : "Libraries" + } + }, + "workspace.library.own" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:107" ], + "translations" : { + "en" : "My libraries" + } + }, + "workspace.library.store" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:108" ], + "translations" : { + "en" : "Store libraries" + } + }, "workspace.options.color" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:124", "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:47", "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:81" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:47", "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:124", "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:81" ], "translations" : { "en" : "Color", "fr" : "Couleur" } }, "workspace.options.font-family" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:203" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:202" ], "translations" : { "en" : "Font Family", "fr" : "Police de caractères" } }, "workspace.options.font-options" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:201" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:200" ], "translations" : { "en" : "Fonts & Font Size", "fr" : "TODO" } }, "workspace.options.font-weight" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:212" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:211" ], "translations" : { "en" : "Font Size & Weight", "fr" : "Taille et graisse" @@ -1061,14 +1146,14 @@ } }, "workspace.options.line-height-letter-spacing" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:244" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:243" ], "translations" : { "en" : "Line height and Letter spacing", "fr" : "Hauteur de ligne et Espacement de caractères" } }, "workspace.options.measures" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:69", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:66", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:62", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:64", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:55", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:66" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:64", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:55", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:66", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:62", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:66", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:68" ], "translations" : { "en" : "Size, position & rotation", "fr" : "Taille, position et rotation" @@ -1082,21 +1167,21 @@ } }, "workspace.options.position" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:98", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:95", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:91", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:92", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:84", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:95" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:92", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:84", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:95", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:91", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:95", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:97" ], "translations" : { "en" : "Position", "fr" : "Position" } }, "workspace.options.rotation-radius" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:115", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:112", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:108", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:107", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:112" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:107", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:112", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:108", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:112", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:114" ], "translations" : { "en" : "Rotation & Radius", "fr" : "TODO" } }, "workspace.options.size" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:114", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:71", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:68", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:64", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:68", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:57", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:68" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:68", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:57", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:68", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:64", "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:114", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:68", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:70" ], "translations" : { "en" : "Size", "fr" : "Taille" @@ -1152,7 +1237,7 @@ } }, "workspace.options.text-align" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:263" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:262" ], "translations" : { "en" : "Text Alignment", "fr" : "Alignement de texte" @@ -1180,7 +1265,7 @@ } }, "workspace.viewport.click-to-close-path" : { - "used-in" : [ "src/uxbox/main/ui/workspace/drawarea.cljs:334" ], + "used-in" : [ "src/uxbox/main/ui/workspace/drawarea.cljs:335" ], "translations" : { "en" : "Click to close the path" } diff --git a/frontend/resources/styles/main.scss b/frontend/resources/styles/main.scss index d6748e9472..cb43312a55 100644 --- a/frontend/resources/styles/main.scss +++ b/frontend/resources/styles/main.scss @@ -42,8 +42,9 @@ //################################################# @import 'main/partials/main-bar'; -@import 'main/partials/workspace-bar'; @import 'main/partials/workspace'; +@import 'main/partials/workspace-bar'; +@import 'main/partials/workspace-libraries'; @import 'main/partials/tool-bar'; @import 'main/partials/project-bar'; @import 'main/partials/sidebar'; @@ -67,6 +68,7 @@ @import 'main/partials/context-menu'; @import 'main/partials/debug-icons-preview'; @import 'main/partials/editable-label'; +@import 'main/partials/tab-container'; //################################################# // Resources diff --git a/frontend/resources/styles/main/partials/color-palette.scss b/frontend/resources/styles/main/partials/color-palette.scss index 3039e78e0f..42a7f3a589 100644 --- a/frontend/resources/styles/main/partials/color-palette.scss +++ b/frontend/resources/styles/main/partials/color-palette.scss @@ -11,21 +11,23 @@ background-color: $color-gray-50; border-top: 1px solid $color-gray-60; display: flex; - padding: 1rem; position: absolute; bottom: 0; left: 0; width: 100%; z-index: 11; - .right-arrow, - .left-arrow { - cursor: pointer; + + & .right-arrow, + & .left-arrow { + cursor: pointer; + svg { fill: $color-gray-light; - height: 30px; + height: 1rem; margin: 0 .5rem; - width: 30px; + width: 1rem; } + &:hover { svg { fill: $color-gray-darker; @@ -35,25 +37,54 @@ display: none; } } + + .left-arrow { - transform: rotate(180deg); + transform: rotate(180deg); + padding-top: 10px; } + &.fade-out-down { @include animation(0,.5s,fadeOutDown); } + + &.left-sidebar-open { + left: 280px; + width: calc(100% - 280px); + } + + & .context-menu-items { + bottom: 1.5rem; + top: initial; + min-width: 10rem; + } } .color-palette-actions { - display: flex; - flex-direction: column; - flex-shrink: 0; - margin-right: .5rem; - width: 200px; - .color-palette-buttons { - align-items: center; + align-self: stretch; + border: 1px solid #1F1F1F; + cursor: pointer; display: flex; - justify-content: space-around; - } + flex-direction: column; + flex-shrink: 0; + justify-content: center; + margin-right: .5rem; + padding: 0.5rem; + + .color-palette-buttons { + align-items: center; + display: flex; + justify-content: space-around; + } +} + +.color-palette-actions-button { + cursor: pointer; + & svg { + width: 1rem; + height: 1rem; + fill: #AFB2BF; + } } .btn-palette { @@ -90,6 +121,8 @@ display: flex; overflow: hidden; width: 100%; + height: 4.8rem; + padding: 0.25rem; } .color-palette-inside { @@ -106,15 +139,14 @@ display: flex; flex-direction: column; flex-shrink: 0; - margin: 0 10px; position: relative; flex-basis: 66px; + .color { background-color: $color-gray-lighter; border: 2px solid $color-gray-60; border-radius: 50%; flex-shrink: 0; - margin-bottom: .4rem; padding: 1.5rem; } .color-text { diff --git a/frontend/resources/styles/main/partials/context-menu.scss b/frontend/resources/styles/main/partials/context-menu.scss index e6acef56b2..58735efef9 100644 --- a/frontend/resources/styles/main/partials/context-menu.scss +++ b/frontend/resources/styles/main/partials/context-menu.scss @@ -17,7 +17,9 @@ border-radius: $br-small; box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25); left: -$size-4; + max-height: 30rem; min-width: 7rem; + overflow: auto; position: absolute; top: $size-3; } @@ -27,9 +29,25 @@ display: block; font-size: $fs12; padding: $size-2 $size-4; + white-space: nowrap; &:hover { color: $color-black; - background: $color-primary-lighter; + background-color: $color-primary-lighter; } } + +.context-menu.is-selectable { + & .context-menu-action { + padding-left: 1.5rem; + } + + & .context-menu-item.is-selected .context-menu-action { + background-image: url(/images/icons/tick.svg); + background-repeat: no-repeat; + background-position: 5% 48%; + background-size: 10px; + font-weight: bold; + } +} + diff --git a/frontend/resources/styles/main/partials/sidebar-sitemap.scss b/frontend/resources/styles/main/partials/sidebar-sitemap.scss index b2c63b6f48..00eae95a61 100644 --- a/frontend/resources/styles/main/partials/sidebar-sitemap.scss +++ b/frontend/resources/styles/main/partials/sidebar-sitemap.scss @@ -6,8 +6,6 @@ // Copyright (c) 2015-2016 Juan de la Cruz .sitemap { - max-height: 180px; - .tool-window-bar { .add-page { diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index 1c0fe1aeac..0200bfda2e 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -11,7 +11,7 @@ height: 100%; position: fixed; right: 0; - width: 230px; + width: 15rem; z-index: 10; &.settings-bar-left { @@ -20,10 +20,23 @@ .settings-bar-inside { align-items: center; - display: flex; + display: grid; + grid-template-columns: 100%; + + &[data-layout*='layers'] { + grid-template-rows: 30% 70%; + } + + &[data-layout*='libraries'] { + grid-template-rows: 100%; + } + + &[data-layout*='layers'][data-layout*='libraries'] { + grid-template-rows: 15% 25% 60%; + } + flex-direction: column; - overflow-y: auto; - overflow-x: hidden; + overflow: hidden; padding-top: 40px; height: 100%; @@ -33,12 +46,14 @@ flex-direction: column; flex: 1; width: 100%; + height: 100%; .tool-window-bar { align-items: center; display: flex; flex-shrink: 0; padding: $small; + overflow: hidden; svg { fill: $color-gray-20; @@ -78,14 +93,8 @@ flex-wrap: wrap; overflow-y: auto; padding-bottom: $medium; + height: 100%; } - - &#layers { - padding-bottom: 30px; - } - } - } - } diff --git a/frontend/resources/styles/main/partials/tab-container.scss b/frontend/resources/styles/main/partials/tab-container.scss new file mode 100644 index 0000000000..601f394ea8 --- /dev/null +++ b/frontend/resources/styles/main/partials/tab-container.scss @@ -0,0 +1,43 @@ + +.tab-container { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; +} + +.tab-container-tabs { + background: $color-gray-60; + cursor: pointer; + display: flex; + flex-direction: row; + font-size: 12px; + height: 2.5rem; + padding: 0 0.25rem; +} + +.tab-container-tab-title { + align-items: center; + background: $color-gray-60; + border-radius: 2px 2px 0px 0px; + color: $color-white; + display: flex; + justify-content: center; + margin: 0.5rem 0.25rem 0 0.25rem ; + width: 100%; + + &.current{ + background: $color-gray-50; + } +} + +.tab-container-content { + flex: 1; + height: 100%; + max-height: 100%; + overflow: hidden; +} + +.tab-element, .tab-element-content { + height: 100%; +} diff --git a/frontend/resources/styles/main/partials/workspace-libraries.scss b/frontend/resources/styles/main/partials/workspace-libraries.scss new file mode 100644 index 0000000000..fe4a8c2f31 --- /dev/null +++ b/frontend/resources/styles/main/partials/workspace-libraries.scss @@ -0,0 +1,127 @@ +.libraries-window-bar { + display: grid; + grid-template-columns: repeat(2, 50%); + padding: 0.5rem; + align-items: center; + + & .context-menu-items { + left: initial; + right: 0; + } +} + +.libraries-window-bar-title { + color: #F0F0F0; +} + +.libraries-window-bar-options { + font-size: 12px; + display: flex; + justify-content: space-between; + padding: 0 0.5rem; + + button { + border: none; + padding: 0; + margin: 0; + background: transparent; + cursor: pointer; + } + & svg { + width: 0.5rem; + height: 0.5rem; + fill: #F0F0F0; + transform: rotate(90deg); + } +} + +.library-tab { + display: flex; + flex-direction: column; + height: 100%; + padding: 0.25rem; +} + +.library-tab-content { + display: grid; + flex-direction: row; + flex-wrap: wrap; + padding: 0.25rem; + overflow-y: scroll; + + .icons-tab & { + grid-template-columns: repeat(3, 1fr); + } + + .images-tab & { + grid-template-columns: repeat(2, 1fr); + } +} + +.library-tab-element { + border-radius: 4px; + border: 1px solid #1F1F1F; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); + box-sizing: border-box; + cursor: pointer; + display: flex; + margin: 0.25rem; + overflow: hidden; + position: relative; + text-align: center; + + & svg, & img { + height: auto; + margin: auto; + max-height: 100%; + max-width: 100%; + width: auto; + } + + &:hover { + border-color: $color-primary; + + & .library-tab-element-name { + display: inline; + } + } + + .icons-tab & { + background: $color-white; + color: $color-black; + height: 4rem; + width: 4rem; + padding: $size-3; + } + + .images-tab & { + height: 4rem; + width: 6.2rem; + color: $color-white; + padding: $size-2 0; + } +} + +.library-tab-element-name { + display: none; + position: absolute; + font-size: 9px; + bottom: 0; + left: 0; + width: 100%; +} + +.library-tab-libraries { + background-color: #303236; + margin: 0.5rem; + width: 90%; + padding: 0.5rem; + box-sizing: border-box; + color: #AFB2BF; + font-size: 12px; + border: 1px solid #7c7c7c; +} + +.library-tab-libraries-item { + padding: 1rem; +} diff --git a/frontend/src/uxbox/main/data/colors.cljs b/frontend/src/uxbox/main/data/colors.cljs index 812844ab01..5639ab5e85 100644 --- a/frontend/src/uxbox/main/data/colors.cljs +++ b/frontend/src/uxbox/main/data/colors.cljs @@ -266,4 +266,4 @@ ptk/UpdateEvent (update [_ state] (-> state - (update-in [:library :selected-items library-id] #(into [item] %) ))))) + (update-in [:library-items :palettes library-id] #(into [item] %) ))))) diff --git a/frontend/src/uxbox/main/data/icons.cljs b/frontend/src/uxbox/main/data/icons.cljs index 132f881fb6..ee0d5717f2 100644 --- a/frontend/src/uxbox/main/data/icons.cljs +++ b/frontend/src/uxbox/main/data/icons.cljs @@ -203,7 +203,7 @@ (update [_ state] (let [{:keys [id] :as item} (assoc item :type :icon)] (-> state - (update-in [:library :selected-items library-id] #(into [item] %))))))) + (update-in [:library-items :icons library-id] #(into [item] %))))))) ;; ;; --- Icon Persisted ;; diff --git a/frontend/src/uxbox/main/data/images.cljs b/frontend/src/uxbox/main/data/images.cljs index d9a5614a4f..38dfcb8c9f 100644 --- a/frontend/src/uxbox/main/data/images.cljs +++ b/frontend/src/uxbox/main/data/images.cljs @@ -395,5 +395,5 @@ ptk/UpdateEvent (update [_ state] (-> state - (update-in [:library :selected-items library-id] #(into [item] %)))))) + (update-in [:library-items :images library-id] #(into [item] %)))))) diff --git a/frontend/src/uxbox/main/data/library.cljs b/frontend/src/uxbox/main/data/library.cljs index ff9805d121..9c716ee850 100644 --- a/frontend/src/uxbox/main/data/library.cljs +++ b/frontend/src/uxbox/main/data/library.cljs @@ -14,30 +14,33 @@ [uxbox.util.router :as r] [uxbox.util.uuid :as uuid])) +(defn initialize-workspace-libraries [] + ()) ;; Retrieve libraries (declare retrieve-libraries-result) (defn retrieve-libraries - [type team-id] - (s/assert ::us/uuid team-id) - (let [method (case type - :icons :icon-libraries - :images :image-libraries - :palettes :color-libraries)] - (ptk/reify ::retrieve-libraries - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/query! method {:team-id team-id}) - (rx/map (partial retrieve-libraries-result type))))))) + ([type] (retrieve-libraries type uuid/zero)) + ([type team-id] + (s/assert ::us/uuid team-id) + (let [method (case type + :icons :icon-libraries + :images :image-libraries + :palettes :color-libraries)] + (ptk/reify ::retrieve-libraries + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query! method {:team-id team-id}) + (rx/map (partial retrieve-libraries-result type team-id)))))))) -(defn retrieve-libraries-result [type result] +(defn retrieve-libraries-result [type team-id result] (ptk/reify ::retrieve-libraries-result ptk/UpdateEvent (update [_ state] (-> state - (assoc-in [:library type] result))))) + (assoc-in [:library type team-id] result))))) ;; Retrieve library data @@ -53,15 +56,15 @@ :images :images :palettes :colors)] (->> (rp/query! method {:library-id library-id}) - (rx/map (partial retrieve-library-data-result library-id))))))) + (rx/map (partial retrieve-library-data-result type library-id))))))) (defn retrieve-library-data-result - [library-id data] + [type library-id data] (ptk/reify ::retrieve-library-data-result ptk/UpdateEvent (update [_ state] (-> state - (assoc-in [:library :selected-items library-id] data))))) + (assoc-in [:library-items type library-id] data))))) ;; Create library @@ -78,23 +81,23 @@ :images :create-image-library :palettes :create-color-library)] (->> (rp/mutation! method {:team-id team-id - :name name}) - (rx/map (partial create-library-result type))))))) + :name name}) + (rx/map (partial create-library-result type team-id))))))) (defn create-library-result - [type result] + [type team-id result] (ptk/reify ::create-library-result ptk/UpdateEvent (update [_ state] (-> state - (update-in [:library type] #(into [result] %)))))) + (update-in [:library type team-id] #(into [result] %)))))) ;; Rename library (declare rename-library-result) (defn rename-library - [type library-id name] + [type team-id library-id name] (ptk/reify ::rename-library ptk/WatchEvent (watch [_ state stream] @@ -104,10 +107,10 @@ :palettes :rename-color-library)] (->> (rp/mutation! method {:id library-id :name name}) - (rx/map #(rename-library-result type library-id name))))))) + (rx/map #(rename-library-result type team-id library-id name))))))) (defn rename-library-result - [type library-id name] + [type team-id library-id name] (ptk/reify ::rename-library-result ptk/UpdateEvent (update [_ state] @@ -118,14 +121,14 @@ (update-fn [libraries] (map change-name libraries))] (-> state - (update-in [:library type] update-fn)))))) + (update-in [:library type team-id] update-fn)))))) ;; Delete library (declare delete-library-result) (defn delete-library - [type library-id] + [type team-id library-id] (ptk/reify ::delete-library ptk/UpdateEvent (update [_ state] @@ -139,17 +142,17 @@ :images :delete-image-library :palettes :delete-color-library)] (->> (rp/mutation! method {:id library-id}) - (rx/map #(delete-library-result type library-id))))))) + (rx/map #(delete-library-result type team-id library-id))))))) (defn delete-library-result - [type library-id] + [type team-id library-id] (ptk/reify ::create-library-result ptk/UpdateEvent (update [_ state] (let [update-fn (fn [libraries] (filterv #(not= library-id (:id %)) libraries))] (-> state - (update-in [:library type] update-fn)))))) + (update-in [:library type team-id] update-fn)))))) ;; Delete library item @@ -175,7 +178,7 @@ (let [update-fn (fn [items] (filterv #(not= item-id (:id %)) items))] (-> state - (update-in [:library :selected-items library-id] update-fn)))))) + (update-in [:library-items type library-id] update-fn)))))) ;; Batch delete @@ -204,4 +207,25 @@ update-fn (fn [items] (filterv #(not (item-ids-set (:id %))) items))] (-> state - (update-in [:library :selected-items library-id] update-fn)))))) + (update-in [:library-items type library-id] update-fn)))))) + +;; Workspace - select library + +(defn select-library + [type library-id] + (ptk/reify ::select-library + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:library-selected type] library-id))))) + + +;; Workspace - change library filter + +(defn change-library-filter + [type filter] + (ptk/reify ::change-library-filter + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:library-filter type] filter))))) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index f1016a9792..f633fb0bd9 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -66,6 +66,7 @@ (declare fetch-users) (declare fetch-images) +(declare fetch-project) (declare handle-who) (declare handle-pointer-update) (declare handle-pointer-send) @@ -294,7 +295,8 @@ (defn initialize "Initialize the workspace state." - [file-id page-id] + [project-id file-id page-id] + (us/verify ::us/uuid project-id) (us/verify ::us/uuid file-id) (us/verify ::us/uuid page-id) (ptk/reify ::initialize @@ -305,9 +307,11 @@ (rx/merge (rx/of (fetch-file-with-users file-id) (fetch-pages file-id) - (fetch-images file-id)) + (fetch-images file-id) + (fetch-project project-id)) (->> (rx/zip (rx/filter (ptk/type? ::pages-fetched) stream) - (rx/filter (ptk/type? ::file-fetched) stream)) + (rx/filter (ptk/type? ::file-fetched) stream) + (rx/filter (ptk/type? ::project-fetched) stream)) (rx/take 1) (rx/do (fn [_] (uxbox.util.timers/schedule 500 #(reset! st/loader false)))) @@ -355,7 +359,8 @@ (rx/of (initialize-page-persistence page-id))))) (defn finalize - [file-id page-id] + [project-id file-id page-id] + (us/verify ::us/uuid project-id) (us/verify ::us/uuid file-id) (us/verify ::us/uuid page-id) (ptk/reify ::finalize @@ -493,6 +498,7 @@ (s/def ::data ::cp/data) (s/def ::file ::dd/file) +(s/def ::project ::dd/project) (s/def ::page (s/keys :req-un [::id ::name @@ -546,6 +552,25 @@ state users)))) +;; --- Fetch Project data +(declare project-fetched) + +(defn fetch-project + [id] + (us/verify ::us/uuid id) + (ptk/reify ::fetch-project + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :project-by-id {:project-id id}) + (rx/map project-fetched))))) + +(defn project-fetched + [project] + (us/verify ::project project) + (ptk/reify ::project-fetched + ptk/UpdateEvent + (update [_ state] + (assoc state :workspace-project project)))) ;; --- Fetch Pages @@ -770,16 +795,20 @@ ;; --- Toggle layout flag (defn toggle-layout-flag - [flag] - (us/verify keyword? flag) + [& flags] + ;; Verify all? + #_(us/verify keyword? flag) (ptk/reify ::toggle-layout-flag ptk/UpdateEvent (update [_ state] - (update state :workspace-layout - (fn [flags] - (if (contains? flags flag) - (disj flags flag) - (conj flags flag))))))) + (let [reduce-fn + (fn [state flag] + (update state :workspace-layout + (fn [flags] + (if (contains? flags flag) + (disj flags flag) + (conj flags flag)))))] + (reduce reduce-fn state flags))))) ;; --- Tooltip @@ -1103,6 +1132,7 @@ (rx/empty)))))) + ;; --- Toggle shape's selection status (selected or deselected) (defn select-shape @@ -1924,7 +1954,8 @@ (ptk/reify ::go-to-page ptk/WatchEvent (watch [_ state stream] - (let [file-id (get-in state [:workspace-page :file-id]) + (let [project-id (get-in state [:workspace-project :id]) + file-id (get-in state [:workspace-page :file-id]) path-params {:file-id file-id} query-params {:page-id page-id}] (rx/of (rt/nav :workspace path-params query-params)))))) @@ -1935,8 +1966,9 @@ (ptk/reify ::go-to-file ptk/WatchEvent (watch [_ state stream] - (let [page-ids (get-in state [:files file-id :pages]) - path-params {:file-id file-id} + (let [project-id (get-in state [:workspace-project :id]) + page-ids (get-in state [:files file-id :pages]) + path-params {:project-id project-id :file-id file-id} query-params {:page-id (first page-ids)}] (rx/of (rt/nav :workspace path-params query-params)))))) diff --git a/frontend/src/uxbox/main/ui.cljs b/frontend/src/uxbox/main/ui.cljs index 312cba7593..38d44ccf9c 100644 --- a/frontend/src/uxbox/main/ui.cljs +++ b/frontend/src/uxbox/main/ui.cljs @@ -73,7 +73,7 @@ ]]] - ["/workspace/:file-id" :workspace]]) + ["/workspace/:project-id/:file-id" :workspace]]) (mf/defc app-error [{:keys [error] :as props}] @@ -119,9 +119,11 @@ (mf/element dashboard #js {:route route}) :workspace - (let [file-id (uuid (get-in route [:params :path :file-id])) + (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]))] - [:& workspace/workspace {:file-id file-id + [:& workspace/workspace {:project-id project-id + :file-id file-id :page-id page-id :key file-id}]) nil))) diff --git a/frontend/src/uxbox/main/ui/components/context_menu.cljs b/frontend/src/uxbox/main/ui/components/context_menu.cljs index 2c7d85a2fd..928baebba7 100644 --- a/frontend/src/uxbox/main/ui/components/context_menu.cljs +++ b/frontend/src/uxbox/main/ui/components/context_menu.cljs @@ -3,7 +3,8 @@ [rumext.alpha :as mf] [goog.object :as gobj] [uxbox.main.ui.components.dropdown :refer [dropdown-container]] - [uxbox.util.uuid :as uuid])) + [uxbox.util.uuid :as uuid] + [uxbox.util.data :refer [classnames]])) (mf/defc context-menu {::mf/wrap-props false} @@ -13,12 +14,16 @@ (assert (vector? (gobj/get props "options")) "missing `options` prop") (let [open? (gobj/get props "show") - options (gobj/get props "options")] + options (gobj/get props "options") + is-selectable (gobj/get props "selectable") + selected (gobj/get props "selected")] (when open? [:> dropdown-container props - [:div.context-menu {:class (when open? "is-open")} + [:div.context-menu {:class (classnames :is-open open? + :is-selectable is-selectable)} [:ul.context-menu-items (for [[action-name action-handler] options] - [:li.context-menu-item {:key action-name} + [:li.context-menu-item {:class (classnames :is-selected (and selected (= action-name selected))) + :key action-name} [:a.context-menu-action {:on-click action-handler} action-name]])]]]))) diff --git a/frontend/src/uxbox/main/ui/components/tab_container.cljs b/frontend/src/uxbox/main/ui/components/tab_container.cljs new file mode 100644 index 0000000000..78accbbcc9 --- /dev/null +++ b/frontend/src/uxbox/main/ui/components/tab_container.cljs @@ -0,0 +1,27 @@ +(ns uxbox.main.ui.components.tab-container + (:require [rumext.alpha :as mf])) + +(mf/defc tab-element + [{:keys [children id title]}] + [:div.tab-element + [:div.tab-element-content children]]) + +(mf/defc tab-container + [{:keys [children selected on-change-tab]}] + (let [first-id (-> children first .-props .-id) + state (mf/use-state {:selected first-id}) + selected (or selected (:selected @state)) + handle-select (fn [tab] + (let [id (-> tab .-props .-id)] + (swap! state assoc :selected id) + (when on-change-tab (on-change-tab id))))] + [:div.tab-container + [:div.tab-container-tabs + (for [tab children] + [:div.tab-container-tab-title + {:key (str "tab-" (-> tab .-props .-id)) + :on-click (partial handle-select tab) + :class (when (= selected (-> tab .-props .-id)) "current")} + (-> tab .-props .-title)])] + [:div.tab-container-content + (filter #(= selected (-> % .-props .-id)) children)]])) diff --git a/frontend/src/uxbox/main/ui/dashboard/grid.cljs b/frontend/src/uxbox/main/ui/dashboard/grid.cljs index b933c3b7e1..eb9146adf7 100644 --- a/frontend/src/uxbox/main/ui/dashboard/grid.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/grid.cljs @@ -41,8 +41,9 @@ :edition false}) locale (i18n/use-locale) on-navigate #(st/emit! (rt/nav :workspace - {:file-id (:id file)} - {:page-id (first (:pages file))})) + {:project-id (:project-id file) + :file-id (:id file)} + {:page-id (first (:pages file))})) delete-fn #(st/emit! nil (dsh/delete-file (:id file))) on-delete #(do (dom/stop-propagation %) @@ -75,7 +76,7 @@ [:h3 (:name file)]) [:& grid-item-metadata {:modified-at (:modified-at file)}]] [:div.project-th-actions {:class (classnames - :force-display (:menu-open @local))} + :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/library.cljs b/frontend/src/uxbox/main/ui/dashboard/library.cljs index a3d52aadc6..d432d6eb56 100644 --- a/frontend/src/uxbox/main/ui/dashboard/library.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/library.cljs @@ -122,7 +122,7 @@ (dlib/retrieve-libraries :icons (:id item)) (st/emit! (rt/nav path {:team-id team-id :library-id (:id item)}))))} [:& editable-label {:value (:name item) - :on-change #(st/emit! (dlib/rename-library section library-id %))}] + :on-change #(st/emit! (dlib/rename-library section team-id library-id %))}] ])]])) (mf/defc library-top-menu @@ -136,7 +136,7 @@ [:& editable-label {:edit (:editing-name @state) :on-change #(do (stop-editing) - (st/emit! (dlib/rename-library section library-id %))) + (st/emit! (dlib/rename-library section team-id library-id %))) :on-cancel #(swap! state assoc :editing-name false) :class-name "library-top-menu-current-element-name" :value (:name selected)}] @@ -155,7 +155,7 @@ (modal/show! confirm-dialog {:on-accept #(do - (st/emit! (dlib/delete-library section library-id)) + (st/emit! (dlib/delete-library section team-id library-id)) (st/emit! (rt/nav path {:team-id team-id}))) :message "Are you sure you want to delete this library?" :accept-text "Delete"})))]]}]] @@ -301,12 +301,12 @@ :message "Are you sure you want to delete this color?" :accept-text "Delete"}))]]}]]]))) -(defn libraries-ref [section] - (-> (comp (l/key :library) (l/key section)) +(defn libraries-ref [section team-id] + (-> (comp (l/key :library) (l/key section) (l/key team-id)) (l/derive st/state))) -(defn selected-items-ref [library-id] - (-> (comp (l/key :library) (l/key :selected-items) (l/key library-id)) +(defn selected-items-ref [section library-id] + (-> (comp (l/key :library-items) (l/key section) (l/key library-id)) (l/derive st/state))) (def last-deleted-library-ref @@ -316,8 +316,8 @@ (mf/defc library-page [{:keys [team-id library-id section]}] (let [state (mf/use-state {:selected #{}}) - libraries (mf/deref (libraries-ref section)) - items (mf/deref (selected-items-ref library-id)) + libraries (mf/deref (libraries-ref section team-id)) + items (mf/deref (selected-items-ref section library-id)) last-deleted-library (mf/deref last-deleted-library-ref) selected-library (first (filter #(= (:id %) library-id) libraries))] diff --git a/frontend/src/uxbox/main/ui/workspace.cljs b/frontend/src/uxbox/main/ui/workspace.cljs index 92d132cd34..403d2a5b32 100644 --- a/frontend/src/uxbox/main/ui/workspace.cljs +++ b/frontend/src/uxbox/main/ui/workspace.cljs @@ -60,16 +60,14 @@ (mf/defc workspace-content [{:keys [page file layout] :as params}] (let [frame (mf/use-ref nil) - left-sidebar? (not (empty? (keep layout [:layers :sitemap - :document-history]))) - right-sidebar? (not (empty? (keep layout [:icons :drawtools - :element-options]))) + left-sidebar? (not (empty? (keep layout [:layers :sitemap :document-history :libraries]))) + right-sidebar? (not (empty? (keep layout [:icons :drawtools :element-options]))) classes (classnames :no-tool-bar-right (not right-sidebar?) :no-tool-bar-left (not left-sidebar?))] [:* (when (:colorpalette layout) - [:& colorpalette]) + [:& colorpalette {:left-sidebar? left-sidebar?}]) [:main.main-content [:& context-menu {}] @@ -101,13 +99,13 @@ (mf/defc workspace - [{:keys [file-id page-id] :as props}] + [{:keys [project-id file-id page-id] :as props}] (-> (mf/deps file-id page-id) (mf/use-effect (fn [] - (st/emit! (dw/initialize file-id page-id)) - #(st/emit! (dw/finalize file-id page-id))))) + (st/emit! (dw/initialize project-id file-id page-id)) + #(st/emit! (dw/finalize project-id file-id page-id))))) (-> (mf/deps file-id) (mf/use-effect diff --git a/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs b/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs index 742717575b..cfb8522e28 100644 --- a/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs @@ -13,16 +13,30 @@ [uxbox.builtins.icons :as i] [uxbox.main.data.colors :as udc] [uxbox.main.data.workspace :as udw] + [uxbox.main.data.library :as dlib] [uxbox.main.store :as st] [uxbox.main.ui.keyboard :as kbd] [uxbox.util.color :refer [hex->rgb]] [uxbox.util.data :refer [read-string seek]] - [uxbox.util.dom :as dom])) + [uxbox.util.dom :as dom] + [uxbox.main.ui.components.context-menu :refer [context-menu]])) ;; --- Refs -(def collections-iref - (-> (l/key :colors-collections) +(def project-ref + (-> (l/key :workspace-project) + (l/derive st/state))) + +(def libraries-ref + (-> (comp (l/key :library) (l/key :palettes)) + (l/derive st/state))) + +(defn selected-items-ref [library-id] + (-> (comp (l/key :library-items) (l/key :palettes) (l/key library-id)) + (l/derive st/state))) + +(def selected-library-ref + (-> (comp (l/key :library-selected) (l/key :palettes)) (l/derive st/state))) ;; --- Components @@ -30,8 +44,6 @@ (mf/defc palette-item [{:keys [color] :as props}] (let [rgb-vec (hex->rgb color) - rgb-color (apply str "" (interpose ", " rgb-vec)) - select-color (fn [event] (if (kbd/shift? event) @@ -41,89 +53,86 @@ [:div.color-cell {:key (str color) :on-click select-color} [:span.color {:style {:background color}}] - [:span.color-text color] - [:span.color-text rgb-color]])) + [:span.color-text color]])) (mf/defc palette - [{:keys [colls] :as props}] - (let [local (mf/use-state {}) - colls (->> colls - (filter :id) - (sort-by :name)) + [{:keys [libraries left-sidebar?] :as props}] - coll (or (:selected @local) - (first colls)) + (when (and libraries (-> libraries count (> 0))) + (let [current-selection (or (mf/deref selected-library-ref) (-> libraries first :id)) + state (mf/use-state {:show-menu false })] + (mf/use-effect + (mf/deps current-selection) + #(st/emit! (dlib/retrieve-library-data :palettes current-selection))) + + (let [items (-> current-selection selected-items-ref mf/deref) + doc-width (.. js/document -documentElement -clientWidth) + width (:width @state (* doc-width 0.84)) + offset (:offset @state 0) + visible (/ width 86) + invisible (- (count items) visible) + close #(st/emit! (udw/toggle-layout-flag :colorpalette)) + container (mf/use-ref nil) + container-child (mf/use-ref nil) - doc-width (.. js/document -documentElement -clientWidth) - width (:width @local (* doc-width 0.84)) - offset (:offset @local 0) - visible (/ width 86) - invisible (- (count (:colors coll)) visible) - close #(st/emit! (udw/toggle-layout-flag :colorpalette)) + on-left-arrow-click + (fn [event] + (when (> offset 0) + (let [element (mf/ref-val container-child)] + (swap! state update :offset dec)))) - container (mf/use-ref nil) - container-child (mf/use-ref nil) + on-right-arrow-click + (fn [event] + (when (< offset invisible) + (let [element (mf/ref-val container-child)] + (swap! state update :offset inc)))) - select-coll - (fn [event] - (let [id (read-string (dom/event->value event)) - selected (seek #(= id (:id %)) colls)] - (swap! local assoc :selected selected :position 0))) + on-scroll + (fn [event] + (if (pos? (.. event -nativeEvent -deltaY)) + (on-right-arrow-click event) + (on-left-arrow-click event))) - on-left-arrow-click - (fn [event] - (when (> offset 0) - (let [element (mf/ref-val container-child)] - (swap! local update :offset dec)))) + after-render + (fn [] + (let [dom (mf/ref-val container) + width (.-clientWidth dom)] + (when (not= (:width @state) width) + (swap! state assoc :width width)))) - on-right-arrow-click - (fn [event] - (when (< offset invisible) - (let [element (mf/ref-val container-child)] - (swap! local update :offset inc)))) + handle-click + (fn [library] + (st/emit! (dlib/select-library :palettes (:id library))))] - on-scroll - (fn [event] - (if (pos? (.. event -nativeEvent -deltaY)) - (on-right-arrow-click event) - (on-left-arrow-click event))) + (mf/use-effect nil after-render) - after-render - (fn [] - (let [dom (mf/ref-val container) - width (.-clientWidth dom)] - (when (not= (:width @local) width) - (swap! local assoc :width width))))] + [:div.color-palette {:class (when left-sidebar? "left-sidebar-open")} + [:& context-menu {:selectable true + :selected (->> libraries (filter #(= (:id %) current-selection)) first :name) + :show (:show-menu @state) + :on-close #(swap! state assoc :show-menu false) + :options (mapv #(vector (:name %) (partial handle-click %)) libraries)} ] + [:div.color-palette-actions + {:on-click #(swap! state assoc :show-menu true)} + [:div.color-palette-actions-button i/actions]] - (mf/use-effect nil after-render) + [:span.left-arrow {:on-click on-left-arrow-click} i/arrow-slide] - [:div.color-palette - [:div.color-palette-actions - [:select.input-select {:on-change select-coll - :default-value (pr-str (:id coll))} - (for [item colls] - [:option {:key (:id item) :value (pr-str (:id item))} - (:name item)])] + [:div.color-palette-content {:ref container :on-wheel on-scroll} + [:div.color-palette-inside {:ref container-child + :style {:position "relative" + :width (str (* 86 (count items)) "px") + :right (str (* 86 offset) "px")}} + (for [item items] + [:& palette-item {:color (:content item) :key (:id item)}])]] - #_[:div.color-palette-buttons - [:div.btn-palette.edit.current i/pencil] - [:div.btn-palette.create i/close]]] - - [:span.left-arrow {:on-click on-left-arrow-click} i/arrow-slide] - - [:div.color-palette-content {:ref container :on-wheel on-scroll} - [:div.color-palette-inside {:ref container-child - :style {:position "relative" - :width (str (* 86 (count (:colors coll))) "px") - :right (str (* 86 offset) "px")}} - (for [color (:colors coll)] - [:& palette-item {:color color :key color}])]] - - [:span.right-arrow {:on-click on-right-arrow-click} i/arrow-slide] - [:span.close-palette {:on-click close} i/close]])) + [:span.right-arrow {:on-click on-right-arrow-click} i/arrow-slide]])))) (mf/defc colorpalette - [props] - (let [colls (mf/deref collections-iref)] - #_(mf/use-effect #(st/emit! (udc/fetch-collections))) - [:& palette {:colls (vals colls)}])) + [{:keys [left-sidebar?]}] + (let [team-id (-> project-ref mf/deref :team-id) + libraries (-> libraries-ref mf/deref vals flatten)] + (mf/use-effect #(st/emit! (dlib/retrieve-libraries :palettes) + (dlib/retrieve-libraries :palettes team-id))) + [:& palette {:left-sidebar? left-sidebar? + :libraries libraries}])) diff --git a/frontend/src/uxbox/main/ui/workspace/left_toolbar.cljs b/frontend/src/uxbox/main/ui/workspace/left_toolbar.cljs index 29048d0c15..3e3cdb29ec 100644 --- a/frontend/src/uxbox/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/uxbox/main/ui/workspace/left_toolbar.cljs @@ -68,10 +68,12 @@ [:li.tooltip.tooltip-right {:alt "Layers" :class (when (contains? layout :layers) "selected") - :on-click #(st/emit! (dw/toggle-layout-flag :layers))} + :on-click #(st/emit! (dw/toggle-layout-flag :layers :sitemap))} i/layers] [:li.tooltip.tooltip-right - {:alt "Libraries"} + {:alt "Libraries" + :class (when (contains? layout :libraries) "selected") + :on-click #(st/emit! (dw/toggle-layout-flag :libraries))} i/icon-set] [:li.tooltip.tooltip-right {:alt "History"} diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs index bd18b669ea..f3fa789802 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs @@ -11,11 +11,13 @@ (ns uxbox.main.ui.workspace.sidebar (:require [rumext.alpha :as mf] + [cuerdas.core :as str] [uxbox.main.ui.workspace.sidebar.history :refer [history-toolbox]] [uxbox.main.ui.workspace.sidebar.icons :refer [icons-toolbox]] [uxbox.main.ui.workspace.sidebar.layers :refer [layers-toolbox]] [uxbox.main.ui.workspace.sidebar.options :refer [options-toolbox]] - [uxbox.main.ui.workspace.sidebar.sitemap :refer [sitemap-toolbox]])) + [uxbox.main.ui.workspace.sidebar.sitemap :refer [sitemap-toolbox]] + [uxbox.main.ui.workspace.sidebar.libraries :refer [libraries-toolbox]])) ;; --- Left Sidebar (Component) @@ -24,12 +26,15 @@ [{:keys [layout page file] :as props}] [:aside.settings-bar.settings-bar-left [:div.settings-bar-inside + {:data-layout (str/join "," layout)} (when (contains? layout :sitemap) [:& sitemap-toolbox {:file file :page page}]) (when (contains? layout :document-history) [:& history-toolbox]) (when (contains? layout :layers) - [:& layers-toolbox {:page page}])]]) + [:& layers-toolbox {:page page}]) + (when (contains? layout :libraries) + [:& libraries-toolbox])]]) ;; --- Right Sidebar (Component) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/libraries.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/libraries.cljs new file mode 100644 index 0000000000..b84c3dfdb4 --- /dev/null +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/libraries.cljs @@ -0,0 +1,165 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns uxbox.main.ui.workspace.sidebar.libraries + (:require + [lentes.core :as l] + [cuerdas.core :as str] + [rumext.alpha :as mf] + [uxbox.common.data :as d] + [uxbox.builtins.icons :as i] + [uxbox.main.data.workspace :as dw] + [uxbox.main.refs :as refs] + [uxbox.main.store :as st] + [uxbox.main.ui.keyboard :as kbd] + [uxbox.main.ui.shapes.icon :as icon] + [uxbox.main.ui.workspace.sortable :refer [use-sortable]] + [uxbox.util.dom :as dom] + [uxbox.util.uuid :as uuid] + [uxbox.util.i18n :as i18n :refer [tr]] + [uxbox.util.data :refer [classnames]] + [uxbox.main.ui.components.tab-container :refer [tab-container tab-element]] + [uxbox.main.data.library :as dlib] + [uxbox.main.ui.components.context-menu :refer [context-menu]])) + +;; --- Refs + +(def project-ref + (-> (l/key :workspace-project) + (l/derive st/state))) + +(defn libraries-ref [section] + (-> (comp (l/key :library) (l/key section)) + (l/derive st/state))) + +(defn selected-items-ref [section library-id] + (-> (comp (l/key :library-items) (l/key section) (l/key library-id)) + (l/derive st/state))) + +(defn selected-library-ref [section] + (-> (comp (l/key :library-selected) (l/key section)) + (l/derive st/state))) + +(defn selected-filter-ref [section] + (-> (comp (l/key :library-filter) (l/key section)) + (l/derive st/state))) + +;; --- Components + +(mf/defc library-tab [{:keys [libraries section]}] + (when (and libraries (-> libraries count (> 0))) + (let [first-id (-> libraries first :id) + current-selection (or (mf/deref (selected-library-ref section)) first-id)] + + ;; Check if the current selection is in the list of libraries + (mf/use-effect + (mf/deps libraries) + #(when (not (some (fn [it] (= current-selection (-> it :id))) libraries)) + (st/emit! (dlib/select-library section first-id)))) + + ;; Retrieve the library data given the current selected library + (mf/use-effect + (mf/deps current-selection) + #(st/emit! (dlib/retrieve-library-data section current-selection))) + + [:div.library-tab + {:class (classnames :icons-tab (= section :icons) + :images-tab (= section :images))} + [:select.input-select.library-tab-libraries + {:on-change #(st/emit! (dlib/select-library section (-> % dom/get-target dom/get-value uuid)))} + (for [library libraries] + [:option.library-tab-libraries-item + {:key (:id library) + :value (:id library) + :selected (= current-selection (:id library))} + (:name library)])] + [:div.library-tab-content + (let [items (mf/deref (selected-items-ref section current-selection))] + (for [item items] + [:div.library-tab-element + {:key (:id item) + :on-click #(st/emit! (dw/select-for-drawing :icon item))} + (if (= section :icons) + [:* ;; ICONS + [:svg {:view-box (->> item :metadata :view-box (str/join " ")) + :width (-> item :metadata :width) + :height (-> item :metadat :height) + :dangerouslySetInnerHTML {:__html (:content item)}}] + [:span.library-tab-element-name (:name item)]] + + [:* ;; IMAGES + [:img {:src (:thumb-uri item)}] + [:span.library-tab-element-name (:name item)]])]))]]))) + +(mf/defc libraries-toolbox + [{:keys [key]}] + (let [state (mf/use-state {:menu-open false}) + selected-filter (fn [section] (or (mf/deref (selected-filter-ref section)) :all)) + team-id (-> project-ref mf/deref :team-id) + + filter-to-str {:all (tr "workspace.library.all") + :own (tr "workspace.library.own") + :store (tr "workspace.library.store")} + + select-option + (fn [option] + (st/emit! + (dlib/change-library-filter :icons option) + (dlib/change-library-filter :images option))) + + filter-libraries + (fn [section libraries] + (case (selected-filter section) + :all (-> libraries vals flatten) + :own (libraries team-id) + :store (libraries uuid/zero))) + + get-libraries + (fn [section] (->> (libraries-ref section) + mf/deref + (filter-libraries section)))] + + (mf/use-effect + (mf/deps team-id) + #(when team-id + (st/emit! (dlib/retrieve-libraries :icons) + (dlib/retrieve-libraries :images) + (dlib/retrieve-libraries :icons team-id) + (dlib/retrieve-libraries :images team-id)))) + + [:div#libraries.tool-window + [:div.libraries-window-bar + [:div.libraries-window-bar-title (tr "workspace.library.libraries")] + [:div.libraries-window-bar-options + {:on-click #(swap! state assoc :menu-open true)} + (filter-to-str (selected-filter :icons)) + [:button + { + :type "button"} + i/arrow-slide + [:& context-menu + {:selectable true + :show (:menu-open @state) + :selected (filter-to-str (selected-filter :icons)) + :on-close #(swap! state assoc :menu-open false) + :options (mapv (fn [[key val]] [val #(select-option key)]) filter-to-str)}]]]] + + [:div.tool-window-content + [:& tab-container {} + [:& tab-element + {:id :icons :title (tr "workspace.library.icons")} + [:& library-tab {:section :icons + :libraries (get-libraries :icons) }]] + + [:& tab-element + {:id :images :title (tr "workspace.library.images")} + [:& library-tab {:section :images + :libraries (get-libraries :images)}]]]]])) + + diff --git a/frontend/src/uxbox/util/data.cljs b/frontend/src/uxbox/util/data.cljs index 4dd2035f5c..61145306d8 100644 --- a/frontend/src/uxbox/util/data.cljs +++ b/frontend/src/uxbox/util/data.cljs @@ -205,3 +205,4 @@ ;; (if (::some-interrupt (ex-data e#)) ;; nil ;; (throw e#))))))) +