diff --git a/backend/tests/uxbox/tests/test_common_pages.clj b/backend/tests/uxbox/tests/test_common_pages.clj index 9f8213737..90c9ecec5 100644 --- a/backend/tests/uxbox/tests/test_common_pages.clj +++ b/backend/tests/uxbox/tests/test_common_pages.clj @@ -14,6 +14,38 @@ [uxbox.common.uuid :as uuid] [uxbox.tests.helpers :as th])) +(t/deftest process-change-set-option + (let [data cp/default-page-data] + (t/testing "Sets option single" + (let [chg {:type :set-option + :option :test + :value "test"} + res (cp/process-changes data [chg])] + (t/is (= "test" (get-in res [:options :test]))))) + + (t/testing "Sets option nested" + (let [chgs [{:type :set-option + :option [:values :test :a] + :value "a"} + {:type :set-option + :option [:values :test :b] + :value "b"}] + res (cp/process-changes data chgs)] + (t/is (= {:a "a" :b "b"} (get-in res [:options :values :test]))))) + + (t/testing "Remove option" + (let [chgs [{:type :set-option + :option [:values :test :a] + :value "a"} + {:type :set-option + :option [:values :test :b] + :value "b"} + {:type :set-option + :option [:values :test] + :value nil}] + res (cp/process-changes data chgs)] + (t/is (= nil (get-in res [:options :values :test]))))))) + (t/deftest process-change-add-obj (let [data cp/default-page-data id-a (uuid/next) diff --git a/common/uxbox/common/pages.cljc b/common/uxbox/common/pages.cljc index e02dfbffe..db454761d 100644 --- a/common/uxbox/common/pages.cljc +++ b/common/uxbox/common/pages.cljc @@ -253,6 +253,12 @@ (defmulti change-spec-impl :type) +(s/def :set-option/option any? #_(s/or keyword? (s/coll-of keyword?))) +(s/def :set-option/value any?) + +(defmethod change-spec-impl :set-option [_] + (s/keys :req-un [:set-option/option :set-option/value])) + (defmethod change-spec-impl :add-obj [_] (s/keys :req-un [::id ::frame-id ::obj] :opt-un [::session-id ::parent-id])) @@ -313,6 +319,12 @@ (declare insert-at-index) +(defmethod process-change :set-option + [data {:keys [option value]}] + (let [path (if (seqable? option) option [option])] + (-> data + (assoc-in (into [:options] path) value)))) + (defmethod process-change :add-obj [data {:keys [id obj frame-id parent-id index] :as change}] (let [parent-id (or parent-id frame-id) diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index a4ca8a71d..d0f049c54 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -1,28 +1,28 @@ { "dashboard.grid.delete" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:61", "src/uxbox/main/ui/dashboard/grid.cljs:92" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:102", "src/uxbox/main/ui/dashboard/project.cljs:62" ], "translations" : { "en" : "Delete" } }, "dashboard.grid.edit" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:60", "src/uxbox/main/ui/dashboard/grid.cljs:91" ], "translations" : { "en" : "Edit" + }, + "unused" : true + }, + "dashboard.grid.empty-files" : { + "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:124" ], + "translations" : { + "en" : "You still have no files here" } }, "dashboard.grid.rename" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:60", "src/uxbox/main/ui/dashboard/grid.cljs:91" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:101", "src/uxbox/main/ui/dashboard/project.cljs:61" ], "translations" : { "en" : "Rename" } }, - "dashboard.grid.empty-files" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:114" ], - "translations" : { - "en" : "You still have no files here" - } - }, "dashboard.header.draft" : { "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:55" ], "translations" : { @@ -63,7 +63,7 @@ } }, "dashboard.header.project" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:68" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:57" ], "translations" : { "en" : "Project %s" } @@ -176,7 +176,7 @@ } }, "ds.button.delete" : { - "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" ], + "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:259", "src/uxbox/main/ui/dashboard/library.cljs:300" ], "translations" : { "en" : "Delete" } @@ -257,7 +257,7 @@ "unused" : true }, "ds.new-file" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:110", "src/uxbox/main/ui/dashboard/grid.cljs:116" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:120", "src/uxbox/main/ui/dashboard/grid.cljs:126" ], "translations" : { "en" : "+ New File", "fr" : null @@ -299,7 +299,7 @@ } }, "ds.updated-at" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:35" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/grid.cljs:45" ], "translations" : { "en" : "Updated: %s", "fr" : "Mis à jour: %s" @@ -341,21 +341,21 @@ } }, "errors.generic" : { - "used-in" : [ "src/uxbox/main/ui.cljs:179" ], + "used-in" : [ "src/uxbox/main/ui.cljs:178" ], "translations" : { "en" : "Something wrong has happened.", "fr" : "Quelque chose c'est mal passé." } }, "errors.network" : { - "used-in" : [ "src/uxbox/main/ui.cljs:173" ], + "used-in" : [ "src/uxbox/main/ui.cljs:172" ], "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:74" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:68" ], "translations" : { "en" : null, "fr" : null @@ -459,7 +459,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!" @@ -702,73 +702,73 @@ } }, "viewer.header.dont-show-interactions" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:40" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:67" ], "translations" : { "en" : "Don't show interactions" } }, "viewer.header.edit-page" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:137" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:164" ], "translations" : { "en" : "Edit page" } }, "viewer.header.fullscreen" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:148" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:175" ], "translations" : { "en" : "Full Screen" } }, "viewer.header.share.copy-link" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:86" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:113" ], "translations" : { "en" : "Copy link" } }, "viewer.header.share.create-link" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:94" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:121" ], "translations" : { "en" : "Create link" } }, "viewer.header.share.placeholder" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:84" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:111" ], "translations" : { "en" : "Share link will apear here" } }, "viewer.header.share.remove-link" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:92" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:119" ], "translations" : { "en" : "Remove link" } }, "viewer.header.share.subtitle" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:88" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:115" ], "translations" : { "en" : "Anyone with the link will have access" } }, "viewer.header.share.title" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:72", "src/uxbox/main/ui/viewer/header.cljs:74", "src/uxbox/main/ui/viewer/header.cljs:80" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:99", "src/uxbox/main/ui/viewer/header.cljs:101", "src/uxbox/main/ui/viewer/header.cljs:107" ], "translations" : { "en" : "Share link" } }, "viewer.header.show-interactions" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:44" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:71" ], "translations" : { "en" : "Show interactions" } }, "viewer.header.show-interactions-on-click" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:48" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:75" ], "translations" : { "en" : "Show interactions on click" } }, "viewer.header.sitemap" : { - "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:121" ], + "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:148" ], "translations" : { "en" : "Sitemap" } @@ -821,70 +821,92 @@ "en" : "Align top" } }, + "workspace.header.menu.disable-dynamic-alignment" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:116" ], + "translations" : { + "en" : "Disable dynamic alignment" + } + }, + "workspace.header.menu.disable-snap-grid" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:92" ], + "translations" : { + "en" : "Disable snap to grid" + } + }, + "workspace.header.menu.enable-dynamic-alignment" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:117" ], + "translations" : { + "en" : "Enable dynamic aligment" + } + }, + "workspace.header.menu.enable-snap-grid" : { + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:93" ], + "translations" : { + "en" : "Snap to grid" + } + }, "workspace.header.menu.hide-grid" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:94" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:86" ], "translations" : { "en" : "Hide grid" } }, "workspace.header.menu.hide-layers" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:101" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:98" ], "translations" : { "en" : "Hide layers" } }, "workspace.header.menu.hide-libraries" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:115" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:110" ], "translations" : { "en" : "Hide libraries" } }, "workspace.header.menu.hide-palette" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:108" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:104" ], "translations" : { "en" : "Hide color palette" } }, "workspace.header.menu.hide-rules" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:87" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:80" ], "translations" : { "en" : "Hide rules" } }, "workspace.header.menu.show-grid" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:95" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:87" ], "translations" : { "en" : "Show grid" } }, "workspace.header.menu.show-layers" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:102" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:99" ], "translations" : { "en" : "Show layers" } }, "workspace.header.menu.show-libraries" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:116" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:111" ], "translations" : { "en" : "Show libraries" } }, "workspace.header.menu.show-palette" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:109" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:105" ], "translations" : { "en" : "Show color palette" } }, "workspace.header.menu.show-rules" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:88" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:81" ], "translations" : { "en" : "Show rules" } }, - "workspace.header.menu.disable-dynamic-alignment": "Disable dynamic alignment", - "workspace.header.menu.enable-dynamic-alignment": "Enable dynamic aligment", "workspace.header.viewer" : { - "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:153" ], + "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:154" ], "translations" : { "en" : "View mode (Ctrl + P)", "fr" : "Mode visualisation (Ctrl + P)" @@ -927,11 +949,11 @@ } }, "workspace.options.color" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:89" ], "translations" : { "en" : "Color", "fr" : "Couleur" - } + }, + "unused" : true }, "workspace.options.design" : { "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options.cljs:76" ], @@ -940,7 +962,7 @@ } }, "workspace.options.fill" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:69", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:446" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:446", "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:71" ], "translations" : { "en" : "Fill", "fr" : "Fond" @@ -1076,11 +1098,11 @@ "unused" : true }, "workspace.options.grid-options" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:76" ], "translations" : { "en" : "Grid settings", "fr" : "Paramètres de la grille" - } + }, + "unused" : true }, "workspace.options.line-height-letter-spacing" : { "translations" : { @@ -1097,13 +1119,13 @@ "unused" : true }, "workspace.options.navigate-to" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:51" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:58" ], "translations" : { "en" : "Navigate to" } }, "workspace.options.none" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:64" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:71" ], "translations" : { "en" : "None" } @@ -1116,7 +1138,7 @@ "unused" : true }, "workspace.options.position" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:135", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:126" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:127", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:138" ], "translations" : { "en" : "Position", "fr" : "Position" @@ -1129,14 +1151,14 @@ } }, "workspace.options.radius" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:183" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:179" ], "translations" : { "en" : "Radius", "fr" : "TODO" } }, "workspace.options.rotation" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:159" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:154" ], "translations" : { "en" : "Rotation", "fr" : "TODO" @@ -1150,78 +1172,78 @@ "unused" : true }, "workspace.options.select-a-shape" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:45" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:52" ], "translations" : { "en" : "Select a shape, artboard or group to drag a connection to other artboard." } }, "workspace.options.select-artboard" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:57" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:64" ], "translations" : { "en" : "Select artboard" } }, "workspace.options.size" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:79", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:107", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:101" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:102", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:110" ], "translations" : { "en" : "Size", "fr" : "Taille" } }, "workspace.options.size-presets" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:83" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:84" ], "translations" : { "en" : "Size presets" } }, "workspace.options.stroke" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:109", "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:173" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:111", "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:175" ], "translations" : { "en" : "Stroke", "fr" : null } }, "workspace.options.stroke.center" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:159" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:161" ], "translations" : { "en" : "Center" } }, "workspace.options.stroke.dashed" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:167" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:169" ], "translations" : { "en" : "Dashed", "fr" : "Tiré" } }, "workspace.options.stroke.dotted" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:166" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:168" ], "translations" : { "en" : "Dotted", "fr" : "Pointillé" } }, "workspace.options.stroke.inner" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:160" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:162" ], "translations" : { "en" : "Inside" } }, "workspace.options.stroke.mixed" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:168" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:170" ], "translations" : { "en" : "Mixed", "fr" : "Mixte" } }, "workspace.options.stroke.outer" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:161" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:163" ], "translations" : { "en" : "Outside" } }, "workspace.options.stroke.solid" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:165" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:167" ], "translations" : { "en" : "Solid", "fr" : "Solide" @@ -1242,7 +1264,7 @@ "unused" : true }, "workspace.options.use-play-button" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:47" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:54" ], "translations" : { "en" : "Use the play button at the header to run the prototype view." } @@ -1366,7 +1388,7 @@ } }, "workspace.viewport.click-to-close-path" : { - "used-in" : [ "src/uxbox/main/ui/workspace/drawarea.cljs:360" ], + "used-in" : [ "src/uxbox/main/ui/workspace/drawarea.cljs:357" ], "translations" : { "en" : "Click to close the path" } diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 42756c248..8f091d3fb 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -591,6 +591,13 @@ margin-bottom: 0.5rem; } +.element-set-content .custom-select.input-option { + border-top: none; + border-left: none; + border-right: none; + margin-left: 0.25rem; +} + .element-set-content .grid-option-main { display: flex; padding: 0.5rem 0; @@ -626,7 +633,6 @@ } } - } .grid-option-main-actions { @@ -661,9 +667,9 @@ .btn-options { cursor: pointer; border: 1px solid $color-black; - background: #1F1F1F; + background: $color-gray-60; border-radius: 2px; - color: #B1B2B5; + color: $color-gray-20; font-size: 11px; line-height: 16px; flex-grow: 1; @@ -673,8 +679,13 @@ margin-right: 0.5rem; } - &:hover { + &:not([disabled]):hover { background: $color-primary; color: $color-black; } + + &[disabled] { + opacity: 0.4; + cursor: auto; + } } diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 699283f89..d607e35a9 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -67,7 +67,8 @@ :element-options :rules :dynamic-alignment - :layouts}) + :display-grid + :snap-grid}) (s/def ::options-mode #{:design :prototype}) @@ -1493,13 +1494,35 @@ ;; Layouts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defonce default-layout-params + {:square {:size 16 + :color {:value "#59B9E2" + :opacity 0.9}} + + :column {:size 12 + :type :stretch + :item-width nil + :gutter 8 + :margin 0 + :color {:value "#DE4762" + :opacity 0.1}} + :row {:size 12 + :type :stretch + :item-height nil + :gutter 8 + :margin 0 + :color {:value "#DE4762" + :opacity 0.1}}}) + (defn add-frame-layout [frame-id] (ptk/reify ::set-frame-layout dwc/IBatchedChange ptk/UpdateEvent (update [_ state] (let [pid (:current-page-id state) - default-params {:size 16 :color {:value "#59B9E2" :opacity 0.9}} + default-params (or + (get-in state [:workspace-data pid :options :saved-layouts :square]) + (:square default-layout-params)) prop-path [:workspace-data pid :objects frame-id :layouts] layout {:type :square :params default-params @@ -1528,14 +1551,13 @@ (defn set-default-layout [type params] (ptk/reify ::set-default-layout - dwc/IBatchedChange - - ;; TODO: Save into the backend - ptk/UpdateEvent - (update [_ state] - (-> - state - (assoc-in [:workspace-page :options :saved-layouts type] params))))) + ptk/WatchEvent + (watch [_ state stream] + (rx/of (dwc/commit-changes [{:type :set-option + :option [:saved-layouts type] + :value params}] + [] + {:commit-local? true}))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Exports diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index 155e0ad81..295530487 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -42,12 +42,6 @@ (def workspace-page (l/derived :workspace-page st/state)) -(def workspace-page-options - (l/derived :options workspace-page)) - -(def workspace-saved-layouts - (l/derived :saved-layouts workspace-page-options)) - (def workspace-page-id (l/derived :id workspace-page)) @@ -74,6 +68,13 @@ (get-in % [:workspace-data page-id])) (l/derived st/state))) +(def workspace-page-options + (l/derived :options workspace-data)) + +(def workspace-saved-layouts + (l/derived :saved-layouts workspace-page-options)) + + (def workspace-objects (l/derived :objects workspace-data)) diff --git a/frontend/src/uxbox/main/snap.cljs b/frontend/src/uxbox/main/snap.cljs index 70c0be0c9..8e5c2c85c 100644 --- a/frontend/src/uxbox/main/snap.cljs +++ b/frontend/src/uxbox/main/snap.cljs @@ -18,10 +18,10 @@ (def ^:private snap-accuracy 5) -(defn- remove-from-snap-points [ids-to-remove] +(defn- remove-from-snap-points [remove-id?] (fn [query-result] (->> query-result - (map (fn [[value data]] [value (remove (comp ids-to-remove second) data)])) + (map (fn [[value data]] [value (remove (comp remove-id? second) data)])) (filter (fn [[_ data]] (not (empty? data))))))) (defn- flatten-to-points @@ -90,24 +90,32 @@ (defn closest-snap-point [page-id shapes layout point] - (if (layout :dynamic-alignment) - (let [frame-id (snap-frame-id shapes) - filter-shapes (into #{} (map :id shapes))] - (->> (closest-snap page-id frame-id [point] filter-shapes) - (rx/map #(gpt/add point %)))) - (rx/of point))) + (let [frame-id (snap-frame-id shapes) + filter-shapes (into #{} (map :id shapes)) + filter-shapes (fn [id] (if (= id :layout) + (or (not (contains? layout :display-grid)) + (not (contains? layout :snap-grid))) + (or (filter-shapes id) + (not (contains? layout :dynamic-alignment)))))] + (->> (closest-snap page-id frame-id [point] filter-shapes) + (rx/map #(gpt/add point %)) + (rx/map gpt/round)))) (defn closest-snap-move [page-id shapes layout movev] - (if (layout :dynamic-alignment) - (let [frame-id (snap-frame-id shapes) - filter-shapes (into #{} (map :id shapes)) - shapes-points (->> shapes - ;; Unroll all the possible snap-points - (mapcat (partial sp/shape-snap-points)) + (let [frame-id (snap-frame-id shapes) + filter-shapes (into #{} (map :id shapes)) + filter-shapes (fn [id] (if (= id :layout) + (or (not (contains? layout :display-grid)) + (not (contains? layout :snap-grid))) + (or (filter-shapes id) + (not (contains? layout :dynamic-alignment))))) + shapes-points (->> shapes + ;; Unroll all the possible snap-points + (mapcat (partial sp/shape-snap-points)) - ;; Move the points in the translation vector - (map #(gpt/add % movev)))] - (->> (closest-snap page-id frame-id shapes-points filter-shapes) - (rx/map #(gpt/add movev %)))) - (rx/of movev))) + ;; Move the points in the translation vector + (map #(gpt/add % movev)))] + (->> (closest-snap page-id frame-id shapes-points filter-shapes) + (rx/map #(gpt/add movev %)) + (rx/map gpt/round)))) diff --git a/frontend/src/uxbox/main/ui/workspace/header.cljs b/frontend/src/uxbox/main/ui/workspace/header.cljs index b01d66bba..ac38e9efb 100644 --- a/frontend/src/uxbox/main/ui/workspace/header.cljs +++ b/frontend/src/uxbox/main/ui/workspace/header.cljs @@ -72,42 +72,42 @@ :on-close #(reset! show-menu? false)} [:ul.menu [:li {:on-click #(st/emit! (dw/toggle-layout-flag :rules))} - [:span i/ruler] [:span (if (contains? layout :rules) (t locale "workspace.header.menu.hide-rules") (t locale "workspace.header.menu.show-rules"))]] - [:li {:on-click #(st/emit! (dw/toggle-layout-flag :grid))} - [:span i/grid] + [:li {:on-click #(st/emit! (dw/toggle-layout-flag :display-grid))} [:span - (if (contains? layout :grid) + (if (contains? layout :display-grid) (t locale "workspace.header.menu.hide-grid") (t locale "workspace.header.menu.show-grid"))]] + [:li {:on-click #(st/emit! (dw/toggle-layout-flag :snap-grid))} + [:span + (if (contains? layout :snap-grid) + (t locale "workspace.header.menu.disable-snap-grid") + (t locale "workspace.header.menu.enable-snap-grid"))]] + [:li {:on-click #(st/emit! (dw/toggle-layout-flag :sitemap :layers))} - [:span i/layers] [:span (if (or (contains? layout :sitemap) (contains? layout :layers)) (t locale "workspace.header.menu.hide-layers") (t locale "workspace.header.menu.show-layers"))]] [:li {:on-click #(st/emit! (dw/toggle-layout-flag :colorpalette))} - [:span i/palette] [:span (if (contains? layout :colorpalette) (t locale "workspace.header.menu.hide-palette") (t locale "workspace.header.menu.show-palette"))]] [:li {:on-click #(st/emit! (dw/toggle-layout-flag :libraries))} - [:span i/icon-set] [:span (if (contains? layout :libraries) (t locale "workspace.header.menu.hide-libraries") (t locale "workspace.header.menu.show-libraries"))]] [:li {:on-click #(st/emit! (dw/toggle-layout-flag :dynamic-alignment))} - [:span i/shape-halign-left] [:span (if (contains? layout :dynamic-alignment) (t locale "workspace.header.menu.disable-dynamic-alignment") diff --git a/frontend/src/uxbox/main/ui/workspace/layout_display.cljs b/frontend/src/uxbox/main/ui/workspace/layout_display.cljs index f04918b48..6facef86d 100644 --- a/frontend/src/uxbox/main/ui/workspace/layout_display.cljs +++ b/frontend/src/uxbox/main/ui/workspace/layout_display.cljs @@ -19,26 +19,27 @@ (let [{:keys [color size] :as params} (-> layout :params) {color-value :value color-opacity :opacity} (-> layout :params :color) {frame-width :width frame-height :height :keys [x y]} frame] - [:g.layout - [:* - (for [xs (range size frame-width size)] - [:line {:key (str (:id frame) "-y-" xs) - :x1 (+ x xs) - :y1 y - :x2 (+ x xs) - :y2 (+ y frame-height) - :style {:stroke color-value - :stroke-opacity color-opacity - :stroke-width (str (/ 1 zoom))}}]) - (for [ys (range size frame-height size)] - [:line {:key (str (:id frame) "-x-" ys) - :x1 x - :y1 (+ y ys) - :x2 (+ x frame-width) - :y2 (+ y ys) - :style {:stroke color-value - :stroke-opacity color-opacity - :stroke-width (str (/ 1 zoom))}}])]])) + (when (> size 0) + [:g.layout + [:* + (for [xs (range size frame-width size)] + [:line {:key (str (:id frame) "-y-" xs) + :x1 (+ x xs) + :y1 y + :x2 (+ x xs) + :y2 (+ y frame-height) + :style {:stroke color-value + :stroke-opacity color-opacity + :stroke-width (str (/ 1 zoom))}}]) + (for [ys (range size frame-height size)] + [:line {:key (str (:id frame) "-x-" ys) + :x1 x + :y1 (+ y ys) + :x2 (+ x frame-width) + :y2 (+ y ys) + :style {:stroke color-value + :stroke-opacity color-opacity + :stroke-width (str (/ 1 zoom))}}])]]))) (mf/defc flex-layout [{:keys [key frame zoom layout]}] (let [{color-value :value color-opacity :opacity} (-> layout :params :color)] diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs index 261bf17d1..55c1cc1bd 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs @@ -31,7 +31,7 @@ [:div.advanced-options {} children]])) -(mf/defc layout-options [{:keys [layout default-layout-params on-change on-remove]}] +(mf/defc layout-options [{:keys [layout default-layout-params on-change on-remove on-save-layout]}] (let [state (mf/use-state {:show-advanced-options false :changes {}}) {:keys [type display params] :as layout} (d/deep-merge layout (:changes @state)) @@ -68,7 +68,15 @@ (fn [event] (let [change-fn (apply handle-change keys)] (-> event dom/get-target dom/get-value parse-integer change-fn)))) - ] + + handle-use-default (fn [] + (emit-changes! #(hash-map :params ((:type layout) default-layout-params)))) + handle-set-as-default (fn [] + (let [current-layout (d/deep-merge layout (-> @state :changes))] + (on-save-layout current-layout))) + + is-default (= (->> @state :changes (d/deep-merge layout) :params) + (->> layout :type default-layout-params))] [:div.grid-option [:div.grid-option-main @@ -85,7 +93,7 @@ (if (= type :square) [:div.input-element.pixels [:input.input-text {:type "number" - :min "0" + :min "1" :no-validate true :value (:size params) :on-change (handle-change-event :params :size)}]] @@ -102,6 +110,8 @@ :on-close toggle-advanced-options} (when (= :square type) [:& input-row {:label "Size" + :class "pixels" + :min 1 :value (:size params) :on-change (handle-change :params :size)}]) @@ -128,52 +138,38 @@ (when (= :row type) [:& input-row {:label "Height" + :class "pixels" :value (or (:item-height params) "") :on-change (handle-change :params :item-height)}]) (when (= :column type) [:& input-row {:label "Width" + :class "pixels" :value (or (:item-width params) "") :on-change (handle-change :params :item-width)}]) (when (#{:row :column} type) [:* [:& input-row {:label "Gutter" + :class "pixels" :value (:gutter params) :on-change (handle-change :params :gutter)}] [:& input-row {:label "Margin" + :class "pixels" :value (:margin params) :on-change (handle-change :params :margin)}]]) [:& color-row {:value (:color params) :on-change (handle-change :params :color)}] [:div.row-flex - [:button.btn-options "Use default"] - [:button.btn-options "Set as default"]]]])) - -(defonce ^:private default-layout-params - {:square {:size 16 - :color {:value "#59B9E2" - :opacity 0.9}} - - :column {:size 12 - :type :stretch - :item-width nil - :gutter 8 - :margin 0 - :color {:value "#DE4762" - :opacity 0.1}} - :row {:size 12 - :type :stretch - :item-height nil - :gutter 8 - :margin 0 - :color {:value "#DE4762" - :opacity 0.1}}}) + [:button.btn-options {:disabled is-default + :on-click handle-use-default} "Use default"] + [:button.btn-options {:disabled is-default + :on-click handle-set-as-default} "Set as default"]]]])) (mf/defc frame-layouts [{:keys [shape]}] (let [id (:id shape) - default-layout-params (merge default-layout-params (mf/deref refs/workspace-saved-layouts)) + default-layout-params (merge dw/default-layout-params (mf/deref refs/workspace-saved-layouts)) handle-create-layout #(st/emit! (dw/add-frame-layout id)) handle-remove-layout (fn [index] #(st/emit! (dw/remove-frame-layout id index))) handle-edit-layout (fn [index] #(st/emit! (dw/set-frame-layout id index %))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs index 5eb3bcb43..98b3c2252 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs @@ -10,95 +10,14 @@ (ns uxbox.main.ui.workspace.sidebar.options.page "Page options menu entries." (:require - [cuerdas.core :as str] [rumext.alpha :as mf] [okulary.core :as l] - [uxbox.common.data :as d] - [uxbox.main.ui.icons :as i] - [uxbox.main.data.workspace :as dw] - [uxbox.main.refs :as refs] - [uxbox.main.store :as st] - [uxbox.main.ui.modal :as modal] - [uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]] - [uxbox.util.dom :as dom] - [uxbox.util.i18n :refer [tr]])) - -(def default-options - "Default data for page metadata." - {:grid-x 10 - :grid-y 10 - :grid-color "#cccccc"}) + [uxbox.main.refs :as refs])) (def options-iref (l/derived :options refs/workspace-data)) -(mf/defc grid-options - {:wrap [mf/memo]} - [props] - (let [options (->> (mf/deref options-iref) - (merge default-options)) - on-x-change - (fn [event] - (let [value (-> (dom/get-target event) - (dom/get-value) - (d/parse-integer 0))] - (st/emit! (dw/update-options {:grid-x value})))) - - on-y-change - (fn [event] - (let [value (-> (dom/get-target event) - (dom/get-value) - (d/parse-integer 0))] - (st/emit! (dw/update-options {:grid-y value})))) - - change-color - (fn [color] - (st/emit! (dw/update-options {:grid-color color}))) - - on-color-input-change - (fn [event] - (let [input (dom/get-target event) - value (dom/get-value input)] - (when (dom/valid? input) - (change-color value)))) - - show-color-picker - (fn [event] - (let [x (.-clientX event) - y (.-clientY event) - props {:x x :y y - :transparent? true - :default "#cccccc" - :attr :grid-color - :on-change change-color}] - (modal/show! colorpicker-modal props)))] - [:div.element-set - [:div.element-set-title (tr "workspace.options.grid-options")] - [:div.element-set-content - [:div.row-flex - [:span.element-set-subtitle (tr "workspace.options.size")] - [:div.input-element.pixels - [:input.input-text {:type "number" - :value (:grid-x options) - :on-change on-x-change}]] - [:div.input-element.pixels - [:input.input-text {:type "number" - :value (:grid-y options) - :on-change on-y-change}]]] - [:div.row-flex.color-data - [:span.element-set-subtitle (tr "workspace.options.color")] - [:span.color-th {:style {:background-color (:grid-color options)} - :on-click show-color-picker}] - [:div.color-info - [:input {:default-value (:grid-color options) - :ref (fn [el] - (when el - (set! (.-value el) (:grid-color options)))) - :pattern "^#(?:[0-9a-fA-F]{3}){1,2}$" - :on-change on-color-input-change}]]]]])) - (mf/defc options - [{:keys [page] :as props}] - [:div - [:& grid-options {:page page}]]) + ;; TODO: Define properties for page + [{:keys [page] :as props}]) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs index ebcb9f120..b5f0d7b31 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -39,7 +39,8 @@ (/ 100))) (mf/defc color-row [{:keys [value on-change]}] - (let [state (mf/use-state value) + (let [value (or value {:value "#FFFFFF" :opacity 1}) + state (mf/use-state value) change-color (fn [color] (let [update-color (fn [state] (assoc state :value color))] (swap! state update-color) @@ -65,6 +66,10 @@ string->opacity change-opacity))] + (mf/use-effect + (mf/deps value) + #(reset! state value)) + [:div.row-flex.color-data [:span.color-th {:style {:background-color (-> @state :value)} @@ -88,3 +93,4 @@ :value (-> @state :opacity opacity->string) :step "1" :on-change handle-opacity-change}]])) + diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/input_row.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/input_row.cljs index 50dbe8a34..ce4d9d48b 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/input_row.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/input_row.cljs @@ -14,17 +14,19 @@ [uxbox.main.ui.components.select :refer [select]] [uxbox.util.dom :as dom])) -(mf/defc input-row [{:keys [label options value on-change]}] - [:div.row-flex.input-row - [:span.element-set-subtitle label] - [:div.input-element - (if options - [:& select {:default-value value - :class "input-option" - :options options - :on-change on-change}] - [:input.input-text - {:placeholder label - :type "number" - :on-change #(-> % dom/get-target dom/get-value d/parse-integer on-change) - :value value}])]]) +(mf/defc input-row [{:keys [label options value class min max on-change]}] + (let [handle-change (fn [value] (when (and (or (not min) (>= value min)) (or (not max) (<= value max))) + (on-change value)))] + [:div.row-flex.input-row + [:span.element-set-subtitle label] + [:div.input-element {:class class} + (if options + [:& select {:default-value value + :class "input-option" + :options options + :on-change on-change}] + [:input.input-text + {:placeholder label + :type "number" + :on-change #(-> % dom/get-target dom/get-value d/parse-integer handle-change) + :value value}])]])) diff --git a/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs b/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs index 53e7289af..b219ee627 100644 --- a/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs +++ b/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs @@ -80,12 +80,17 @@ :point point :zoom zoom}])]))])) -(mf/defc snap-feedback [{:keys []}] +(mf/defc snap-feedback [{:keys [layout]}] (let [page-id (mf/deref refs/workspace-page-id) selected (mf/deref refs/selected-shapes) selected-shapes (mf/deref (refs/objects-by-id selected)) drawing (mf/deref refs/current-drawing-shape) filter-shapes (mf/deref refs/selected-shapes-with-children) + filter-shapes (fn [id] (if (= id :layout) + (or (not (contains? layout :display-grid)) + (not (contains? layout :snap-grid))) + (or (filter-shapes id) + (not (contains? layout :dynamic-alignment))))) current-transform (mf/deref refs/current-transform) snap-data (mf/deref refs/workspace-snap-data) shapes (if drawing [drawing] selected-shapes) diff --git a/frontend/src/uxbox/main/ui/workspace/viewport.cljs b/frontend/src/uxbox/main/ui/workspace/viewport.cljs index 9819ea115..0dbe00d00 100644 --- a/frontend/src/uxbox/main/ui/workspace/viewport.cljs +++ b/frontend/src/uxbox/main/ui/workspace/viewport.cljs @@ -386,10 +386,10 @@ :zoom zoom :modifiers (:modifiers local)}]) - (when (contains? layout :layouts) + (when (contains? layout :display-grid) [:& layout-display {:zoom zoom}]) - [:& snap-feedback] + [:& snap-feedback {:layout layout}] (when tooltip [:& cursor-tooltip {:zoom zoom :tooltip tooltip}])] diff --git a/frontend/src/uxbox/util/geom/layout.cljs b/frontend/src/uxbox/util/geom/layout.cljs index 0e6c23275..e1ff4a2d0 100644 --- a/frontend/src/uxbox/util/geom/layout.cljs +++ b/frontend/src/uxbox/util/geom/layout.cljs @@ -74,11 +74,12 @@ (case type :square (let [{:keys [x y width height]} shape size (-> params :size)] - (if (= coord :x) - (mapcat #(vector (gpt/point (+ x %) y) - (gpt/point (+ x %) (+ y height))) (range size width size)) - (mapcat #(vector (gpt/point x (+ y %)) - (gpt/point (+ x width) (+ y %))) (range size height size)))) + (when (> size 0) + (if (= coord :x) + (mapcat #(vector (gpt/point (+ x %) y) + (gpt/point (+ x %) (+ y height))) (range size width size)) + (mapcat #(vector (gpt/point x (+ y %)) + (gpt/point (+ x width) (+ y %))) (range size height size))))) :column (when (= coord :x) (->> (layout-rects shape layout) (mapcat layout-rect-points))) diff --git a/frontend/src/uxbox/util/geom/point.cljs b/frontend/src/uxbox/util/geom/point.cljs index de1b3744d..5433334b2 100644 --- a/frontend/src/uxbox/util/geom/point.cljs +++ b/frontend/src/uxbox/util/geom/point.cljs @@ -151,11 +151,12 @@ (defn round "Change the precision of the point coordinates." - [{:keys [x y] :as p} decimanls] - (assert (point? p)) - (assert (number? decimanls)) - (Point. (mth/precision x decimanls) - (mth/precision y decimanls))) + ([point] (round point 0)) + ([{:keys [x y] :as p} decimanls] + (assert (point? p)) + (assert (number? decimanls)) + (Point. (mth/precision x decimanls) + (mth/precision y decimanls)))) (defn transform "Transform a point applying a matrix transfomation."