Merge pull request #179 from uxbox/fixes-2020-04-15

Bugfixes
This commit is contained in:
Hirunatan 2020-04-16 11:25:41 +02:00 committed by GitHub
commit f5e16eb469
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 681 additions and 699 deletions

View file

@ -54,6 +54,7 @@
(t/is (= [id-b id-c id-a] (get-in res [:objects uuid/zero :shapes])))))))
(t/deftest process-change-mod-obj
(t/testing "simple mod-obj"
(let [data cp/default-page-data
chg {:type :mod-obj
:id uuid/zero
@ -63,6 +64,16 @@
res (cp/process-changes data [chg])]
(t/is (= "foobar" (get-in res [:objects uuid/zero :name])))))
(t/testing "mod-obj for not existing shape"
(let [data cp/default-page-data
chg {:type :mod-obj
:id (uuid/next)
:operations [{:type :set
:attr :name
:val "foobar"}]}
res (cp/process-changes data [chg])]
(t/is (= res cp/default-page-data)))))
(t/deftest process-change-del-obj-1
(let [id (uuid/next)
@ -202,15 +213,15 @@
))
(t/deftest process-changes-move-objects
(let [frame-a-id (uuid/next)
frame-b-id (uuid/next)
group-a-id (uuid/next)
group-b-id (uuid/next)
rect-a-id (uuid/next)
rect-b-id (uuid/next)
rect-c-id (uuid/next)
rect-d-id (uuid/next)
rect-e-id (uuid/next)
(let [frame-a-id (uuid/custom 1)
frame-b-id (uuid/custom 2)
group-a-id (uuid/custom 3)
group-b-id (uuid/custom 4)
rect-a-id (uuid/custom 5)
rect-b-id (uuid/custom 6)
rect-c-id (uuid/custom 7)
rect-d-id (uuid/custom 8)
rect-e-id (uuid/custom 9)
data (-> cp/default-page-data
(assoc-in [cp/root :shapes] [frame-a-id])
(assoc-in [:objects frame-a-id]
@ -345,12 +356,47 @@
(t/is (= data res))))))
(t/deftest process-changes-move-objects-3
(let [shape-2-id (uuid/custom 1 2)
shape-3-id (uuid/custom 1 3)
frame-id (uuid/custom 1 1)
changes [{:type :add-obj
:id frame-id
:frame-id uuid/zero
:obj {:type :frame
:name "Frame"}}
{:type :add-obj
:frame-id frame-id
:id shape-2-id
:obj {:type :shape
:name "Shape"}}
{:type :add-obj
:id shape-3-id
:frame-id uuid/zero
:obj {:type :rect
:name "Shape"}}]
data (cp/process-changes cp/default-page-data changes)]
(t/testing "move inside->outside-inside"
(let [changes [{:type :mov-objects
:shapes [shape-3-id]
:parent-id frame-id}
{:type :mov-objects
:shapes [shape-3-id]
:parent-id uuid/zero}]
res (cp/process-changes data changes)]
(t/is (= (get-in res [:objects shape-2-id :frame-id])
(get-in data [:objects shape-2-id :frame-id])))
(t/is (= (get-in res [:objects shape-3-id :frame-id])
(get-in data [:objects shape-3-id :frame-id])))))))
(t/deftest process-changes-move-objects-2
(let [shape-1-id (uuid/custom 1 1)
shape-2-id (uuid/custom 1 2)
shape-3-id (uuid/custom 1 3)
shape-4-id (uuid/custom 1 4)
group-1-id (uuid/custom 2 1)
group-1-id (uuid/custom 1 5)
changes [{:type :add-obj
:id shape-1-id
:frame-id cp/root

View file

@ -139,7 +139,7 @@
(defmethod change-spec-impl :add-obj [_]
(s/keys :req-un [::id ::frame-id ::obj]
:opt-un [::session-id]))
:opt-un [::session-id ::parent-id]))
(defmethod change-spec-impl :mod-obj [_]
(s/keys :req-un [::id ::operations]
@ -149,10 +149,6 @@
(s/keys :req-un [::id]
:opt-un [::session-id]))
(defmethod change-spec-impl :mov-obj [_]
(s/keys :req-un [::id ::frame-id]
:opt-un [::session-id]))
(defmethod change-spec-impl :mov-objects [_]
(s/keys :req-un [::parent-id ::shapes]
:opt-un [::index]))
@ -194,40 +190,39 @@
(defn process-changes
[data items]
(->> (us/verify ::changes items)
(reduce #(or (process-change %1 %2) %1) data)))
(reduce #(do
;; (prn "process-change" (:type %2) (:id %2))
(or (process-change %1 %2) %1))
data)))
(declare insert-at-index)
(defmethod process-change :add-obj
[data {:keys [id obj frame-id index] :as change}]
(assert (contains? (:objects data) frame-id) "process-change/add-obj")
[data {:keys [id obj frame-id parent-id index] :as change}]
(let [parent-id (or parent-id frame-id)
objects (:objects data)]
(when (and (contains? objects parent-id)
(contains? objects frame-id))
(let [obj (assoc obj
:frame-id frame-id
:id id)]
(-> data
(update :objects assoc id obj)
(update-in [:objects frame-id :shapes]
(update-in [:objects parent-id :shapes]
(fn [shapes]
(let [shapes (or shapes [])]
(cond
(some #{id} shapes) shapes
(nil? index) (conj shapes id)
:else (insert-at-index shapes index [id])))))))
:else (insert-at-index shapes index [id]))))))))))
(defmethod process-change :mod-obj
[data {:keys [id operations] :as change}]
(assert (contains? (:objects data) id) "process-change/mod-obj")
(update-in data [:objects id]
#(reduce process-operation % operations)))
(defmethod process-change :mov-obj
[data {:keys [id frame-id] :as change}]
(assert (contains? (:objects data) frame-id))
(let [frame-id' (get-in data [:objects id :frame-id])]
(when (not= frame-id frame-id')
(-> data
(update-in [:objects frame-id' :shapes] (fn [s] (filterv #(not= % id) s)))
(update-in [:objects id] assoc :frame-id frame-id)
(update-in [:objects frame-id :shapes] conj id)))))
(update data :objects
(fn [objects]
(if-let [obj (get objects id)]
(assoc objects id (reduce process-operation obj operations))
objects))))
(defmethod process-change :del-obj
[data {:keys [id] :as change}]
@ -283,7 +278,12 @@
(let [prev-shapes (or prev-shapes [])]
(if index
(insert-at-index prev-shapes index shapes)
(into prev-shapes shapes))))
(reduce (fn [acc id]
(if (some #{id} acc)
acc
(conj acc id)))
prev-shapes
shapes))))
strip-id
(fn [id]
@ -316,17 +316,18 @@
;; Updates the frame-id references that might be outdated
update-frame-ids
(fn update-frame-ids [data shape-id]
(as-> data $
(assoc-in $ [:objects shape-id :frame-id] frame-id)
(reduce update-frame-ids $ (get-in $ [:objects shape-id :shapes]))))]
(fn update-frame-ids [data id]
(let [data (assoc-in data [:objects id :frame-id] frame-id)
obj (get-in data [:objects id])]
(cond-> data
(not= :frame (:type obj))
(as-> $$ (reduce update-frame-ids $$ (:shapes obj))))))]
(if valid?
(when valid?
(as-> data $
(update-in $ [:objects parent-id :shapes] insert-items)
(reduce remove-in-parent $ shapes)
(reduce update-frame-ids $ (get-in $ [:objects parent-id :shapes])))
data)))
(reduce update-frame-ids $ (get-in $ [:objects parent-id :shapes]))))))
(defmethod process-operation :set
[shape op]

View file

@ -54,9 +54,9 @@
"dev": true
},
"@types/react": {
"version": "16.9.27",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.27.tgz",
"integrity": "sha512-j+RvQb9w7a2kZFBOgTh+s/elCwtqWUMN6RJNdmz0ntmwpeoMHKnyhUcmYBu7Yw94Rtj9938D+TJSn6WGcq2+OA==",
"version": "16.9.34",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.34.tgz",
"integrity": "sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==",
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
@ -375,13 +375,13 @@
"dev": true
},
"autoprefixer": {
"version": "9.7.5",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.5.tgz",
"integrity": "sha512-URo6Zvt7VYifomeAfJlMFnYDhow1rk2bufwkbamPEAtQFcL11moLk4PnR7n9vlu7M+BkXAZkHFA0mIcY7tjQFg==",
"version": "9.7.6",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.6.tgz",
"integrity": "sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ==",
"dev": true,
"requires": {
"browserslist": "^4.11.0",
"caniuse-lite": "^1.0.30001036",
"browserslist": "^4.11.1",
"caniuse-lite": "^1.0.30001039",
"chalk": "^2.4.2",
"normalize-range": "^0.1.2",
"num2fraction": "^1.2.2",
@ -650,15 +650,15 @@
}
},
"browserslist": {
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.11.0.tgz",
"integrity": "sha512-WqEC7Yr5wUH5sg6ruR++v2SGOQYpyUdYYd4tZoAq1F7y+QXoLoYGXVbxhtaIqWmAJjtNTRjVD3HuJc1OXTel2A==",
"version": "4.11.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.11.1.tgz",
"integrity": "sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001035",
"electron-to-chromium": "^1.3.380",
"node-releases": "^1.1.52",
"pkg-up": "^3.1.0"
"caniuse-lite": "^1.0.30001038",
"electron-to-chromium": "^1.3.390",
"node-releases": "^1.1.53",
"pkg-up": "^2.0.0"
}
},
"buffer": {
@ -731,9 +731,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001038",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001038.tgz",
"integrity": "sha512-zii9quPo96XfOiRD4TrfYGs+QsGZpb2cGiMAzPjtf/hpFgB6zCPZgJb7I1+EATeMw/o+lG8FyRAnI+CWStHcaQ==",
"version": "1.0.30001042",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001042.tgz",
"integrity": "sha512-igMQ4dlqnf4tWv0xjaaE02op9AJ2oQzXKjWf4EuAHFN694Uo9/EfPVIPJcmn2WkU9RqozCxx5e2KPcVClHDbDw==",
"dev": true
},
"caseless": {
@ -1041,9 +1041,9 @@
}
},
"core-js-pure": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz",
"integrity": "sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw=="
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz",
"integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA=="
},
"core-util-is": {
"version": "1.0.2",
@ -1139,9 +1139,9 @@
"dev": true
},
"css-selector-parser": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.3.0.tgz",
"integrity": "sha1-XxrUPi2O77/cME/NOaUhZklD4+s=",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz",
"integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g==",
"dev": true
},
"css-tree": {
@ -1200,9 +1200,9 @@
"dev": true
},
"csstype": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz",
"integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q=="
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
"integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w=="
},
"d": {
"version": "1.0.1",
@ -1224,9 +1224,9 @@
}
},
"date-fns": {
"version": "2.11.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.11.1.tgz",
"integrity": "sha512-3RdUoinZ43URd2MJcquzBbDQo+J87cSzB8NkXdZiN5ia1UNyep0oCyitfiL88+R7clGTeq/RniXAc16gWyAu1w=="
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.12.0.tgz",
"integrity": "sha512-qJgn99xxKnFgB1qL4jpxU7Q2t0LOn1p8KMIveef3UZD7kqjT3tpFNNdXJelEHhE+rUgffriXriw/sOSU+cS1Hw=="
},
"dateformat": {
"version": "3.0.3",
@ -1461,9 +1461,9 @@
}
},
"electron-to-chromium": {
"version": "1.3.390",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.390.tgz",
"integrity": "sha512-4RvbM5x+002gKI8sltkqWEk5pptn0UnzekUx8RTThAMPDSb8jjpm6SwGiSnEve7f85biyZl8DMXaipaCxDjXag==",
"version": "1.3.409",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.409.tgz",
"integrity": "sha512-CB2HUXiMsaVYY5VvcpELhDShiTRhI2FfN7CuacEZ5mDmMFuSG/ZVm8HoSya0+S61RvUd3TjIjFSKywqHZpRPzQ==",
"dev": true
},
"elliptic": {
@ -1809,9 +1809,9 @@
},
"dependencies": {
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
@ -1901,12 +1901,12 @@
}
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
"dev": true,
"requires": {
"locate-path": "^3.0.0"
"locate-path": "^2.0.0"
}
},
"findup-sync": {
@ -2821,13 +2821,13 @@
}
},
"gulp-mustache": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/gulp-mustache/-/gulp-mustache-4.1.2.tgz",
"integrity": "sha512-4nJKL6akiP77Znbog2my7XbJ94VnS8HmMBwLYvUNoSYmD99I9vCopIok6XXT1nOu0zXztc5HxQ2PndYt06PWAQ==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/gulp-mustache/-/gulp-mustache-5.0.0.tgz",
"integrity": "sha512-8tk0R1Fd+l6+e/t954e3UheFo25dKkTapPLD1sWoSroPXfIPxyHVgbhfH5VJGqeXl3te5GOwPtfcxxZJ+PYoFg==",
"dev": true,
"requires": {
"escape-string-regexp": "^2.0.0",
"mustache": "^3.0.1",
"mustache": "^4.0.1",
"plugin-error": "^1.0.0",
"replace-ext": "^1.0.0",
"through2": "^3.0.1"
@ -3498,12 +3498,12 @@
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
"integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
"dev": true,
"requires": {
"p-locate": "^3.0.0",
"p-locate": "^2.0.0",
"path-exists": "^3.0.0"
}
},
@ -3877,9 +3877,9 @@
}
},
"mkdirp": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz",
"integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==",
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
"mocha": {
@ -3962,9 +3962,9 @@
"dev": true
},
"mustache": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-3.2.1.tgz",
"integrity": "sha512-RERvMFdLpaFfSRIEe632yDm5nsd0SDKn8hGmcUwswnyiE5mtdZLDybtHAz6hjJhawokF0hXvGLtx9mrQfm6FkA==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.1.tgz",
"integrity": "sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA==",
"dev": true
},
"mute-stdout": {
@ -4318,27 +4318,27 @@
"dev": true
},
"p-limit": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
"integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
"integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
"p-try": "^1.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
"integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
"dev": true,
"requires": {
"p-limit": "^2.0.0"
"p-limit": "^1.1.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
"dev": true
},
"pako": {
@ -4525,12 +4525,12 @@
}
},
"pkg-up": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
"integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz",
"integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=",
"dev": true,
"requires": {
"find-up": "^3.0.0"
"find-up": "^2.1.0"
}
},
"plugin-error": {
@ -4993,9 +4993,9 @@
"dev": true
},
"resolve": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
"integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==",
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.16.0.tgz",
"integrity": "sha512-LarL/PIKJvc09k1jaeT4kQb/8/7P+qV4qSnN2K80AES+OHdfZELAKVOBjxsvtToT/uLOfFbvYvKfZmV8cee7nA==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
@ -5052,9 +5052,9 @@
}
},
"rxjs": {
"version": "7.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.0.0-alpha.1.tgz",
"integrity": "sha512-wQB1ZRQysSXSXzPHhhsw+OaVmNnVSylgR3UkUvVEfexkDmfHEs9bpDz1oiMfIG9JL1hPXL1ABtrL0H6XhstbWg==",
"version": "7.0.0-beta.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.0.0-beta.0.tgz",
"integrity": "sha512-MMsqDczs2RzsTvBiH6SKjJkdAh7WaI6Q0axP/DX+1ljwFm6+18AhQ3kVT8gD7G0dHIVfh5hDFoqLaW79pkiGag==",
"requires": {
"tslib": "^1.9.0"
}
@ -5165,9 +5165,9 @@
}
},
"shadow-cljs": {
"version": "2.8.94",
"resolved": "https://registry.npmjs.org/shadow-cljs/-/shadow-cljs-2.8.94.tgz",
"integrity": "sha512-o3ykB9TpCnMGem4cjmbAkVeYe/saGMVl6RhC/BdO1UNlhPZUp3hwdVp9mBDN079O5jJEtC8RmjHY6xe9utYokw==",
"version": "2.8.95",
"resolved": "https://registry.npmjs.org/shadow-cljs/-/shadow-cljs-2.8.95.tgz",
"integrity": "sha512-2y5wgD3Bjbtu9hX6kRf7fzWHDga5BzW9CWU+eTrwddX0B2XbcoIKOiHYQTj6QrFFqYc1vkWGZLyYE9kEYN8N0A==",
"dev": true,
"requires": {
"node-libs-browser": "^2.0.0",
@ -5597,24 +5597,46 @@
"strip-ansi": "^3.0.0"
}
},
"string.prototype.trimleft": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz",
"integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==",
"string.prototype.trimend": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
"integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"function-bind": "^1.1.1"
"es-abstract": "^1.17.5"
}
},
"string.prototype.trimleft": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
"integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5",
"string.prototype.trimstart": "^1.0.0"
}
},
"string.prototype.trimright": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz",
"integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
"integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"function-bind": "^1.1.1"
"es-abstract": "^1.17.5",
"string.prototype.trimend": "^1.0.0"
}
},
"string.prototype.trimstart": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
"integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5"
}
},
"string_decoder": {
@ -5721,6 +5743,15 @@
"wrap-ansi": "^2.0.0"
}
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
"locate-path": "^3.0.0"
}
},
"invert-kv": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
@ -5742,15 +5773,31 @@
"invert-kv": "^2.0.0"
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"mustache": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-3.2.1.tgz",
"integrity": "sha512-RERvMFdLpaFfSRIEe632yDm5nsd0SDKn8hGmcUwswnyiE5mtdZLDybtHAz6hjJhawokF0hXvGLtx9mrQfm6FkA==",
"dev": true
},
"os-locale": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
@ -5762,6 +5809,30 @@
"mem": "^4.0.0"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@ -5841,9 +5912,9 @@
},
"dependencies": {
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"

View file

@ -13,22 +13,22 @@
],
"scripts": {},
"devDependencies": {
"autoprefixer": "^9.7.4",
"autoprefixer": "^9.7.6",
"clean-css": "^4.2.3",
"gulp": "4.0.2",
"gulp-gzip": "^1.4.2",
"gulp-if": "^3.0.0",
"gulp-mustache": "^4.1.2",
"gulp-mustache": "^5.0.0",
"gulp-rename": "^2.0.0",
"gulp-svg-sprite": "^1.5.0",
"mkdirp": "^1.0.3",
"mkdirp": "^1.0.4",
"postcss": "^7.0.27",
"rimraf": "^3.0.0",
"sass": "^1.26.0",
"shadow-cljs": "^2.8.94"
"shadow-cljs": "^2.8.95"
},
"dependencies": {
"date-fns": "^2.11.1",
"date-fns": "^2.12.0",
"mousetrap": "^1.6.5",
"randomcolor": "^0.5.4",
"react": "^16.13.1",
@ -36,7 +36,7 @@
"react-dnd": "^10.0.2",
"react-dnd-html5-backend": "^10.0.2",
"react-dom": "^16.13.1",
"rxjs": "^7.0.0-alpha.1",
"rxjs": "^7.0.0-beta.0",
"source-map-support": "^0.5.16",
"tdigest": "^0.1.1",
"xregexp": "^4.3.0"

View file

@ -7,16 +7,15 @@
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.data.helpers)
(ns uxbox.main.data.helpers
(:require [uxbox.common.data :as d]))
(defn get-children
"Retrieve all children ids recursively for a given shape"
[shape-id objects]
(let [shapes (get-in objects [shape-id :shapes])]
(if shapes
(concat
shapes
(mapcat #(get-children % objects) shapes))
(d/concat shapes (mapcat #(get-children % objects) shapes))
[])))
(defn is-shape-grouped
@ -29,7 +28,7 @@
(some contains-shape-fn shapes)))
(defn get-parent
"Retrieve the id of the parent for the shape-id (if exists"
"Retrieve the id of the parent for the shape-id (if exists)"
[shape-id objects]
(let [check-parenthood
(fn [shape] (when (and (:shapes shape)
@ -55,9 +54,10 @@
(rec-fn shape-id [])))
(defn replace-shapes
"Replace inside shapes the value `to-replace-id` for the value in items keeping the same order.
`to-replace-id` can be a set, a sequable or a single value. Any of these will be changed into a
set to make the replacement"
"Replace inside shapes the value `to-replace-id` for the value in
items keeping the same order. `to-replace-id` can be a set, a
sequable or a single value. Any of these will be changed into a set
to make the replacement"
[shape to-replace-id items]
(let [should-replace
(cond

View file

@ -9,21 +9,22 @@
(ns uxbox.main.data.workspace
(:require
[clojure.set :as set]
[beicon.core :as rx]
[goog.object :as gobj]
[goog.events :as events]
[cljs.spec.alpha :as s]
[clojure.set :as set]
[goog.events :as events]
[goog.object :as gobj]
[potok.core :as ptk]
[uxbox.common.data :as d]
[uxbox.common.exceptions :as ex]
[uxbox.common.pages :as cp]
[uxbox.common.spec :as us]
[uxbox.common.exceptions :as ex]
[uxbox.common.uuid :as uuid]
[uxbox.config :as cfg]
[uxbox.main.constants :as c]
[uxbox.main.data.icons :as udi]
[uxbox.main.data.dashboard :as dd]
[uxbox.main.data.helpers :as helpers]
[uxbox.main.data.icons :as udi]
[uxbox.main.geom :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.repo :as rp]
@ -38,12 +39,7 @@
[uxbox.util.router :as rt]
[uxbox.util.time :as dt]
[uxbox.util.transit :as t]
[uxbox.common.uuid :as uuid]
[uxbox.util.webapi :as wapi]
#_[vendor.randomcolor])
(:import goog.events.EventType
goog.events.KeyCodes
goog.ui.KeyboardShortcutHandler))
[uxbox.util.webapi :as wapi]))
;; TODO: temporal workaround
(def clear-ruler nil)
@ -52,8 +48,11 @@
;; --- Specs
(s/def ::shape-attrs ::cp/shape-attrs)
(s/def ::set-of-uuid
(s/every uuid? :kind set?))
(s/def ::set-of-string
(s/every string? :kind set?))
;; --- Expose inner functions
@ -368,7 +367,9 @@
ptk/UpdateEvent
(update [_ state]
(let [local (:workspace-local state)]
(assoc-in state [:workspace-cache page-id] local)))))
(-> state
(assoc-in [:workspace-cache page-id] local)
(update :workspace-data dissoc page-id))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -525,7 +526,10 @@
(->> (rp/query :file-with-users {:id id})
(rx/merge-map (fn [result]
(rx/of (file-fetched (dissoc result :users))
(users-fetched (:users result)))))))))
(users-fetched (:users result)))))
(rx/catch (fn [{:keys [type] :as error}]
(when (= :not-found type)
(rx/of (rt/nav :not-found)))))))))
(defn fetch-file
[id]
(us/verify ::us/uuid id)
@ -860,24 +864,6 @@
(update [_ state]
(assoc-in state [:workspace-local :zoom] 2))))
;; --- Grid Alignment
;; (defn initialize-alignment
;; [id]
;; (us/verify ::us/uuid id)
;; (ptk/reify ::initialize-alignment
;; ptk/WatchEvent
;; (watch [_ state stream]
;; (let [metadata (get-in state [:workspace-page :metadata])
;; params {:width c/viewport-width
;; :height c/viewport-height
;; :x-axis (:grid-x-axis metadata c/grid-x-axis)
;; :y-axis (:grid-y-axis metadata c/grid-y-axis)}]
;; (rx/concat
;; (rx/of (deactivate-flag :grid-indexed))
;; (->> (uwrk/initialize-alignment params)
;; (rx/map #(activate-flag :grid-indexed))))))))
;; --- Selection Rect
(declare select-shapes-by-current-selrect)
@ -928,44 +914,63 @@
;; Shapes events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Toggle shape's selection status (selected or deselected)
(defn select-shape
[id]
(us/verify ::us/uuid id)
(ptk/reify ::select-shape
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :selected]
(fn [selected]
(if (contains? selected id)
(disj selected id)
(conj selected id)))))))
(defn select-shapes
[ids]
(us/verify ::set-of-uuid ids)
(ptk/reify ::select-shapes
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :selected] ids))))
(def deselect-all
"Clear all possible state of drawing, edition
or any similar action taken by the user."
(ptk/reify ::deselect-all
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local #(-> %
(assoc :selected #{})
(dissoc :selected-frame))))))
;; --- Add shape to Workspace
(defn impl-retrieve-used-names
(defn- retrieve-used-names
[objects]
(into #{} (map :name) (vals objects)))
(defn extract-numeric-suffix
(defn- extract-numeric-suffix
[basename]
(if-let [[match p1 p2] (re-find #"(.*)-([0-9]+)$" basename)]
[p1 (+ 1 (d/parse-integer p2))]
[basename 1]))
(defn impl-generate-unique-name
(defn- generate-unique-name
"A unique name generator"
[objects basename]
(let [used (impl-retrieve-used-names objects)
[prefix initial] (extract-numeric-suffix basename)]
[used basename]
(s/assert ::set-of-string used)
(s/assert ::us/string basename)
(let [[prefix initial] (extract-numeric-suffix basename)]
(loop [counter initial]
(let [candidate (str prefix "-" counter)]
(if (contains? used candidate)
(recur (inc counter))
candidate)))))
(defn impl-assoc-shape
"Add a shape to the current workspace page, inside a given frame.
Give it a name that is unique in the page"
[state {:keys [id frame-id] :as data}]
(let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
name (impl-generate-unique-name objects (:name data))
shape (assoc data :name name)
page-id (::page-id state)]
(-> state
(update-in [:workspace-data page-id :objects frame-id :shapes] conj id)
(update-in [:workspace-data page-id :objects] assoc id shape))))
(declare select-shape)
(defn- calculate-frame-overlap
[objects shape]
(let [rshp (geom/shape->rect-shape shape)
@ -985,172 +990,161 @@
(defn add-shape
[attrs]
(us/verify ::shape-attrs attrs)
(let [id (uuid/next)]
(ptk/reify ::add-shape
ptk/UpdateEvent
(update [_ state]
(let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
shape (-> (geom/setup-proportions attrs)
(assoc :id id))
frame-id (calculate-frame-overlap objects shape)
shape (merge cp/default-shape-attrs shape {:frame-id frame-id})]
(-> state
(impl-assoc-shape shape)
(assoc-in [:workspace-local :selected] #{id}))))
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (::page-id state)
obj (get-in state [:workspace-data page-id :objects id])]
(rx/of (commit-changes [{:type :add-obj
objects (get-in state [:workspace-data page-id :objects])
id (uuid/next)
shape (geom/setup-proportions attrs)
unames (retrieve-used-names objects)
name (generate-unique-name unames (:name shape))
frame-id (if (= :frame (:type shape))
uuid/zero
(calculate-frame-overlap objects shape))
shape (merge
(if (= :frame (:type shape))
cp/default-frame-attrs
cp/default-shape-attrs)
(assoc shape
:id id
:frame-id (:frame-id obj)
:obj obj}]
[{:type :del-obj
:id id}])))))))
(defn add-frame
[data]
(us/verify ::shape-attrs data)
(let [id (uuid/next)]
(ptk/reify ::add-frame
ptk/UpdateEvent
(update [_ state]
(let [shape (-> (geom/setup-proportions data)
(assoc :id id))
shape (merge cp/default-frame-attrs shape)]
(impl-assoc-shape state shape)))
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (::page-id state)
obj (get-in state [:workspace-data page-id :objects id])]
(rx/of (commit-changes [{:type :add-obj
:id id
:frame-id (:frame-id obj)
:obj obj}]
[{:type :del-obj
:id id}])))))))
;; --- Duplicate Selected
;; TODO: handle properly naming
(defn duplicate-shapes
[shapes]
(ptk/reify ::duplicate-shapes
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
rchanges (mapv (fn [id]
(let [obj (assoc (get objects id)
:id (uuid/next))]
{:type :add-obj
:id (:id obj)
:frame-id (:frame-id obj)
:obj obj
:session-id (:session-id state)}))
shapes)
uchanges (mapv (fn [rch]
{:type :del-obj
:id (:id rch)
:session-id (:session-id state)})
rchanges)]
(rx/of (commit-changes rchanges uchanges {:commit-local? true}))))))
(defn duplicate-frame
[frame-id]
(ptk/reify ::duplicate-frame
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
frame (get objects frame-id)
frame-id (uuid/next)
rchanges (mapv (fn [id]
(let [obj (assoc (get objects id)
:id (uuid/next))]
{:type :add-obj
:id (:id obj)
:frame-id frame-id
:obj (assoc obj :frame-id frame-id)}))
(:shapes frame))
uchanges (mapv (fn [rch]
{:type :del-obj
:id (:id rch)})
rchanges)
shapes (mapv :id rchanges)
:name name
:frame-id frame-id))
rchange {:type :add-obj
:id id
:frame-id frame-id
:obj shape}
uchange {:type :del-obj
:id id}]
(rx/of (commit-changes [rchange] [uchange] {:commit-local? true})
(select-shapes #{id}))))))
;; --- Duplicate Shapes
(declare prepare-duplicate-changes)
(declare prepare-duplicate-change)
(declare prepare-duplicate-frame-change)
(declare prepare-duplicate-shape-change)
(def ^:private change->name #(get-in % [:obj :name]))
(defn- prepare-duplicate-changes
"Prepare objects to paste: generate new id, give them unique names,
move to the position of mouse pointer, and find in what frame they
fit."
[objects names ids delta]
(loop [names names
chgs []
id (first ids)
ids (rest ids)]
(if (nil? id)
chgs
(let [result (prepare-duplicate-change objects names id delta)
result (if (vector? result) result [result])]
(recur
(into names (map change->name) result)
(into chgs result)
(first ids)
(rest ids))))))
(defn- prepare-duplicate-change
[objects names id delta]
(let [obj (get objects id)]
(if (= :frame (:type obj))
(prepare-duplicate-frame-change objects names obj delta)
(prepare-duplicate-shape-change objects names obj delta nil nil))))
(defn- prepare-duplicate-shape-change
[objects names obj delta frame-id parent-id]
(let [id (uuid/next)
name (generate-unique-name names (:name obj))
renamed-obj (assoc obj :id id :name name)
moved-obj (geom/move renamed-obj delta)
frame-id (if frame-id
frame-id
(calculate-frame-overlap objects moved-obj))
parent-id (or parent-id frame-id)
children-changes
(loop [names names
result []
cid (first (:shapes obj))
cids (rest (:shapes obj))]
(if (nil? cid)
result
(let [obj (get objects cid)
changes (prepare-duplicate-shape-change objects names obj delta frame-id id)]
(recur
(into names (map change->name changes))
(into result changes)
(first cids)
(rest cids)))))
reframed-obj (-> moved-obj
(assoc :frame-id frame-id)
(dissoc :shapes))]
(into [{:type :add-obj
:id id
:old-id (:id obj)
:frame-id frame-id
:parent-id parent-id
:obj (dissoc reframed-obj :shapes)}]
children-changes)))
(defn- prepare-duplicate-frame-change
[objects names obj delta]
(let [frame-id (uuid/next)
frame-name (generate-unique-name names (:name obj))
sch (->> (map #(get objects %) (:shapes obj))
(mapcat #(prepare-duplicate-shape-change objects names % delta frame-id frame-id)))
renamed-frame (-> obj
(assoc :id frame-id)
(assoc :name frame-name)
(assoc :frame-id uuid/zero)
(dissoc :shapes))
moved-frame (geom/move renamed-frame delta)
fch {:type :add-obj
:old-id (:id obj)
:id frame-id
:frame-id uuid/zero
:obj (assoc frame
:id frame-id
:shapes shapes)
:obj moved-frame}]
:session-id (:session-id state)}
uchange {:type :del-obj
:id frame-id
:session-id (:session-id state)}]
(rx/of (commit-changes (d/concat [rchange] rchanges)
(d/concat [] uchanges [uchange])
{:commit-local? true}))))))
(into [fch] sch)))
(declare select-shapes)
(def duplicate-selected
(ptk/reify ::duplicate-selected
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
selected (get-in state [:workspace-local :selected])
lookup #(get objects %)
shapes (map lookup selected)
shape? #(not= (:type %) :frame)]
(cond
(and (= (count shapes) 1)
(= (:type (first shapes)) :frame))
(rx/of (duplicate-frame (first selected)))
objects (get-in state [:workspace-data page-id :objects])
delta (gpt/point 0 0)
unames (retrieve-used-names objects)
(and (pos? (count shapes))
(every? shape? shapes))
(rx/of (duplicate-shapes selected))
rchanges (prepare-duplicate-changes objects unames selected delta)
uchanges (mapv #(array-map :type :del-obj :id (:id %))
(reverse rchanges))
:else
(rx/empty))))))
selected (->> rchanges
(filter #(selected (:old-id %)))
(map #(get-in % [:obj :id]))
(into #{}))]
;; --- Toggle shape's selection status (selected or deselected)
(defn select-shape
[id]
(us/verify ::us/uuid id)
(ptk/reify ::select-shape
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :selected]
(fn [selected]
(if (contains? selected id)
(disj selected id)
(conj selected id)))))))
(def deselect-all
"Clear all possible state of drawing, edition
or any similar action taken by the user."
(ptk/reify ::deselect-all
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local #(-> %
(assoc :selected #{})
(dissoc :selected-frame))))))
(rx/of (commit-changes rchanges uchanges {:commit-local? true})
(select-shapes selected))))))
;; --- Select Shapes (By selrect)
@ -1318,30 +1312,30 @@
(let [page-id (::page-id state)
session-id (:session-id state)
objects (get-in state [:workspace-data page-id :objects])
rchanges (mapv #(array-map :type :del-obj :id %) ids)
uchanges (mapv (fn [id]
(let [obj (get objects id)
frm (get objects (:frame-id obj))
idx (d/index-of (:shapes frm) id)]
cpindex (helpers/calculate-child-parent-map objects)
del-change #(array-map :type :del-obj :id %)
rchanges
(reduce (fn [res id]
(let [chd (helpers/get-children id objects)]
(into res (d/concat
(mapv del-change (reverse chd))
[(del-change id)]))))
[]
ids)
uchanges
(mapv (fn [id]
(let [obj (get objects id)]
{:type :add-obj
:id id
:frame-id (:id frm)
:index idx
:frame-id (:frame-id obj)
:parent-id (get cpindex id)
:obj obj}))
(reverse ids))]
(reverse (map :id rchanges)))]
(rx/of (commit-changes rchanges uchanges {:commit-local? true}))))))
(defn- delete-frame
[id]
(ptk/reify ::delete-shapes
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
obj (get objects id)
ids (d/concat [] (:shapes obj) [(:id obj)])]
(rx/of (delete-shapes ids))))))
(def delete-selected
"Deselect all and remove all selected shapes."
(ptk/reify ::delete-selected
@ -1353,17 +1347,8 @@
shapes (map lookup selected)
shape? #(not= (:type %) :frame)]
(cond
(and (= (count shapes) 1)
(= (:type (first shapes)) :frame))
(rx/of (delete-frame (first selected)))
(rx/of (delete-shapes selected))))))
(and (pos? (count shapes))
(every? shape? shapes))
(rx/of (delete-shapes selected))
:else
(rx/empty))))))
;; --- Rename Shape
@ -1417,7 +1402,7 @@
(us/verify ::us/uuid ref-id)
(us/verify number? index)
(ptk/reify ::reloacate-shape
(ptk/reify ::relocate-shape
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (::page-id state)
@ -1431,28 +1416,6 @@
[]
{:commit-local? true}))))))
(defn commit-shape-order-change
[id]
(ptk/reify ::commit-shape-order-change
ptk/WatchEvent
(watch [_ state stream]
(let [pid (::page-id state)
obj (get-in state [:workspace-data pid :objects id])
cfrm (get-in state [:workspace-data pid :objects (:frame-id obj)])
pfrm (get-in state [:pages-data pid :objects (:frame-id obj)])
cindex (d/index-of (:shapes cfrm) id)
pindex (d/index-of (:shapes pfrm) id)
rchange {:type :mod-obj
:id (:id cfrm)
:operations [{:type :abs-order :id id :index cindex}]}
uchange {:type :mod-obj
:id (:id cfrm)
:operations [{:type :abs-order :id id :index pindex}]}]
(rx/of (commit-changes [rchange] [uchange]))))))
;; --- Shape / Selection Alignment and Distribution
@ -1505,40 +1468,64 @@
;; --- Temportal displacement for Shape / Selection
(defn- rehash-shape-frame-relationship
[ids]
(letfn [(impl-diff [state]
(defn- retrieve-toplevel-shapes
[objects]
(let [lookup #(get objects %)
root (lookup uuid/zero)
childs (:shapes root)]
(loop [id (first childs)
ids (rest childs)
res []]
(if (nil? id)
res
(let [obj (lookup id)
typ (:type obj)]
(recur (first ids)
(rest ids)
(if (= :frame typ)
(into res (:shapes obj))
(conj res id))))))))
(defn- calculate-shape-to-frame-relationship-changes
[objects ids]
(loop [id (first ids)
ids (rest ids)
rch []
uch []]
(if (nil? id)
[rch uch]
(let [pid (::page-id state)
objects (get-in state [:workspace-data pid :objects])
obj (get objects id)
(let [obj (get objects id)
fid (calculate-frame-overlap objects obj)]
(if (not= fid (:frame-id obj))
(recur (first ids)
(rest ids)
(conj rch {:type :mov-obj
:id id
:frame-id fid})
(conj uch {:type :mov-obj
:id id
:frame-id (:frame-id obj)}))
(conj rch {:type :mov-objects
:parent-id fid
:shapes [id]})
(conj uch {:type :mov-objects
:parent-id (:frame-id obj)
:shapes [id]}))
(recur (first ids)
(rest ids)
rch
uch))))))]
uch))))))
(defn- rehash-shape-frame-relationship
[ids]
(ptk/reify ::rehash-shape-frame-relationship
ptk/WatchEvent
(watch [_ state stream]
(let [[rch uch] (impl-diff state)]
(when-not (empty? rch)
(rx/of (commit-changes rch uch {:commit-local? true}))))))))
(let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
ids (retrieve-toplevel-shapes objects)
[rch uch] (calculate-shape-to-frame-relationship-changes objects ids)
]
(defn- adjust-group-shapes [state ids]
(when-not (empty? rch)
(rx/of (commit-changes rch uch {:commit-local? true})))))))
(defn- adjust-group-shapes
[state ids]
(let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
groups-to-adjust (->> ids
@ -1944,25 +1931,6 @@
(let [page-id (::page-id state)]
(update-in state [:workspace-data page-id :objects id :segments index] gpt/add delta)))))
;; --- Initial Path Point Alignment
;; ;; TODO: revisit on alignemt refactor
;; (deftype InitialPathPointAlign [id index]
;; ptk/WatchEvent
;; (watch [_ state s]
;; (let [shape (get-in state [:workspace-data :objects id])
;; point (get-in shape [:segments index])]
;; (->> (uwrk/align-point point)
;; (rx/map #(update-path id index %))))))
;; (defn initial-path-point-align
;; "Event responsible of align a specified point of the
;; shape by `index` with the grid."
;; [id index]
;; {:pre [(uuid? id)
;; (number? index)
;; (not (neg? index))]}
;; (InitialPathPointAlign. id index))
;; --- Shape Visibility
@ -2137,18 +2105,17 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def copy-selected
(letfn [(prepare-selected [state selected]
(let [data (reduce #(prepare %1 state %2) {} selected)]
(letfn [(prepare-selected [objects selected]
(let [data (reduce #(prepare %1 objects %2) {} selected)]
{:type :copied-shapes
:data (assoc data :selected selected)}))
:selected selected
:objects data}))
(prepare [result state id]
(let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
object (get objects id)]
(cond-> (assoc-in result [:objects id] object)
(= :frame (:type object))
(as-> $ (reduce #(prepare %1 state %2) $ (:shapes object))))))
(prepare [result objects id]
(let [obj (get objects id)]
(as-> result $$
(assoc $$ id obj)
(reduce #(prepare %1 objects %2) $$ (:shapes obj)))))
(on-copy-error [error]
(js/console.error "Clipboard blocked:" error)
@ -2157,98 +2124,18 @@
(ptk/reify ::copy-selected
ptk/WatchEvent
(watch [_ state stream]
(let [selected (get-in state [:workspace-local :selected])
cdata (prepare-selected state selected)]
(let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
selected (get-in state [:workspace-local :selected])
cdata (prepare-selected objects selected)]
(->> (t/encode cdata)
(wapi/write-to-clipboard)
(rx/from)
(rx/catch on-copy-error)
(rx/ignore)))))))
(declare select-pasted-objs)
(defn- paste-impl
[{:keys [selected objects] :as data}]
(letfn [(prepare-changes [state delta]
"Prepare objects to paste: generate new id, give them unique names, move
to the position of mouse pointer, and find in what frame they fit."
(let [page-id (::page-id state)]
(loop [existing-objs (get-in state [:workspace-data page-id :objects])
chgs []
id (first selected)
ids (rest selected)]
(if (nil? id)
chgs
(let [result (prepare-change id existing-objs delta)
result (if (vector? result) result [result])]
(recur
(reduce #(assoc %1 (:id %2) (:obj %2)) existing-objs result)
(into chgs result)
(first ids)
(rest ids)))))))
(prepare-change [id existing-objs delta]
(let [obj (get objects id)]
(if (= :frame (:type obj))
(prepare-frame-change existing-objs obj delta)
(prepare-shape-change existing-objs obj delta nil))))
(prepare-shape-change [objects obj delta frame-id]
(let [id (uuid/next)
name (impl-generate-unique-name objects (:name obj))
renamed-obj (assoc obj :id id :name name)
moved-obj (geom/move renamed-obj delta)
frame-id (if frame-id
frame-id
(calculate-frame-overlap objects moved-obj))
prepare-child
(fn [child-id]
(prepare-shape-change objects (get objects child-id) delta frame-id))
children-changes (mapcat prepare-child (:shapes obj))
is-child? (set (:shapes obj))
children-uuids (->> children-changes
(filter #(and (= :add-obj (:type %)) (is-child? (:old-id %))))
(map #(:id %)))
move-change (when (not (empty? (:shapes obj)))
[{:type :mov-objects
:parent-id id
:shapes (vec children-uuids)}])
reframed-obj (-> moved-obj
(assoc :frame-id frame-id)
(dissoc :shapes))]
(into [{:type :add-obj
:id id
:old-id (:id obj)
:frame-id frame-id
:obj (dissoc reframed-obj :shapes)}]
(concat children-changes move-change))))
(prepare-frame-change [objects obj delta]
(let [frame-id (uuid/next)
frame-name (impl-generate-unique-name objects (:name obj))
sch (->> (map #(get objects %) (:shapes obj))
(mapcat #(prepare-shape-change objects % delta frame-id)))
is-child? (set (:shapes obj))
children-uuids (->> sch
(filter #(and (= :add-obj (:type %)) (is-child? (:old-id %))))
(map #(:id %)))
renamed-frame (-> obj
(assoc :id frame-id)
(assoc :name frame-name)
(assoc :frame-id uuid/zero)
(assoc :shapes (vec children-uuids)))
moved-frame (geom/move renamed-frame delta)
fch {:type :add-obj
:id frame-id
:frame-id uuid/zero
:obj moved-frame}]
(into [fch] sch)))]
(ptk/reify ::paste-impl
ptk/WatchEvent
(watch [_ state stream]
@ -2258,15 +2145,20 @@
mouse-pos @ms/mouse-position
delta (gpt/subtract mouse-pos orig-pos)
rchanges (prepare-changes state delta)
uchanges (map (fn [ch]
{:type :del-obj
:id (:id ch)})
(filter #(= :add-obj (:type %)) rchanges))]
(rx/of (commit-changes (vec rchanges)
(vec (reverse uchanges))
{:commit-local? true})
(select-pasted-objs selected rchanges)))))))
page-id (::page-id state)
unames (-> (get-in state [:workspace-data page-id :objects])
(retrieve-used-names))
rchanges (prepare-duplicate-changes objects unames selected delta)
uchanges (mapv #(array-map :type :del-obj :id (:id %))
(reverse rchanges))
selected (->> rchanges
(filter #(selected (:old-id %)))
(map #(get-in % [:obj :id]))
(into #{}))]
(rx/of (commit-changes rchanges uchanges {:commit-local? true})
(select-shapes selected))))))
(def paste
(ptk/reify ::paste
@ -2275,23 +2167,12 @@
(->> (rx/from (wapi/read-from-clipboard))
(rx/map t/decode)
(rx/filter #(= :copied-shapes (:type %)))
(rx/pr-log "pasting:")
(rx/map :data)
(rx/map #(select-keys % [:selected :objects]))
(rx/map paste-impl)
(rx/catch (fn [err]
(js/console.error "Clipboard error:" err)
(rx/empty)))))))
(defn select-pasted-objs
[selected rchanges]
(ptk/reify ::select-pasted-objs
ptk/UpdateEvent
(update [_ state]
(let [new-selected (->> rchanges
(filter #(selected (:old-id %)))
(map #(get-in % [:obj :id]))
(into #{}))]
(assoc-in state [:workspace-local :selected] new-selected)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Page Changes Reactions
@ -2317,14 +2198,8 @@
;; GROUPS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn get-parent [object-id objects]
(let [include-object
(fn [object]
(and (:shapes object)
(some #(= object-id %) (:shapes object))))]
(first (filter include-object objects))))
(defn group-shape [id frame-id selected selection-rect]
(defn group-shape
[id frame-id selected selection-rect]
{:id id
:type :group
:name (name (gensym "Group-"))
@ -2335,13 +2210,13 @@
:width (:width selection-rect)
:height (:height selection-rect)})
(defn create-group []
(let [id (uuid/next)]
(def create-group
(ptk/reify ::create-group
ptk/WatchEvent
(watch [_ state stream]
(let [selected (get-in state [:workspace-local :selected])]
(if (not-empty selected)
(let [id (uuid/next)
selected (get-in state [:workspace-local :selected])]
(when (not-empty selected)
(let [page-id (get-in state [:workspace-page :id])
objects (get-in state [:workspace-data page-id :objects])
selected-objects (map (partial get objects) selected)
@ -2349,43 +2224,46 @@
frame-id (-> selected-objects first :frame-id)
group-shape (group-shape id frame-id selected selection-rect)
frame-children (get-in objects [frame-id :shapes])
index-frame (->> frame-children (map-indexed vector) (filter #(selected (second %))) first first)]
index-frame (->> frame-children
(map-indexed vector)
(filter #(selected (second %)))
(ffirst))
(let [rchanges [{:type :add-obj
rchanges [{:type :add-obj
:id id
:frame-id frame-id
:obj group-shape
:index index-frame}
{:type :mov-objects
:parent-id id
:shapes (into [] selected)}]
:shapes (vec selected)}]
uchanges [{:type :mov-objects
:parent-id frame-id
:shapes (into [] selected)}
:shapes (vec selected)}
{:type :del-obj
:id id}]]
(rx/of (commit-changes rchanges uchanges {:commit-local? true})
(fn [state] (assoc-in state [:workspace-local :selected] #{id})))))
rx/empty))))))
(select-shapes #{id}))))))))
(defn remove-group
[]
(def remove-group
(ptk/reify ::remove-group
ptk/WatchEvent
(watch [_ state stream]
(let [selected (get-in state [:workspace-local :selected])
(let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
selected (get-in state [:workspace-local :selected])
group-id (first selected)
group (get-in state [:workspace-data (::page-id state) :objects group-id])]
(if (and (= (count selected) 1) (= (:type group) :group))
(let [objects (get-in state [:workspace-data (::page-id state) :objects])
shapes (get-in objects [group-id :shapes])
group (get objects group-id)]
(when (and (= 1 (count selected))
(= (:type group) :group))
(let [shapes (:shapes group)
parent-id (helpers/get-parent group-id objects)
parent (get objects parent-id)
index-in-parent (->> (:shapes parent)
(map-indexed vector)
(filter #(#{group-id} (second %)))
first first)]
(let [rchanges [{:type :mov-objects
(ffirst))
rchanges [{:type :mov-objects
:parent-id parent-id
:shapes shapes
:index index-in-parent}]
@ -2400,8 +2278,7 @@
:parent-id parent-id
:shapes [group-id]
:index index-in-parent}]]
(rx/of (commit-changes rchanges uchanges {:commit-local? true}))))
rx/empty)))))
(rx/of (commit-changes rchanges uchanges {:commit-local? true}))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts
@ -2415,8 +2292,8 @@
"ctrl+shift+l" #(st/emit! (toggle-layout-flag :layers))
"+" #(st/emit! increase-zoom)
"-" #(st/emit! decrease-zoom)
"ctrl+g" #(st/emit! (create-group))
"ctrl+shift+g" #(st/emit! (remove-group))
"ctrl+g" #(st/emit! create-group)
"ctrl+shift+g" #(st/emit! remove-group)
"shift+0" #(st/emit! zoom-to-50)
"shift+1" #(st/emit! reset-zoom)
"shift+2" #(st/emit! zoom-to-200)

View file

@ -22,7 +22,7 @@
[uxbox.main.store :as st]
[uxbox.main.ui.dashboard :refer [dashboard]]
[uxbox.main.ui.login :refer [login-page]]
[uxbox.main.ui.not-found :refer [not-found-page]]
[uxbox.main.ui.static :refer [not-found-page not-authorized-page]]
[uxbox.main.ui.profile.recovery :refer [profile-recovery-page]]
[uxbox.main.ui.profile.recovery-request :refer [profile-recovery-request-page]]
[uxbox.main.ui.profile.register :refer [profile-register-page]]
@ -47,6 +47,7 @@
["/view/:page-id" :viewer]
["/not-found" :not-found]
["/not-authorized" :not-authorized]
(when *assert*
["/debug/icons-preview" :debug-icons-preview])
@ -132,8 +133,11 @@
:page-id page-id
:key file-id}])
:not-authorized
[:& not-authorized-page]
:not-found
[:& not-found-page {}]))
[:& not-found-page]))
(mf/defc app
[]

View file

@ -7,7 +7,7 @@
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.not-found
(ns uxbox.main.ui.static
(:require
[cljs.spec.alpha :as s]
[rumext.alpha :as mf]
@ -15,7 +15,6 @@
(mf/defc not-found-page
[{:keys [error] :as props}]
(js/console.log "not-found" error)
[:section.not-found-layout
[:div.not-found-header i/logo]
[:div.not-found-content
@ -25,3 +24,14 @@
[:div.desc-message "Oops! Page not found"]
[:a.btn-primary.btn-small "Go back"]]]])
(mf/defc not-authorized-page
[{:keys [error] :as props}]
[:section.not-found-layout
[:div.not-found-header i/logo]
[:div.not-found-content
[:div.message-container
[:div.error-img i/icon-lock]
[:div.main-message "403"]
[:div.desc-message "Sorry, you are not authorized to access this page."]
#_[:a.btn-primary.btn-small "Go back"]]]])

View file

@ -32,30 +32,14 @@
[uxbox.main.ui.workspace.left-toolbar :refer [left-toolbar]]
[uxbox.util.data :refer [classnames]]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.rdnd :as rdnd]))
[uxbox.util.geom.point :as gpt]))
;; --- Workspace
;; (defn- on-scroll
;; [event]
;; (let [target (.-target event)
;; top (.-scrollTop target)
;; left (.-scrollLeft target)]
;; (st/emit! (ms/->ScrollEvent (gpt/point left top)))))
;; (defn- on-wheel
;; [event frame]
;; (when (kbd/ctrl? event)
;; (let [dom (mf/ref-val frame)
;; scroll-position (scroll/get-current-position-absolute dom)
;; mouse-point @ms/mouse-position]
;; (scroll/scroll-to-point dom mouse-point scroll-position))))
(mf/defc workspace-content
[{:keys [page file layout] :as params}]
(let [frame (mf/use-ref nil)
left-sidebar? (not (empty? (keep layout [:layers :sitemap :document-history :libraries])))
(let [left-sidebar? (not (empty? (keep layout [:layers :sitemap
:document-history :libraries])))
right-sidebar? (not (empty? (keep layout [:icons :drawtools :element-options])))
classes (classnames
:no-tool-bar-right (not right-sidebar?)
@ -67,12 +51,7 @@
[:& messages]
[:& context-menu]
[:section.workspace-content
{:class classes
;; :on-scroll on-scroll
;; :on-wheel #(on-wheel % frame)
}
[:section.workspace-content {:class classes}
[:& history-dialog]
;; Rules
@ -81,12 +60,10 @@
[:& horizontal-rule]
[:& vertical-rule]])
[:section.workspace-viewport {:id "workspace-viewport"
:ref frame}
[:section.workspace-viewport {:id "workspace-viewport"}
[:& viewport {:page page :file file}]]]
[:& left-toolbar {:page page
:layout layout}]
[:& left-toolbar {:page page :layout layout}]
;; Aside
(when left-sidebar?
@ -94,21 +71,20 @@
(when right-sidebar?
[:& right-sidebar {:page page :layout layout}])]))
(mf/defc workspace
[{:keys [project-id file-id page-id] :as props}]
(-> (mf/deps file-id page-id)
(mf/use-effect
(mf/deps file-id page-id)
(fn []
(st/emit! (dw/initialize project-id file-id page-id))
#(st/emit! (dw/finalize project-id file-id page-id)))))
#(st/emit! (dw/finalize project-id file-id page-id))))
(-> (mf/deps file-id)
(mf/use-effect
(mf/deps file-id)
(fn []
(st/emit! (dw/initialize-ws file-id))
#(st/emit! (dw/finalize-ws file-id)))))
#(st/emit! (dw/finalize-ws file-id))))
(hooks/use-shortcuts dw/shortcuts)

View file

@ -58,8 +58,8 @@
do-hide-shape #(st/emit! (dw/hide-shape (:id shape)))
do-lock-shape #(st/emit! (dw/block-shape (:id shape)))
do-unlock-shape #(st/emit! (dw/unblock-shape (:id shape)))
do-create-group #(st/emit! (dw/create-group))
do-remove-group #(st/emit! (dw/remove-group))]
do-create-group #(st/emit! dw/create-group)
do-remove-group #(st/emit! dw/remove-group)]
[:*
[:& menu-entry {:title "Copy"
:shortcut "Ctrl + c"

View file

@ -275,10 +275,7 @@
shape)
shape (dissoc shape ::initialized? :resize-modifier)]
;; Add & select the created shape to the workspace
(rx/of dw/deselect-all
(if (= :frame (:type shape))
(dw/add-frame shape)
(dw/add-shape shape))))))))))
(rx/of dw/deselect-all (dw/add-shape shape)))))))))
(def close-drawing-path
(ptk/reify ::close-drawing-path