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/is (= [id-b id-c id-a] (get-in res [:objects uuid/zero :shapes])))))))
(t/deftest process-change-mod-obj (t/deftest process-change-mod-obj
(t/testing "simple mod-obj"
(let [data cp/default-page-data (let [data cp/default-page-data
chg {:type :mod-obj chg {:type :mod-obj
:id uuid/zero :id uuid/zero
@ -63,6 +64,16 @@
res (cp/process-changes data [chg])] res (cp/process-changes data [chg])]
(t/is (= "foobar" (get-in res [:objects uuid/zero :name]))))) (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 (t/deftest process-change-del-obj-1
(let [id (uuid/next) (let [id (uuid/next)
@ -202,15 +213,15 @@
)) ))
(t/deftest process-changes-move-objects (t/deftest process-changes-move-objects
(let [frame-a-id (uuid/next) (let [frame-a-id (uuid/custom 1)
frame-b-id (uuid/next) frame-b-id (uuid/custom 2)
group-a-id (uuid/next) group-a-id (uuid/custom 3)
group-b-id (uuid/next) group-b-id (uuid/custom 4)
rect-a-id (uuid/next) rect-a-id (uuid/custom 5)
rect-b-id (uuid/next) rect-b-id (uuid/custom 6)
rect-c-id (uuid/next) rect-c-id (uuid/custom 7)
rect-d-id (uuid/next) rect-d-id (uuid/custom 8)
rect-e-id (uuid/next) rect-e-id (uuid/custom 9)
data (-> cp/default-page-data data (-> cp/default-page-data
(assoc-in [cp/root :shapes] [frame-a-id]) (assoc-in [cp/root :shapes] [frame-a-id])
(assoc-in [:objects frame-a-id] (assoc-in [:objects frame-a-id]
@ -345,12 +356,47 @@
(t/is (= data res)))))) (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 (t/deftest process-changes-move-objects-2
(let [shape-1-id (uuid/custom 1 1) (let [shape-1-id (uuid/custom 1 1)
shape-2-id (uuid/custom 1 2) shape-2-id (uuid/custom 1 2)
shape-3-id (uuid/custom 1 3) shape-3-id (uuid/custom 1 3)
shape-4-id (uuid/custom 1 4) 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 changes [{:type :add-obj
:id shape-1-id :id shape-1-id
:frame-id cp/root :frame-id cp/root

View file

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

View file

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

View file

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

View file

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

View file

@ -9,21 +9,22 @@
(ns uxbox.main.data.workspace (ns uxbox.main.data.workspace
(:require (:require
[clojure.set :as set]
[beicon.core :as rx] [beicon.core :as rx]
[goog.object :as gobj]
[goog.events :as events]
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[clojure.set :as set]
[goog.events :as events]
[goog.object :as gobj]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.common.data :as d] [uxbox.common.data :as d]
[uxbox.common.exceptions :as ex]
[uxbox.common.pages :as cp] [uxbox.common.pages :as cp]
[uxbox.common.spec :as us] [uxbox.common.spec :as us]
[uxbox.common.exceptions :as ex] [uxbox.common.uuid :as uuid]
[uxbox.config :as cfg] [uxbox.config :as cfg]
[uxbox.main.constants :as c] [uxbox.main.constants :as c]
[uxbox.main.data.icons :as udi]
[uxbox.main.data.dashboard :as dd] [uxbox.main.data.dashboard :as dd]
[uxbox.main.data.helpers :as helpers] [uxbox.main.data.helpers :as helpers]
[uxbox.main.data.icons :as udi]
[uxbox.main.geom :as geom] [uxbox.main.geom :as geom]
[uxbox.main.refs :as refs] [uxbox.main.refs :as refs]
[uxbox.main.repo :as rp] [uxbox.main.repo :as rp]
@ -38,12 +39,7 @@
[uxbox.util.router :as rt] [uxbox.util.router :as rt]
[uxbox.util.time :as dt] [uxbox.util.time :as dt]
[uxbox.util.transit :as t] [uxbox.util.transit :as t]
[uxbox.common.uuid :as uuid] [uxbox.util.webapi :as wapi]))
[uxbox.util.webapi :as wapi]
#_[vendor.randomcolor])
(:import goog.events.EventType
goog.events.KeyCodes
goog.ui.KeyboardShortcutHandler))
;; TODO: temporal workaround ;; TODO: temporal workaround
(def clear-ruler nil) (def clear-ruler nil)
@ -52,8 +48,11 @@
;; --- Specs ;; --- Specs
(s/def ::shape-attrs ::cp/shape-attrs) (s/def ::shape-attrs ::cp/shape-attrs)
(s/def ::set-of-uuid (s/def ::set-of-uuid
(s/every uuid? :kind set?)) (s/every uuid? :kind set?))
(s/def ::set-of-string
(s/every string? :kind set?))
;; --- Expose inner functions ;; --- Expose inner functions
@ -368,7 +367,9 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [local (:workspace-local 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}) (->> (rp/query :file-with-users {:id id})
(rx/merge-map (fn [result] (rx/merge-map (fn [result]
(rx/of (file-fetched (dissoc result :users)) (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 (defn fetch-file
[id] [id]
(us/verify ::us/uuid id) (us/verify ::us/uuid id)
@ -860,24 +864,6 @@
(update [_ state] (update [_ state]
(assoc-in state [:workspace-local :zoom] 2)))) (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 ;; --- Selection Rect
(declare select-shapes-by-current-selrect) (declare select-shapes-by-current-selrect)
@ -928,44 +914,63 @@
;; Shapes events ;; 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 ;; --- Add shape to Workspace
(defn impl-retrieve-used-names (defn- retrieve-used-names
[objects] [objects]
(into #{} (map :name) (vals objects))) (into #{} (map :name) (vals objects)))
(defn extract-numeric-suffix (defn- extract-numeric-suffix
[basename] [basename]
(if-let [[match p1 p2] (re-find #"(.*)-([0-9]+)$" basename)] (if-let [[match p1 p2] (re-find #"(.*)-([0-9]+)$" basename)]
[p1 (+ 1 (d/parse-integer p2))] [p1 (+ 1 (d/parse-integer p2))]
[basename 1])) [basename 1]))
(defn impl-generate-unique-name (defn- generate-unique-name
"A unique name generator" "A unique name generator"
[objects basename] [used basename]
(let [used (impl-retrieve-used-names objects) (s/assert ::set-of-string used)
[prefix initial] (extract-numeric-suffix basename)] (s/assert ::us/string basename)
(let [[prefix initial] (extract-numeric-suffix basename)]
(loop [counter initial] (loop [counter initial]
(let [candidate (str prefix "-" counter)] (let [candidate (str prefix "-" counter)]
(if (contains? used candidate) (if (contains? used candidate)
(recur (inc counter)) (recur (inc counter))
candidate))))) 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 (defn- calculate-frame-overlap
[objects shape] [objects shape]
(let [rshp (geom/shape->rect-shape shape) (let [rshp (geom/shape->rect-shape shape)
@ -985,172 +990,161 @@
(defn add-shape (defn add-shape
[attrs] [attrs]
(us/verify ::shape-attrs attrs) (us/verify ::shape-attrs attrs)
(let [id (uuid/next)]
(ptk/reify ::add-shape (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 ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [page-id (::page-id state) (let [page-id (::page-id state)
obj (get-in state [:workspace-data page-id :objects id])] objects (get-in state [:workspace-data page-id :objects])
(rx/of (commit-changes [{:type :add-obj
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 :id id
:frame-id (:frame-id obj) :name name
:obj obj}] :frame-id frame-id))
[{: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)
rchange {:type :add-obj 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 :id frame-id
:frame-id uuid/zero :frame-id uuid/zero
:obj (assoc frame :obj moved-frame}]
:id frame-id
:shapes shapes)
:session-id (:session-id state)} (into [fch] sch)))
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}))))))
(declare select-shapes)
(def duplicate-selected (def duplicate-selected
(ptk/reify ::duplicate-selected (ptk/reify ::duplicate-selected
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [page-id (::page-id state) (let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects])
selected (get-in state [:workspace-local :selected]) selected (get-in state [:workspace-local :selected])
lookup #(get objects %) objects (get-in state [:workspace-data page-id :objects])
shapes (map lookup selected) delta (gpt/point 0 0)
shape? #(not= (:type %) :frame)] unames (retrieve-used-names objects)
(cond
(and (= (count shapes) 1)
(= (:type (first shapes)) :frame))
(rx/of (duplicate-frame (first selected)))
(and (pos? (count shapes)) rchanges (prepare-duplicate-changes objects unames selected delta)
(every? shape? shapes)) uchanges (mapv #(array-map :type :del-obj :id (:id %))
(rx/of (duplicate-shapes selected)) (reverse rchanges))
:else selected (->> rchanges
(rx/empty)))))) (filter #(selected (:old-id %)))
(map #(get-in % [:obj :id]))
(into #{}))]
(rx/of (commit-changes rchanges uchanges {:commit-local? true})
(select-shapes selected))))))
;; --- 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))))))
;; --- Select Shapes (By selrect) ;; --- Select Shapes (By selrect)
@ -1318,30 +1312,30 @@
(let [page-id (::page-id state) (let [page-id (::page-id state)
session-id (:session-id state) session-id (:session-id state)
objects (get-in state [:workspace-data page-id :objects]) objects (get-in state [:workspace-data page-id :objects])
rchanges (mapv #(array-map :type :del-obj :id %) ids) cpindex (helpers/calculate-child-parent-map objects)
uchanges (mapv (fn [id]
(let [obj (get objects id) del-change #(array-map :type :del-obj :id %)
frm (get objects (:frame-id obj))
idx (d/index-of (:shapes frm) 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 {:type :add-obj
:id id :id id
:frame-id (:id frm) :frame-id (:frame-id obj)
:index idx :parent-id (get cpindex id)
:obj obj})) :obj obj}))
(reverse ids))] (reverse (map :id rchanges)))]
(rx/of (commit-changes rchanges uchanges {:commit-local? true})))))) (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 (def delete-selected
"Deselect all and remove all selected shapes." "Deselect all and remove all selected shapes."
(ptk/reify ::delete-selected (ptk/reify ::delete-selected
@ -1353,17 +1347,8 @@
shapes (map lookup selected) shapes (map lookup selected)
shape? #(not= (:type %) :frame)] shape? #(not= (:type %) :frame)]
(cond (rx/of (delete-shapes selected))))))
(and (= (count shapes) 1)
(= (:type (first shapes)) :frame))
(rx/of (delete-frame (first selected)))
(and (pos? (count shapes))
(every? shape? shapes))
(rx/of (delete-shapes selected))
:else
(rx/empty))))))
;; --- Rename Shape ;; --- Rename Shape
@ -1417,7 +1402,7 @@
(us/verify ::us/uuid ref-id) (us/verify ::us/uuid ref-id)
(us/verify number? index) (us/verify number? index)
(ptk/reify ::reloacate-shape (ptk/reify ::relocate-shape
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [page-id (::page-id state) (let [page-id (::page-id state)
@ -1431,28 +1416,6 @@
[] []
{:commit-local? true})))))) {: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 ;; --- Shape / Selection Alignment and Distribution
@ -1505,40 +1468,64 @@
;; --- Temportal displacement for Shape / Selection ;; --- Temportal displacement for Shape / Selection
(defn- rehash-shape-frame-relationship (defn- retrieve-toplevel-shapes
[ids] [objects]
(letfn [(impl-diff [state] (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) (loop [id (first ids)
ids (rest ids) ids (rest ids)
rch [] rch []
uch []] uch []]
(if (nil? id) (if (nil? id)
[rch uch] [rch uch]
(let [pid (::page-id state) (let [obj (get objects id)
objects (get-in state [:workspace-data pid :objects])
obj (get objects id)
fid (calculate-frame-overlap objects obj)] fid (calculate-frame-overlap objects obj)]
(if (not= fid (:frame-id obj)) (if (not= fid (:frame-id obj))
(recur (first ids) (recur (first ids)
(rest ids) (rest ids)
(conj rch {:type :mov-obj (conj rch {:type :mov-objects
:id id :parent-id fid
:frame-id fid}) :shapes [id]})
(conj uch {:type :mov-obj (conj uch {:type :mov-objects
:id id :parent-id (:frame-id obj)
:frame-id (:frame-id obj)})) :shapes [id]}))
(recur (first ids) (recur (first ids)
(rest ids) (rest ids)
rch rch
uch))))))] uch))))))
(defn- rehash-shape-frame-relationship
[ids]
(ptk/reify ::rehash-shape-frame-relationship (ptk/reify ::rehash-shape-frame-relationship
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [[rch uch] (impl-diff state)] (let [page-id (::page-id state)
(when-not (empty? rch) objects (get-in state [:workspace-data page-id :objects])
(rx/of (commit-changes rch uch {:commit-local? true})))))))) 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) (let [page-id (::page-id state)
objects (get-in state [:workspace-data page-id :objects]) objects (get-in state [:workspace-data page-id :objects])
groups-to-adjust (->> ids groups-to-adjust (->> ids
@ -1944,25 +1931,6 @@
(let [page-id (::page-id state)] (let [page-id (::page-id state)]
(update-in state [:workspace-data page-id :objects id :segments index] gpt/add delta))))) (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 ;; --- Shape Visibility
@ -2137,18 +2105,17 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def copy-selected (def copy-selected
(letfn [(prepare-selected [state selected] (letfn [(prepare-selected [objects selected]
(let [data (reduce #(prepare %1 state %2) {} selected)] (let [data (reduce #(prepare %1 objects %2) {} selected)]
{:type :copied-shapes {:type :copied-shapes
:data (assoc data :selected selected)})) :selected selected
:objects data}))
(prepare [result state id] (prepare [result objects id]
(let [page-id (::page-id state) (let [obj (get objects id)]
objects (get-in state [:workspace-data page-id :objects]) (as-> result $$
object (get objects id)] (assoc $$ id obj)
(cond-> (assoc-in result [:objects id] object) (reduce #(prepare %1 objects %2) $$ (:shapes obj)))))
(= :frame (:type object))
(as-> $ (reduce #(prepare %1 state %2) $ (:shapes object))))))
(on-copy-error [error] (on-copy-error [error]
(js/console.error "Clipboard blocked:" error) (js/console.error "Clipboard blocked:" error)
@ -2157,98 +2124,18 @@
(ptk/reify ::copy-selected (ptk/reify ::copy-selected
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [selected (get-in state [:workspace-local :selected]) (let [page-id (::page-id state)
cdata (prepare-selected state selected)] objects (get-in state [:workspace-data page-id :objects])
selected (get-in state [:workspace-local :selected])
cdata (prepare-selected objects selected)]
(->> (t/encode cdata) (->> (t/encode cdata)
(wapi/write-to-clipboard) (wapi/write-to-clipboard)
(rx/from) (rx/from)
(rx/catch on-copy-error) (rx/catch on-copy-error)
(rx/ignore))))))) (rx/ignore)))))))
(declare select-pasted-objs)
(defn- paste-impl (defn- paste-impl
[{:keys [selected objects] :as data}] [{: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/reify ::paste-impl
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
@ -2258,15 +2145,20 @@
mouse-pos @ms/mouse-position mouse-pos @ms/mouse-position
delta (gpt/subtract mouse-pos orig-pos) delta (gpt/subtract mouse-pos orig-pos)
rchanges (prepare-changes state delta) page-id (::page-id state)
uchanges (map (fn [ch] unames (-> (get-in state [:workspace-data page-id :objects])
{:type :del-obj (retrieve-used-names))
:id (:id ch)})
(filter #(= :add-obj (:type %)) rchanges))] rchanges (prepare-duplicate-changes objects unames selected delta)
(rx/of (commit-changes (vec rchanges) uchanges (mapv #(array-map :type :del-obj :id (:id %))
(vec (reverse uchanges)) (reverse rchanges))
{:commit-local? true})
(select-pasted-objs selected 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 (def paste
(ptk/reify ::paste (ptk/reify ::paste
@ -2275,23 +2167,12 @@
(->> (rx/from (wapi/read-from-clipboard)) (->> (rx/from (wapi/read-from-clipboard))
(rx/map t/decode) (rx/map t/decode)
(rx/filter #(= :copied-shapes (:type %))) (rx/filter #(= :copied-shapes (:type %)))
(rx/pr-log "pasting:") (rx/map #(select-keys % [:selected :objects]))
(rx/map :data)
(rx/map paste-impl) (rx/map paste-impl)
(rx/catch (fn [err] (rx/catch (fn [err]
(js/console.error "Clipboard error:" err) (js/console.error "Clipboard error:" err)
(rx/empty))))))) (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 ;; Page Changes Reactions
@ -2317,14 +2198,8 @@
;; GROUPS ;; GROUPS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn get-parent [object-id objects] (defn group-shape
(let [include-object [id frame-id selected selection-rect]
(fn [object]
(and (:shapes object)
(some #(= object-id %) (:shapes object))))]
(first (filter include-object objects))))
(defn group-shape [id frame-id selected selection-rect]
{:id id {:id id
:type :group :type :group
:name (name (gensym "Group-")) :name (name (gensym "Group-"))
@ -2335,13 +2210,13 @@
:width (:width selection-rect) :width (:width selection-rect)
:height (:height selection-rect)}) :height (:height selection-rect)})
(defn create-group [] (def create-group
(let [id (uuid/next)]
(ptk/reify ::create-group (ptk/reify ::create-group
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [selected (get-in state [:workspace-local :selected])] (let [id (uuid/next)
(if (not-empty selected) selected (get-in state [:workspace-local :selected])]
(when (not-empty selected)
(let [page-id (get-in state [:workspace-page :id]) (let [page-id (get-in state [:workspace-page :id])
objects (get-in state [:workspace-data page-id :objects]) objects (get-in state [:workspace-data page-id :objects])
selected-objects (map (partial get objects) selected) selected-objects (map (partial get objects) selected)
@ -2349,43 +2224,46 @@
frame-id (-> selected-objects first :frame-id) frame-id (-> selected-objects first :frame-id)
group-shape (group-shape id frame-id selected selection-rect) group-shape (group-shape id frame-id selected selection-rect)
frame-children (get-in objects [frame-id :shapes]) 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 :id id
:frame-id frame-id :frame-id frame-id
:obj group-shape :obj group-shape
:index index-frame} :index index-frame}
{:type :mov-objects {:type :mov-objects
:parent-id id :parent-id id
:shapes (into [] selected)}] :shapes (vec selected)}]
uchanges [{:type :mov-objects uchanges [{:type :mov-objects
:parent-id frame-id :parent-id frame-id
:shapes (into [] selected)} :shapes (vec selected)}
{:type :del-obj {:type :del-obj
:id id}]] :id id}]]
(rx/of (commit-changes rchanges uchanges {:commit-local? true}) (rx/of (commit-changes rchanges uchanges {:commit-local? true})
(fn [state] (assoc-in state [:workspace-local :selected] #{id}))))) (select-shapes #{id}))))))))
rx/empty))))))
(defn remove-group (def remove-group
[]
(ptk/reify ::remove-group (ptk/reify ::remove-group
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (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-id (first selected)
group (get-in state [:workspace-data (::page-id state) :objects group-id])] group (get objects group-id)]
(if (and (= (count selected) 1) (= (:type group) :group)) (when (and (= 1 (count selected))
(let [objects (get-in state [:workspace-data (::page-id state) :objects]) (= (:type group) :group))
shapes (get-in objects [group-id :shapes]) (let [shapes (:shapes group)
parent-id (helpers/get-parent group-id objects) parent-id (helpers/get-parent group-id objects)
parent (get objects parent-id) parent (get objects parent-id)
index-in-parent (->> (:shapes parent) index-in-parent (->> (:shapes parent)
(map-indexed vector) (map-indexed vector)
(filter #(#{group-id} (second %))) (filter #(#{group-id} (second %)))
first first)] (ffirst))
(let [rchanges [{:type :mov-objects rchanges [{:type :mov-objects
:parent-id parent-id :parent-id parent-id
:shapes shapes :shapes shapes
:index index-in-parent}] :index index-in-parent}]
@ -2400,8 +2278,7 @@
:parent-id parent-id :parent-id parent-id
:shapes [group-id] :shapes [group-id]
:index index-in-parent}]] :index index-in-parent}]]
(rx/of (commit-changes rchanges uchanges {:commit-local? true})))) (rx/of (commit-changes rchanges uchanges {:commit-local? true}))))))))
rx/empty)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts ;; Shortcuts
@ -2415,8 +2292,8 @@
"ctrl+shift+l" #(st/emit! (toggle-layout-flag :layers)) "ctrl+shift+l" #(st/emit! (toggle-layout-flag :layers))
"+" #(st/emit! increase-zoom) "+" #(st/emit! increase-zoom)
"-" #(st/emit! decrease-zoom) "-" #(st/emit! decrease-zoom)
"ctrl+g" #(st/emit! (create-group)) "ctrl+g" #(st/emit! create-group)
"ctrl+shift+g" #(st/emit! (remove-group)) "ctrl+shift+g" #(st/emit! remove-group)
"shift+0" #(st/emit! zoom-to-50) "shift+0" #(st/emit! zoom-to-50)
"shift+1" #(st/emit! reset-zoom) "shift+1" #(st/emit! reset-zoom)
"shift+2" #(st/emit! zoom-to-200) "shift+2" #(st/emit! zoom-to-200)

View file

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

View file

@ -7,7 +7,7 @@
;; ;;
;; Copyright (c) 2020 UXBOX Labs SL ;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.not-found (ns uxbox.main.ui.static
(:require (:require
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[rumext.alpha :as mf] [rumext.alpha :as mf]
@ -15,7 +15,6 @@
(mf/defc not-found-page (mf/defc not-found-page
[{:keys [error] :as props}] [{:keys [error] :as props}]
(js/console.log "not-found" error)
[:section.not-found-layout [:section.not-found-layout
[:div.not-found-header i/logo] [:div.not-found-header i/logo]
[:div.not-found-content [:div.not-found-content
@ -25,3 +24,14 @@
[:div.desc-message "Oops! Page not found"] [:div.desc-message "Oops! Page not found"]
[:a.btn-primary.btn-small "Go back"]]]]) [: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.main.ui.workspace.left-toolbar :refer [left-toolbar]]
[uxbox.util.data :refer [classnames]] [uxbox.util.data :refer [classnames]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt] [uxbox.util.geom.point :as gpt]))
[uxbox.util.rdnd :as rdnd]))
;; --- Workspace ;; --- 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 (mf/defc workspace-content
[{:keys [page file layout] :as params}] [{:keys [page file layout] :as params}]
(let [frame (mf/use-ref nil) (let [left-sidebar? (not (empty? (keep layout [:layers :sitemap
left-sidebar? (not (empty? (keep layout [:layers :sitemap :document-history :libraries]))) :document-history :libraries])))
right-sidebar? (not (empty? (keep layout [:icons :drawtools :element-options]))) right-sidebar? (not (empty? (keep layout [:icons :drawtools :element-options])))
classes (classnames classes (classnames
:no-tool-bar-right (not right-sidebar?) :no-tool-bar-right (not right-sidebar?)
@ -67,12 +51,7 @@
[:& messages] [:& messages]
[:& context-menu] [:& context-menu]
[:section.workspace-content [:section.workspace-content {:class classes}
{:class classes
;; :on-scroll on-scroll
;; :on-wheel #(on-wheel % frame)
}
[:& history-dialog] [:& history-dialog]
;; Rules ;; Rules
@ -81,12 +60,10 @@
[:& horizontal-rule] [:& horizontal-rule]
[:& vertical-rule]]) [:& vertical-rule]])
[:section.workspace-viewport {:id "workspace-viewport" [:section.workspace-viewport {:id "workspace-viewport"}
:ref frame}
[:& viewport {:page page :file file}]]] [:& viewport {:page page :file file}]]]
[:& left-toolbar {:page page [:& left-toolbar {:page page :layout layout}]
:layout layout}]
;; Aside ;; Aside
(when left-sidebar? (when left-sidebar?
@ -94,21 +71,20 @@
(when right-sidebar? (when right-sidebar?
[:& right-sidebar {:page page :layout layout}])])) [:& right-sidebar {:page page :layout layout}])]))
(mf/defc workspace (mf/defc workspace
[{:keys [project-id file-id page-id] :as props}] [{:keys [project-id file-id page-id] :as props}]
(-> (mf/deps file-id page-id)
(mf/use-effect (mf/use-effect
(mf/deps file-id page-id)
(fn [] (fn []
(st/emit! (dw/initialize project-id file-id page-id)) (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/use-effect
(mf/deps file-id)
(fn [] (fn []
(st/emit! (dw/initialize-ws file-id)) (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) (hooks/use-shortcuts dw/shortcuts)

View file

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

View file

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