mirror of
https://github.com/penpot/penpot.git
synced 2026-04-26 19:58:09 +00:00
commit
fe08810340
@ -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)
|
||||
|
||||
@ -9,7 +9,9 @@
|
||||
(:refer-clojure :exclude [concat read-string])
|
||||
(:require [clojure.set :as set]
|
||||
#?(:cljs [cljs.reader :as r]
|
||||
:clj [clojure.edn :as r])))
|
||||
:clj [clojure.edn :as r])
|
||||
#?(:cljs [cljs.core :as core]
|
||||
:clj [clojure.core :as core])))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Structures Manipulation
|
||||
@ -94,6 +96,12 @@
|
||||
(persistent!
|
||||
(reduce #(dissoc! %1 %2) (transient data) keys)))
|
||||
|
||||
(defn remove-at-index
|
||||
[v index]
|
||||
(vec (core/concat
|
||||
(subvec v 0 index)
|
||||
(subvec v (inc index)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Parsing / Conversion
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:67" ],
|
||||
"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:113" ],
|
||||
"translations" : {
|
||||
"en" : "Disable dynamic alignment"
|
||||
}
|
||||
},
|
||||
"workspace.header.menu.disable-snap-grid" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:89" ],
|
||||
"translations" : {
|
||||
"en" : "Disable snap to grid"
|
||||
}
|
||||
},
|
||||
"workspace.header.menu.enable-dynamic-alignment" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:114" ],
|
||||
"translations" : {
|
||||
"en" : "Enable dynamic aligment"
|
||||
}
|
||||
},
|
||||
"workspace.header.menu.enable-snap-grid" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:90" ],
|
||||
"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:83" ],
|
||||
"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:95" ],
|
||||
"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:107" ],
|
||||
"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:101" ],
|
||||
"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:77" ],
|
||||
"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:84" ],
|
||||
"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:96" ],
|
||||
"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:108" ],
|
||||
"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:102" ],
|
||||
"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:78" ],
|
||||
"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:151" ],
|
||||
"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,10 +1098,124 @@
|
||||
"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.grid.auto" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:40" ],
|
||||
"translations" : {
|
||||
"en" : "Auto"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.column" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:125" ],
|
||||
"translations" : {
|
||||
"en" : "Columns"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.columns" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:164" ],
|
||||
"translations" : {
|
||||
"en" : "Columns"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.gutter" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:191" ],
|
||||
"translations" : {
|
||||
"en" : "Gutter"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.height" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:184" ],
|
||||
"translations" : {
|
||||
"en" : "Height"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.margin" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:196" ],
|
||||
"translations" : {
|
||||
"en" : "Margin"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.rows" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:156" ],
|
||||
"translations" : {
|
||||
"en" : "Rows"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.set-default" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:208" ],
|
||||
"translations" : {
|
||||
"en" : "Set as default"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.size" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:149" ],
|
||||
"translations" : {
|
||||
"en" : "Size"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.type" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:172" ],
|
||||
"translations" : {
|
||||
"en" : "Type"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.type.center" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:176" ],
|
||||
"translations" : {
|
||||
"en" : "Center"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.type.left" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:175" ],
|
||||
"translations" : {
|
||||
"en" : "Left"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.type.right" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:177" ],
|
||||
"translations" : {
|
||||
"en" : "Right"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.type.stretch" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:174" ],
|
||||
"translations" : {
|
||||
"en" : "Stretch"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.use-default" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:206" ],
|
||||
"translations" : {
|
||||
"en" : "Use default"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.params.width" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:183" ],
|
||||
"translations" : {
|
||||
"en" : "Width"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.row" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:126" ],
|
||||
"translations" : {
|
||||
"en" : "Rows"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.square" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:124" ],
|
||||
"translations" : {
|
||||
"en" : "Square"
|
||||
}
|
||||
},
|
||||
"workspace.options.grid.title" : {
|
||||
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame_grid.cljs:220" ],
|
||||
"translations" : {
|
||||
"en" : "Grid & Layouts"
|
||||
}
|
||||
},
|
||||
"workspace.options.line-height-letter-spacing" : {
|
||||
@ -1097,13 +1233,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 +1252,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 +1265,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 +1286,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 +1378,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 +1502,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"
|
||||
}
|
||||
|
||||
@ -366,6 +366,9 @@ ul.slider-dots {
|
||||
// Input amounts
|
||||
|
||||
&.pixels {
|
||||
& input {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "px";
|
||||
|
||||
@ -279,63 +279,103 @@
|
||||
font-size: $fs13;
|
||||
}
|
||||
|
||||
.custom-select-dropdown {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 12;
|
||||
|
||||
}
|
||||
.custom-select-dropdown {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 12;
|
||||
max-height: 30rem;
|
||||
min-width: 7rem;
|
||||
overflow-y: auto;
|
||||
|
||||
background-color: $color-white;
|
||||
border-radius: $br-small;
|
||||
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25);
|
||||
|
||||
.presets {
|
||||
width: 200px;
|
||||
max-height: 30rem;
|
||||
min-width: 7rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
background-color: $color-white;
|
||||
border-radius: $br-small;
|
||||
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25);
|
||||
hr {
|
||||
margin: 0;
|
||||
border-color: $color-gray-20;
|
||||
}
|
||||
|
||||
li {
|
||||
color: $color-gray-60;
|
||||
cursor: pointer;
|
||||
font-size: $fs14;
|
||||
display: flex;
|
||||
padding: $small;
|
||||
li {
|
||||
color: $color-gray-60;
|
||||
cursor: pointer;
|
||||
font-size: $fs14;
|
||||
display: flex;
|
||||
padding: $small;
|
||||
|
||||
span {
|
||||
color: $color-gray-20;
|
||||
margin-left: auto;
|
||||
}
|
||||
span {
|
||||
color: $color-gray-20;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&.dropdown-separator:not(:last-child) {
|
||||
border-bottom: 1px solid $color-gray-10;
|
||||
}
|
||||
&.dropdown-separator:not(:last-child) {
|
||||
border-bottom: 1px solid $color-gray-10;
|
||||
}
|
||||
|
||||
&.dropdown-label:not(:first-child) {
|
||||
border-top: 1px solid $color-gray-10;
|
||||
}
|
||||
&.dropdown-label:not(:first-child) {
|
||||
border-top: 1px solid $color-gray-10;
|
||||
}
|
||||
|
||||
&.dropdown-label span {
|
||||
margin-left: 0;
|
||||
}
|
||||
&.dropdown-label span {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $color-primary-lighter;
|
||||
}
|
||||
&:hover {
|
||||
background-color: $color-primary-lighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& li.checked-element {
|
||||
padding-left: 0;
|
||||
|
||||
& span {
|
||||
margin: 0;
|
||||
color: $color-black;
|
||||
}
|
||||
|
||||
& svg {
|
||||
visibility: hidden;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: none;
|
||||
margin: 0.25rem;
|
||||
fill: $color-black;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
& svg {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editable-select {
|
||||
position: relative;
|
||||
height: 38px;
|
||||
margin-right: $small;
|
||||
position: relative;
|
||||
width: 60%;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-40;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.input-text {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
|
||||
.input-select {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
@ -352,6 +392,45 @@
|
||||
font-size: $fs12;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-button {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.input-option {
|
||||
height: 2rem;
|
||||
border-bottom: 1px solid #64666A;
|
||||
width: 100%;
|
||||
margin-left: 0.25rem;
|
||||
|
||||
.input-text {
|
||||
border: none;
|
||||
margin: 0;
|
||||
width: calc(100% - 12px);
|
||||
height: 100%;
|
||||
top: auto;
|
||||
color: #b1b2b5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.element-set-content .grid-option-main {
|
||||
.editable-select.input-option .input-text {
|
||||
padding: 0;
|
||||
padding-top: 0.18rem;
|
||||
}
|
||||
|
||||
.input-element {
|
||||
width: 55px;
|
||||
margin: 0 0.2rem;
|
||||
}
|
||||
|
||||
.input-text {
|
||||
padding-left: 0;
|
||||
color: #b1b2b5;
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,7 +465,12 @@
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.presets {
|
||||
.custom-select-dropdown {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.row-flex.align-icons {
|
||||
@ -527,7 +611,7 @@
|
||||
height: 18px;
|
||||
position: relative;
|
||||
width: 18px;
|
||||
|
||||
|
||||
svg {
|
||||
fill: $color-gray-30;
|
||||
height: 16px;
|
||||
@ -535,3 +619,129 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-button {
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
& svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
fill: $color-gray-20;
|
||||
}
|
||||
|
||||
&:hover svg, &.is-active svg {
|
||||
fill: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.element-set-content .input-row {
|
||||
& .element-set-subtitle {
|
||||
width: 5.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-option {
|
||||
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;
|
||||
border: 1px solid $color-black;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #1F1F1F;
|
||||
}
|
||||
|
||||
& .custom-select {
|
||||
min-width: 4.75rem;
|
||||
height: 2rem;
|
||||
border: none;
|
||||
border-bottom: 1px solid #65666A;
|
||||
}
|
||||
|
||||
& .input-element {
|
||||
width: 50px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
& .custom-select-dropdown {
|
||||
width: 96px;
|
||||
}
|
||||
|
||||
& .input-option {
|
||||
margin-left: 0.5rem;
|
||||
|
||||
& .custom-select-dropdown {
|
||||
width: 56px;
|
||||
min-width: 56px;
|
||||
max-height: 10rem;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.grid-option-main-actions {
|
||||
display: flex;
|
||||
visibility: hidden;
|
||||
|
||||
.grid-option:hover & {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.focus-overlay {
|
||||
background: $color-black;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.element-set-content .advanced-options {
|
||||
background-color: #303236;
|
||||
border-radius: 4px;
|
||||
left: -8px;
|
||||
padding: 0.5rem;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
width: calc(100% + 16px);
|
||||
}
|
||||
|
||||
.btn-options {
|
||||
cursor: pointer;
|
||||
border: 1px solid $color-black;
|
||||
background: $color-gray-60;
|
||||
border-radius: 2px;
|
||||
color: $color-gray-20;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
flex-grow: 1;
|
||||
padding: 0.25rem 0;
|
||||
|
||||
&:first-child {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
&:not([disabled]):hover {
|
||||
background: $color-primary;
|
||||
color: $color-black;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.4;
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +66,9 @@
|
||||
:layers
|
||||
:element-options
|
||||
:rules
|
||||
:dynamic-alignment})
|
||||
:dynamic-alignment
|
||||
:display-grid
|
||||
:snap-grid})
|
||||
|
||||
(s/def ::options-mode #{:design :prototype})
|
||||
|
||||
|
||||
83
frontend/src/uxbox/main/data/workspace/grid.cljs
Normal file
83
frontend/src/uxbox/main/data/workspace/grid.cljs
Normal file
@ -0,0 +1,83 @@
|
||||
;; 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.data.workspace.grid
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.data.workspace.common :as dwc]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Grid
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defonce ^:private default-square-params
|
||||
{:size 16
|
||||
:color {:value "#59B9E2"
|
||||
:opacity 0.9}})
|
||||
|
||||
(defonce ^:private default-layout-params
|
||||
{:size 12
|
||||
:type :stretch
|
||||
:item-length nil
|
||||
:gutter 8
|
||||
:margin 0
|
||||
:color {:value "#DE4762"
|
||||
:opacity 0.1}})
|
||||
|
||||
(defonce default-grid-params
|
||||
{:square default-square-params
|
||||
:column default-layout-params
|
||||
:row default-layout-params})
|
||||
|
||||
(defn add-frame-grid [frame-id]
|
||||
(ptk/reify ::set-frame-grid
|
||||
dwc/IBatchedChange
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (:current-page-id state)
|
||||
default-params (or
|
||||
(get-in state [:workspace-data pid :options :saved-grids :square])
|
||||
(:square default-grid-params))
|
||||
prop-path [:workspace-data pid :objects frame-id :grids]
|
||||
grid {:type :square
|
||||
:params default-params
|
||||
:display true}]
|
||||
(-> state
|
||||
(update-in prop-path #(if (nil? %) [grid] (conj % grid))))))))
|
||||
|
||||
(defn remove-frame-grid [frame-id index]
|
||||
(ptk/reify ::set-frame-grid
|
||||
dwc/IBatchedChange
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (:current-page-id state)]
|
||||
(-> state
|
||||
(update-in [:workspace-data pid :objects frame-id :grids] #(d/remove-at-index % index)))))))
|
||||
|
||||
(defn set-frame-grid [frame-id index data]
|
||||
(ptk/reify ::set-frame-grid
|
||||
dwc/IBatchedChange
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (:current-page-id state)]
|
||||
(->
|
||||
state
|
||||
(assoc-in [:workspace-data pid :objects frame-id :grids index] data))))))
|
||||
|
||||
(defn set-default-grid [type params]
|
||||
(ptk/reify ::set-default-grid
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (dwc/commit-changes [{:type :set-option
|
||||
:option [:saved-grids type]
|
||||
:value params}]
|
||||
[]
|
||||
{:commit-local? true})))))
|
||||
@ -68,9 +68,18 @@
|
||||
(get-in % [:workspace-data page-id]))
|
||||
(l/derived st/state)))
|
||||
|
||||
(def workspace-page-options
|
||||
(l/derived :options workspace-data))
|
||||
|
||||
(def workspace-saved-grids
|
||||
(l/derived :saved-grids workspace-page-options))
|
||||
|
||||
(def workspace-objects
|
||||
(l/derived :objects workspace-data))
|
||||
|
||||
(def workspace-frames
|
||||
(l/derived cp/select-frames workspace-objects))
|
||||
|
||||
(defn object-by-id
|
||||
[id]
|
||||
(letfn [(selector [state]
|
||||
|
||||
@ -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))))
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
:onChangeComplete on-change-complete
|
||||
:style {:box-shadow "none"}}]))
|
||||
|
||||
|
||||
(def most-used-colors
|
||||
(letfn [(selector [{:keys [objects]}]
|
||||
(as-> {} $
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
;; 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.components.context-menu
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
;; 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.components.editable-label
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
|
||||
70
frontend/src/uxbox/main/ui/components/editable_select.cljs
Normal file
70
frontend/src/uxbox/main/ui/components/editable_select.cljs
Normal file
@ -0,0 +1,70 @@
|
||||
;; 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.components.editable-select
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.uuid :as uuid]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]))
|
||||
|
||||
(mf/defc editable-select [{:keys [value type options class on-change]}]
|
||||
(let [state (mf/use-state {:id (uuid/next)
|
||||
:is-open? false
|
||||
:current-value value})
|
||||
open-dropdown #(swap! state assoc :is-open? true)
|
||||
close-dropdown #(swap! state assoc :is-open? false)
|
||||
|
||||
select-item (fn [value]
|
||||
(fn [event]
|
||||
(swap! state assoc :current-value value)
|
||||
(when on-change (on-change value))))
|
||||
|
||||
as-key-value (fn [item] (if (map? item) [(:value item) (:label item)] [item item]))
|
||||
|
||||
labels-map (into {} (->> options (map as-key-value)))
|
||||
|
||||
value->label (fn [value] (get labels-map value value))
|
||||
|
||||
handle-change-input (fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-value)
|
||||
value (or (d/parse-integer value) value)]
|
||||
(swap! state assoc :current-value value)
|
||||
(when on-change (on-change value))))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps value)
|
||||
#(reset! state {:current-value value}))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps options)
|
||||
#(reset! state {:is-open? false
|
||||
:current-value value}))
|
||||
|
||||
[:div.editable-select {:class class}
|
||||
[:input.input-text {:value (or (-> @state :current-value value->label) "")
|
||||
:on-change handle-change-input
|
||||
:type type}]
|
||||
[:span.dropdown-button {:on-click open-dropdown} i/arrow-down]
|
||||
|
||||
[:& dropdown {:show (get @state :is-open? false)
|
||||
:on-close close-dropdown}
|
||||
[:ul.custom-select-dropdown
|
||||
(for [[index item] (map-indexed vector options)]
|
||||
(cond
|
||||
(= :separator item) [:hr {:key (str (:id @state) "-" index)}]
|
||||
:else (let [[value label] (as-key-value item)]
|
||||
[:li.checked-element
|
||||
{:key (str (:id @state) "-" index)
|
||||
:class (when (= value (-> @state :current-value)) "is-selected")
|
||||
:on-click (select-item value)}
|
||||
[:span.check-icon i/tick]
|
||||
[:span label]])))]]]))
|
||||
51
frontend/src/uxbox/main/ui/components/select.cljs
Normal file
51
frontend/src/uxbox/main/ui/components/select.cljs
Normal file
@ -0,0 +1,51 @@
|
||||
;; 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.components.select
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.uuid :as uuid]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]))
|
||||
|
||||
(mf/defc select [{:keys [default-value options class on-change]}]
|
||||
(let [state (mf/use-state {:id (uuid/next)
|
||||
:is-open? false
|
||||
:current-value default-value})
|
||||
open-dropdown #(swap! state assoc :is-open? true)
|
||||
close-dropdown #(swap! state assoc :is-open? false)
|
||||
select-item (fn [value] (fn [event]
|
||||
(swap! state assoc :current-value value)
|
||||
(when on-change (on-change value))))
|
||||
as-key-value (fn [item] (if (map? item) [(:value item) (:label item)] [item item]))
|
||||
value->label (into {} (->> options
|
||||
(map as-key-value))) ]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps options)
|
||||
#(reset! state {:is-open? false
|
||||
:current-value default-value}))
|
||||
|
||||
[:div.custom-select {:on-click open-dropdown
|
||||
:class class}
|
||||
[:span (-> @state :current-value value->label)]
|
||||
[:span.dropdown-button i/arrow-down]
|
||||
[:& dropdown {:show (:is-open? @state)
|
||||
:on-close close-dropdown}
|
||||
[:ul.custom-select-dropdown
|
||||
(for [[index item] (map-indexed vector options)]
|
||||
(cond
|
||||
(= :separator item) [:hr {:key (str (:id @state) "-" index)}]
|
||||
:else (let [[value label] (as-key-value item)]
|
||||
[:li.checked-element
|
||||
{:key (str (:id @state) "-" index)
|
||||
:class (when (= value (-> @state :current-value)) "is-selected")
|
||||
:on-click (select-item value)}
|
||||
[:span.check-icon i/tick]
|
||||
[:span label]])))]]]))
|
||||
@ -1,3 +1,12 @@
|
||||
;; 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.components.tab-container
|
||||
(:require [rumext.alpha :as mf]))
|
||||
|
||||
|
||||
@ -110,6 +110,7 @@
|
||||
(def unlock (icon-xref :unlock))
|
||||
(def uppercase (icon-xref :uppercase))
|
||||
(def user (icon-xref :user))
|
||||
(def tick (icon-xref :tick))
|
||||
|
||||
(def loader-pencil
|
||||
(mf/html
|
||||
|
||||
@ -73,7 +73,8 @@
|
||||
[:& viewport {:page page
|
||||
:key (:id page)
|
||||
:file file
|
||||
:local local}]]]
|
||||
:local local
|
||||
:layout layout}]]]
|
||||
|
||||
[:& left-toolbar {:page page :layout layout}]
|
||||
|
||||
|
||||
76
frontend/src/uxbox/main/ui/workspace/frame_grid.cljs
Normal file
76
frontend/src/uxbox/main/ui/workspace/frame_grid.cljs
Normal file
@ -0,0 +1,76 @@
|
||||
;; 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.frame-grid
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.common.pages :as cp]
|
||||
[uxbox.util.geom.shapes :as gsh]
|
||||
[uxbox.util.geom.grid :as gg]))
|
||||
|
||||
(mf/defc square-grid [{:keys [frame zoom grid] :as props}]
|
||||
(let [{:keys [color size] :as params} (-> grid :params)
|
||||
{color-value :value color-opacity :opacity} (-> grid :params :color)
|
||||
{frame-width :width frame-height :height :keys [x y]} frame]
|
||||
(when (> size 0)
|
||||
[:g.grid
|
||||
[:*
|
||||
(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 layout-grid [{:keys [key frame zoom grid]}]
|
||||
(let [{color-value :value color-opacity :opacity} (-> grid :params :color)]
|
||||
[:g.grid
|
||||
(for [{:keys [x y width height]} (gg/grid-areas frame grid)]
|
||||
[:rect {:key (str key "-" x "-" y)
|
||||
:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:style {:fill color-value
|
||||
:opacity color-opacity}}])]))
|
||||
|
||||
(mf/defc grid-display-frame [{:keys [frame zoom]}]
|
||||
(let [grids (:grids frame)]
|
||||
(for [[index {:keys [type display] :as grid}] (map-indexed vector grids)]
|
||||
(let [props #js {:key (str (:id frame) "-grid-" index)
|
||||
:frame frame
|
||||
:zoom zoom
|
||||
:grid grid}]
|
||||
(when display
|
||||
(case type
|
||||
:square [:> square-grid props]
|
||||
:column [:> layout-grid props]
|
||||
:row [:> layout-grid props]))))))
|
||||
|
||||
|
||||
(mf/defc frame-grid [{:keys [zoom]}]
|
||||
(let [frames (mf/deref refs/workspace-frames)]
|
||||
[:g.grid-display {:style {:pointer-events "none"}}
|
||||
(for [frame frames]
|
||||
[:& grid-display-frame {:key (str "grid-" (:id frame))
|
||||
:zoom zoom
|
||||
:frame (gsh/transform-shape frame)}])]))
|
||||
@ -1,43 +0,0 @@
|
||||
;; 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.grid
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.refs :as refs]))
|
||||
|
||||
;; --- Grid (Component)
|
||||
|
||||
(def options-iref
|
||||
(l/derived :options refs/workspace-data))
|
||||
|
||||
(mf/defc grid
|
||||
{:wrap [mf/memo]}
|
||||
[props]
|
||||
(let [options (mf/deref options-iref)
|
||||
width (:grid-x options 10)
|
||||
height (:grid-y options 10)
|
||||
color (:grid-color options "#cccccc")]
|
||||
[:g.grid
|
||||
[:defs
|
||||
[:pattern {:id "grid-pattern"
|
||||
:x "0" :y "0"
|
||||
:width width :height height
|
||||
:patternUnits "userSpaceOnUse"}
|
||||
[:path {:d (str/format "M 0 %s L %s %s L %s 0" height width height width)
|
||||
:fill "transparent"
|
||||
:stroke color}]]]
|
||||
[:rect {:style {:pointer-events "none"}
|
||||
:x 0 :y 0
|
||||
:width "100%"
|
||||
:height "100%"
|
||||
:fill "url(#grid-pattern)"}]]))
|
||||
@ -21,7 +21,7 @@
|
||||
[uxbox.main.ui.workspace.images :refer [import-image-modal]]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]
|
||||
[uxbox.main.ui.workspace.presence :as presence]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]
|
||||
[uxbox.util.i18n :as i18n :refer [t]]
|
||||
[uxbox.util.data :refer [classnames]]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.util.router :as rt]))
|
||||
@ -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")
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.workspace.ruler
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.math :as mth]))
|
||||
|
||||
(mf/defc ruler-text
|
||||
[{:keys [zoom ruler] :as props}]
|
||||
#_(let [{:keys [start end]} ruler
|
||||
distance (-> (gpt/distance (gpt/divide end zoom)
|
||||
(gpt/divide start zoom))
|
||||
(mth/precision 2))
|
||||
angle (-> (gpt/angle end start)
|
||||
(mth/precision 2))
|
||||
transform1 (str "translate(" (+ (:x end) 35) "," (- (:y end) 10) ")")
|
||||
transform2 (str "translate(" (+ (:x end) 25) "," (- (:y end) 30) ")")]
|
||||
[:g
|
||||
[:rect {:fill "black"
|
||||
:fill-opacity "0.4"
|
||||
:rx "3"
|
||||
:ry "3"
|
||||
:width "90"
|
||||
:height "50"
|
||||
:transform transform2}]
|
||||
[:text {:transform transform1
|
||||
:fill "white"}
|
||||
[:tspan {:x "0"}
|
||||
(str distance " px")]
|
||||
[:tspan {:x "0" :y "20"}
|
||||
(str angle "°")]]]))
|
||||
|
||||
(mf/defc ruler-line
|
||||
[{:keys [zoom ruler] :as props}]
|
||||
#_(let [{:keys [start end]} ruler]
|
||||
[:line {:x1 (:x start)
|
||||
:y1 (:y start)
|
||||
:x2 (:x end)
|
||||
:y2 (:y end)
|
||||
:style {:cursor "cell"}
|
||||
:stroke-width "1"
|
||||
:stroke "red"}]))
|
||||
|
||||
(mf/defc ruler
|
||||
[{:keys [ruler zoom] :as props}]
|
||||
#_(letfn [(on-mouse-down [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! :interrupt
|
||||
(udw/assign-cursor-tooltip nil)
|
||||
(udw/start-ruler)))
|
||||
(on-mouse-up [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! :interrupt))
|
||||
(on-unmount []
|
||||
(st/emit! :interrupt
|
||||
(udw/clear-ruler)))]
|
||||
(mf/use-effect (constantly on-unmount))
|
||||
[:svg {:on-mouse-down on-mouse-down
|
||||
:on-mouse-up on-mouse-up}
|
||||
[:rect {:style {:fill "transparent"
|
||||
:stroke "transparent"
|
||||
:cursor "cell"}
|
||||
:width c/viewport-width
|
||||
:height c/viewport-height}]
|
||||
(when ruler
|
||||
[:g
|
||||
[:& ruler-line {:ruler ruler}]
|
||||
[:& ruler-text {:ruler ruler :zoom zoom}]])]))
|
||||
|
||||
@ -64,15 +64,14 @@
|
||||
on-context-menu (mf/use-callback (mf/deps shape)
|
||||
#(common/on-context-menu % shape))
|
||||
|
||||
|
||||
shape (geom/transform-shape shape)
|
||||
{:keys [x y width height]} shape
|
||||
|
||||
inv-zoom (/ 1 zoom)
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
ds-modifier (get-in shape [:modifiers :displacement])
|
||||
|
||||
label-pos (cond-> (gpt/point x (- y 10))
|
||||
(gmt/matrix? ds-modifier) (gpt/transform ds-modifier))
|
||||
label-pos (gpt/point x (- y (/ 10 zoom)))
|
||||
|
||||
on-double-click
|
||||
(mf/use-callback
|
||||
@ -104,7 +103,6 @@
|
||||
:on-click on-double-click}
|
||||
(:name shape)]
|
||||
[:& frame-shape
|
||||
{:shape (geom/transform-shape shape)
|
||||
{:shape shape
|
||||
:childs childs}]])))))
|
||||
|
||||
|
||||
|
||||
@ -12,16 +12,17 @@
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.math :as math]))
|
||||
[uxbox.util.math :as math]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.frame-grid :refer [frame-grid]]))
|
||||
|
||||
(declare +size-presets+)
|
||||
|
||||
@ -37,10 +38,10 @@
|
||||
|
||||
on-orientation-clicked
|
||||
(fn [orientation]
|
||||
(let [width (:width shape)
|
||||
height (:height shape)
|
||||
new-width (if (= orientation :horiz) (max width height) (min width height))
|
||||
new-height (if (= orientation :horiz) (min width height) (max width height))]
|
||||
(let [width (:width shape)
|
||||
height (:height shape)
|
||||
new-width (if (= orientation :horiz) (max width height) (min width height))
|
||||
new-height (if (= orientation :horiz) (min width height) (max width height))]
|
||||
(st/emit! (udw/update-rect-dimensions (:id shape) :width new-width)
|
||||
(udw/update-rect-dimensions (:id shape) :height new-height))))
|
||||
|
||||
@ -79,14 +80,14 @@
|
||||
[:div.element-set-content
|
||||
|
||||
[:div.row-flex
|
||||
[:div.custom-select.flex-grow {:on-click #(reset! show-presets-dropdown? true)}
|
||||
[:div.presets.custom-select.flex-grow {:on-click #(reset! show-presets-dropdown? true)}
|
||||
[:span (tr "workspace.options.size-presets")]
|
||||
[:span.dropdown-button i/arrow-down]
|
||||
[:& dropdown {:show @show-presets-dropdown?
|
||||
:on-close #(reset! show-presets-dropdown? false)}
|
||||
[:ul.custom-select-dropdown
|
||||
(for [size-preset +size-presets+]
|
||||
(if-not (:width size-preset)
|
||||
[:& dropdown {:show @show-presets-dropdown?
|
||||
:on-close #(reset! show-presets-dropdown? false)}
|
||||
[:ul.custom-select-dropdown
|
||||
(for [size-preset +size-presets+]
|
||||
(if-not (:width size-preset)
|
||||
[:li.dropdown-label {:key (:name size-preset)}
|
||||
[:span (:name size-preset)]]
|
||||
[:li {:key (:name size-preset)
|
||||
@ -202,4 +203,6 @@
|
||||
[:div
|
||||
[:& measures-menu {:shape shape}]
|
||||
[:& fill-menu {:shape shape}]
|
||||
[:& stroke-menu {:shape shape}]])
|
||||
[:& stroke-menu {:shape shape}]
|
||||
[:& frame-grid {:shape shape}]])
|
||||
|
||||
|
||||
@ -0,0 +1,233 @@
|
||||
;; 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.options.frame-grid
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :as d]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.common.data :refer [parse-integer]]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.data.workspace.grid :as dw]
|
||||
[uxbox.util.geom.grid :as gg]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]]
|
||||
[uxbox.main.ui.components.select :refer [select]]
|
||||
[uxbox.main.ui.components.editable-select :refer [editable-select]]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]))
|
||||
|
||||
(mf/defc advanced-options [{:keys [visible? on-close children]}]
|
||||
(when visible?
|
||||
[:*
|
||||
[:div.focus-overlay {:on-click #(when on-close
|
||||
(do
|
||||
(dom/stop-propagation %)
|
||||
(on-close)))}]
|
||||
[:div.advanced-options {}
|
||||
children]]))
|
||||
|
||||
(def ^:private size-options
|
||||
[{:value :auto :label (tr "workspace.options.grid.auto")}
|
||||
:separator
|
||||
18 12 10 8 6 4 3 2])
|
||||
|
||||
(mf/defc grid-options [{:keys [frame grid default-grid-params on-change on-remove on-save-grid]}]
|
||||
(let [locale (i18n/use-locale)
|
||||
state (mf/use-state {:show-advanced-options false
|
||||
:changes {}})
|
||||
{:keys [type display params] :as grid} (d/deep-merge grid (:changes @state))
|
||||
|
||||
toggle-advanced-options #(swap! state update :show-advanced-options not)
|
||||
|
||||
emit-changes!
|
||||
(fn [update-fn]
|
||||
(swap! state update :changes update-fn)
|
||||
(when on-change (on-change (d/deep-merge grid (-> @state :changes update-fn)))))
|
||||
|
||||
handle-toggle-visibility
|
||||
(fn [event]
|
||||
(emit-changes! (fn [changes] (update changes :display #(if (nil? %) false (not %))))))
|
||||
|
||||
handle-remove-grid
|
||||
(fn [event]
|
||||
(when on-remove (on-remove)))
|
||||
|
||||
handle-change-type
|
||||
(fn [type]
|
||||
(let [defaults (type default-grid-params)
|
||||
keys (keys defaults)
|
||||
params (->> @state :changes params (select-keys keys) (merge defaults))
|
||||
to-merge {:type type :params params}]
|
||||
(emit-changes! #(d/deep-merge % to-merge))))
|
||||
|
||||
handle-change
|
||||
(fn [& keys]
|
||||
(fn [value]
|
||||
(emit-changes! #(assoc-in % keys value))))
|
||||
|
||||
handle-change-event
|
||||
(fn [& keys]
|
||||
(fn [event]
|
||||
(let [change-fn (apply handle-change keys)]
|
||||
(-> event dom/get-target dom/get-value parse-integer change-fn))))
|
||||
|
||||
handle-change-size
|
||||
(fn [size]
|
||||
(let [grid (d/deep-merge grid (:changes @state))
|
||||
{:keys [margin gutter item-length]} (:params grid)
|
||||
frame-length (if (= :column (:type grid)) (:width frame) (:height frame))
|
||||
item-length (if (or (nil? size) (= :auto size))
|
||||
(-> (gg/calculate-default-item-length frame-length margin gutter)
|
||||
(mth/round))
|
||||
item-length)]
|
||||
(emit-changes! #(-> %
|
||||
(assoc-in [:params :size] size)
|
||||
(assoc-in [:params :item-length] item-length)))))
|
||||
|
||||
handle-change-item-length
|
||||
(fn [item-length]
|
||||
(let [{:keys [margin gutter size]} (->> @state :changes :params (d/deep-merge (:params grid)))
|
||||
size (if (and (nil? item-length) (or (nil? size) (= :auto size))) 12 size)]
|
||||
(emit-changes! #(-> %
|
||||
(assoc-in [:params :size] size)
|
||||
(assoc-in [:params :item-length] item-length)))))
|
||||
|
||||
handle-use-default
|
||||
(fn []
|
||||
(emit-changes! #(hash-map :params ((:type grid) default-grid-params))))
|
||||
|
||||
handle-set-as-default
|
||||
(fn []
|
||||
(let [current-grid (d/deep-merge grid (-> @state :changes))]
|
||||
(on-save-grid current-grid)))
|
||||
|
||||
is-default (= (->> @state :changes (d/deep-merge grid) :params)
|
||||
(->> grid :type default-grid-params))]
|
||||
|
||||
[:div.grid-option
|
||||
[:div.grid-option-main
|
||||
[:button.custom-button {:class (when (:show-advanced-options @state) "is-active")
|
||||
:on-click toggle-advanced-options} i/actions]
|
||||
|
||||
[:& select {:class "flex-grow"
|
||||
:default-value type
|
||||
:options [{:value :square :label (t locale "workspace.options.grid.square")}
|
||||
{:value :column :label (t locale "workspace.options.grid.column")}
|
||||
{:value :row :label (t locale "workspace.options.grid.row")}]
|
||||
:on-change handle-change-type}]
|
||||
|
||||
(if (= type :square)
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text {:type "number"
|
||||
:min "1"
|
||||
:no-validate true
|
||||
:value (:size params)
|
||||
:on-change (handle-change-event :params :size)}]]
|
||||
[:& editable-select {:value (:size params)
|
||||
:type (when (number? (:size params)) "number" )
|
||||
:class "input-option"
|
||||
:options size-options
|
||||
:on-change handle-change-size}])
|
||||
|
||||
[:div.grid-option-main-actions
|
||||
[:button.custom-button {:on-click handle-toggle-visibility} (if display i/eye i/eye-closed)]
|
||||
[:button.custom-button {:on-click handle-remove-grid} i/trash]]]
|
||||
|
||||
[:& advanced-options {:visible? (:show-advanced-options @state)
|
||||
:on-close toggle-advanced-options}
|
||||
(when (= :square type)
|
||||
[:& input-row {:label (t locale "workspace.options.grid.params.size")
|
||||
:class "pixels"
|
||||
:min 1
|
||||
:value (:size params)
|
||||
:on-change (handle-change :params :size)}])
|
||||
|
||||
(when (= :row type)
|
||||
[:& input-row {:label (t locale "workspace.options.grid.params.rows")
|
||||
:type :editable-select
|
||||
:options size-options
|
||||
:value (:size params)
|
||||
:min 1
|
||||
:on-change handle-change-size}])
|
||||
|
||||
(when (= :column type)
|
||||
[:& input-row {:label (t locale "workspace.options.grid.params.columns")
|
||||
:type :editable-select
|
||||
:options size-options
|
||||
:value (:size params)
|
||||
:min 1
|
||||
:on-change handle-change-size}])
|
||||
|
||||
(when (#{:row :column} type)
|
||||
[:& input-row {:label (t locale "workspace.options.grid.params.type")
|
||||
:type :select
|
||||
:options [{:value :stretch :label (t locale "workspace.options.grid.params.type.stretch")}
|
||||
{:value :left :label (t locale "workspace.options.grid.params.type.left")}
|
||||
{:value :center :label (t locale "workspace.options.grid.params.type.center")}
|
||||
{:value :right :label (t locale "workspace.options.grid.params.type.right")}]
|
||||
:value (:type params)
|
||||
:on-change (handle-change :params :type)}])
|
||||
|
||||
(when (#{:row :column} type)
|
||||
[:& input-row {:label (if (= :row type)
|
||||
(t locale "workspace.options.grid.params.height")
|
||||
(t locale "workspace.options.grid.params.width"))
|
||||
:class "pixels"
|
||||
:value (or (:item-length params) "")
|
||||
:on-change handle-change-item-length}])
|
||||
|
||||
(when (#{:row :column} type)
|
||||
[:*
|
||||
[:& input-row {:label (t locale "workspace.options.grid.params.gutter")
|
||||
:class "pixels"
|
||||
:value (:gutter params)
|
||||
:min 0
|
||||
:on-change (handle-change :params :gutter)}]
|
||||
[:& input-row {:label (t locale "workspace.options.grid.params.margin")
|
||||
:class "pixels"
|
||||
:min 0
|
||||
: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 {:disabled is-default
|
||||
:on-click handle-use-default} (t locale "workspace.options.grid.params.use-default")]
|
||||
[:button.btn-options {:disabled is-default
|
||||
:on-click handle-set-as-default} (t locale "workspace.options.grid.params.set-default")]]]]))
|
||||
|
||||
(mf/defc frame-grid [{:keys [shape]}]
|
||||
(let [locale (i18n/use-locale)
|
||||
id (:id shape)
|
||||
default-grid-params (merge dw/default-grid-params (mf/deref refs/workspace-saved-grids))
|
||||
handle-create-grid #(st/emit! (dw/add-frame-grid id))
|
||||
handle-remove-grid (fn [index] #(st/emit! (dw/remove-frame-grid id index)))
|
||||
handle-edit-grid (fn [index] #(st/emit! (dw/set-frame-grid id index %)))
|
||||
handle-save-grid (fn [grid] (st/emit! (dw/set-default-grid (:type grid) (:params grid))))]
|
||||
[:div.element-set
|
||||
[:div.element-set-title
|
||||
[:span (t locale "workspace.options.grid.title")]
|
||||
[:div.add-page {:on-click handle-create-grid} i/close]]
|
||||
|
||||
(when (not (empty? (:grids shape)))
|
||||
[:div.element-set-content
|
||||
(for [[index grid] (map-indexed vector (:grids shape))]
|
||||
[:& grid-options {:key (str (:id shape) "-" index)
|
||||
:grid grid
|
||||
:default-grid-params default-grid-params
|
||||
:frame shape
|
||||
:on-change (handle-edit-grid index)
|
||||
:on-remove (handle-remove-grid index)
|
||||
:on-save-grid handle-save-grid}])])]))
|
||||
|
||||
@ -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}])
|
||||
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
;; 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.options.rows.color-row
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.util.math :as math]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
|
||||
[uxbox.common.data :as d]))
|
||||
|
||||
(defn color-picker-callback [color handle-change-color]
|
||||
(fn [event]
|
||||
(let [x (.-clientX event)
|
||||
y (.-clientY event)
|
||||
props {:x x
|
||||
:y y
|
||||
:on-change handle-change-color
|
||||
:value (:value color)
|
||||
:transparent? true}]
|
||||
(modal/show! colorpicker-modal props))))
|
||||
|
||||
(defn opacity->string [opacity]
|
||||
(str (-> opacity
|
||||
(d/coalesce 1)
|
||||
(* 100)
|
||||
(math/round))))
|
||||
|
||||
(defn string->opacity [opacity-str]
|
||||
(-> opacity-str
|
||||
(d/parse-integer 1)
|
||||
(/ 100)))
|
||||
|
||||
(mf/defc color-row [{:keys [value on-change]}]
|
||||
(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)
|
||||
(when on-change (on-change (update-color @state)))))
|
||||
|
||||
change-opacity (fn [opacity]
|
||||
(let [update-opacity (fn [state] (assoc state :opacity opacity))]
|
||||
(swap! state update-opacity)
|
||||
(when on-change (on-change (update-opacity @state)))))
|
||||
|
||||
handle-pick-color (fn [color]
|
||||
(change-color color))
|
||||
|
||||
handle-input-color-change (fn [event]
|
||||
(let [target (dom/get-target event)
|
||||
value (dom/get-value target)]
|
||||
(when (dom/valid? target)
|
||||
(change-color value))))
|
||||
handle-opacity-change (fn [event]
|
||||
(-> event
|
||||
dom/get-target
|
||||
dom/get-value
|
||||
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)}
|
||||
:on-click (color-picker-callback @state handle-pick-color)}]
|
||||
|
||||
[:div.color-info
|
||||
[:input {:value (-> @state :value)
|
||||
:pattern "^#(?:[0-9a-fA-F]{3}){1,2}$"
|
||||
:on-change handle-input-color-change}]]
|
||||
|
||||
[:div.input-element.percentail
|
||||
[:input.input-text {:type "number"
|
||||
:value (-> @state :opacity opacity->string)
|
||||
:on-change handle-opacity-change
|
||||
:min "0"
|
||||
:max "100"}]]
|
||||
|
||||
[:input.slidebar {:type "range"
|
||||
:min "0"
|
||||
:max "100"
|
||||
:value (-> @state :opacity opacity->string)
|
||||
:step "1"
|
||||
:on-change handle-opacity-change}]]))
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
;; 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.options.rows.input-row
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.ui.components.select :refer [select]]
|
||||
[uxbox.main.ui.components.editable-select :refer [editable-select]]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
(mf/defc input-row [{:keys [label options value class min max on-change type]}]
|
||||
[:div.row-flex.input-row
|
||||
[:span.element-set-subtitle label]
|
||||
[:div.input-element {:class class}
|
||||
|
||||
(case type
|
||||
:select
|
||||
[:& select {:default-value value
|
||||
:class "input-option"
|
||||
:options options
|
||||
:on-change on-change}]
|
||||
:editable-select
|
||||
[:& editable-select {:value value
|
||||
:class "input-option"
|
||||
:options options
|
||||
:type (when (number? value) "number")
|
||||
:on-change on-change}]
|
||||
|
||||
(let [handle-change
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-value d/parse-integer)]
|
||||
(when (and (not (nil? on-change))
|
||||
(or (not min) (>= value min))
|
||||
(or (not max) (<= value max)))
|
||||
(on-change value))))]
|
||||
[:input.input-text
|
||||
{:placeholder label
|
||||
:type "number"
|
||||
:on-change handle-change
|
||||
:value value}]))
|
||||
|
||||
]])
|
||||
@ -45,8 +45,15 @@
|
||||
(mf/defc snap-feedback-points
|
||||
[{:keys [shapes page-id filter-shapes zoom] :as props}]
|
||||
(let [state (mf/use-state [])
|
||||
subject (mf/use-memo #(rx/subject))]
|
||||
subject (mf/use-memo #(rx/subject))
|
||||
|
||||
;; We use sets to store points/lines so there are no points/lines repeated
|
||||
;; can cause problems with react keys
|
||||
snap-points (into #{} (mapcat (fn [[point snaps coord]]
|
||||
(when (not-empty snaps) (concat [point] snaps))) @state))
|
||||
|
||||
snap-lines (into #{} (mapcat (fn [[point snaps coord]]
|
||||
(when (not-empty snaps) (map #(vector point %) snaps))) @state))]
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(->> subject
|
||||
@ -61,31 +68,29 @@
|
||||
(fn []
|
||||
(rx/push! subject props)))
|
||||
|
||||
|
||||
[:g.snap-feedback
|
||||
(for [[point snaps coord] @state]
|
||||
(if (not-empty snaps)
|
||||
[:g.point {:key (str "point-" (:x point) "-" (:y point) "-" (name coord))}
|
||||
[:& snap-point {:key (str "point-" (:x point) "-" (:y point) "-" (name coord))
|
||||
:point point
|
||||
:zoom zoom}]
|
||||
(for [[from-point to-point] snap-lines]
|
||||
[:& snap-line {:key (str "line-" (:x from-point) "-" (:y from-point) "-" (:x to-point) "-" (:y to-point) "-")
|
||||
:snap from-point
|
||||
:point to-point
|
||||
:zoom zoom}])
|
||||
(for [point snap-points]
|
||||
[:& snap-point {:key (str "point-" (:x point) "-" (:y point))
|
||||
:point point
|
||||
:zoom zoom}])]))
|
||||
|
||||
(for [snap snaps]
|
||||
[:& snap-point {:key (str "snap-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap) "-" (name coord))
|
||||
:point snap
|
||||
:zoom zoom}])
|
||||
|
||||
(for [snap snaps]
|
||||
[:& snap-line {:key (str "line-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap) "-" (name coord))
|
||||
:snap snap
|
||||
: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)
|
||||
|
||||
@ -26,11 +26,10 @@
|
||||
[uxbox.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]]
|
||||
[uxbox.main.ui.workspace.shapes.interactions :refer [interactions]]
|
||||
[uxbox.main.ui.workspace.drawarea :refer [draw-area start-drawing]]
|
||||
[uxbox.main.ui.workspace.grid :refer [grid]]
|
||||
[uxbox.main.ui.workspace.ruler :refer [ruler]]
|
||||
[uxbox.main.ui.workspace.selection :refer [selection-handlers]]
|
||||
[uxbox.main.ui.workspace.presence :as presence]
|
||||
[uxbox.main.ui.workspace.snap-feedback :refer [snap-feedback]]
|
||||
[uxbox.main.ui.workspace.frame-grid :refer [frame-grid]]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.object :as obj]
|
||||
@ -127,7 +126,7 @@
|
||||
:key (:id item)}]))]))
|
||||
|
||||
(mf/defc viewport
|
||||
[{:keys [page local] :as props}]
|
||||
[{:keys [page local layout] :as props}]
|
||||
(let [{:keys [drawing-tool
|
||||
options-mode
|
||||
zoom
|
||||
@ -354,6 +353,7 @@
|
||||
]
|
||||
|
||||
(mf/use-effect on-mount)
|
||||
|
||||
[:svg.viewport
|
||||
{:preserveAspectRatio "xMidYMid meet"
|
||||
:width (:width vport 0)
|
||||
@ -381,22 +381,18 @@
|
||||
:zoom zoom
|
||||
:edition edition}])
|
||||
|
||||
|
||||
(when-let [drawing-shape (:drawing local)]
|
||||
[:& draw-area {:shape drawing-shape
|
||||
:zoom zoom
|
||||
:modifiers (:modifiers local)}])
|
||||
|
||||
[:& snap-feedback]
|
||||
(when (contains? layout :display-grid)
|
||||
[:& frame-grid {:zoom zoom}])
|
||||
|
||||
(when (contains? flags :grid)
|
||||
[:& grid])]
|
||||
[:& snap-feedback {:layout layout}]
|
||||
|
||||
(when tooltip
|
||||
[:& cursor-tooltip {:zoom zoom :tooltip tooltip}])
|
||||
|
||||
(when (contains? flags :ruler)
|
||||
[:& ruler {:zoom zoom :ruler (:ruler local)}])
|
||||
(when tooltip
|
||||
[:& cursor-tooltip {:zoom zoom :tooltip tooltip}])]
|
||||
|
||||
[:& presence/active-cursors {:page page}]
|
||||
[:& selection-rect {:data (:selrect local)}]
|
||||
|
||||
116
frontend/src/uxbox/util/geom/grid.cljs
Normal file
116
frontend/src/uxbox/util/geom/grid.cljs
Normal file
@ -0,0 +1,116 @@
|
||||
;; 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.util.geom.grid
|
||||
(:require
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
(def ^:private default-items 12)
|
||||
|
||||
(defn calculate-default-item-length
|
||||
"Calculates the item-length so the default number of items fits inside the frame-length"
|
||||
[frame-length margin gutter]
|
||||
(/ (- frame-length (+ margin (- margin gutter)) (* gutter default-items)) default-items))
|
||||
|
||||
(defn calculate-size
|
||||
"Calculates the number of rows/columns given the other grid parameters"
|
||||
[frame-length item-length margin gutter]
|
||||
(let [item-length (or item-length (calculate-default-item-length frame-length margin gutter))
|
||||
frame-length-no-margins (- frame-length (+ margin (- margin gutter)))]
|
||||
(mth/floor (/ frame-length-no-margins (+ item-length gutter)))))
|
||||
|
||||
(defn- calculate-column-grid
|
||||
[{:keys [width height x y] :as frame} {:keys [size gutter margin item-length type] :as params}]
|
||||
(let [size (if (number? size) size (calculate-size width item-length margin gutter))
|
||||
parts (/ width size)
|
||||
item-width (or item-length (+ parts (- gutter) (/ gutter size) (- (/ (* margin 2) size))))
|
||||
item-height height
|
||||
initial-offset (case type
|
||||
:right (- width (* item-width size) (* gutter (dec size)) margin)
|
||||
:center (/ (- width (* item-width size) (* gutter (dec size))) 2)
|
||||
margin)
|
||||
gutter (if (= :stretch type) (/ (- width (* item-width size) (* margin 2)) (dec size)) gutter)
|
||||
next-x (fn [cur-val] (+ initial-offset x (* (+ item-width gutter) cur-val)))
|
||||
next-y (fn [cur-val] y)]
|
||||
[size item-width item-height next-x next-y]))
|
||||
|
||||
(defn- calculate-row-grid
|
||||
[{:keys [width height x y] :as frame} {:keys [size gutter margin item-length type] :as params}]
|
||||
(let [size (if (number? size) size (calculate-size height item-length margin gutter))
|
||||
parts (/ height size)
|
||||
item-width width
|
||||
item-height (or item-length (+ parts (- gutter) (/ gutter size) (- (/ (* margin 2) size))))
|
||||
initial-offset (case type
|
||||
:right (- height (* item-height size) (* gutter (dec size)) margin)
|
||||
:center (/ (- height (* item-height size) (* gutter (dec size))) 2)
|
||||
margin)
|
||||
gutter (if (= :stretch type) (/ (- height (* item-height size) (* margin 2)) (dec size)) gutter)
|
||||
next-x (fn [cur-val] x)
|
||||
next-y (fn [cur-val] (+ initial-offset y (* (+ item-height gutter) cur-val)))]
|
||||
[size item-width item-height next-x next-y]))
|
||||
|
||||
(defn- calculate-square-grid
|
||||
[{:keys [width height x y] :as frame} {:keys [size] :as params}]
|
||||
(let [col-size (quot width size)
|
||||
row-size (quot height size)
|
||||
as-row-col (fn [value] [(quot value col-size) (rem value col-size)])
|
||||
next-x (fn [cur-val]
|
||||
(let [[_ col] (as-row-col cur-val)] (+ x (* col size))))
|
||||
next-y (fn [cur-val]
|
||||
(let [[row _] (as-row-col cur-val)] (+ y (* row size))))]
|
||||
[(* col-size row-size) size size next-x next-y]))
|
||||
|
||||
(defn grid-areas
|
||||
"Given a frame and the grid parameters returns the areas defined on the grid"
|
||||
[frame grid]
|
||||
(let [grid-fn (case (-> grid :type)
|
||||
:column calculate-column-grid
|
||||
:row calculate-row-grid
|
||||
:square calculate-square-grid)
|
||||
[num-items item-width item-height next-x next-y] (grid-fn frame (-> grid :params))]
|
||||
(->>
|
||||
(range 0 num-items)
|
||||
(map #(hash-map :x (next-x %)
|
||||
:y (next-y %)
|
||||
:width item-width
|
||||
:height item-height)))))
|
||||
|
||||
(defn grid-area-points
|
||||
[{:keys [x y width height]}]
|
||||
[(gpt/point x y)
|
||||
(gpt/point (+ x width) y)
|
||||
(gpt/point (+ x width) (+ y height))
|
||||
(gpt/point x (+ y height))])
|
||||
|
||||
(defn grid-snap-points
|
||||
"Returns the snap points for a given grid"
|
||||
([shape coord] (mapcat #(grid-snap-points shape % coord) (:grids shape)))
|
||||
([shape {:keys [type display params] :as grid} coord]
|
||||
(when (:display grid)
|
||||
(case type
|
||||
:square
|
||||
(let [{:keys [x y width height]} shape
|
||||
size (-> params :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)
|
||||
(->> (grid-areas shape grid)
|
||||
(mapcat grid-area-points)))
|
||||
|
||||
:row
|
||||
(when (= coord :y)
|
||||
(->> (grid-areas shape grid)
|
||||
(mapcat grid-area-points)))))))
|
||||
@ -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."
|
||||
|
||||
@ -14,36 +14,15 @@
|
||||
[uxbox.util.geom.shapes :as gsh]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
(defn- frame-snap-points [{:keys [x y width height]}]
|
||||
#{(gpt/point x y)
|
||||
(gpt/point (+ x (/ width 2)) y)
|
||||
(gpt/point (+ x width) y)
|
||||
(gpt/point (+ x width) (+ y (/ height 2)))
|
||||
(gpt/point (+ x width) (+ y height))
|
||||
(gpt/point (+ x (/ width 2)) (+ y height))
|
||||
(gpt/point x (+ y height))
|
||||
(gpt/point x (+ y (/ height 2)))})
|
||||
|
||||
(defn- frame-snap-points-resize [{:keys [x y width height]} handler]
|
||||
(case handler
|
||||
:top-left (gpt/point x y)
|
||||
:top (gpt/point (+ x (/ width 2)) y)
|
||||
:top-right (gpt/point (+ x width) y)
|
||||
:right (gpt/point (+ x width) (+ y (/ height 2)))
|
||||
:bottom-right (gpt/point (+ x width) (+ y height))
|
||||
:bottom (gpt/point (+ x (/ width 2)) (+ y height))
|
||||
:bottom-left (gpt/point x (+ y height))
|
||||
:left (gpt/point x (+ y (/ height 2)))))
|
||||
|
||||
(def ^:private handler->point-idx
|
||||
{:top-left 0
|
||||
:top 0
|
||||
:top-right 1
|
||||
:right 1
|
||||
:bottom-right 2
|
||||
:bottom 2
|
||||
:bottom-left 3
|
||||
:left 3})
|
||||
(defn- frame-snap-points [{:keys [x y width height] :as frame}]
|
||||
(into #{(gpt/point x y)
|
||||
(gpt/point (+ x (/ width 2)) y)
|
||||
(gpt/point (+ x width) y)
|
||||
(gpt/point (+ x width) (+ y (/ height 2)))
|
||||
(gpt/point (+ x width) (+ y height))
|
||||
(gpt/point (+ x (/ width 2)) (+ y height))
|
||||
(gpt/point x (+ y height))
|
||||
(gpt/point x (+ y (/ height 2)))}))
|
||||
|
||||
(defn shape-snap-points
|
||||
[shape]
|
||||
|
||||
@ -14,17 +14,24 @@
|
||||
[uxbox.common.pages :as cp]
|
||||
[uxbox.worker.impl :as impl]
|
||||
[uxbox.util.range-tree :as rt]
|
||||
[uxbox.util.geom.snap-points :as snap]))
|
||||
[uxbox.util.geom.snap-points :as snap]
|
||||
[uxbox.util.geom.grid :as gg]))
|
||||
|
||||
(defonce state (l/atom {}))
|
||||
|
||||
(defn- create-coord-data
|
||||
"Initializes the range tree given the shapes"
|
||||
[shapes coord]
|
||||
[frame-id shapes coord]
|
||||
(let [process-shape (fn [coord]
|
||||
(fn [shape]
|
||||
(let [points (snap/shape-snap-points shape)]
|
||||
(map #(vector % (:id shape)) points))))
|
||||
(concat
|
||||
(let [points (snap/shape-snap-points shape)]
|
||||
(map #(vector % (:id shape)) points))
|
||||
|
||||
;; The grid points are only added by the "root" of the coord-dat
|
||||
(if (= (:id shape) frame-id)
|
||||
(let [points (gg/grid-snap-points shape coord)]
|
||||
(map #(vector % :layout) points))))))
|
||||
into-tree (fn [tree [point _ :as data]]
|
||||
(rt/insert tree (coord point) data))]
|
||||
(->> shapes
|
||||
@ -34,7 +41,7 @@
|
||||
(defn- mapm
|
||||
"Map over the values of a map"
|
||||
[mfn coll]
|
||||
(into {} (map (fn [[key val]] [key (mfn val)]) coll)))
|
||||
(into {} (map (fn [[key val]] [key (mfn key val)]) coll)))
|
||||
|
||||
(defn- initialize-snap-data
|
||||
"Initialize the snap information with the current workspace information"
|
||||
@ -44,8 +51,8 @@
|
||||
(group-by :frame-id))
|
||||
frame-shapes (->> (cp/select-frames objects)
|
||||
(reduce #(update %1 (:id %2) conj %2) frame-shapes))]
|
||||
(mapm (fn [shapes] {:x (create-coord-data shapes :x)
|
||||
:y (create-coord-data shapes :y)})
|
||||
(mapm (fn [frame-id shapes] {:x (create-coord-data frame-id shapes :x)
|
||||
:y (create-coord-data frame-id shapes :y)})
|
||||
frame-shapes)))
|
||||
|
||||
(defn- log-state
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user