mirror of
https://github.com/penpot/penpot.git
synced 2025-04-06 12:01:19 -05:00
Merge branch 'develop' into refactor-ui-integration
This commit is contained in:
commit
9c1c613c90
32 changed files with 551 additions and 402 deletions
|
@ -8,7 +8,6 @@ if [ ! -e ~/local/.fixtures-loaded ]; then
|
|||
touch ~/local/.fixtures-loaded
|
||||
fi
|
||||
|
||||
clojure -J-XX:-OmitStackTraceInFastThrow -m uxbox.main
|
||||
|
||||
clojure ${CLOJURE_OPTS} -m uxbox.main
|
||||
|
||||
|
5
backend/bin/start-prod
Executable file
5
backend/bin/start-prod
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
clojure ${CLOJURE_OPTIONS} -m uxbox.main
|
|
@ -61,15 +61,17 @@
|
|||
:test
|
||||
{:extra-deps {lambdaisland/kaocha {:mvn/version "0.0-554"}}}
|
||||
|
||||
:nrepl
|
||||
{:extra-deps {nrepl/nrepl {:mvn/version "0.6.0"}}
|
||||
:main-opts ["-m" "nrepl.cmdline"]}
|
||||
|
||||
:ancient
|
||||
{:main-opts ["-m" "deps-ancient.deps-ancient"]
|
||||
:extra-deps {deps-ancient {:mvn/version "RELEASE"}}}
|
||||
|
||||
:jmx-remote
|
||||
{:jvm-opts ["-Dcom.sun.management.jmxremote"
|
||||
"-Dcom.sun.management.jmxremote.port=9090"
|
||||
"-Dcom.sun.management.jmxremote.rmi.port=9090"
|
||||
"-Dcom.sun.management.jmxremote.local.only=false"
|
||||
"-Dcom.sun.management.jmxremote.authenticate=false"
|
||||
"-Dcom.sun.management.jmxremote.ssl=false"
|
||||
"-Djava.rmi.server.hostname=localhost"]}
|
||||
|
||||
}}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
[uxbox.http.session :as session]
|
||||
[uxbox.http.handlers :as handlers]
|
||||
[uxbox.http.debug :as debug]
|
||||
[uxbox.http.ws :as ws]
|
||||
[vertx.core :as vc]
|
||||
[vertx.http :as vh]
|
||||
[vertx.web :as vw]
|
||||
|
@ -43,7 +44,12 @@
|
|||
interceptors/format-response-body
|
||||
(vxi/errors errors/handle)]
|
||||
|
||||
routes [["/api" {:interceptors interceptors}
|
||||
routes [["/sub/:page-id" {:interceptors [(vxi/cookies)
|
||||
(vxi/cors cors-opts)
|
||||
(session/auth)]
|
||||
:get ws/handler}]
|
||||
|
||||
["/api" {:interceptors interceptors}
|
||||
["/echo" {:all handlers/echo-handler}]
|
||||
["/login" {:post handlers/login-handler}]
|
||||
["/logout" {:post handlers/logout-handler}]
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
[uxbox.services.init]
|
||||
[uxbox.services.mutations :as sm]
|
||||
[uxbox.services.queries :as sq]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
[uxbox.util.uuid :as uuid]
|
||||
[vertx.web :as vw]
|
||||
[vertx.eventbus :as ve]))
|
||||
|
||||
(defn query-handler
|
||||
[req]
|
||||
|
@ -45,7 +47,7 @@
|
|||
(p/then #(session/create (:id %) user-agent))
|
||||
(p/then' (fn [token]
|
||||
{:status 204
|
||||
:cookies {"auth-token" {:value token}}
|
||||
:cookies {"auth-token" {:value token :path "/"}}
|
||||
:body ""})))))
|
||||
|
||||
(defn logout-handler
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
(spx/terminate (assoc data ::unauthorized true)))))
|
||||
(vc/handle-on-context))))
|
||||
:leave (fn [data]
|
||||
(if (::unauthorized data)
|
||||
(if (and (::unauthorized data) (:response data))
|
||||
(update data :response
|
||||
assoc :status 403 :body {:type :authentication
|
||||
:code :unauthorized})
|
||||
|
|
105
backend/src/uxbox/http/ws.clj
Normal file
105
backend/src/uxbox/http/ws.clj
Normal file
|
@ -0,0 +1,105 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.http.ws
|
||||
"Web Socket handlers"
|
||||
(:require
|
||||
[promesa.core :as p]
|
||||
[uxbox.emails :as emails]
|
||||
[uxbox.http.session :as session]
|
||||
[uxbox.services.init]
|
||||
[uxbox.services.mutations :as sm]
|
||||
[uxbox.services.queries :as sq]
|
||||
[uxbox.util.uuid :as uuid]
|
||||
[uxbox.util.blob :as blob]
|
||||
[vertx.http :as vh]
|
||||
[vertx.web :as vw]
|
||||
[vertx.util :as vu]
|
||||
[vertx.eventbus :as ve])
|
||||
(:import
|
||||
io.vertx.core.Future
|
||||
io.vertx.core.Promise
|
||||
io.vertx.core.Handler
|
||||
io.vertx.core.Vertx
|
||||
io.vertx.core.buffer.Buffer
|
||||
io.vertx.core.http.HttpServerRequest
|
||||
io.vertx.core.http.HttpServerResponse
|
||||
io.vertx.core.http.ServerWebSocket))
|
||||
|
||||
(declare ws-websocket)
|
||||
(declare ws-send!)
|
||||
(declare ws-on-message!)
|
||||
(declare ws-on-close!)
|
||||
|
||||
;; --- Public API
|
||||
|
||||
(declare on-message)
|
||||
(declare on-close)
|
||||
(declare on-eventbus-message)
|
||||
|
||||
(def state (atom {}))
|
||||
|
||||
(defn handler
|
||||
[{:keys [user] :as req}]
|
||||
(letfn [(on-init [ws]
|
||||
(let [vsm (::vw/execution-context req)
|
||||
tpc "test.foobar"
|
||||
pid (get-in req [:path-params :page-id])
|
||||
sem (ve/consumer vsm tpc #(on-eventbus-message ws %2))]
|
||||
(swap! state update pid (fnil conj #{}) user)
|
||||
(assoc ws ::sem sem)))
|
||||
|
||||
(on-message [ws message]
|
||||
(let [pid (get-in req [:path-params :page-id])]
|
||||
(ws-send! ws (str (::counter ws 0)))
|
||||
(update ws ::counter (fnil inc 0))))
|
||||
|
||||
(on-close [ws]
|
||||
(let [pid (get-in req [:path-params :page-id])]
|
||||
(swap! state update pid disj user)
|
||||
(.unregister (::sem ws))))]
|
||||
|
||||
;; (ws-websocket :on-init on-init
|
||||
;; :on-message on-message
|
||||
;; :on-close on-close)))
|
||||
|
||||
(-> (ws-websocket)
|
||||
(assoc :on-init on-init
|
||||
:on-message on-message
|
||||
:on-close on-close))))
|
||||
|
||||
(defn- on-eventbus-message
|
||||
[ws {:keys [body] :as message}]
|
||||
(ws-send! ws body))
|
||||
|
||||
;; --- Internal (vertx api) (experimental)
|
||||
|
||||
(defrecord WebSocket [on-init on-message on-close]
|
||||
vh/IAsyncResponse
|
||||
(-handle-response [this ctx]
|
||||
(let [^HttpServerRequest req (::vh/request ctx)
|
||||
^ServerWebSocket ws (.upgrade req)
|
||||
local (volatile! (assoc this :ws ws))]
|
||||
(-> (p/do! (on-init @local))
|
||||
(p/then (fn [data]
|
||||
(vreset! local data)
|
||||
(.textMessageHandler ws (vu/fn->handler
|
||||
(fn [msg]
|
||||
(-> (p/do! (on-message @local msg))
|
||||
(p/then (fn [data]
|
||||
(when (instance? WebSocket data)
|
||||
(vreset! local data))
|
||||
(.fetch ws 1)))))))
|
||||
(.closeHandler ws (vu/fn->handler (fn [& args] (on-close @local))))))))))
|
||||
|
||||
(defn ws-websocket
|
||||
[]
|
||||
(->WebSocket nil nil nil))
|
||||
|
||||
(defn ws-send!
|
||||
[ws msg]
|
||||
(.writeTextMessage ^ServerWebSocket (:ws ws)
|
||||
^String msg))
|
|
@ -14,21 +14,21 @@
|
|||
[uxbox.services.queries.project-pages :refer [decode-row]]
|
||||
[uxbox.services.util :as su]
|
||||
[uxbox.common.pages :as cp]
|
||||
[uxbox.common.spec :as cs]
|
||||
[uxbox.util.exceptions :as ex]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.sql :as sql]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
(s/def ::id ::cs/uuid)
|
||||
(s/def ::name ::cs/string)
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::data ::cp/data)
|
||||
(s/def ::user ::cs/uuid)
|
||||
(s/def ::project-id ::cs/uuid)
|
||||
(s/def ::user ::us/uuid)
|
||||
(s/def ::project-id ::us/uuid)
|
||||
(s/def ::metadata ::cp/metadata)
|
||||
(s/def ::ordering ::cs/number)
|
||||
(s/def ::ordering ::us/number)
|
||||
|
||||
;; --- Mutation: Create Page
|
||||
|
||||
|
@ -157,21 +157,22 @@
|
|||
:stored-version (:version page)}))
|
||||
(let [ops (:operations params)
|
||||
data (-> (:data page)
|
||||
(blob/decode)
|
||||
(cp/process-ops ops)
|
||||
(blob/encode))
|
||||
(blob/decode)
|
||||
(cp/process-ops ops)
|
||||
(blob/encode))
|
||||
|
||||
page (assoc page
|
||||
:user-id (:user params)
|
||||
:data data
|
||||
:version (inc (:version page))
|
||||
:operations (blob/encode ops))]
|
||||
|
||||
(-> (update-page-data conn page)
|
||||
(p/then (fn [_] (insert-page-snapshot conn page)))
|
||||
(p/then (fn [s] (retrieve-lagged-operations conn s params))))))
|
||||
|
||||
(su/defstr sql:lagged-snapshots
|
||||
"select s.id, s.page_id, s.version, s.operations,
|
||||
s.created_at, s.modified_at, s.user_id
|
||||
"select s.id, s.operations
|
||||
from project_page_snapshots as s
|
||||
where s.page_id = $1
|
||||
and s.version > $2")
|
||||
|
@ -179,8 +180,14 @@
|
|||
(defn- retrieve-lagged-operations
|
||||
[conn snapshot params]
|
||||
(let [sql sql:lagged-snapshots]
|
||||
(-> (db/query conn [sql (:id params) (:version params)])
|
||||
(p/then (partial mapv decode-row)))))
|
||||
(-> (db/query conn [sql (:id params) (:version params) #_(:id snapshot)])
|
||||
(p/then (fn [rows]
|
||||
{:id (:id params)
|
||||
:version (:version snapshot)
|
||||
:operations (into [] (comp (map decode-row)
|
||||
(map :operations)
|
||||
(mapcat identity))
|
||||
rows)})))))
|
||||
|
||||
;; --- Mutation: Delete Page
|
||||
|
||||
|
@ -219,7 +226,7 @@
|
|||
;; (some-> (db/fetch-one conn sqlv)
|
||||
;; (decode-row))))
|
||||
|
||||
;; (s/def ::label ::cs/string)
|
||||
;; (s/def ::label ::us/string)
|
||||
;; (s/def ::update-page-history
|
||||
;; (s/keys :req-un [::user ::id ::pinned ::label]))
|
||||
|
||||
|
|
|
@ -111,13 +111,13 @@
|
|||
(s/def ::pinned ::us/boolean)
|
||||
(s/def ::since ::us/integer)
|
||||
|
||||
(s/def ::page-history
|
||||
(s/def ::project-page-snapshots
|
||||
(s/keys :req-un [::page-id ::user]
|
||||
:opt-un [::max ::pinned ::since]))
|
||||
|
||||
(defn retrieve-page-history
|
||||
[{:keys [page-id user since max pinned] :or {since Long/MAX_VALUE max 10}}]
|
||||
(let [sql (-> (sql/from ["pages_history" "ph"])
|
||||
(defn retrieve-page-snapshots
|
||||
[conn {:keys [page-id user since max pinned] :or {since Long/MAX_VALUE max 10}}]
|
||||
(let [sql (-> (sql/from ["project_page_snapshots" "ph"])
|
||||
(sql/select "ph.*")
|
||||
(sql/where ["ph.user_id = ?" user]
|
||||
["ph.page_id = ?" page-id]
|
||||
|
@ -126,15 +126,14 @@
|
|||
["ph.pinned = ?" true]))
|
||||
(sql/order "ph.version desc")
|
||||
(sql/limit max))]
|
||||
(-> (db/query db/pool (sql/fmt sql))
|
||||
(-> (db/query conn (sql/fmt sql))
|
||||
(p/then (partial mapv decode-row)))))
|
||||
|
||||
(sq/defquery ::page-history
|
||||
(sq/defquery ::project-page-snapshots
|
||||
[{:keys [page-id user] :as params}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(p/do! (retrieve-page conn {:id page-id :user user})
|
||||
(retrieve-page-history conn params))))
|
||||
|
||||
(retrieve-page-snapshots conn params))))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
"A helpers for work with exceptions."
|
||||
(:require [clojure.spec.alpha :as s]))
|
||||
|
||||
;; TODO: moved to uxbox.common.exceptions
|
||||
|
||||
(s/def ::type keyword?)
|
||||
(s/def ::code keyword?)
|
||||
(s/def ::mesage string?)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[datoteka.core :as fs]
|
||||
[expound.alpha :as expound]
|
||||
[uxbox.util.exceptions :as ex])
|
||||
(:import java.time.Instant))
|
||||
|
||||
|
@ -23,23 +24,18 @@
|
|||
(def uuid-rx
|
||||
#"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
|
||||
|
||||
(def number-rx
|
||||
#"^[+-]?([0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)([eE][+-]?[0-9]+)?$")
|
||||
|
||||
;; --- Public Api
|
||||
|
||||
(defn conform
|
||||
[spec data]
|
||||
(let [result (s/conform spec data)]
|
||||
(if (= result ::s/invalid)
|
||||
[nil (s/explain-data spec data)]
|
||||
[result nil])))
|
||||
|
||||
(defn valid?
|
||||
[spec data]
|
||||
(if (s/valid? spec data)
|
||||
true
|
||||
(s/explain spec data)))
|
||||
(when (= result ::s/invalid)
|
||||
(throw (ex/error :type :validation
|
||||
:code :spec-validation
|
||||
:explain (with-out-str
|
||||
(expound/printer data))
|
||||
:data (::s/problems data))))
|
||||
result))
|
||||
|
||||
;; --- Predicates
|
||||
|
||||
|
@ -113,7 +109,7 @@
|
|||
[v]
|
||||
(cond
|
||||
(number? v) v
|
||||
(re-matches number-rx v) (Double/parseDouble v)
|
||||
(str/numeric? v) (Double/parseDouble v)
|
||||
:else ::s/invalid))
|
||||
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
io.vertx.core.http.HttpServer
|
||||
io.vertx.core.http.HttpServerRequest
|
||||
io.vertx.core.http.HttpServerResponse
|
||||
io.vertx.core.http.HttpServerOptions))
|
||||
io.vertx.core.http.HttpServerOptions
|
||||
io.vertx.core.http.ServerWebSocket))
|
||||
|
||||
(declare opts->http-server-options)
|
||||
(declare resolve-handler)
|
||||
|
@ -126,7 +127,14 @@
|
|||
(let [body (:body data)
|
||||
res (::response ctx)]
|
||||
(assign-status-and-headers! res data)
|
||||
(-handle-body body res))))
|
||||
(-handle-body body res)))
|
||||
|
||||
;; ServerWebSocket
|
||||
;; (-handle-response [sws ctx]
|
||||
;; (.accept ^ServerWebSocket sws))
|
||||
|
||||
nil
|
||||
(-handle-response [sws ctx]))
|
||||
|
||||
(extend-protocol IAsyncBody
|
||||
(Class/forName "[B")
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
io.vertx.core.http.HttpServerOptions
|
||||
io.vertx.core.http.HttpServerRequest
|
||||
io.vertx.core.http.HttpServerResponse
|
||||
io.vertx.core.http.ServerWebSocket
|
||||
io.vertx.ext.web.Route
|
||||
io.vertx.ext.web.Router
|
||||
io.vertx.ext.web.RoutingContext
|
||||
|
@ -140,7 +141,6 @@
|
|||
(.setDeleteUploadedFilesOnEnd delete-uploads?)
|
||||
(.setUploadsDirectory upload-dir)))
|
||||
|
||||
|
||||
(.handler
|
||||
(reify Handler
|
||||
(handle [_ rc]
|
||||
|
|
|
@ -117,13 +117,42 @@
|
|||
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (= 1 (count (:result out))))
|
||||
(t/is (= (:id data) (get-in out [:result 0 :page-id])))
|
||||
(t/is (= 1 (count (get-in out [:result 0 :operations]))))
|
||||
(t/is (= :add-shape (get-in out [:result 0 :operations 0 0])))
|
||||
(t/is (= sid (get-in out [:result 0 :operations 0 1])))
|
||||
(t/is (= 0 (count (:result out))))
|
||||
;; (t/is (= 1 (count (:result out))))
|
||||
;; (t/is (= (:id data) (get-in out [:result 0 :page-id])))
|
||||
;; (t/is (= 1 (count (get-in out [:result 0 :operations]))))
|
||||
;; (t/is (= :add-shape (get-in out [:result 0 :operations 0 0])))
|
||||
;; (t/is (= sid (get-in out [:result 0 :operations 0 1])))
|
||||
))
|
||||
|
||||
(t/deftest mutation-update-project-page-3
|
||||
(let [user @(th/create-user db/pool 1)
|
||||
proj @(th/create-project db/pool (:id user) 1)
|
||||
file @(th/create-project-file db/pool (:id user) (:id proj) 1)
|
||||
page @(th/create-project-page db/pool (:id user) (:id file) 1)
|
||||
|
||||
sid (uuid/next)
|
||||
data {::sm/type :update-project-page
|
||||
:id (:id page)
|
||||
:version 0
|
||||
:user (:id user)
|
||||
:operations [[:add-shape sid {:id sid :type :rect}]]}
|
||||
|
||||
out1 (th/try-on! (sm/handle data))
|
||||
out2 (th/try-on! (sm/handle data))]
|
||||
|
||||
;; (th/print-result! out1)
|
||||
;; (th/print-result! out2)
|
||||
|
||||
(t/is (nil? (:error out1)))
|
||||
(t/is (nil? (:error out2)))
|
||||
(t/is (= 0 (count (:result out1))))
|
||||
(t/is (= 1 (count (:result out2))))
|
||||
(t/is (= (:id data) (get-in out2 [:result 0 :page-id])))
|
||||
(t/is (= 1 (count (get-in out2 [:result 0 :operations]))))
|
||||
(t/is (= :add-shape (get-in out2 [:result 0 :operations 0 0])))
|
||||
(t/is (= sid (get-in out2 [:result 0 :operations 0 1])))
|
||||
))
|
||||
|
||||
(t/deftest mutation-delete-project-page
|
||||
(let [user @(th/create-user db/pool 1)
|
||||
|
|
50
common/uxbox/common/data.cljc
Normal file
50
common/uxbox/common/data.cljc
Normal file
|
@ -0,0 +1,50 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.common.data
|
||||
"Data manipulation and query helper functions."
|
||||
(:refer-clojure :exclude [concat])
|
||||
(:require [clojure.set :as set]))
|
||||
|
||||
(defn concat
|
||||
[& colls]
|
||||
(loop [result (first colls)
|
||||
colls (rest colls)]
|
||||
(if (seq colls)
|
||||
(recur (reduce conj result (first colls))
|
||||
(rest colls))
|
||||
result)))
|
||||
|
||||
(defn seek
|
||||
([pred coll]
|
||||
(seek pred coll nil))
|
||||
([pred coll not-found]
|
||||
(reduce (fn [_ x]
|
||||
(if (pred x)
|
||||
(reduced x)
|
||||
not-found))
|
||||
not-found coll)))
|
||||
|
||||
(defn diff-maps
|
||||
[ma mb]
|
||||
(let [ma-keys (set (keys ma))
|
||||
mb-keys (set (keys mb))
|
||||
added (set/difference mb-keys ma-keys)
|
||||
removed (set/difference ma-keys mb-keys)
|
||||
both (set/intersection ma-keys mb-keys)]
|
||||
(concat
|
||||
(mapv #(vector :add % (get mb %)) added)
|
||||
(mapv #(vector :del % nil) removed)
|
||||
(loop [k (first both)
|
||||
r (rest both)
|
||||
rs []]
|
||||
(if k
|
||||
(let [vma (get ma k)
|
||||
vmb (get mb k)]
|
||||
(if (= vma vmb)
|
||||
(recur (first r) (rest r) rs)
|
||||
(recur (first r) (rest r) (conj rs [:mod k vmb]))))
|
||||
rs)))))
|
|
@ -1,32 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.common.exceptions
|
||||
"A helpers for work with exceptions."
|
||||
(:require [clojure.spec.alpha :as s]))
|
||||
|
||||
(s/def ::type keyword?)
|
||||
(s/def ::code keyword?)
|
||||
(s/def ::mesage string?)
|
||||
(s/def ::hint string?)
|
||||
|
||||
(s/def ::error
|
||||
(s/keys :req-un [::type]
|
||||
:opt-un [::code
|
||||
::hint
|
||||
::mesage]))
|
||||
|
||||
(defn error
|
||||
[& {:keys [type code message hint cause] :as params}]
|
||||
(s/assert ::error params)
|
||||
(let [message (or message hint "")
|
||||
payload (dissoc params :cause :message)]
|
||||
(ex-info message payload cause)))
|
||||
|
||||
#?(:clj
|
||||
(defmacro raise
|
||||
[& args]
|
||||
`(throw (error ~@args))))
|
|
@ -1,37 +1,36 @@
|
|||
(ns uxbox.common.pages
|
||||
"A common (clj/cljs) functions and specs for pages."
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[uxbox.common.spec :as cs]))
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
;; --- Specs
|
||||
|
||||
(s/def ::id ::cs/uuid)
|
||||
(s/def ::id uuid?)
|
||||
(s/def ::name string?)
|
||||
(s/def ::type keyword?)
|
||||
|
||||
;; Metadata related
|
||||
(s/def ::grid-x-axis ::cs/number)
|
||||
(s/def ::grid-y-axis ::cs/number)
|
||||
(s/def ::grid-x-axis number?)
|
||||
(s/def ::grid-y-axis number?)
|
||||
(s/def ::grid-color string?)
|
||||
(s/def ::background string?)
|
||||
(s/def ::background-opacity ::cs/number)
|
||||
(s/def ::background-opacity number?)
|
||||
|
||||
;; Page related
|
||||
(s/def ::file-id ::cs/uuid)
|
||||
(s/def ::user ::cs/uuid)
|
||||
(s/def ::created-at ::cs/inst)
|
||||
(s/def ::modified-at ::cs/inst)
|
||||
(s/def ::version ::cs/number)
|
||||
(s/def ::ordering ::cs/number)
|
||||
(s/def ::file-id uuid?)
|
||||
(s/def ::user uuid?)
|
||||
(s/def ::created-at inst?)
|
||||
(s/def ::modified-at inst?)
|
||||
(s/def ::version number?)
|
||||
(s/def ::ordering number?)
|
||||
|
||||
;; Page Data related
|
||||
(s/def ::shape
|
||||
(s/keys :req-un [::type ::name]
|
||||
:opt-un [::id]))
|
||||
|
||||
(s/def ::shapes (s/coll-of ::cs/uuid :kind vector?))
|
||||
(s/def ::canvas (s/coll-of ::cs/uuid :kind vector?))
|
||||
(s/def ::shapes (s/coll-of uuid? :kind vector?))
|
||||
(s/def ::canvas (s/coll-of uuid? :kind vector?))
|
||||
|
||||
(s/def ::shapes-by-id
|
||||
(s/map-of uuid? ::shape))
|
||||
|
@ -49,11 +48,13 @@
|
|||
::background
|
||||
::background-opacity]))
|
||||
|
||||
(s/def ::shape-change
|
||||
(s/tuple #{:add :mod :del} keyword? any?))
|
||||
|
||||
(s/def ::operation
|
||||
(s/or :mod-shape (s/cat :name #(= % :mod-shape)
|
||||
:id uuid?
|
||||
:attr keyword?
|
||||
:value any?)
|
||||
:changes (s/* ::shape-change))
|
||||
:add-shape (s/cat :name #(= % :add-shape)
|
||||
:id uuid?
|
||||
:data any?)
|
||||
|
@ -68,7 +69,6 @@
|
|||
(s/def ::operations
|
||||
(s/coll-of ::operation :kind vector?))
|
||||
|
||||
|
||||
;; --- Operations Processing Impl
|
||||
|
||||
(declare process-operation)
|
||||
|
@ -80,42 +80,55 @@
|
|||
|
||||
(defn process-ops
|
||||
[data operations]
|
||||
(->> (cs/conform ::operations operations)
|
||||
(->> (s/assert ::operations operations)
|
||||
(reduce process-operation data)))
|
||||
|
||||
(defn- process-operation
|
||||
[data operation]
|
||||
(case (first operation)
|
||||
:mod-shape (process-mod-shape data operation)
|
||||
:add-shape (process-add-shape data operation)
|
||||
:del-shape (process-del-shape data operation)
|
||||
:add-canvas (process-add-canvas data operation)
|
||||
:del-canvas (process-del-canvas data operation)))
|
||||
[data [op & rest]]
|
||||
(case op
|
||||
:mod-shape (process-mod-shape data rest)
|
||||
:add-shape (process-add-shape data rest)
|
||||
:del-shape (process-del-shape data rest)
|
||||
:add-canvas (process-add-canvas data rest)
|
||||
:del-canvas (process-del-canvas data rest)))
|
||||
|
||||
(defn- process-mod-shape
|
||||
[data {:keys [id attr value]}]
|
||||
(update-in data [:shapes-by-id id] assoc attr value))
|
||||
[data [id & changes]]
|
||||
(if (get-in data [:shapes-by-id id])
|
||||
(update-in data [:shapes-by-id id]
|
||||
#(reduce (fn [shape [op att val]]
|
||||
(if (= op :del)
|
||||
(dissoc shape att)
|
||||
(assoc shape att val)))
|
||||
% changes))
|
||||
data))
|
||||
|
||||
(defn- process-add-shape
|
||||
[data {:keys [id data]}]
|
||||
[data [id sdata]]
|
||||
(-> data
|
||||
(update :shapes conj id)
|
||||
(update :shapes-by-id assoc id data)))
|
||||
(update :shapes (fn [shapes]
|
||||
(if (some #{id} shapes)
|
||||
shapes
|
||||
(conj shapes id))))
|
||||
(update :shapes-by-id assoc id sdata)))
|
||||
|
||||
(defn- process-del-shape
|
||||
[data {:keys [id attr value]}]
|
||||
[data [id]]
|
||||
(-> data
|
||||
(update :shapes (fn [s] (filterv #(not= % id) s)))
|
||||
(update :shapes-by-id dissoc id)))
|
||||
|
||||
(defn- process-add-canvas
|
||||
[data {:keys [id data]}]
|
||||
[data [id sdata]]
|
||||
(-> data
|
||||
(update :canvas conj id)
|
||||
(update :shapes-by-id assoc id data)))
|
||||
(update :canvas (fn [shapes]
|
||||
(if (some #{id} shapes)
|
||||
shapes
|
||||
(conj shapes id))))
|
||||
(update :shapes-by-id assoc id sdata)))
|
||||
|
||||
(defn- process-del-canvas
|
||||
[data {:keys [id attr value]}]
|
||||
[data [id]]
|
||||
(-> data
|
||||
(update :canvas (fn [s] (filterv #(not= % id) s)))
|
||||
(update :shapes-by-id dissoc id)))
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.common.spec
|
||||
(:require
|
||||
#?(:clj [datoteka.core :as fs])
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[expound.alpha :as expound]
|
||||
[uxbox.common.exceptions :as ex]))
|
||||
|
||||
(s/check-asserts true)
|
||||
|
||||
;; --- Constants
|
||||
|
||||
(def email-rx
|
||||
#"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
|
||||
|
||||
(def uuid-rx
|
||||
#"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
|
||||
|
||||
;; --- Public API
|
||||
|
||||
(defn conform
|
||||
[spec data]
|
||||
(let [result (s/conform spec data)]
|
||||
(when (= result ::s/invalid)
|
||||
(ex/raise :type :validation
|
||||
:code :spec-validation
|
||||
:explain (with-out-str
|
||||
(expound/printer data))
|
||||
:data (::s/problems data)))
|
||||
result))
|
||||
|
||||
;; --- Predicates
|
||||
|
||||
(defn email?
|
||||
[v]
|
||||
(and (string? v)
|
||||
(re-matches email-rx v)))
|
||||
|
||||
;; --- Conformers
|
||||
|
||||
(defn- uuid-conformer
|
||||
[v]
|
||||
(cond
|
||||
(uuid? v) v
|
||||
(string? v)
|
||||
(cond
|
||||
(re-matches uuid-rx v)
|
||||
#?(:clj (java.util.UUID/fromString v)
|
||||
:cljs (uuid v))
|
||||
|
||||
(str/empty? v)
|
||||
nil
|
||||
|
||||
:else
|
||||
::s/invalid)
|
||||
:else ::s/invalid))
|
||||
|
||||
(defn- integer-conformer
|
||||
[v]
|
||||
(cond
|
||||
(integer? v) v
|
||||
(string? v)
|
||||
(if (re-matches #"^[-+]?\d+$" v)
|
||||
(Long/parseLong v)
|
||||
::s/invalid)
|
||||
:else ::s/invalid))
|
||||
|
||||
(defn boolean-conformer
|
||||
[v]
|
||||
(cond
|
||||
(boolean? v) v
|
||||
(string? v)
|
||||
(if (re-matches #"^(?:t|true|false|f|0|1)$" v)
|
||||
(contains? #{"t" "true" "1"} v)
|
||||
::s/invalid)
|
||||
:else ::s/invalid))
|
||||
|
||||
(defn boolean-unformer
|
||||
[v]
|
||||
(if v "true" "false"))
|
||||
|
||||
#?(:clj
|
||||
(defn path-conformer
|
||||
[v]
|
||||
(cond
|
||||
(string? v) (fs/path v)
|
||||
(fs/path? v) v
|
||||
:else ::s/invalid)))
|
||||
|
||||
(defn- number-conformer
|
||||
[v]
|
||||
(cond
|
||||
(number? v) v
|
||||
|
||||
(str/numeric? v)
|
||||
#?(:clj (Double/parseDouble v)
|
||||
:cljs (js/parseFloat v))
|
||||
|
||||
:else ::s/invalid))
|
||||
|
||||
;; --- Default Specs
|
||||
|
||||
(s/def ::string string?)
|
||||
(s/def ::integer (s/conformer integer-conformer str))
|
||||
(s/def ::uuid (s/conformer uuid-conformer str))
|
||||
(s/def ::boolean (s/conformer boolean-conformer boolean-unformer))
|
||||
(s/def ::number (s/conformer number-conformer str))
|
||||
|
||||
(s/def ::inst inst?)
|
||||
(s/def ::positive pos?)
|
||||
(s/def ::negative neg?)
|
||||
(s/def ::uploaded-file any?)
|
||||
(s/def ::email email?)
|
||||
(s/def ::file any?)
|
||||
|
||||
;; Clojure Specific
|
||||
#?(:clj
|
||||
(do
|
||||
(s/def ::bytes bytes?)
|
||||
(s/def ::name ::string)
|
||||
(s/def ::size ::integer)
|
||||
(s/def ::mtype ::string)
|
||||
(s/def ::path (s/conformer path-conformer str))
|
||||
(s/def ::upload
|
||||
(s/keys :req-un [::name ::path ::size ::mtype]))))
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
FROM azul/zulu-openjdk:12
|
||||
LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
|
||||
|
||||
ENV CLOJURE_VERSION=1.10.1.469 \
|
||||
ENV CLOJURE_VERSION=1.10.1.492 \
|
||||
LANG=en_US.UTF-8 \
|
||||
LC_ALL=C.UTF-8
|
||||
|
||||
|
@ -33,4 +33,4 @@ WORKDIR /srv/uxbox/
|
|||
EXPOSE 6060
|
||||
|
||||
ENTRYPOINT ["bash", "/entrypoint.sh"]
|
||||
CMD ["clojure", "-m", "uxbox.main"]
|
||||
CMD ["/srv/uxbox/bin/start-prod"]
|
||||
|
|
|
@ -5,7 +5,7 @@ ARG EXTERNAL_UID=1000
|
|||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ENV NODE_VERSION=v10.16.3 \
|
||||
CLOJURE_VERSION=1.10.1.469 \
|
||||
CLOJURE_VERSION=1.10.1.492 \
|
||||
LANG=en_US.UTF-8 \
|
||||
LC_ALL=C.UTF-8
|
||||
|
||||
|
@ -86,10 +86,10 @@ RUN set -ex; \
|
|||
COPY files/bashrc /home/uxbox/.bashrc
|
||||
COPY files/zshrc /home/uxbox/.zshrc
|
||||
COPY files/vimrc /home/uxbox/.vimrc
|
||||
COPY files/start.sh /home/uxbox/start.sh
|
||||
COPY files/start-tmux.sh /home/uxbox/start-tmux.sh
|
||||
COPY files/tmux.conf /home/uxbox/.tmux.conf
|
||||
COPY files/entrypoint.sh /home/uxbox/
|
||||
COPY files/init.sh /home/uxbox/
|
||||
|
||||
ENTRYPOINT ["zsh", "/home/uxbox/entrypoint.sh"]
|
||||
CMD ["/home/uxbox/start.sh"]
|
||||
CMD ["/home/uxbox/init.sh"]
|
||||
|
|
|
@ -35,6 +35,7 @@ services:
|
|||
- 9090:9090
|
||||
|
||||
environment:
|
||||
- CLOJURE_OPTS="-J-XX:-OmitStackTraceInFastThrow"
|
||||
- UXBOX_DATABASE_URI="postgresql://postgres/uxbox"
|
||||
- UXBOX_DATABASE_USERNAME="uxbox"
|
||||
- UXBOX_DATABASE_PASSWORD="uxbox"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env zsh
|
||||
|
||||
set -ex
|
||||
exec "$@"
|
||||
|
|
|
@ -10,7 +10,7 @@ tmux send-keys -t uxbox 'clojure -Adev tools.clj figwheel' enter
|
|||
tmux new-window -t uxbox:2 -n 'backend'
|
||||
tmux select-window -t uxbox:2
|
||||
tmux send-keys -t uxbox 'cd uxbox/backend' enter C-l
|
||||
tmux send-keys -t uxbox './bin/start' enter
|
||||
tmux send-keys -t uxbox './bin/start-dev' enter
|
||||
|
||||
tmux rename-window -t uxbox:0 'gulp'
|
||||
tmux select-window -t uxbox:0
|
|
@ -14,11 +14,11 @@
|
|||
funcool/beicon {:mvn/version "5.1.0"}
|
||||
funcool/cuerdas {:mvn/version "2.2.0"}
|
||||
funcool/lentes {:mvn/version "1.3.3"}
|
||||
funcool/potok {:mvn/version "2.7.0"}
|
||||
funcool/potok {:mvn/version "2.8.0-SNAPSHOT"}
|
||||
funcool/promesa {:mvn/version "4.0.2"}
|
||||
funcool/rumext {:mvn/version "2.0.0-SNAPSHOT"}
|
||||
}
|
||||
:paths ["src" "vendor" "resources"]
|
||||
:paths ["src" "vendor" "resources" "../common"]
|
||||
:aliases
|
||||
{:dev
|
||||
{:extra-deps
|
||||
|
|
|
@ -82,8 +82,8 @@
|
|||
"A marker protocol for mark events that alters the
|
||||
page and is subject to perform a backend synchronization.")
|
||||
|
||||
(defprotocol IPageOps
|
||||
(-ops [_] "Get a list of ops for the event."))
|
||||
(defprotocol IPagePersistentOps
|
||||
(-persistent-ops [o] "Get a list of ops for the event."))
|
||||
|
||||
(defn page-update?
|
||||
[o]
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
[cljs.spec.alpha :as s]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.common.pages :as cp]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.icons :as udi]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
|
@ -33,6 +35,8 @@
|
|||
(def clear-ruler nil)
|
||||
(def start-ruler nil)
|
||||
|
||||
(declare shapes-overlaps?)
|
||||
|
||||
;; --- Specs
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
|
@ -196,6 +200,21 @@
|
|||
:workspace-data data
|
||||
:workspace-page page)))))
|
||||
|
||||
;; --- Toggle layout flag
|
||||
|
||||
(defn toggle-layout-flag
|
||||
[flag]
|
||||
(s/assert keyword? flag)
|
||||
(ptk/reify ::toggle-layout-flag
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-layout
|
||||
(fn [flags]
|
||||
(if (contains? flags flag)
|
||||
(disj flags flag)
|
||||
(conj flags flag)))))))
|
||||
|
||||
|
||||
;; --- Workspace Flags
|
||||
|
||||
(defn activate-flag
|
||||
|
@ -234,18 +253,6 @@
|
|||
[txt]
|
||||
::todo)
|
||||
|
||||
(defn toggle-layout-flag
|
||||
[flag]
|
||||
(s/assert keyword? flag)
|
||||
(ptk/reify ::toggle-layout-flag
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-layout
|
||||
(fn [flags]
|
||||
(if (contains? flags flag)
|
||||
(disj flags flag)
|
||||
(conj flags flag)))))))
|
||||
|
||||
;; --- Workspace Ruler
|
||||
|
||||
(defrecord ActivateRuler []
|
||||
|
@ -450,17 +457,24 @@
|
|||
(update-in $ [:workspace-data :shapes] conj id))
|
||||
(assoc-in $ [:workspace-data :shapes-by-id id] shape))))
|
||||
|
||||
(declare commit-shapes-changes)
|
||||
(declare recalculate-shape-canvas-relation)
|
||||
|
||||
(defn add-shape
|
||||
[data]
|
||||
(ptk/reify ::add-shape
|
||||
udp/IPageDataUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
;; TODO: revisit the `setup-proportions` seems unnecesary
|
||||
(let [page-id (get-in state [:workspace-local :id])
|
||||
shape (assoc (geom/setup-proportions data)
|
||||
:id (uuid/random))]
|
||||
(impl-assoc-shape state shape)))))
|
||||
(let [id (uuid/random)]
|
||||
(ptk/reify ::add-shape
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [shape (-> (geom/setup-proportions data)
|
||||
(assoc :id id))
|
||||
shape (recalculate-shape-canvas-relation state shape)]
|
||||
(impl-assoc-shape state shape)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [shape (get-in state [:workspace-data :shapes-by-id id])]
|
||||
(rx/of (commit-shapes-changes [[:add-shape id shape]])))))))
|
||||
|
||||
;; --- Duplicate Selected
|
||||
|
||||
|
@ -471,47 +485,14 @@
|
|||
|
||||
(def duplicate-selected
|
||||
(ptk/reify ::duplicate-selected
|
||||
udp/IPageDataUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:workspace-local :selected])
|
||||
duplicate (partial impl-duplicate-shape state)
|
||||
shapes (map duplicate selected)]
|
||||
(reduce impl-assoc-shape state shapes)))))
|
||||
|
||||
;; --- Delete shape to Workspace
|
||||
|
||||
(defn impl-dissoc-shape
|
||||
"Given a shape, removes it from the state."
|
||||
[state {:keys [id type] :as shape}]
|
||||
(as-> state $$
|
||||
(if (= :canvas type)
|
||||
(update-in $$ [:workspace-data :canvas]
|
||||
(fn [items] (vec (remove #(= % id) items))))
|
||||
(update-in $$ [:workspace-data :shapes]
|
||||
(fn [items] (vec (remove #(= % id) items)))))
|
||||
(update-in $$ [:workspace-data :shapes-by-id] dissoc id)))
|
||||
|
||||
(defn delete-shape
|
||||
[id]
|
||||
(s/assert ::us/uuid id)
|
||||
(ptk/reify ::delete-shape
|
||||
udp/IPageDataUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [shape (get-in state [:workspace-data :shapes-by-id id])]
|
||||
(impl-dissoc-shape state shape)))))
|
||||
|
||||
(defn delete-many-shapes
|
||||
[ids]
|
||||
(s/assert ::us/set ids)
|
||||
(ptk/reify ::delete-many-shapes
|
||||
udp/IPageDataUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(reduce impl-dissoc-shape state
|
||||
(map #(get-in state [:workspace-data :shapes-by-id %]) ids)))))
|
||||
|
||||
(rx/merge
|
||||
(rx/from-coll (map (fn [s] #(impl-assoc-shape % s)) shapes))
|
||||
(rx/of (commit-shapes-changes (mapv #(vector :add-shape (:id %) %) shapes))))))))
|
||||
|
||||
;; --- Toggle shape's selection status (selected or deselected)
|
||||
|
||||
|
@ -593,12 +574,30 @@
|
|||
(defn update-shape-attrs
|
||||
[id attrs]
|
||||
(s/assert ::us/uuid id)
|
||||
(s/assert ::attributes attrs)
|
||||
(let [atts (s/conform ::attributes attrs)]
|
||||
(ptk/reify ::update-shape-attrs
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-data :shapes-by-id id] merge attrs)))))
|
||||
(if (map? attrs)
|
||||
(update-in state [:workspace-data :shapes-by-id id] merge attrs)
|
||||
state)))))
|
||||
|
||||
(defn update-shape
|
||||
[id & attrs]
|
||||
(let [attrs' (->> (apply hash-map attrs)
|
||||
(s/conform ::attributes))]
|
||||
(ptk/reify ::update-shape
|
||||
udp/IPagePersistentOps
|
||||
(-persistent-ops [_]
|
||||
(->> (partition-all 2 attrs)
|
||||
(mapv (fn [[key val]] [:mod-shape id key val]))))
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(cond-> state
|
||||
(not= attrs' ::s/invalid)
|
||||
(update-in [:workspace-data :shapes-by-id id] merge attrs'))))))
|
||||
|
||||
|
||||
;; --- Update Selected Shapes attrs
|
||||
|
||||
|
@ -615,8 +614,9 @@
|
|||
|
||||
;; --- Move Selected
|
||||
|
||||
;; Event used for apply displacement transformation
|
||||
;; to the selected shapes throught the keyboard shortcuts.
|
||||
(declare initial-selection-align)
|
||||
(declare apply-temporal-displacement-in-bulk)
|
||||
(declare materialize-temporal-modifier-in-bulk)
|
||||
|
||||
(defn- get-displacement
|
||||
"Retrieve the correct displacement delta point for the
|
||||
|
@ -639,13 +639,13 @@
|
|||
:fast (gpt/point (if align? (* 3 gx) 10)
|
||||
(if align? (* 3 gy) 10))}))
|
||||
|
||||
(declare initial-selection-align)
|
||||
(declare materialize-current-modifier-in-bulk)
|
||||
(declare apply-temporal-displacement-in-bulk)
|
||||
|
||||
(s/def ::direction #{:up :down :right :left})
|
||||
(s/def ::speed #{:std :fast})
|
||||
|
||||
;; Event used for apply displacement transformation
|
||||
;; to the selected shapes throught the keyboard shortcuts.
|
||||
|
||||
(defn move-selected
|
||||
[direction speed]
|
||||
(s/assert ::direction direction)
|
||||
|
@ -662,7 +662,7 @@
|
|||
(rx/concat
|
||||
(when align? (rx/of (initial-selection-align selected)))
|
||||
(rx/of (apply-temporal-displacement-in-bulk selected displacement))
|
||||
(rx/of (materialize-current-modifier-in-bulk selected)))))))
|
||||
(rx/of (materialize-temporal-modifier-in-bulk selected)))))))
|
||||
|
||||
;; --- Update Shape Position
|
||||
|
||||
|
@ -679,13 +679,30 @@
|
|||
|
||||
;; --- Delete Selected
|
||||
|
||||
(defn impl-dissoc-shape
|
||||
"Given a shape, removes it from the state."
|
||||
[state {:keys [id type] :as shape}]
|
||||
(as-> state $$
|
||||
(if (= :canvas type)
|
||||
(update-in $$ [:workspace-data :canvas]
|
||||
(fn [items] (vec (remove #(= % id) items))))
|
||||
(update-in $$ [:workspace-data :shapes]
|
||||
(fn [items] (vec (remove #(= % id) items)))))
|
||||
(update-in $$ [:workspace-data :shapes-by-id] dissoc id)))
|
||||
|
||||
(def delete-selected
|
||||
"Deselect all and remove all selected shapes."
|
||||
(ptk/reify ::delete-selected
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [selected (get-in state [:workspace-local :selected])]
|
||||
(reduce impl-dissoc-shape state
|
||||
(map #(get-in state [:workspace-data :shapes-by-id %]) selected))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:workspace-local :selected])]
|
||||
(rx/of (delete-many-shapes selected))))))
|
||||
(rx/of (commit-shapes-changes (mapv #(vector :del-shape %) selected)))))))
|
||||
|
||||
;; --- Rename Shape
|
||||
|
||||
|
@ -695,24 +712,30 @@
|
|||
(ptk/reify ::rename-shape
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:shapes id :name] name))))
|
||||
(assoc-in state [:shapes id :name] name))
|
||||
|
||||
;; --- Change Shape Order (D&D Ordering)
|
||||
|
||||
(defn change-shape-order
|
||||
[{:keys [id index] :as params}]
|
||||
{:pre [(uuid? id) (number? index)]}
|
||||
(ptk/reify ::change-shape-order
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [shapes (get-in state [:workspace-data :shapes])
|
||||
shapes (into [] (remove #(= % id)) shapes)
|
||||
[before after] (split-at index shapes)
|
||||
shapes (vec (concat before [id] after))]
|
||||
(assoc-in state [:workspace-data :shapes] shapes)))))
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (commit-shapes-changes [[:mod-shape id [:mod :name name]]])))))
|
||||
|
||||
;; --- Shape Vertical Ordering
|
||||
|
||||
(declare impl-order-shape)
|
||||
|
||||
(defn order-selected-shapes
|
||||
[loc]
|
||||
(s/assert ::direction loc)
|
||||
(ptk/reify ::move-selected-layer
|
||||
udp/IPageDataUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (first (get-in state [:workspace-local :selected]))
|
||||
type (get-in state [:workspace-data :shapes-by-id id :type])]
|
||||
;; NOTE: multiple selection ordering not supported
|
||||
(if (and id (not= type :canvas))
|
||||
(impl-order-shape state id loc)
|
||||
state)))))
|
||||
|
||||
(defn impl-order-shape
|
||||
[state sid opt]
|
||||
(let [shapes (get-in state [:workspace-data :shapes])
|
||||
|
@ -727,19 +750,19 @@
|
|||
(split-at index))]
|
||||
(into [] (concat fst [sid] snd)))))))
|
||||
|
||||
(defn order-selected-shapes
|
||||
[loc]
|
||||
(s/assert ::direction loc)
|
||||
(ptk/reify ::move-selected-layer
|
||||
udp/IPageDataUpdate
|
||||
;; --- Change Shape Order (D&D Ordering)
|
||||
|
||||
(defn change-shape-order
|
||||
[{:keys [id index] :as params}]
|
||||
{:pre [(uuid? id) (number? index)]}
|
||||
(ptk/reify ::change-shape-order
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (first (get-in state [:workspace-local :selected]))
|
||||
type (get-in state [:workspace-data :shapes-by-id id :type])]
|
||||
;; NOTE: multiple selection ordering not supported
|
||||
(if (and id (not= type :canvas))
|
||||
(impl-order-shape state id loc)
|
||||
state)))))
|
||||
(let [shapes (get-in state [:workspace-data :shapes])
|
||||
shapes (into [] (remove #(= % id)) shapes)
|
||||
[before after] (split-at index shapes)
|
||||
shapes (vec (concat before [id] after))]
|
||||
(assoc-in state [:workspace-data :shapes] shapes)))))
|
||||
|
||||
;; --- Change Canvas Order (D&D Ordering)
|
||||
|
||||
|
@ -776,6 +799,15 @@
|
|||
|
||||
;; --- Temportal displacement for Shape / Selection
|
||||
|
||||
(defn assoc-temporal-modifier-in-bulk
|
||||
[ids xfmt]
|
||||
(s/assert ::set-of-uuid ids)
|
||||
(s/assert gmt/matrix? xfmt)
|
||||
(ptk/reify ::assoc-temporal-modifier-in-bulk
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(reduce #(assoc-in %1 [:workspace-data :shapes-by-id %2 :modifier-mtx] xfmt) state ids))))
|
||||
|
||||
(defn apply-temporal-displacement-in-bulk
|
||||
"Apply the same displacement delta to all shapes identified by the
|
||||
set if ids."
|
||||
|
@ -787,39 +819,91 @@
|
|||
xfmt (gmt/translate prev delta)]
|
||||
(assoc-in state [:workspace-data :shapes-by-id id :modifier-mtx] xfmt)))]
|
||||
(ptk/reify ::apply-temporal-displacement-in-bulk
|
||||
;; udp/IPageOps
|
||||
;; (-ops [_]
|
||||
;; (mapv #(vec :udp/shape id :move delta) ids))
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(reduce process-shape state ids)))))
|
||||
|
||||
;; --- Modifiers
|
||||
(defn- recalculate-shape-canvas-relation
|
||||
[state shape]
|
||||
(let [xfmt (comp (map #(get-in state [:workspace-data :shapes-by-id %]))
|
||||
(map geom/shape->rect-shape)
|
||||
(filter #(geom/overlaps? % shape))
|
||||
(map :id))
|
||||
|
||||
(defn assoc-temporal-modifier-in-bulk
|
||||
[ids xfmt]
|
||||
(s/assert ::set-of-uuid ids)
|
||||
(s/assert gmt/matrix? xfmt)
|
||||
(ptk/reify ::assoc-temporal-modifier-in-bulk
|
||||
id (->> (get-in state [:workspace-data :canvas])
|
||||
(into [] xfmt)
|
||||
(first))]
|
||||
(assoc shape :canvas id)))
|
||||
|
||||
(defn materialize-temporal-modifier-in-bulk
|
||||
[ids]
|
||||
(letfn [(process-shape [state id]
|
||||
(let [shape (get-in state [:workspace-data :shapes-by-id id])
|
||||
xfmt (or (:modifier-mtx shape) (gmt/matrix))
|
||||
shape-old (dissoc shape :modifier-mtx)
|
||||
shape-new (geom/transform shape-old xfmt)
|
||||
shape-new (recalculate-shape-canvas-relation state shape-new)
|
||||
diff (d/diff-maps shape-old shape-new)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-data :shapes-by-id id] shape-new)
|
||||
(update ::tmp-changes (fnil conj []) (into [:mod-shape id] diff)))))]
|
||||
|
||||
(ptk/reify ::materialize-temporal-modifier-in-bulk
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(reduce process-shape state ids))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [changes (::tmp-changes state)]
|
||||
(rx/of (commit-shapes-changes changes)
|
||||
#(dissoc state ::tmp-changes)))))))
|
||||
|
||||
(declare shapes-changes-commited)
|
||||
|
||||
(defn commit-shapes-changes
|
||||
[operations]
|
||||
(s/assert ::cp/operations operations)
|
||||
(ptk/reify ::commit-shapes-changes
|
||||
;; Commits the just performed changes to root pages-data
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(reduce #(assoc-in %1 [:workspace-data :shapes-by-id %2 :modifier-mtx] xfmt) state ids))))
|
||||
(let [pid (get-in state [:workspace-local :page-id])
|
||||
data (get-in state [:pages-data pid])]
|
||||
(update-in state [:pages-data pid] cp/process-ops operations)))
|
||||
|
||||
(defn materialize-current-modifier-in-bulk
|
||||
[ids]
|
||||
(s/assert ::us/set ids)
|
||||
(letfn [(process-shape [state id]
|
||||
(let [xfmt (get-in state [:workspace-data :shapes-by-id id :modifier-mtx])]
|
||||
(if (gmt/matrix? xfmt)
|
||||
(-> state
|
||||
(update-in [:workspace-data :shapes-by-id id] geom/transform xfmt)
|
||||
(update-in [:workspace-data :shapes-by-id id] dissoc :modifier-mtx))
|
||||
state)))]
|
||||
(ptk/reify ::materialize-current-modifier-in-bulk
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(reduce process-shape state ids)))))
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page (:workspace-page state)
|
||||
params {:id (:id page)
|
||||
:version (:version page)
|
||||
:operations operations}]
|
||||
(prn "commit-shapes-changes" params)
|
||||
(->> (rp/mutation :update-project-page params)
|
||||
;; (rx/tap #(prn "foobar" %))
|
||||
(rx/map shapes-changes-commited))))
|
||||
|
||||
;; ptk/EffectEvent
|
||||
;; (effect [_ state stream]
|
||||
;; (let [data {:shapes []
|
||||
;; :shapes-by-id {}}]
|
||||
;; (prn "commit-shapes-changes$effect" (cp/process-ops data operations))))
|
||||
))
|
||||
|
||||
(s/def ::shapes-changes-commited
|
||||
(s/keys :req-un [::id ::version ::cp/operations]))
|
||||
|
||||
(defn shapes-changes-commited
|
||||
[{:keys [id version operations] :as params}]
|
||||
(s/assert ::shapes-changes-commited params)
|
||||
(ptk/reify ::shapes-changes-commited
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:workspace-page :version] version)
|
||||
(assoc-in [:pages id :version] version)
|
||||
(update-in [:pages-data id] cp/process-ops operations)
|
||||
(update :workspace-data cp/process-ops operations)))))
|
||||
|
||||
;; --- Start shape "edition mode"
|
||||
|
||||
|
@ -1226,3 +1310,10 @@
|
|||
;; (rx/ignore)
|
||||
;; (rx/take-until stoper)))))))
|
||||
|
||||
(defn shapes-overlaps?
|
||||
[canvas shape]
|
||||
(let [shape1 (geom/shape->rect-shape canvas)
|
||||
shape2 (geom/shape->rect-shape shape)]
|
||||
(geom/overlaps? shape1 shape2)))
|
||||
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
(->> (uws/mouse-position-deltas position)
|
||||
(rx/map #(dw/apply-temporal-displacement-in-bulk selected %))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (dw/materialize-current-modifier-in-bulk selected)
|
||||
(rx/of (dw/materialize-temporal-modifier-in-bulk selected)
|
||||
::dw/page-data-update))))))
|
||||
|
||||
(defn on-mouse-down
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
(rx/map normalize-proportion-lock)
|
||||
(rx/mapcat (partial resize shape))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (dw/materialize-current-modifier-in-bulk ids)
|
||||
(rx/of (dw/materialize-temporal-modifier-in-bulk ids)
|
||||
::dw/page-data-update)))))))
|
||||
|
||||
;; --- Controls (Component)
|
||||
|
|
|
@ -22,20 +22,21 @@
|
|||
;; --- Shortcuts
|
||||
|
||||
(defonce +shortcuts+
|
||||
{:shift+g #(st/emit! (dw/toggle-flag :grid))
|
||||
:ctrl+shift+m #(st/emit! (dw/toggle-flag :sitemap))
|
||||
:ctrl+shift+f #(st/emit! (dw/toggle-flag :drawtools))
|
||||
:ctrl+shift+i #(st/emit! (dw/toggle-flag :icons))
|
||||
:ctrl+shift+l #(st/emit! (dw/toggle-flag :layers))
|
||||
{
|
||||
;; :shift+g #(st/emit! (dw/toggle-flag :grid))
|
||||
:ctrl+shift+m #(st/emit! (dw/toggle-layout-flag :sitemap))
|
||||
:ctrl+shift+f #(st/emit! (dw/toggle-layout-flag :drawtools))
|
||||
:ctrl+shift+i #(st/emit! (dw/toggle-layout-flag :icons))
|
||||
:ctrl+shift+l #(st/emit! (dw/toggle-layout-flag :layers))
|
||||
:ctrl+0 #(st/emit! (dw/reset-zoom))
|
||||
:ctrl+r #(st/emit! (dw/toggle-flag :ruler))
|
||||
;; :ctrl+r #(st/emit! (dw/toggle-flag :ruler))
|
||||
:ctrl+d #(st/emit! dw/duplicate-selected)
|
||||
:ctrl+c #(st/emit! (dw/copy-to-clipboard))
|
||||
:ctrl+v #(st/emit! (dw/paste-from-clipboard))
|
||||
:ctrl+shift+v #(dl/open! :clipboard)
|
||||
:ctrl+z #(st/emit! du/undo)
|
||||
:ctrl+shift+z #(st/emit! du/redo)
|
||||
:ctrl+y #(st/emit! du/redo)
|
||||
;; :ctrl+c #(st/emit! (dw/copy-to-clipboard))
|
||||
;; :ctrl+v #(st/emit! (dw/paste-from-clipboard))
|
||||
;; :ctrl+shift+v #(dl/open! :clipboard)
|
||||
;; :ctrl+z #(st/emit! du/undo)
|
||||
;; :ctrl+shift+z #(st/emit! du/redo)
|
||||
;; :ctrl+y #(st/emit! du/redo)
|
||||
:ctrl+b #(st/emit! (dw/select-for-drawing :rect))
|
||||
:ctrl+e #(st/emit! (dw/select-for-drawing :circle))
|
||||
:ctrl+t #(st/emit! (dw/select-for-drawing :text))
|
||||
|
|
|
@ -94,7 +94,6 @@
|
|||
(rx/subscribe-with ob sub)
|
||||
sub))
|
||||
|
||||
|
||||
(defn mouse-position-deltas
|
||||
[current]
|
||||
(->> (rx/concat (rx/of current)
|
||||
|
|
|
@ -197,9 +197,10 @@
|
|||
:pprint-config false
|
||||
:load-warninged-code false
|
||||
:auto-testing false
|
||||
:reload-clj-files true
|
||||
:css-dirs ["resources/public/css"]
|
||||
:ring-server-options {:port 3449 :host "0.0.0.0"}
|
||||
:watch-dirs ["src" "test"]})
|
||||
:watch-dirs ["src" "test" "../common"]})
|
||||
|
||||
(defmethod task "figwheel"
|
||||
[& args]
|
||||
|
|
|
@ -37,7 +37,7 @@ function run-devenv {
|
|||
start-devenv
|
||||
fi
|
||||
|
||||
docker exec -ti uxboxdev-main /home/uxbox/start.sh;
|
||||
docker exec -ti uxboxdev-main /home/uxbox/start-tmux.sh
|
||||
}
|
||||
|
||||
function run-all-tests {
|
||||
|
|
Loading…
Add table
Reference in a new issue