0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-11 06:21:30 -05:00

🎉 Add option to save the layouts

This commit is contained in:
alonso.torres 2020-05-18 15:30:40 +02:00 committed by Andrés Moya
parent 8d9e772dca
commit 1d2ae6d5eb
17 changed files with 310 additions and 271 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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"
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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))

View file

@ -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))))

View file

@ -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")

View file

@ -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)]

View file

@ -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 %)))

View file

@ -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}])

View file

@ -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}]]))

View file

@ -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}])]]))

View file

@ -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)

View file

@ -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}])]

View file

@ -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)))

View file

@ -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."