Merge branch 'wip/shape-data-structure-refactor' into develop

This commit is contained in:
Andrey Antukh 2020-01-07 09:38:51 +01:00
commit 188872d712
125 changed files with 7967 additions and 3972 deletions

View file

@ -68,6 +68,17 @@
{:d
"M298.766 343.507C295.152 347.12 371.3 500 381.92 500c10.62 0 104.594-152.013 102.007-156.493-2.583-4.474-63.805-.626-63.805-.626s26.42-111.48-50.044-195.794c-87.817-96.833-193.242-82.57-193.242-82.57s6.47-60.767 0-64.432C171.166-3.13 15.942 87.23 16.02 96.417c.08 9.187 149.695 93.815 156.386 90.08 6.692-3.738 1.877-63.18 1.877-63.18s77.484-9.466 147.628 61.927c63.27 64.394 36.283 158.888 36.283 158.888s-55.935-4.117-59.427-.625z"}]]]))
(def artboard
(html
[:svg
{:viewBox "0 0 500 500"
:height "500"
:width "500"}
[:g
[:path
{:d
"M0 0v134.00391L134.00586 0H0zm166.32227 0v44.453125h289.22461V455.54688H44.453125V164.07617H0V500h500V0H166.32227z"}]]]))
(def chat
(html
[:svg#svg2

View file

@ -38,10 +38,6 @@
(rx/of du/fetch-profile
(rt/navigate :dashboard-projects)))))
(defn logged-in?
[v]
(= (ptk/type v) ::logged-in))
;; --- Login
(s/def ::login-params
@ -50,7 +46,7 @@
(defn login
[{:keys [username password] :as data}]
(s/assert ::login-params data)
(reify
(ptk/reify ::login
ptk/UpdateEvent
(update [_ state]
(merge state (dissoc initial-state :route :router)))

View file

@ -17,21 +17,14 @@
[uxbox.util.time :as dt]
[uxbox.util.uuid :as uuid]))
;; TODO: need a good refactor
;; --- Initialize
(declare fetch-collections)
(declare persist-collections)
(declare collections-fetched?)
(defrecord Initialize []
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:dashboard :colors] {:selected #{}})))
(defn initialize
[]
(Initialize.))
;; --- Collections Fetched
(defrecord CollectionsFetched [data]
@ -56,7 +49,7 @@
(defrecord FetchCollections []
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query! :kvstore-entry {:key "color-collections"})
(->> (rp/query! :user-attr {:key "color-collections"})
(rx/map collections-fetched)
(rx/catch (fn [{:keys [type] :as error}]
(if (= type :not-found)
@ -99,10 +92,9 @@
version (or (get state ::version) -1)
value (->> (get state :colors-collections)
(into {} xform))
data {:id "color-collections"
:version version
:value value}]
(->> (rp/mutation! :upsert-kvstore data)
data {:key "color-collections"
:val value}]
(->> (rp/mutation! :upsert-user-attr data)
(rx/map collections-fetched)))))
(defn persist-collections

View file

@ -9,7 +9,7 @@
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[potok.core :as ptk]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.projects :as dp]
[uxbox.main.repo :as rp]
[uxbox.util.data :refer [replace-by-id index-by]]
[uxbox.util.spec :as us]))
@ -26,7 +26,7 @@
(s/def ::user ::us/uuid)
(s/def ::shapes
(s/every ::udp/minimal-shape :kind vector?))
(s/every ::dp/minimal-shape :kind vector?))
(s/def ::data
(s/keys :req-un [::shapes]))
@ -77,7 +77,7 @@
(watch [_ state stream]
#_(let [stopper (rx/filter #(= % ::stop-page-watcher) stream)]
(->> stream
(rx/filter udp/page-persisted?)
(rx/filter dp/page-persisted?)
(rx/debounce 1000)
(rx/flat-map #(rx/of (fetch-history id)
(fetch-pinned-history id)))
@ -192,7 +192,7 @@
(assoc :history true
:data (:data item)))]
(-> state
(udp/unpack-page page)
(dp/unpack-page page)
(assoc-in [:workspace pid :history :selected] version))))))
;; --- Apply Selected History
@ -209,7 +209,7 @@
ptk/WatchEvent
(watch [_ state s]
#_(let [pid (get-in state [:workspace :current])]
(rx/of (udp/persist-page pid))))))
(rx/of (dp/persist-page pid))))))
;; --- Deselect Page History
@ -219,7 +219,7 @@
(update [_ state]
#_(let [pid (get-in state [:workspace :current])
packed (get-in state [:packed-pages pid])]
(-> (udp/unpack-page state packed)
(-> (dp/unpack-page state packed)
(assoc-in [:workspace pid :history :selected] nil))))))
;; --- Refresh Page History

View file

@ -6,6 +6,7 @@
(ns uxbox.main.data.icons
(:require
[cljs.spec.alpha :as s]
[beicon.core :as rx]
[cuerdas.core :as str]
[potok.core :as ptk]
@ -18,30 +19,32 @@
[uxbox.util.router :as r]
[uxbox.util.uuid :as uuid]))
;; --- Initialize
(s/def ::id uuid?)
(s/def ::name string?)
(s/def ::created-at inst?)
(s/def ::modified-at inst?)
(s/def ::user-id uuid?)
(def initialize
(ptk/reify ::initialize
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:dashboard :icons] {:selected #{}}))))
;; (s/def ::collection-id (s/nilable ::us/uuid))
;; --- Select a Collection
;; (s/def ::mimetype string?)
;; (s/def ::thumbnail us/url-str?)
;; (s/def ::width number?)
;; (s/def ::height number?)
;; (s/def ::url us/url-str?)
(defn select-collection
([type]
(select-collection type nil))
([type id]
{:pre [(keyword? type)]}
(ptk/reify ::select-collection
ptk/WatchEvent
(watch [_ state stream]
(rx/of (r/navigate :dashboard/icons {:type type :id id}))))))
(s/def ::collection
(s/keys :req-un [::id
::name
::created-at
::modified-at
::user-id]))
;; --- Collections Fetched
(defn collections-fetched
[items]
(s/assert (s/every ::collection) items)
(ptk/reify ::collections-fetched
cljs.core/IDeref
(-deref [_] items)
@ -55,10 +58,6 @@
state
items))))
(defn collections-fetched?
[v]
(= ::collections-fetched (ptk/type v)))
;; --- Fetch Collections
(def fetch-collections
@ -72,15 +71,12 @@
(defn collection-created
[item]
(s/assert ::collection item)
(ptk/reify ::collection-created
ptk/UpdateEvent
(update [_ state]
(let [{:keys [id] :as item} (assoc item :type :own)]
(update state :icons-collections assoc id item)))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (select-collection :own (:id item))))))
(update state :icons-collections assoc id item)))))
;; --- Create Collection
@ -100,7 +96,7 @@
(ptk/reify ::collection-updated
ptk/UpdateEvent
(update [_ state]
(update-in state [:icons-collections (:id item)] merge item))))
(update-in state [:icons-collections (:id item)] merge item))))
;; --- Update Collection
@ -141,7 +137,7 @@
(watch [_ state s]
(let [type (get-in state [:dashboard :icons :type])]
(->> (rp/mutation! :delete-icons-collection {:id id})
(rx/map #(select-collection type))))))
(rx/map #(r/nav :dashboard-icons {:type type}))))))
(defn delete-collection
[id]
@ -179,44 +175,42 @@
(dom/append-child! gc child))
(recur (dom/get-first-child svg)))
(let [width (.. svg -width -baseVal -value)
header (.. svg -height -baseVal -value)
height (.. svg -height -baseVal -value)
view-box [(.. svg -viewBox -baseVal -x)
(.. svg -viewBox -baseVal -y)
(.. svg -viewBox -baseVal -width)
(.. svg -viewBox -baseVal -height)]
props {:width width
:mimetype "image/svg+xml"
:height header
:height height
:view-box view-box}]
[(dom/get-outer-html g) props])))))
(defrecord CreateIcons [id files]
ptk/WatchEvent
(watch [_ state s]
(letfn [(parse [file]
(->> (files/read-as-text file)
(rx/map parse-svg)))
(allowed? [file]
(= (.-type file) "image/svg+xml"))
(prepare [[content metadata]]
{:collection-id id
:content content
:id (uuid/random)
;; TODO Keep the name of the original icon
:name (str "Icon " (gensym "i"))
:metadata metadata})]
(->> (rx/from-coll files)
(rx/filter allowed?)
(rx/flat-map parse)
(rx/map prepare)
(rx/flat-map #(rp/mutation! :create-icon %))
(rx/map :payload)
(rx/map icon-created)))))
(defn create-icons
[id files]
{:pre [(or (uuid? id) (nil? id))]}
(CreateIcons. id files))
(s/assert (s/nilable uuid?) id)
(ptk/reify ::create-icons
ptk/WatchEvent
(watch [_ state s]
(letfn [(parse [file]
(->> (files/read-as-text file)
(rx/map parse-svg)))
(allowed? [file]
(= (.-type file) "image/svg+xml"))
(prepare [[content metadata]]
{:collection-id id
:content content
:id (uuid/random)
;; TODO Keep the name of the original icon
:name (str "Icon " (gensym "i"))
:metadata metadata})]
(->> (rx/from files)
(rx/filter allowed?)
(rx/flat-map parse)
(rx/map prepare)
(rx/flat-map #(rp/mutation! :create-icon %))
(rx/map icon-created))))))
;; --- Icon Persisted
@ -345,14 +339,14 @@
(watch [_ state stream]
(let [selected (get-in state [:dashboard :icons :selected])]
(rx/merge
(->> (rx/from-coll selected)
(->> (rx/from selected)
(rx/map #(get-in state [:icons %]))
(rx/map #(dissoc % :id))
(rx/map #(assoc % :collection-id id))
(rx/flat-map #(rp/mutation :create-icon %))
(rx/map :payload)
(rx/map icon-created))
(->> (rx/from-coll selected)
(->> (rx/from selected)
(rx/map deselect-icon))))))
(defn copy-selected
@ -375,9 +369,9 @@
(watch [_ state stream]
(let [selected (get-in state [:dashboard :icons :selected])]
(rx/merge
(->> (rx/from-coll selected)
(->> (rx/from selected)
(rx/map persist-icon))
(->> (rx/from-coll selected)
(->> (rx/from selected)
(rx/map deselect-icon))))))
(defn move-selected
@ -391,7 +385,7 @@
ptk/WatchEvent
(watch [_ state stream]
(let [selected (get-in state [:dashboard :icons :selected])]
(->> (rx/from-coll selected)
(->> (rx/from selected)
(rx/map delete-icon)))))
(defn delete-selected

View file

@ -55,80 +55,50 @@
::url
::user-id]))
;; --- Initialize
(defrecord Initialize []
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:dashboard :images] {:selected #{}})))
(defn initialize
[]
(Initialize.))
;; --- Color Collections Fetched
(defrecord CollectionsFetched [items]
ptk/UpdateEvent
(update [_ state]
(reduce (fn [state {:keys [id user] :as item}]
(let [type (if (uuid/zero? (:user-id item)) :builtin :own)
item (assoc item :type type)]
(assoc-in state [:images-collections id] item)))
state
items)))
;; --- Collections Fetched
(defn collections-fetched
[items]
{:pre [(us/valid? (s/every ::collection-entity) items)]}
(CollectionsFetched. items))
(s/assert (s/every ::collection-entity) items)
(ptk/reify ::collections-fetched
ptk/UpdateEvent
(update [_ state]
(reduce (fn [state {:keys [id user] :as item}]
(let [type (if (uuid/zero? (:user-id item)) :builtin :own)
item (assoc item :type type)]
(assoc-in state [:images-collections id] item)))
state
items))))
;; --- Fetch Color Collections
(defrecord FetchCollections []
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query! :images-collections)
(rx/map collections-fetched))))
(defn fetch-collections
[]
(FetchCollections.))
(def fetch-collections
(ptk/reify ::fetch-collections
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query! :images-collections)
(rx/map collections-fetched)))))
;; --- Collection Created
(defrecord CollectionCreated [item]
ptk/UpdateEvent
(update [_ state]
(let [{:keys [id] :as item} (assoc item :type :own)]
(update state :images-collections assoc id item)))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (rt/nav :dashboard/images nil {:type :own :id (:id item)}))))
(defn collection-created
[item]
{:pre [(us/valid? ::collection-entity item)]}
(CollectionCreated. item))
(s/assert ::collection-entity item)
(ptk/reify ::collection-created
ptk/UpdateEvent
(update [_ state]
(let [{:keys [id] :as item} (assoc item :type :own)]
(update state :images-collections assoc id item)))))
;; --- Create Collection
(defrecord CreateCollection []
ptk/WatchEvent
(watch [_ state s]
(let [data {:name (tr "ds.default-library-title" (gensym "c"))}]
(->> (rp/mutation! :create-image-collection data)
(rx/map :payload)
(rx/map collection-created)))))
(defn create-collection
[]
(CreateCollection.))
(defn collections-fetched?
[v]
(instance? CollectionsFetched v))
(def create-collection
(ptk/reify ::create-collection
ptk/WatchEvent
(watch [_ state s]
(let [data {:name (tr "ds.default-library-title" (gensym "c"))}]
(->> (rp/mutation! :create-image-collection data)
(rx/map collection-created))))))
;; --- Collection Updated
@ -189,61 +159,55 @@
;; --- Image Created
(defrecord ImageCreated [item]
ptk/UpdateEvent
(update [_ state]
(update state :images assoc (:id item) item)))
(defn image-created
[item]
{:pre [(us/valid? ::image-entity item)]}
(ImageCreated. item))
(s/assert ::image-entity item)
(ptk/reify ::image-created
ptk/UpdateEvent
(update [_ state]
(update state :images assoc (:id item) item))))
;; --- Create Image
(def allowed-file-types #{"image/jpeg" "image/png"})
(defrecord CreateImages [id files on-uploaded]
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:dashboard :images :uploading] true))
ptk/WatchEvent
(watch [_ state stream]
(letfn [(image-size [file]
(->> (files/get-image-size file)
(rx/map (partial vector file))))
(allowed-file? [file]
(contains? allowed-file-types (.-type file)))
(finalize-upload [state]
(assoc-in state [:dashboard :images :uploading] false))
(prepare [[file [width height]]]
(cond-> {:name (.-name file)
:mimetype (.-type file)
:id (uuid/random)
:file file
:width width
:height height}
id (assoc :collection-id id)))]
(->> (rx/from-coll files)
(rx/filter allowed-file?)
(rx/mapcat image-size)
(rx/map prepare)
(rx/mapcat #(rp/mutation! :create-image %))
(rx/map :payload)
(rx/reduce conj [])
(rx/do #(st/emit! finalize-upload))
(rx/do on-uploaded)
(rx/mapcat identity)
(rx/map image-created)))))
(defn create-images
([id files]
{:pre [(or (uuid? id) (nil? id))]}
(CreateImages. id files (constantly nil)))
([id files] (create-images id files identity))
([id files on-uploaded]
{:pre [(or (uuid? id) (nil? id)) (fn? on-uploaded)]}
(CreateImages. id files on-uploaded)))
(s/assert (s/nilable ::us/uuid) id)
(s/assert fn? on-uploaded)
(ptk/reify ::create-images
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:dashboard :images :uploading] true))
ptk/WatchEvent
(watch [_ state stream]
(letfn [(image-size [file]
(->> (files/get-image-size file)
(rx/map (partial vector file))))
(allowed-file? [file]
(contains? allowed-file-types (.-type file)))
(finalize-upload [state]
(assoc-in state [:dashboard :images :uploading] false))
(prepare [[file [width height]]]
(cond-> {:name (.-name file)
:mimetype (.-type file)
:id (uuid/random)
:file file
:width width
:height height}
id (assoc :collection-id id)))]
(->> (rx/from files)
(rx/filter allowed-file?)
(rx/mapcat image-size)
(rx/map prepare)
(rx/mapcat #(rp/mutation! :create-image %))
(rx/reduce conj [])
(rx/do #(st/emit! finalize-upload))
(rx/do on-uploaded)
(rx/mapcat identity)
(rx/map image-created)))))))
;; --- Update Image
@ -259,32 +223,29 @@
;; --- Images Fetched
(defrecord ImagesFetched [items]
ptk/UpdateEvent
(update [_ state]
(reduce (fn [state {:keys [id] :as image}]
(assoc-in state [:images id] image))
state
items)))
(defn images-fetched
[items]
(ImagesFetched. items))
(s/assert (s/every ::image-entity) items)
(ptk/reify ::images-fetched
ptk/UpdateEvent
(update [_ state]
(reduce (fn [state {:keys [id] :as image}]
(assoc-in state [:images id] image))
state
items))))
;; --- Fetch Images
(defrecord FetchImages [id]
ptk/WatchEvent
(watch [_ state s]
(let [params (cond-> {} id (assoc :collection-id id))]
(->> (rp/query! :images-by-collection params)
(rx/map images-fetched)))))
(defn fetch-images
"Fetch a list of images of the selected collection"
[id]
{:pre [(or (uuid? id) (nil? id))]}
(FetchImages. id))
(s/assert (s/nilable ::us/uuid) id)
(ptk/reify ::fetch-images
ptk/WatchEvent
(watch [_ state s]
(let [params (cond-> {} id (assoc :collection-id id))]
(->> (rp/query! :images-by-collection params)
(rx/map images-fetched))))))
;; --- Fetch Image
@ -392,10 +353,10 @@
(watch [_ state stream]
(let [selected (get-in state [:dashboard :images :selected])]
(rx/merge
(->> (rx/from-coll selected)
(->> (rx/from selected)
(rx/flat-map #(rp/mutation! :copy-image {:id % :collection-id id}))
(rx/map image-created))
(->> (rx/from-coll selected)
(->> (rx/from selected)
(rx/map deselect-image))))))
(defn copy-selected
@ -418,9 +379,9 @@
(watch [_ state stream]
(let [selected (get-in state [:dashboard :images :selected])]
(rx/merge
(->> (rx/from-coll selected)
(->> (rx/from selected)
(rx/map persist-image))
(->> (rx/from-coll selected)
(->> (rx/from selected)
(rx/map deselect-image))))))
(defn move-selected
@ -434,7 +395,7 @@
ptk/WatchEvent
(watch [_ state stream]
(let [selected (get-in state [:dashboard :images :selected])]
(->> (rx/from-coll selected)
(->> (rx/from selected)
(rx/map delete-image)))))
(defn delete-selected

View file

@ -1,325 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.pages
"Page related events (for workspace mainly)."
(:require
[cljs.spec.alpha :as s]
[beicon.core :as rx]
[cuerdas.core :as str]
[potok.core :as ptk]
[uxbox.main.repo.core :as rp]
[uxbox.main.data.projects :as dp]
[uxbox.util.data :refer [index-by-id concatv]]
[uxbox.util.spec :as us]
[uxbox.util.timers :as ts]
[uxbox.util.uuid :as uuid]))
;; --- Struct
(s/def ::id ::us/uuid)
(s/def ::name ::us/string)
(s/def ::inst ::us/inst)
(s/def ::type ::us/keyword)
(s/def ::file-id ::us/uuid)
(s/def ::created-at ::us/inst)
(s/def ::modified-at ::us/inst)
(s/def ::version ::us/number)
(s/def ::width (s/and ::us/number ::us/positive))
(s/def ::height (s/and ::us/number ::us/positive))
(s/def ::grid-x-axis ::us/number)
(s/def ::grid-y-axis ::us/number)
(s/def ::grid-color ::us/string)
(s/def ::background ::us/string)
(s/def ::background-opacity ::us/number)
(s/def ::ordering ::us/number)
(s/def ::user ::us/uuid)
(s/def ::metadata
(s/keys :opt-un [::grid-y-axis
::grid-x-axis
::grid-color
::background
::background-opacity]))
;; TODO: start using uxbox.common.pagedata/data spec ...
(s/def ::minimal-shape
(s/keys :req-un [::type ::name]
:opt-un [::id]))
(s/def ::shapes (s/coll-of ::us/uuid :kind vector?))
(s/def ::canvas (s/coll-of ::us/uuid :kind vector?))
(s/def ::shapes-by-id
(s/map-of ::us/uuid ::minimal-shape))
(s/def ::data
(s/keys :req-un [::shapes ::canvas ::shapes-by-id]))
(s/def ::page
(s/keys :req-un [::id
::name
::file-id
::created-at
::modified-at
::user-id
::ordering
::metadata
::data]))
(s/def ::pages
(s/every ::page :kind vector?))
;; --- Protocols
(defprotocol IPageDataUpdate
"A marker protocol for mark events that alters the
page and is subject to perform a backend synchronization.")
(defprotocol IPagePersistentOps
(-persistent-ops [o] "Get a list of ops for the event."))
(defn page-update?
[o]
(or (satisfies? IPageDataUpdate o)
(= ::page-data-update o)))
;; --- Helpers
;; (defn pack-page
;; "Return a packed version of page object ready
;; for send to remore storage service."
;; [state id]
;; (letfn [(pack-shapes [ids]
;; (mapv #(get-in state [:shapes %]) ids))]
;; (let [page (get-in state [:pages id])
;; data {:shapes (pack-shapes (concatv (:canvas page)
;; (:shapes page)))}]
;; (-> page
;; (assoc :data data)
;; (dissoc :shapes)))))
(defn unpack-page
[state {:keys [id data metadata] :as page}]
(-> state
(update :pages assoc id (dissoc page :data))
(update :pages-data assoc id data)))
(defn purge-page
"Remove page and all related stuff from the state."
[state id]
(if-let [file-id (get-in state [:pages id :file-id])]
(-> state
(update-in [:files file-id :pages] #(filterv (partial not= id) %))
(update-in [:workspace-file :pages] #(filterv (partial not= id) %))
(update :pages dissoc id)
(update :pages-data dissoc id))
state))
;; --- Fetch Pages (by File ID)
(declare pages-fetched)
(defn fetch-pages
[file-id]
(s/assert ::us/uuid file-id)
(reify
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query :project-pages {:file-id file-id})
(rx/map pages-fetched)))))
;; --- Pages Fetched
(defn pages-fetched
[pages]
(s/assert ::pages pages)
(ptk/reify ::pages-fetched
IDeref
(-deref [_] pages)
ptk/UpdateEvent
(update [_ state]
(reduce unpack-page state pages))))
;; --- Fetch Page (By ID)
(declare page-fetched)
(defn fetch-page
"Fetch page by id."
[id]
(s/assert ::us/uuid id)
(reify
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query :project-page {:id id})
(rx/map page-fetched)))))
;; --- Page Fetched
(defn page-fetched
[data]
(s/assert ::page data)
(ptk/reify ::page-fetched
IDeref
(-deref [_] data)
ptk/UpdateEvent
(update [_ state]
(unpack-page state data))))
;; --- Create Page
(declare page-created)
(s/def ::create-page
(s/keys :req-un [::name ::file-id]))
(defn create-page
[{:keys [file-id name] :as data}]
(s/assert ::create-page data)
(ptk/reify ::create-page
ptk/WatchEvent
(watch [this state s]
(let [ordering (count (get-in state [:files file-id :pages]))
params {:name name
:file-id file-id
:ordering ordering
:data {:shapes []
:canvas []
:shapes-by-id {}}
:metadata {}}]
(->> (rp/mutation :create-project-page params)
(rx/map page-created))))))
;; --- Page Created
(defn page-created
[{:keys [id file-id] :as page}]
(s/assert ::page page)
(ptk/reify ::page-created
cljs.core/IDeref
(-deref [_] page)
ptk/UpdateEvent
(update [_ state]
(let [data (:data page)
page (dissoc page :data)]
(-> state
(update-in [:workspace-file :pages] (fnil conj []) id)
(update :pages assoc id page)
(update :pages-data assoc id data))))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (uxbox.main.data.projects/fetch-file file-id)))))
;; --- Rename Page
(s/def ::rename-page
(s/keys :req-un [::id ::name]))
(defn rename-page
[{:keys [id name] :as data}]
(s/assert ::rename-page data)
(ptk/reify ::rename-page
ptk/UpdateEvent
(update [_ state]
(let [pid (get-in state [:workspace-page :id])
state (assoc-in state [:pages id :name] name)]
(cond-> state
(= pid id) (assoc-in [:workspace-page :name] name))))
ptk/WatchEvent
(watch [_ state stream]
(let [params {:id id :name name}]
(->> (rp/mutation :rename-page params)
(rx/map #(ptk/data-event ::page-renamed data)))))))
;; --- Delete Page (by ID)
(defn delete-page
[id]
{:pre [(uuid? id)]}
(reify
ptk/UpdateEvent
(update [_ state]
(purge-page state id))
ptk/WatchEvent
(watch [_ state s]
(let [page (:workspace-page state)]
(rx/merge
(->> (rp/mutation :delete-project-page {:id id})
(rx/flat-map (fn [_]
(if (= id (:id page))
(rx/of (dp/go-to (:file-id page)))
(rx/empty))))))))))
;; --- Persist Page
(declare page-persisted)
(def persist-current-page
(ptk/reify ::persist-page
ptk/WatchEvent
(watch [this state s]
(let [local (:workspace-local state)
page (:workspace-page state)
data (:workspace-data state)]
(if (:history local)
(rx/empty)
(let [page (assoc page :data data)]
(->> (rp/mutation :update-project-page-data page)
(rx/map (fn [res] (merge page res)))
(rx/map page-persisted)
(rx/catch (fn [err] (rx/of ::page-persist-error))))))))))
;; --- Page Persisted
(defn page-persisted
[{:keys [id] :as page}]
(s/assert ::page page)
(ptk/reify ::page-persisted
cljs.core/IDeref
(-deref [_] page)
ptk/UpdateEvent
(update [_ state]
(let [data (:data page)
page (dissoc page :data)]
(-> state
(assoc :workspace-data data)
(assoc :workspace-page page)
(update :pages assoc id page)
(update :pages-data assoc id data))))))
;; --- Update Page
;; TODO: deprecated, need refactor (this is used on page options)
(defn update-page-attrs
[{:keys [id] :as data}]
(s/assert ::page data)
(ptk/reify ::update-page-attrs
ptk/UpdateEvent
(update [_ state]
(update state :workspace-page merge (dissoc data :id :version)))))
;; --- Update Page Metadata
;; TODO: deprecated, need refactor (this is used on page options)
(defn update-metadata
[id metadata]
(s/assert ::id id)
(s/assert ::metadata metadata)
(reify
ptk/UpdateEvent
(update [this state]
(assoc-in state [:pages id :metadata] metadata))))

View file

@ -6,26 +6,34 @@
(ns uxbox.main.data.projects
(:require
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[beicon.core :as rx]
[potok.core :as ptk]
[uxbox.common.pages :as cp]
[uxbox.main.repo.core :as rp]
[uxbox.util.uuid :as uuid]
[uxbox.util.router :as rt]
[uxbox.util.spec :as us]
[uxbox.util.time :as dt]
[uxbox.util.router :as rt]))
[uxbox.util.timers :as ts]
[uxbox.util.uuid :as uuid]))
;; --- Specs
(s/def ::id uuid?)
(s/def ::name string?)
(s/def ::version integer?)
(s/def ::user-id uuid?)
(s/def ::created-at inst?)
(s/def ::modified-at inst?)
(s/def ::id ::us/uuid)
(s/def ::name ::us/string)
(s/def ::user ::us/uuid)
(s/def ::type ::us/keyword)
(s/def ::file-id ::us/uuid)
(s/def ::project-id ::us/uuid)
(s/def ::created-at ::us/inst)
(s/def ::modified-at ::us/inst)
(s/def ::version ::us/number)
(s/def ::ordering ::us/number)
(s/def ::metadata (s/nilable ::cp/metadata))
(s/def ::data ::cp/data)
(s/def ::project-entity
(s/def ::project
(s/keys ::req-un [::id
::name
::version
@ -33,23 +41,47 @@
::created-at
::modified-at]))
(declare fetch-projects)
(declare projects-fetched?)
(s/def ::file
(s/keys :req-un [::id
::name
::created-at
::modified-at
::project-id]))
(s/def ::page
(s/keys :req-un [::id
::name
::file-id
::version
::created-at
::modified-at
::user-id
::ordering
::metadata
::data]))
;; --- Helpers
(defn assoc-project
"A reduce function for assoc the project to the state map."
[state {:keys [id] :as project}]
(s/assert ::project-entity project)
(update-in state [:projects id] merge project))
(defn unpack-page
[state {:keys [id data metadata] :as page}]
(-> state
(update :pages assoc id (dissoc page :data))
(update :pages-data assoc id data)))
(defn dissoc-project
"A reduce function for dissoc the project from the state map."
(defn purge-page
"Remove page and all related stuff from the state."
[state id]
(update state :projects dissoc id))
(if-let [file-id (get-in state [:pages id :file-id])]
(-> state
(update-in [:files file-id :pages] #(filterv (partial not= id) %))
(update-in [:workspace-file :pages] #(filterv (partial not= id) %))
(update :pages dissoc id)
(update :pages-data dissoc id))
state))
;; --- Initialize
;; --- Initialize Dashboard
(declare fetch-projects)
(declare fetch-files)
(declare initialized)
@ -89,21 +121,8 @@
(when order {:order order})
(when filter {:filter filter})))))
;; --- Projects Fetched
(defn projects-fetched
[projects]
(s/assert (s/every ::project-entity) projects)
(ptk/reify ::projects-fetched
ptk/UpdateEvent
(update [_ state]
(reduce assoc-project state projects))))
(defn projects-fetched?
[v]
(= ::projects-fetched (ptk/type v)))
;; --- Fetch Projects
(declare projects-fetched)
(def fetch-projects
(ptk/reify ::fetch-projects
@ -112,6 +131,17 @@
(->> (rp/query :projects)
(rx/map projects-fetched)))))
;; --- Projects Fetched
(defn projects-fetched
[projects]
(s/assert (s/every ::project) projects)
(ptk/reify ::projects-fetched
ptk/UpdateEvent
(update [_ state]
(let [assoc-project #(update-in %1 [:projects (:id %2)] merge %2)]
(reduce assoc-project state projects)))))
;; --- Fetch Files
(declare files-fetched)
@ -138,11 +168,9 @@
;; --- Files Fetched
(s/def ::files any?)
(defn files-fetched
[files]
(s/assert ::files files)
(s/assert (s/every ::file) files)
(ptk/reify ::files-fetched
cljs.core/IDeref
(-deref [_] files)
@ -152,6 +180,34 @@
(let [assoc-file #(assoc-in %1 [:files (:id %2)] %2)]
(reduce assoc-file state files)))))
;; --- Create Project
(declare project-created)
(def create-project
(ptk/reify ::create-project
ptk/WatchEvent
(watch [this state stream]
(let [name (str "Project Name " (gensym "p"))]
(->> (rp/mutation! :create-project {:name name})
(rx/map (fn [data]
(projects-fetched [data]))))))))
;; --- Create File
(defn create-file
[{:keys [project-id] :as params}]
(ptk/reify ::create-file
ptk/WatchEvent
(watch [this state stream]
(let [name (str "File Name " (gensym "p"))
params {:name name :project-id project-id}]
(->> (rp/mutation! :create-project-file params)
(rx/mapcat
(fn [data]
(rx/of (files-fetched [data])
#(update-in % [:dashboard-projects :files project-id] conj (:id data))))))))))
;; --- Rename Project
(defn rename-project
@ -176,7 +232,7 @@
(ptk/reify ::delete-project
ptk/UpdateEvent
(update [_ state]
(dissoc-project state id))
(update state :projects dissoc id))
ptk/WatchEvent
(watch [_ state s]
@ -198,32 +254,6 @@
(->> (rp/mutation :delete-project-file {:id id})
(rx/ignore)))))
;; --- Create Project
(declare project-created)
(s/def ::create-project
(s/keys :req-un [::name]))
(defn create-project
[{:keys [name] :as params}]
(s/assert ::create-project params)
(ptk/reify ::create-project
ptk/WatchEvent
(watch [this state stream]
(->> (rp/mutation :create-project {:name name})
(rx/map project-created)))))
;; --- Project Created
(defn project-created
[data]
(ptk/reify ::project-created
ptk/UpdateEvent
(update [_ state]
(assoc-project state data))))
;; --- Rename Project
(defn rename-file
@ -262,3 +292,206 @@
(if (nil? id)
(rx/of (rt/nav :dashboard-projects {} {}))
(rx/of (rt/nav :dashboard-projects {} {:project-id (str id)}))))))
;; --- Fetch Pages (by File ID)
(declare pages-fetched)
(defn fetch-pages
[file-id]
(s/assert ::us/uuid file-id)
(reify
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query :project-pages {:file-id file-id})
(rx/map pages-fetched)))))
;; --- Pages Fetched
(defn pages-fetched
[pages]
(s/assert (s/every ::page) pages)
(ptk/reify ::pages-fetched
IDeref
(-deref [_] pages)
ptk/UpdateEvent
(update [_ state]
(reduce unpack-page state pages))))
;; --- Fetch Page (By ID)
(declare page-fetched)
(defn fetch-page
"Fetch page by id."
[id]
(s/assert ::us/uuid id)
(reify
ptk/WatchEvent
(watch [_ state s]
(->> (rp/query :project-page {:id id})
(rx/map page-fetched)))))
;; --- Page Fetched
(defn page-fetched
[data]
(s/assert ::page data)
(ptk/reify ::page-fetched
IDeref
(-deref [_] data)
ptk/UpdateEvent
(update [_ state]
(unpack-page state data))))
;; --- Create Page
(declare page-created)
(s/def ::create-page
(s/keys :req-un [::name ::file-id]))
(defn create-page
[{:keys [file-id name] :as data}]
(s/assert ::create-page data)
(ptk/reify ::create-page
ptk/WatchEvent
(watch [this state s]
(let [ordering (count (get-in state [:files file-id :pages]))
params {:name name
:file-id file-id
:ordering ordering
:data {:shapes []
:canvas []
:shapes-by-id {}}
:metadata {}}]
(->> (rp/mutation :create-project-page params)
(rx/map page-created))))))
;; --- Page Created
(defn page-created
[{:keys [id file-id] :as page}]
(s/assert ::page page)
(ptk/reify ::page-created
cljs.core/IDeref
(-deref [_] page)
ptk/UpdateEvent
(update [_ state]
(let [data (:data page)
page (dissoc page :data)]
(-> state
(update-in [:workspace-file :pages] (fnil conj []) id)
(update :pages assoc id page)
(update :pages-data assoc id data))))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (uxbox.main.data.projects/fetch-file file-id)))))
;; --- Rename Page
(s/def ::rename-page
(s/keys :req-un [::id ::name]))
(defn rename-page
[{:keys [id name] :as data}]
(s/assert ::rename-page data)
(ptk/reify ::rename-page
ptk/UpdateEvent
(update [_ state]
(let [pid (get-in state [:workspace-page :id])
state (assoc-in state [:pages id :name] name)]
(cond-> state
(= pid id) (assoc-in [:workspace-page :name] name))))
ptk/WatchEvent
(watch [_ state stream]
(let [params {:id id :name name}]
(->> (rp/mutation :rename-page params)
(rx/map #(ptk/data-event ::page-renamed data)))))))
;; --- Delete Page (by ID)
(defn delete-page
[id]
{:pre [(uuid? id)]}
(reify
ptk/UpdateEvent
(update [_ state]
(purge-page state id))
ptk/WatchEvent
(watch [_ state s]
(let [page (:workspace-page state)]
(rx/merge
(->> (rp/mutation :delete-project-page {:id id})
(rx/flat-map (fn [_]
(if (= id (:id page))
(rx/of (go-to (:file-id page)))
(rx/empty))))))))))
;; --- Persist Page
(declare page-persisted)
(def persist-current-page
(ptk/reify ::persist-page
ptk/WatchEvent
(watch [this state s]
(let [local (:workspace-local state)
page (:workspace-page state)
data (:workspace-data state)]
(if (:history local)
(rx/empty)
(let [page (assoc page :data data)]
(->> (rp/mutation :update-project-page-data page)
(rx/map (fn [res] (merge page res)))
(rx/map page-persisted)
(rx/catch (fn [err] (rx/of ::page-persist-error))))))))))
;; --- Page Persisted
(defn page-persisted
[{:keys [id] :as page}]
(s/assert ::page page)
(ptk/reify ::page-persisted
cljs.core/IDeref
(-deref [_] page)
ptk/UpdateEvent
(update [_ state]
(let [data (:data page)
page (dissoc page :data)]
(-> state
(assoc :workspace-data data)
(assoc :workspace-page page)
(update :pages assoc id page)
(update :pages-data assoc id data))))))
;; --- Update Page
;; TODO: deprecated, need refactor (this is used on page options)
(defn update-page-attrs
[{:keys [id] :as data}]
(s/assert ::page data)
(ptk/reify ::update-page-attrs
ptk/UpdateEvent
(update [_ state]
(update state :workspace-page merge (dissoc data :id :version)))))
;; --- Update Page Metadata
;; TODO: deprecated, need refactor (this is used on page options)
(defn update-metadata
[id metadata]
(s/assert ::id id)
(s/assert ::metadata metadata)
(reify
ptk/UpdateEvent
(update [this state]
(assoc-in state [:pages id :metadata] metadata))))

View file

@ -9,7 +9,7 @@
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[potok.core :as ptk]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.projects :as dp]
[uxbox.main.store :as st]
[uxbox.util.spec :as us]))
@ -28,9 +28,9 @@
(ptk/reify ::watch-page-changes
ptk/WatchEvent
(watch [_ state stream]
#_(let [stopper (rx/filter #(= % ::udp/stop-page-watcher) stream)]
#_(let [stopper (rx/filter #(= % ::dp/stop-page-watcher) stream)]
(->> stream
(rx/filter udp/page-update?)
(rx/filter dp/page-update?)
(rx/filter #(not (undo? %)))
(rx/filter #(not (redo? %)))
(rx/map #(save-undo-entry id))
@ -49,7 +49,7 @@
(ptk/reify ::save-undo-entry
ptk/UpdateEvent
(update [_ state]
#_(let [page (udp/pack-page state id)
#_(let [page (dp/pack-page state id)
undo {:data (:data page)
:metadata (:metadata page)}]
(-> state
@ -82,7 +82,7 @@
;; (pp/pprint packed)
(-> state
(udp/unpack-page packed)
(dp/unpack-page packed)
(assoc-in [:undo pid :selected] pointer))))))))
(defn undo?
@ -113,7 +113,7 @@
;; (pp/pprint packed)
(-> state
(udp/unpack-page packed)
(dp/unpack-page packed)
(assoc-in [:undo pid :selected] pointer))))))))
(defn redo?

View file

@ -8,6 +8,7 @@
(:require
[cljs.spec.alpha :as s]
[beicon.core :as rx]
[cuerdas.core :as str]
[potok.core :as ptk]
[uxbox.main.repo.core :as rp]
[uxbox.util.i18n :as i18n :refer [tr]]
@ -31,7 +32,7 @@
;; --- Profile Fetched
(s/def ::profile-fetched-params
(s/def ::profile-fetched
(s/keys :req-un [::id
::username
::fullname
@ -41,8 +42,8 @@
(defn profile-fetched
[data]
(s/assert ::profile-fetched-params data)
(reify
(s/assert ::profile-fetched data)
(ptk/reify ::profile-fetched
ptk/UpdateEvent
(update [_ state]
(assoc state :profile data))

View file

@ -12,9 +12,9 @@
[uxbox.config :as cfg]
[uxbox.common.data :as d]
[uxbox.common.pages :as cp]
[uxbox.main.websockets :as ws]
[uxbox.main.constants :as c]
[uxbox.main.data.icons :as udi]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.projects :as dp]
[uxbox.main.geom :as geom]
[uxbox.main.refs :as refs]
@ -28,15 +28,16 @@
[uxbox.util.perf :as perf]
[uxbox.util.router :as rt]
[uxbox.util.spec :as us]
[uxbox.util.transit :as t]
[uxbox.util.time :as dt]
[uxbox.util.uuid :as uuid]))
[uxbox.util.uuid :as uuid]
[vendor.randomcolor]))
;; TODO: temporal workaround
(def clear-ruler nil)
(def start-ruler nil)
(declare shapes-overlaps?)
;; --- Specs
(s/def ::id ::us/uuid)
@ -49,7 +50,6 @@
(s/def ::font-size number?)
(s/def ::font-style string?)
(s/def ::font-weight string?)
(s/def ::height number?)
(s/def ::hidden boolean?)
(s/def ::id uuid?)
(s/def ::letter-spacing number?)
@ -66,12 +66,13 @@
(s/def ::stroke-style #{:none :solid :dotted :dashed :mixed})
(s/def ::stroke-width number?)
(s/def ::text-align #{"left" "right" "center" "justify"})
(s/def ::type #{:rect :path :circle :image :text})
(s/def ::type #{:rect :path :circle :image :text :canvas})
(s/def ::x number?)
(s/def ::y number?)
(s/def ::cx number?)
(s/def ::cy number?)
(s/def ::width number?)
(s/def ::x1 number?)
(s/def ::x2 number?)
(s/def ::y1 number?)
(s/def ::y2 number?)
(s/def ::height number?)
(s/def ::attributes
(s/keys :opt-un [::blocked
@ -90,13 +91,14 @@
::proportion
::proportion-lock
::rx ::ry
::cx ::cy
::x ::y
::stroke-color
::stroke-opacity
::stroke-style
::stroke-width
::text-align
::x1 ::x2
::y1 ::y2]))
::width ::height]))
(s/def ::minimal-shape
(s/keys :req-un [::id ::page ::type ::name]))
@ -114,6 +116,103 @@
(defn interrupt? [e] (= e :interrupt))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Websockets Events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Initialize WebSocket
(declare fetch-users)
(declare handle-who)
(declare handle-pointer-update)
(declare handle-page-snapshot)
(declare shapes-changes-commited)
(s/def ::type keyword?)
(s/def ::message
(s/keys :req-un [::type]))
(defn initialize-ws
[file-id]
(ptk/reify ::initialize
ptk/UpdateEvent
(update [_ state]
(let [uri (str "ws://localhost:6060/sub/" file-id)]
(assoc-in state [:ws file-id] (ws/open uri))))
ptk/WatchEvent
(watch [_ state stream]
(let [wsession (get-in state [:ws file-id])]
(->> (rx/merge
(rx/of (fetch-users file-id))
(->> (ws/-stream wsession)
(rx/filter #(= :message (:type %)))
(rx/map (comp t/decode :payload))
(rx/filter #(s/valid? ::message %))
(rx/map (fn [{:keys [type] :as msg}]
(case type
:who (handle-who msg)
:pointer-update (handle-pointer-update msg)
:page-snapshot (handle-page-snapshot msg)
::unknown)))))
(rx/take-until
(rx/filter #(= ::finalize %) stream)))))))
;; --- Finalize Websocket
(defn finalize-ws
[file-id]
(ptk/reify ::finalize
ptk/WatchEvent
(watch [_ state stream]
(ws/-close (get-in state [:ws file-id]))
(rx/of ::finalize))))
;; --- Handle: Who
;; TODO: assign color
(defn- assign-user-color
[state user-id]
(let [user (get-in state [:workspace-users :by-id user-id])
color (js/randomcolor)
user (if (string? (:color user))
user
(assoc user :color color))]
(assoc-in state [:workspace-users :by-id user-id] user)))
(defn handle-who
[{:keys [users] :as msg}]
(s/assert set? users)
(ptk/reify ::handle-who
ptk/UpdateEvent
(update [_ state]
(as-> state $$
(assoc-in $$ [:workspace-users :active] users)
(reduce assign-user-color $$ users)))))
(defn handle-pointer-update
[{:keys [user-id page-id x y] :as msg}]
(ptk/reify ::handle-pointer-update
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-users :pointer user-id]
{:page-id page-id
:user-id user-id
:x x
:y y}))))
(defn handle-page-snapshot
[{:keys [user-id page-id version operations] :as msg}]
(ptk/reify ::handle-page-snapshot
ptk/WatchEvent
(watch [_ state stream]
(let [local (:workspace-local state)]
(when (= (:page-id local) page-id)
(rx/of (shapes-changes-commited msg)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; General workspace events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -122,7 +221,7 @@
(declare initialize-alignment)
(def default-layout #{:sitemap :drawtools :layers :element-options :rules})
(def default-layout #{:sitemap :layers :element-options :rules})
(def workspace-default
{:zoom 1
@ -133,72 +232,122 @@
:tooltip nil})
(declare initialized)
(declare watch-page-changes)
;; (declare watch-events)
(defn initialize
"Initialize the workspace state."
[file-id page-id]
[file-id]
(s/assert ::us/uuid file-id)
(s/assert ::us/uuid page-id)
(ptk/reify ::initialize
ptk/UpdateEvent
(update [_ state]
(let [local (assoc workspace-default
:file-id file-id
:page-id page-id)]
:file-id file-id)
;; :page-id page-id)
;; TODO: this need to be parametrized
uri (str "ws://localhost:6060/sub/" file-id)]
(-> state
(assoc :workspace-layout default-layout)
;; (update :workspace-layout
;; (fn [data]
;; (if (nil? data) default-layout data)))
(assoc :workspace-local local))))
(assoc :workspace-local local)
(assoc-in [:ws file-id] (ws/open uri)))))
ptk/WatchEvent
(watch [_ state stream]
#_(when-not (get-in state [:pages page-id])
(reset! st/loader true))
(let [wsession (get-in state [:ws file-id])]
(rx/merge
;; Stop possible previous watchers and re-fetch the main page
;; and all project related pages.
(rx/of ::stop-watcher
(dp/fetch-file file-id)
(dp/fetch-pages file-id)
(fetch-users file-id))
(rx/merge
;; Stop possible previous watchers and re-fetch the main page
;; and all project related pages.
(rx/of ::stop-watcher
(udp/fetch-page page-id)
(dp/fetch-file file-id)
(udp/fetch-pages file-id))
;; When main page is fetched, schedule the main initialization.
(->> (rx/zip (rx/filter (ptk/type? ::dp/pages-fetched) stream)
(rx/filter (ptk/type? ::dp/files-fetched) stream))
(rx/take 1)
(rx/do #(reset! st/loader false))
(rx/mapcat #(rx/of (initialized file-id)
#_(initialize-alignment page-id))))
;; When main page is fetched, schedule the main initialization.
(->> (rx/zip (rx/filter (ptk/type? ::udp/page-fetched) stream)
(rx/filter (ptk/type? ::dp/files-fetched) stream))
(rx/take 1)
(rx/do #(reset! st/loader false))
(rx/mapcat #(rx/of (initialized file-id page-id)
#_(initialize-alignment page-id))))
;; WebSocket Incoming Messages Handling
(->> (ws/-stream wsession)
(rx/filter #(= :message (:type %)))
(rx/map (comp t/decode :payload))
(rx/filter #(s/valid? ::message %))
(rx/map (fn [{:keys [type] :as msg}]
(case type
:who (handle-who msg)
:pointer-update (handle-pointer-update msg)
:page-snapshot (handle-page-snapshot msg)
::unknown))))
;; When workspace is initialized, run the event watchers.
(->> (rx/filter (ptk/type? ::initialized) stream)
(rx/take 1)
(rx/mapcat #(rx/of watch-page-changes)))))
ptk/EffectEvent
(effect [_ state stream]
;; Optimistic prefetch of projects if them are not already fetched
#_(when-not (seq (:projects state))
(st/emit! (dp/fetch-projects))))))
#_(->> stream
;; TODO: this need to be rethinked
(rx/filter uxbox.main.ui.workspace.streams/pointer-event?)
(rx/sample 150)
(rx/tap (fn [{:keys [pt] :as event}]
(let [msg {:type :pointer-update
:page-id page-id
:x (:x pt)
:y (:y pt)}]
(ws/-send (get-in state [:ws file-id]) (t/encode msg)))))
(rx/ignore)
(rx/take-until (rx/filter #(= ::finalize %) stream))))))))
(defn- initialized
[file-id page-id]
[file-id]
(s/assert ::us/uuid file-id)
(s/assert ::us/uuid page-id)
(ptk/reify ::initialized
ptk/UpdateEvent
(update [_ state]
(let [file (get-in state [:files file-id])
page (get-in state [:pages page-id])
(let [file (get-in state [:files file-id])]
(assoc state :workspace-file file)))))
(defn finalize
[file-id]
(ptk/reify ::finalize
cljs.core/IDeref
(-deref [_] file-id)
ptk/EffectEvent
(effect [_ state stream]
(ws/-close (get-in state [:ws file-id])))))
(defn initialize-page
[page-id]
(ptk/reify ::initialize-page
ptk/UpdateEvent
(update [_ state]
(let [page (get-in state [:pages page-id])
data (get-in state [:pages-data page-id])]
(assoc state
:workspace-file file
:workspace-data data
:workspace-page page)))))
:workspace-page page)))
ptk/EffectEvent
(effect [_ state stream])))
;; --- Fetch Workspace Users
(declare users-fetched)
(defn fetch-users
[file-id]
(ptk/reify ::fetch-users
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :project-file-users {:file-id file-id})
(rx/map users-fetched)))))
(defn users-fetched
[users]
(ptk/reify ::users-fetched
ptk/UpdateEvent
(update [_ state]
(reduce (fn [state user]
(update-in state [:workspace-users :by-id (:id user)] merge user))
state
users))))
;; --- Toggle layout flag
@ -214,7 +363,6 @@
(disj flags flag)
(conj flags flag)))))))
;; --- Workspace Flags
(defn activate-flag
@ -237,7 +385,6 @@
(update [_ state]
(update-in state [:workspace-local :flags] disj flag))))
(defn toggle-flag
[flag]
(s/assert keyword? flag)
@ -458,23 +605,59 @@
(assoc-in $ [:workspace-data :shapes-by-id id] shape))))
(declare commit-shapes-changes)
(declare select-shape)
(declare recalculate-shape-canvas-relation)
(def shape-default-attrs
{:stroke-color "#000000"
:stroke-opacity 1
:fill-color "#000000"
:fill-opacity 1})
(defn add-shape
[data]
(s/assert ::attributes data)
(let [id (uuid/random)]
(ptk/reify ::add-shape
ptk/UpdateEvent
(update [_ state]
(let [shape (-> (geom/setup-proportions data)
(assoc :id id))
shape (merge shape-default-attrs shape)
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]])))))))
(rx/of (commit-shapes-changes [[:add-shape id shape]])
(select-shape id)))))))
(def canvas-default-attrs
{:stroke-color "#000000"
:stroke-opacity 1
:fill-color "#ffffff"
:fill-opacity 1})
(defn add-canvas
[data]
(s/assert ::attributes data)
(let [id (uuid/random)]
(ptk/reify ::add-canvas
ptk/UpdateEvent
(update [_ state]
(let [shape (-> (geom/setup-proportions data)
(assoc :id id))
shape (merge canvas-default-attrs shape)
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-canvas id shape]])
(select-shape id)))))))
;; --- Duplicate Selected
@ -491,13 +674,12 @@
duplicate (partial impl-duplicate-shape state)
shapes (map duplicate selected)]
(rx/merge
(rx/from-coll (map (fn [s] #(impl-assoc-shape % s)) shapes))
(rx/from (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)
(defn select-shape
"Mark a shape selected for drawing."
[id]
(s/assert ::us/uuid id)
(ptk/reify ::select-shape
@ -523,18 +705,6 @@
(assoc :selected #{})
(dissoc :selected-canvas))))))
;; --- Select First Shape
;; TODO: first???
(def select-first-shape
(ptk/reify ::select-first-shape
ptk/UpdateEvent
(update [_ state]
(let [pid (get-in state [:workspace-local :id])
sid (first (get-in state [:workspace-data :shapes]))]
(assoc-in state [:workspace-local :selected] #{sid})))))
;; --- Select Shapes (By selrect)
(defn- impl-try-match-shape
@ -571,46 +741,35 @@
;; --- Update Shape Attrs
(defn update-shape-attrs
[id attrs]
(s/assert ::us/uuid id)
(let [atts (s/conform ::attributes attrs)]
(ptk/reify ::update-shape-attrs
ptk/UpdateEvent
(update [_ state]
(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'))))))
[id attrs]
(s/assert ::attributes attrs)
(ptk/reify ::update-shape
ptk/UpdateEvent
(update [_ state]
(let [shape-old (get-in state [:workspace-data :shapes-by-id id])
shape-new (merge shape-old attrs)
diff (d/diff-maps shape-old shape-new)]
(-> state
(assoc-in [:workspace-data :shapes-by-id id] shape-new)
(assoc ::tmp-change (into [:mod-shape id] diff)))))
ptk/WatchEvent
(watch [_ state stream]
(let [change (::tmp-change state)]
(prn "update-shape" change)
(rx/of (commit-shapes-changes [change])
#(dissoc state ::tmp-change))))))
;; --- Update Selected Shapes attrs
;; TODO: improve performance of this event
(defn update-selected-shapes-attrs
[attrs]
(s/assert ::attributes attrs)
(defn update-selected-shapes
[& attrs]
(ptk/reify ::update-selected-shapes-attrs
ptk/WatchEvent
(watch [_ state stream]
(let [selected (get-in state [:workspace-local :selected])]
(rx/from-coll (map #(update-shape-attrs % attrs) selected))))))
(rx/from (map #(apply update-shape % attrs) selected))))))
;; --- Move Selected
@ -664,19 +823,6 @@
(rx/of (apply-temporal-displacement-in-bulk selected displacement))
(rx/of (materialize-temporal-modifier-in-bulk selected)))))))
;; --- Update Shape Position
(deftype UpdateShapePosition [id point]
ptk/UpdateEvent
(update [_ state]
(update-in state [:shapes id] geom/absolute-move point)))
(defn update-position
"Update the start position coordenate of the shape."
[id point]
{:pre [(uuid? id) (gpt/point? point)]}
(UpdateShapePosition. id point))
;; --- Delete Selected
(defn impl-dissoc-shape
@ -726,7 +872,6 @@
[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]))
@ -825,9 +970,10 @@
(defn- recalculate-shape-canvas-relation
[state shape]
(let [xfmt (comp (map #(get-in state [:workspace-data :shapes-by-id %]))
(let [shape' (geom/shape->rect-shape shape)
xfmt (comp (map #(get-in state [:workspace-data :shapes-by-id %]))
(map geom/shape->rect-shape)
(filter #(geom/overlaps? % shape))
(filter #(geom/overlaps? % shape'))
(map :id))
id (->> (get-in state [:workspace-data :canvas])
@ -859,8 +1005,6 @@
(rx/of (commit-shapes-changes changes)
#(dissoc state ::tmp-changes)))))))
(declare shapes-changes-commited)
(defn commit-shapes-changes
[operations]
(s/assert ::cp/operations operations)
@ -878,31 +1022,22 @@
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))))
))
(rx/map shapes-changes-commited))))))
(s/def ::shapes-changes-commited
(s/keys :req-un [::id ::version ::cp/operations]))
(s/keys :req-un [::page-id ::version ::cp/operations]))
(defn shapes-changes-commited
[{:keys [id version operations] :as params}]
[{:keys [page-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)
(assoc-in [:pages page-id :version] version)
(update-in [:pages-data page-id] cp/process-ops operations)
(update :workspace-data cp/process-ops operations)))))
;; --- Start shape "edition mode"
@ -936,52 +1071,28 @@
(ptk/reify ::select-for-drawing
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local assoc :drawing-tool tool :drawing data)))))
(update state :workspace-local assoc :drawing-tool tool :drawing data))
;; --- Shape Proportions
;; TODO: revisit
(deftype LockShapeProportions [id]
ptk/UpdateEvent
(update [_ state]
(let [[width height] (-> (get-in state [:shapes id])
(geom/size)
(keep [:width :height]))
proportion (/ width height)]
(update-in state [:shapes id] assoc
:proportion proportion
:proportion-lock true))))
(defn lock-proportions
"Mark proportions of the shape locked and save the current
proportion as additional precalculated property."
[id]
{:pre [(uuid? id)]}
(LockShapeProportions. id))
;; TODO: revisit
(deftype UnlockShapeProportions [id]
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:shapes id :proportion-lock] false)))
(defn unlock-proportions
[id]
{:pre [(uuid? id)]}
(UnlockShapeProportions. id))
ptk/WatchEvent
(watch [_ state stream]
(let [cancel-event? (fn [event]
(interrupt? event))
stoper (rx/filter (ptk/type? ::clear-drawing) stream)]
(->> (rx/filter cancel-event? stream)
(rx/take 1)
(rx/map (constantly clear-drawing))
(rx/take-until stoper)))))))
;; --- Update Dimensions
;; TODO: revisit
(s/def ::width (s/and ::us/number ::us/positive))
(s/def ::height (s/and ::us/number ::us/positive))
(s/def ::width ::us/number)
(s/def ::height ::us/number)
(s/def ::update-dimensions
(s/keys :opt-un [::width ::height]))
;; TODO: emit commit-changes
(defn update-dimensions
"A helper event just for update the position
of the shape using the width and height attrs
@ -992,35 +1103,31 @@
(ptk/reify ::update-dimensions
ptk/UpdateEvent
(update [_ state]
(update-in state [:shapes id] geom/resize-dim dimensions))))
(update-in state [:workspace-data :shapes-by-id id] geom/resize-dim dimensions))))
;; --- Update Interaction
;; --- Shape Proportions
;; TODO: revisit
(deftype UpdateInteraction [shape interaction]
ptk/UpdateEvent
(update [_ state]
(let [id (or (:id interaction)
(uuid/random))
data (assoc interaction :id id)]
(assoc-in state [:shapes shape :interactions id] data))))
(defn toggle-shape-proportion-lock
[id]
(ptk/reify ::toggle-shape-proportion-lock
ptk/UpdateEvent
(update [_ state]
(let [shape (get-in state [:workspace-data :shapes-by-id id])]
(if (:proportion-lock shape)
(assoc-in state [:workspace-data :shapes-by-id id :proportion-lock] false)
(->> (geom/assign-proportions (assoc shape :proportion-lock true))
(assoc-in state [:workspace-data :shapes-by-id id])))))))
(defn update-interaction
[shape interaction]
(UpdateInteraction. shape interaction))
;; --- Update Shape Position
;; --- Delete Interaction
;; TODO: revisit
(deftype DeleteInteracton [shape id]
ptk/UpdateEvent
(update [_ state]
(update-in state [:shapes shape :interactions] dissoc id)))
(defn delete-interaction
[shape id]
{:pre [(uuid? id) (uuid? shape)]}
(DeleteInteracton. shape id))
(defn update-position
[id point]
(s/assert ::us/uuid id)
(s/assert gpt/point? point)
(ptk/reify ::update-position
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-data :shapes-by-id id] geom/absolute-move point))))
;; --- Path Modifications
@ -1141,46 +1248,6 @@
{:pre [(uuid? id)]}
(UnlockShape. id))
;; --- Recalculate Shapes relations (Shapes <-> Canvas)
(def rehash-shapes-relationships
(letfn [(overlaps? [canvas shape]
(let [shape1 (geom/shape->rect-shape canvas)
shape2 (geom/shape->rect-shape shape)]
(geom/overlaps? shape1 shape2)))]
(ptk/reify ::rehash-shapes-relationships
ptk/UpdateEvent
(update [_ state]
(let [data (:workspace-data state)
canvas (map #(get-in data [:shapes-by-id %]) (:canvas data))
shapes (map #(get-in data [:shapes-by-id %]) (:shapes data))]
(reduce (fn [state {:keys [id] :as shape}]
(let [canvas (first (filter #(overlaps? % shape) canvas))]
(update-in state [:workspace-data :shapes-by-id id] assoc :canvas (:id canvas))))
state
shapes))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Selection Rect IMPL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO: move to shapes impl maybe...
(defn selection->rect
[data]
(let [start (:start data)
stop (:stop data)
start-x (min (:x start) (:x stop))
start-y (min (:y start) (:y stop))
end-x (max (:x start) (:x stop))
end-y (max (:y start) (:y stop))]
(assoc data
:x1 start-x
:y1 start-y
:x2 end-x
:y2 end-y
:type :rect)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Canvas Interactions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1193,24 +1260,6 @@
(update [_ state]
(update state :workspace-local assoc :selected-canvas id))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Server Interactions DEPRECATED
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Update Metadata
;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event.
(defn update-metadata
[id metadata]
(s/assert ::us/uuid id)
(s/assert ::udp/metadata metadata)
(ptk/reify ::update-metadata
ptk/WatchEvent
(watch [_ state s]
#_(rx/of (udp/update-metadata id metadata)
(initialize-alignment id)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Navigation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1254,66 +1303,3 @@
pages (vec (concat before [id] after))]
(assoc-in state [:projects (:project-id page) :pages] pages)))))
;; -- Page Changes Watcher
(def watch-page-changes
(ptk/reify ::watch-page-changes
ptk/WatchEvent
(watch [_ state stream]
(let [stopper (rx/filter #(= % ::stop-watcher) stream)]
(->> stream
(rx/filter udp/page-update?)
(rx/debounce 500)
(rx/mapcat #(rx/of rehash-shapes-relationships
udp/persist-current-page))
(rx/take-until stopper))))))
;; (def watch-shapes-changes
;; (letfn [(look-for-changes [[old new]]
;; (reduce-kv (fn [acc k v]
;; (if (identical? v (get old k))
;; acc
;; (conj acc k)))
;; #{}
;; new))
;; (select-shapes [state]
;; (get-in state [:workspace-data :shapes-by-id]))
;; ]
;; (ptk/reify ::watch-page-changes
;; ptk/WatchEvent
;; (watch [_ state stream]
;; (let [stopper (rx/filter #(= % ::stop-page-watcher) stream)]
;; (->> stream
;; (rx/filter udp/page-update?)
;; (rx/debounce 1000)
;; (rx/mapcat #(rx/merge (rx/of persist-page
;; (->> (rx/filter page-persisted? stream)
;; (rx/timeout 1000 (rx/empty))
;; (rx/take 1)
;; (rx/ignore)))))
;; (rx/take-until stopper))))))
;; (let [stoper (rx/filter #(= % ::stop-shapes-watcher) stream)
;; into' (fn [dst srcs] (reduce #(into %1 %2) dst srcs))]
;; (->> (rx/merge st/store (rx/of state))
;; (rx/map #(get-in % [:workspace-data :shapes-by-id]))
;; (rx/buffer 2 1)
;; (rx/map look-for-changes)
;; (rx/buffer-time 300)
;; (rx/map #(into' #{} %))
;; (rx/filter (complement empty?))
;; ;; (rx/tap #(prn "changed" %))
;; ;; (rx/mapcat (fn [items] (rx/from-coll
;; ;; (map rehash-shape-relationship items))))
;; (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)))

View file

@ -0,0 +1,127 @@
;; 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.main.data.workspace-websocket
(:require
[beicon.core :as rx]
[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.websockets :as ws]
[uxbox.main.data.icons :as udi]
[uxbox.main.data.projects :as dp]
[uxbox.main.repo.core :as rp]
[uxbox.main.store :as st]
[uxbox.util.transit :as t]
[vendor.randomcolor]))
;; --- Initialize WebSocket
(declare fetch-users)
(declare handle-who)
(declare handle-pointer-update)
(declare handle-page-snapshot)
(s/def ::type keyword?)
(s/def ::message
(s/keys :req-un [::type]))
(defn initialize
[file-id]
(ptk/reify ::initialize
ptk/UpdateEvent
(update [_ state]
(let [uri (str "ws://localhost:6060/sub/" file-id)]
(assoc-in state [:ws file-id] (ws/open uri))))
ptk/WatchEvent
(watch [_ state stream]
(let [wsession (get-in state [:ws file-id])]
(->> (rx/merge
(rx/of (fetch-users file-id))
(->> (ws/-stream wsession)
(rx/filter #(= :message (:type %)))
(rx/map (comp t/decode :payload))
(rx/filter #(s/valid? ::message %))
(rx/map (fn [{:keys [type] :as msg}]
(case type
:who (handle-who msg)
:pointer-update (handle-pointer-update msg)
:page-snapshot (handle-page-snapshot msg)
::unknown)))))
(rx/take-until
(rx/filter #(= ::finalize %) stream)))))))
;; --- Finalize Websocket
(defn finalize
[file-id]
(ptk/reify ::finalize
ptk/WatchEvent
(watch [_ state stream]
(ws/-close (get-in state [:ws file-id]))
(rx/of ::finalize))))
;; --- Fetch Workspace Users
(declare users-fetched)
(defn fetch-users
[file-id]
(ptk/reify ::fetch-users
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :project-file-users {:file-id file-id})
(rx/map users-fetched)))))
(defn users-fetched
[users]
(ptk/reify ::users-fetched
ptk/UpdateEvent
(update [_ state]
(reduce (fn [state user]
(assoc-in state [:workspace-users :by-id (:id user)] user))
state
users))))
;; --- Handle: Who
;; TODO: assign color
(defn handle-who
[{:keys [users] :as msg}]
(s/assert set? users)
(ptk/reify ::handle-who
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-users :active] users))))
(defn handle-pointer-update
[{:keys [user-id page-id x y] :as msg}]
(ptk/reify ::handle-pointer-update
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-users :pointer user-id]
{:page-id page-id
:user-id user-id
:x x
:y y}))))
(defn handle-page-snapshot
[{:keys [user-id page-id version operations :as msg]}]
(ptk/reify ::handle-page-snapshot
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-page :version] version)
(assoc-in [:pages page-id :version] version)
(update-in [:pages-data page-id] cp/process-ops operations)
(update :workspace-data cp/process-ops operations)))))

View file

@ -15,7 +15,6 @@
(declare move-rect)
(declare move-path)
(declare move-circle)
(declare move-group)
(defn move
"Move the shape relativelly to its current
@ -28,18 +27,15 @@
:text (move-rect shape dpoint)
:curve (move-path shape dpoint)
:path (move-path shape dpoint)
:circle (move-circle shape dpoint)
:group (move-group shape dpoint)))
:circle (move-circle shape dpoint)))
(defn- move-rect
"A specialized function for relative movement
for rect-like shapes."
[shape {dx :x dy :y}]
(assoc shape
:x1 (mth/round (+ (:x1 shape) dx))
:y1 (mth/round (+ (:y1 shape) dy))
:x2 (mth/round (+ (:x2 shape) dx))
:y2 (mth/round (+ (:y2 shape) dy))))
:x (mth/round (+ (:x shape) dx))
:y (mth/round (+ (:y shape) dy))))
(defn- move-circle
"A specialized function for relative movement
@ -49,14 +45,6 @@
:cx (mth/round (+ (:cx shape) dx))
:cy (mth/round (+ (:cy shape) dy))))
(defn- move-group
"A specialized function for relative movement
for group shapes."
[shape {dx :x dy :y}]
(assoc shape
:dx (mth/round (+ (:dx shape 0) dx))
:dy (mth/round (+ (:dy shape 0) dy))))
(defn- move-path
"A specialized function for relative movement
for path shapes."
@ -71,7 +59,6 @@
(declare absolute-move-rect)
(declare absolute-move-circle)
(declare absolute-move-group)
(defn absolute-move
"Move the shape to the exactly specified position."
@ -80,31 +67,24 @@
:icon (absolute-move-rect shape point)
:image (absolute-move-rect shape point)
:rect (absolute-move-rect shape point)
:circle (absolute-move-circle shape point)
:group (absolute-move-group shape point)))
:circle (absolute-move-circle shape point)))
(defn- absolute-move-rect
"A specialized function for absolute moviment
for rect-like shapes."
[shape {:keys [x y] :as pos}]
(let [dx (if x (- x (:x1 shape)) 0)
dy (if y (- y (:y1 shape)) 0)]
(let [dx (if x (- x (:x shape)) 0)
dy (if y (- y (:y shape)) 0)]
(move shape (gpt/point dx dy))))
(defn- absolute-move-circle
"A specialized function for absolute moviment
for rect-like shapes."
[shape {:keys [x y] :as pos}]
(let [dx (if x (- x(:cx shape)) 0)
(let [dx (if x (- x (:cx shape)) 0)
dy (if y (- y (:cy shape)) 0)]
(move shape (gpt/point dx dy))))
(defn- absolute-move-group
"A specialized function for absolute moviment
for rect-like shapes."
[shape {:keys [x y] :as pos}]
(throw (ex-info "Not implemented (TODO)" {})))
;; --- Rotation
;; TODO: maybe we can consider apply the rotation
@ -159,6 +139,30 @@
(merge shape {:width (* rx 2)
:height (* ry 2)}))
;; --- Proportions
(declare assign-proportions-path)
(declare assign-proportions-circle)
(declare assign-proportions-rect)
(defn assign-proportions
[{:keys [type] :as shape}]
(case type
:circle (assign-proportions-circle shape)
:path (assign-proportions-path shape)
(assign-proportions-rect shape)))
(defn- assign-proportions-rect
[{:keys [width height] :as shape}]
(assoc shape :proportion (/ width height)))
(defn- assign-proportions-circle
[{:as shape}]
(prn "assign-proportions-circle" shape)
(assoc shape :proportion 1))
;; TODO: implement the rest of shapes
;; --- Paths
(defn update-path-point
@ -171,20 +175,16 @@
;; --- Setup Proportions
(declare setup-proportions-rect)
(declare setup-proportions-const)
(declare setup-proportions-image)
(defn setup-proportions
[shape]
(case (:type shape)
:canvas (setup-proportions-rect shape)
:rect (setup-proportions-rect shape)
:circle (setup-proportions-rect shape)
:icon (setup-proportions-image shape)
:image (setup-proportions-image shape)
:text shape
:curve (setup-proportions-rect shape)
:path (setup-proportions-rect shape)))
(setup-proportions-const shape)))
(defn setup-proportions-image
[{:keys [metadata] :as shape}]
@ -193,12 +193,11 @@
:proportion (/ width height)
:proportion-lock false)))
(defn setup-proportions-rect
(defn setup-proportions-const
[shape]
(let [{:keys [width height]} (size shape)]
(assoc shape
:proportion (/ width height)
:proportion-lock false)))
(assoc shape
:proportion 1
:proportion-lock false))
;; --- Resize (Dimentsions)
@ -216,20 +215,19 @@
:circle (resize-dim-circle shape opts)))
(defn- resize-dim-rect
[{:keys [proportion proportion-lock x1 y1] :as shape}
{:keys [width height]}]
{:pre [(not (and width height))]}
[{:keys [proportion proportion-lock x y] :as shape}
{:keys [width height] :as dimensions}]
(if-not proportion-lock
(if width
(assoc shape :x2 (+ x1 width))
(assoc shape :y2 (+ y1 height)))
(assoc shape :width width)
(assoc shape :height height))
(if width
(-> shape
(assoc :x2 (+ x1 width))
(assoc :y2 (+ y1 (/ width proportion))))
(assoc :width width)
(assoc :height (/ width proportion)))
(-> shape
(assoc :y2 (+ y1 height))
(assoc :x2 (+ x1 (* height proportion)))))))
(assoc :height height)
(assoc :width (* height proportion))))))
(defn- resize-dim-circle
[{:keys [proportion proportion-lock] :as shape}
@ -299,11 +297,11 @@
:bottom-right
(-> (gmt/matrix)
(gmt/translate (+ (:x1 shape))
(+ (:y1 shape)))
(gmt/translate (+ (:x shape))
(+ (:y shape)))
(gmt/scale scalex scaley)
(gmt/translate (- (:x1 shape))
(- (:y1 shape))))
(gmt/translate (- (:x shape))
(- (:y shape))))
:bottom
(-> (gmt/matrix)
@ -373,8 +371,8 @@
:height (if lock? (/ width proportion) height)))
:bottom-right
(let [width (- x (:x1 shape))
height (- y (:y1 shape))
(let [width (- x (:x shape))
height (- y (:y shape))
proportion (:proportion shape 1)]
(assoc shape
:width width
@ -421,38 +419,39 @@
(defn- setup-rect
"A specialized function for setup rect-like shapes."
[shape {:keys [x1 y1 x2 y2]}]
[shape {:keys [x y width height]}]
(assoc shape
:x1 x1
:y1 y1
:x2 x2
:y2 y2))
:x x
:y y
:width width
:height height))
(defn- setup-circle
"A specialized function for setup circle shapes."
[shape {:keys [x1 y1 x2 y2]}]
[shape {:keys [x y width height]}]
(assoc shape
:cx x1
:cy y1
:rx (mth/abs (- x2 x1))
:ry (mth/abs (- y2 y1))))
:cx x
:cy y
:rx (mth/abs width)
:ry (mth/abs height)))
(defn- setup-image
[{:keys [metadata] :as shape} {:keys [x1 y1 x2 y2] :as props}]
(let [{:keys [width height]} metadata]
(assoc shape
:x1 x1
:y1 y1
:x2 x2
:y2 y2
:proportion (/ width height)
:proportion-lock true)))
[{:keys [metadata] :as shape} {:keys [x y width height] :as props}]
(assoc shape
:x x
:y y
:width width
:height height
:proportion (/ (:width metadata)
(:height metadata))
:proportion-lock true))
;; --- Coerce to Rect-like shape.
(declare circle->rect-shape)
(declare path->rect-shape)
(declare group->rect-shape)
(declare rect->rect-shape)
(defn shape->rect-shape
"Coerce shape to rect like shape."
@ -461,7 +460,7 @@
:circle (circle->rect-shape shape)
:path (path->rect-shape shape)
:curve (path->rect-shape shape)
shape))
(rect->rect-shape shape)))
(defn shapes->rect-shape
[shapes]
@ -474,29 +473,41 @@
:y1 miny
:x2 maxx
:y2 maxy
:x minx
:y miny
:width (- maxx minx)
:height (- maxy miny)
:type :rect}))
(defn shapes->rect-shape'
[shapes]
(let [shapes (mapv shape->rect-shape shapes)
total (count shapes)]
(loop [idx (int 0)
minx js/Number.POSITIVE_INFINITY
miny js/Number.POSITIVE_INFINITY
maxx js/Number.NEGATIVE_INFINITY
maxy js/Number.NEGATIVE_INFINITY]
(if (> total idx)
(let [{:keys [x1 y1 x2 y2]} (nth shapes idx)]
(recur (inc idx)
(min minx x1)
(min miny y1)
(max maxx x2)
(max maxy y2)))
{:x1 minx
:y1 miny
:x2 maxx
:y2 maxy
:type :rect}))))
;; (defn shapes->rect-shape'
;; [shapes]
;; (let [shapes (mapv shape->rect-shape shapes)
;; total (count shapes)]
;; (loop [idx (int 0)
;; minx js/Number.POSITIVE_INFINITY
;; miny js/Number.POSITIVE_INFINITY
;; maxx js/Number.NEGATIVE_INFINITY
;; maxy js/Number.NEGATIVE_INFINITY]
;; (if (> total idx)
;; (let [{:keys [x1 y1 x2 y2]} (nth shapes idx)]
;; (recur (inc idx)
;; (min minx x1)
;; (min miny y1)
;; (max maxx x2)
;; (max maxy y2)))
;; {:x1 minx
;; :y1 miny
;; :x2 maxx
;; :y2 maxy
;; :type :rect}))))
(defn- rect->rect-shape
[{:keys [x y width height] :as shape}]
(assoc shape
:x1 x
:y1 y
:x2 (+ x width)
:y2 (+ y height)))
(defn- path->rect-shape
[{:keys [segments] :as shape}]
@ -508,7 +519,11 @@
:x1 minx
:y1 miny
:x2 maxx
:y2 maxy)))
:y2 maxy
:x minx
:y miny
:width (- maxx minx)
:height (- maxy miny))))
(defn- circle->rect-shape
[{:keys [cx cy rx ry] :as shape}]
@ -520,7 +535,11 @@
:x1 x1
:y1 y1
:x2 (+ x1 width)
:y2 (+ y1 height))))
:y2 (+ y1 height)
:x x1
:y y1
:width width
:height height)))
;; --- Transform Shape
@ -542,21 +561,23 @@
:circle (transform-circle shape xfmt)))
(defn- transform-rect
[{:keys [x1 y1] :as shape} mx]
(let [{:keys [width height]} (size shape)
tl (gpt/transform [x1 y1] mx)
tr (gpt/transform [(+ x1 width) y1] mx)
bl (gpt/transform [x1 (+ y1 height)] mx)
br (gpt/transform [(+ x1 width) (+ y1 height)] mx)
[{:keys [x y width height] :as shape} mx]
(let [tl (gpt/transform [x y] mx)
tr (gpt/transform [(+ x width) y] mx)
bl (gpt/transform [x (+ y height)] mx)
br (gpt/transform [(+ x width) (+ y height)] mx)
minx (apply min (map :x [tl tr bl br]))
maxx (apply max (map :x [tl tr bl br]))
miny (apply min (map :y [tl tr bl br]))
maxy (apply max (map :y [tl tr bl br]))]
(assoc shape
:x1 minx
:y1 miny
:x2 (+ minx (- maxx minx))
:y2 (+ miny (- maxy miny)))))
:x minx
:y miny
:width (- maxx minx)
:height (- maxy miny))))
;; :x2 (+ minx (- maxx minx))
;; :y2 (+ miny (- maxy miny)))))
(defn- transform-circle
[{:keys [cx cy rx ry] :as shape} xfmt]
@ -585,8 +606,6 @@
;; --- Outer Rect
(declare selection-rect-generic)
(defn rotation-matrix
"Generate a rotation matrix from shape."
[{:keys [x1 y1 rotation] :as shape}]
@ -607,15 +626,12 @@
(defn selection-rect
"Return the selection rect for the shape."
([shape]
(selection-rect @st/state shape))
([state shape]
(let [modifier (:modifier-mtx shape)]
(-> (shape->rect-shape shape)
(assoc :type :rect :id (:id shape))
(transform (or modifier (gmt/matrix)))
(rotate-shape)
(size)))))
[shape]
(let [modifier (:modifier-mtx shape)]
(-> (shape->rect-shape shape)
(assoc :type :rect :id (:id shape))
(transform (or modifier (gmt/matrix)))
#_(rotate-shape))))
;; --- Helpers
@ -623,8 +639,8 @@
"Check if a shape is contained in the
provided selection rect."
[shape selrect]
(let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} selrect
{rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} shape]
(let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} (shape->rect-shape selrect)
{rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} (shape->rect-shape shape)]
(and (neg? (- sy1 ry1))
(neg? (- sx1 rx1))
(pos? (- sy2 ry2))
@ -633,8 +649,8 @@
(defn overlaps?
"Check if a shape overlaps with provided selection rect."
[shape selrect]
(let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} selrect
{rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} shape]
(let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} (shape->rect-shape selrect)
{rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} (shape->rect-shape shape)]
(and (< rx1 sx2)
(> rx2 sx1)
(< ry1 sy2)

View file

@ -15,6 +15,7 @@
"ds.num-files" ["No files"
"%s file"
"%s files"]
"ds.new-file" "+ New File"
"ds.project-title" "Your projects"
"ds.project-new" "+ New project"
"ds.project-thumbnail.alt" "Project title"
@ -79,33 +80,8 @@
"ds.project.placeholder" "New project name"
"ds.project.new" "New project"
"ds.radius" "Radius"
"ds.size" "Size"
"ds.width" "Width"
"ds.height" "Height"
"ds.style" "Style"
"ds.none" "None"
"ds.solid" "Solid"
"ds.dotted" "Dotted"
"ds.dashed" "Dashed"
"ds.mixed" "Mixed"
"ds.position" "Position"
"ds.rotation" "Rotation"
"ds.opacity" "Opacity"
"ds.color" "Color"
"ds.background-color" "Background color"
"ds.font-family" "Font family"
"ds.size-weight" "Size and Weight"
"ds.font-size" "Font Size"
"ds.line-height-letter-spacing" "Line height and Letter spacing"
"ds.line-height" "Line height"
"ds.letter-spacing" "Letter spacing"
"ds.text-align" "Text align"
"ds.name" "Name"
"ds.go" "Go go go!"
"ds.accept" "Accept"
"ds.cancel" "Cancel"
"common.accept" "Accept"
"common.cancel" "Cancel"
"ds.settings.icons" "Icons"
"ds.settings.element-options" "Element options"
@ -121,43 +97,69 @@
"ds.history.versions" "History"
"ds.history.pinned" "Pinned"
"ds.help.rect" "Box (Ctrl + B)"
"ds.help.circle" "Circle (Ctrl + E)"
"ds.help.line" "Line (Ctrl + L)"
"ds.help.text" "Text"
"ds.help.path" "Path"
"ds.help.curve" "Curve"
"ds.help.ruler" "Ruler"
"ds.help.canvas" "Canvas"
"workspace.header.rect" "Box (Ctrl + B)"
"workspace.header.circle" "Circle (Ctrl + E)"
"workspace.header.line" "Line (Ctrl + L)"
"workspace.header.text" "Text"
"workspace.header.path" "Path"
"workspace.header.curve" "Curve"
"workspace.header.ruler" "Ruler"
"workspace.header.canvas" "Canvas"
"ds.user.profile" "Profile"
"ds.user.password" "Password"
"ds.user.notifications" "Notifications"
"ds.user.exit" "Exit"
"header.sitemap" "Sitemap (Ctrl + Shift + M)"
"header.draw-tools" "Draw tools (Ctrl + Shift + S)"
"header.color-palette" "Color Palette (---)"
"header.icons" "Icons (Ctrl + Shift + I)"
"header.layers" "Layers (Ctrl + Shift + L)"
"header.element-options" "Element options (Ctrl + Shift + O)"
"header.document-history" "History (Ctrl + Shift + H)"
"header.undo" "Undo (Ctrl + Z)"
"header.redo" "Redo (Ctrl + Shift + Z)"
"header.download" "Download (Ctrl + E)"
"header.image" "Image (Ctrl + I)"
"header.rules" "Rules"
"header.grid" "Grid (Ctrl + G)"
"header.grid-snap" "Snap to grid"
"header.align" "Align (Ctrl + A)"
"header.view-mode" "View mode (Ctrl + P)"
"workspace.header.sitemap" "Sitemap (Ctrl + Shift + M)"
"workspace.header.draw-tools" "Draw tools (Ctrl + Shift + S)"
"workspace.header.color-palette" "Color Palette (---)"
"workspace.header.icons" "Icons (Ctrl + Shift + I)"
"workspace.header.layers" "Layers (Ctrl + Shift + L)"
"workspace.header.element-options" "Element options (Ctrl + Shift + O)"
"workspace.header.document-history" "History (Ctrl + Shift + H)"
"workspace.header.undo" "Undo (Ctrl + Z)"
"workspace.header.redo" "Redo (Ctrl + Shift + Z)"
"workspace.header.download" "Download (Ctrl + E)"
"workspace.header.image" "Image (Ctrl + I)"
"workspace.header.rules" "Rules"
"workspace.header.grid" "Grid (Ctrl + G)"
"workspace.header.grid-snap" "Snap to grid"
"workspace.header.align" "Align (Ctrl + A)"
"workspace.header.view-mode" "View mode (Ctrl + P)"
"workspace.options.radius" "Radius"
"workspace.options.size" "Size"
"workspace.options.width" "Width"
"workspace.options.height" "Height"
"workspace.options.stroke.style" "Style"
"workspace.options.stroke.none" "None"
"workspace.options.stroke.solid" "Solid"
"workspace.options.stroke.dotted" "Dotted"
"workspace.options.stroke.dashed" "Dashed"
"workspace.options.stroke.mixed" "Mixed"
"workspace.options.position" "Position"
"workspace.options.rotation" "Rotation"
"workspace.options.opacity" "Opacity"
"workspace.options.color" "Color"
"workspace.options.background-color" "Background color"
"workspace.options.font-family" "Font family"
"workspace.options.size-weight" "Size and Weight"
"workspace.options.font-size" "Font Size"
"workspace.options.line-height-letter-spacing" "Line height and Letter spacing"
"workspace.options.line-height" "Line height"
"workspace.options.letter-spacing" "Letter spacing"
"workspace.options.text-align" "Text align"
"workspace.options.name" "Name"
"workspace.options.go" "Go go go!"
"workspace.options.measures" "Size, position & rotation"
"workspace.options.rotation-radius" "Rotation & Radius"
"element.measures" "Size, position & rotation"
"element.fill" "Fill"
"element.stroke" "Stroke"
"workspace.options.stroke" "Stroke"
"element.text" "Text"
"element.interactions" "Interactions"
"element.page-measures" "Page settings"
"workspace.options.page-measures" "Page settings"
"element.page-grid-options" "Grid settings"
"image.new" "New image"

View file

@ -11,6 +11,11 @@
[uxbox.main.constants :as c]
[uxbox.main.store :as st]))
(def profile
(-> (l/key :profile)
(l/derive st/state)))
(def workspace
(-> (l/key :workspace-local)
(l/derive st/state)))
@ -31,6 +36,10 @@
(-> (l/key :workspace-file)
(l/derive st/state)))
(def workspace-users
(-> (l/key :workspace-users)
(l/derive st/state)))
(def workspace-data
(-> (l/key :workspace-data)
(l/derive st/state)))

View file

@ -82,9 +82,6 @@
;; Something else
:else
(do
(js/console.error "Unhandled Error:"
"\n - message:" (ex-message error)
"\n - data:" (pr-str (ex-data error)))
(js/console.error error)
(ts/schedule 100 #(st/emit! (uum/error (tr "errors.generic")))))))

View file

@ -17,28 +17,28 @@
id (when (uuid-str? id) (uuid id))]
[:main.dashboard-main
[:& messages-widget]
[:& header {:section :dashboard/projects}]
[:& header {:section :dashboard-projects}]
[:& projects/projects-page {:id id}]]))
(mf/defc dashboard-assets
[{:keys [route] :as props}]
(let [section (:name route)
(let [section (get-in route [:data :name])
{:keys [id type]} (get-in route [:params :query])
id (cond
;; (str/digits? id) (parse-int id)
(uuid-str? id) (uuid id)
(str/empty-or-nil? id) nil
:else id)
type (when (str/alpha? type) (keyword type))]
type (if (str/alpha? type) (keyword type) :own)]
[:main.dashboard-main
[:& messages-widget]
[:& header {:section section}]
(case section
:dashboard/icons
:dashboard-icons
[:& icons/icons-page {:type type :id id}]
:dashboard/images
:dashboard-images
[:& images/images-page {:type type :id id}]
:dashboard/colors
:dashboard-colors
[:& colors/colors-page {:type type :id id}])]))

View file

@ -61,7 +61,7 @@
(delete []
(st/emit!
(dc/delete-collection (:id coll))
(rt/nav :dashboard/colors nil {:type (:type coll)})))
(rt/nav :dashboard-colors nil {:type (:type coll)})))
(on-delete []
(modal/show! confirm-dialog {:on-accept delete}))]
@ -79,7 +79,7 @@
editable? (= type :own)]
(letfn [(on-click [event]
(let [type (or type :own)]
(st/emit! (rt/nav :dashboard/colors nil {:type type :id id}))))
(st/emit! (rt/nav :dashboard-colors nil {:type type :id id}))))
(on-input-change [event]
(let [value (dom/get-target event)
value (dom/get-value value)]
@ -107,14 +107,14 @@
:on-key-down on-input-keyup}]
[:span.close {:on-click on-cancel} i/close]]
[:span.element-title name])
[:span.element-subtitle
#_[:span.element-subtitle
(tr "ds.num-elements" (t/c colors))]])))
(mf/defc nav
[{:keys [id type colls selected-coll] :as props}]
(let [own? (= type :own)
builtin? (= type :builtin)
select-tab #(st/emit! (rt/nav :dashboard/colors nil {:type %}))]
select-tab #(st/emit! (rt/nav :dashboard-colors nil {:type %}))]
[:div.library-bar
[:div.library-bar-inside
[:ul.library-tabs
@ -259,7 +259,8 @@
[{:keys [id type coll] :as props}]
(let [selected (mf/deref selected-colors-iref)]
[:section.dashboard-grid.library
[:& grid-header {:coll coll}]
(when coll
[:& grid-header {:coll coll}])
[:& grid {:coll coll :id id :type type :selected selected}]
(when (seq selected)
[:& grid-options {:id id :type type
@ -282,14 +283,12 @@
(first colls))
id (:id selected-coll)]
(mf/use-effect #(st/emit! (dc/initialize)) #js [id type])
(mf/use-effect #(st/emit! (dc/fetch-collections)))
[:section.dashboard-content
[:& nav {:type type
:id id
:colls colls
:selected-coll selected-coll}]
:colls colls}]
[:& content {:type type
:id id
:coll selected-coll}]]))

View file

@ -25,26 +25,26 @@
(mf/defc header
[{:keys [section] :as props}]
(let [projects? (= section :dashboard/projects)
icons? (= section :dashboard/icons)
images? (= section :dashboard/images)
colors? (= section :dashboard/colors)]
(let [projects? (= section :dashboard-projects)
icons? (= section :dashboard-icons)
images? (= section :dashboard-images)
colors? (= section :dashboard-colors)]
[:header#main-bar.main-bar
[:div.main-logo
[:& header-link {:section :dashboard/projects
[:& header-link {:section :dashboard-projects
:content i/logo}]]
[:ul.main-nav
[:li {:class (when projects? "current")}
[:& header-link {:section :dashboard/projects
[:& header-link {:section :dashboard-projects
:content (tr "ds.projects")}]]
[:li {:class (when icons? "current")}
[:& header-link {:section :dashboard/icons
[:& header-link {:section :dashboard-icons
:content (tr "ds.icons")}]]
[:li {:class (when images? "current")}
[:& header-link {:section :dashboard/images
[:& header-link {:section :dashboard-images
:content (tr "ds.images")}]]
[:li {:class (when colors? "current")}
[:& header-link {:section :dashboard/colors
[:& header-link {:section :dashboard-colors
:content (tr "ds.colors")}]]]
[:& user]]))

View file

@ -79,29 +79,14 @@
;; --- Nav
(defn- make-num-icons-iref
[id]
(letfn [(selector [icons]
(->> (vals icons)
(filter #(= id (:collection-id %)))
(count)))]
(-> (comp (l/key :icons)
(l/lens selector))
(l/derive st/state))))
(mf/defc nav-item
[{:keys [coll selected?] :as props}]
(let [local (mf/use-state {})
{:keys [id type name num-icons]} coll
;; TODO: recalculate the num-icons on crud operations for
;; avod doing this on UI.
;; num-icons-iref (mf/use-memo {:deps #js [id]
;; :fn #(make-num-icons-iref (:id coll))})
;; num-icons (mf/deref num-icons-iref)
{:keys [id type name]} coll
editable? (= type :own)]
(letfn [(on-click [event]
(let [type (or type :own)]
(st/emit! (rt/nav :dashboard/icons {} {:type type :id id}))))
(st/emit! (rt/nav :dashboard-icons {} {:type type :id id}))))
(on-input-change [event]
(-> (dom/get-target event)
(dom/get-value)
@ -127,15 +112,14 @@
:on-change on-input-change
:on-key-down on-input-keyup}]
[:span.close {:on-click on-cancel} i/close]]
[:span.element-title (if id name "Storage")])
[:span.element-subtitle (tr "ds.num-elements" (t/c num-icons))]])))
[:span.element-title (if id name "Storage")])])))
(mf/defc nav
[{:keys [id type colls selected-coll] :as props}]
(let [own? (= type :own)
builtin? (= type :builtin)
select-tab #(st/emit! (rt/nav :dashboard/icons nil {:type %}))]
select-tab #(st/emit! (rt/nav :dashboard-icons nil {:type %}))]
[:div.library-bar
[:div.library-bar-inside
[:ul.library-tabs
@ -350,54 +334,12 @@
:selected (contains? (:selected opts) (:id icon))
:edition? (= (:edition opts) (:id icon))}])]]]))
;; --- Menu
(mf/defc menu
[{:keys [opts coll] :as props}]
(let [ordering (:order opts :name)
filtering (:filter opts "")
icount (count (:icons coll))]
(letfn [(on-term-change [event]
(let [term (-> (dom/get-target event)
(dom/get-value))]
(st/emit! (di/update-opts :filter term))))
(on-ordering-change [event]
(let [value (dom/event->value event)
value (read-string value)]
(st/emit! (di/update-opts :order value))))
(on-clear [event]
(st/emit! (di/update-opts :filter "")))]
[:section.dashboard-bar.library-gap
[:div.dashboard-info
;; Counter
[:span.dashboard-icons (tr "ds.num-icons" (t/c icount))]
;; Sorting
[:div
[:span (tr "ds.ordering")]
[:select.input-select {:on-change on-ordering-change
:value (pr-str ordering)}
(for [[key value] (seq +ordering-options+)]
[:option {:key key :value (pr-str key)} (tr value)])]]
;; Search
[:form.dashboard-search
[:input.input-text {:key :icons-search-box
:type "text"
:on-change on-term-change
:auto-focus true
:placeholder (tr "ds.search.placeholder")
:value filtering}]
[:div.clear-search {:on-click on-clear} i/close]]]])))
;; --- Content
(mf/defc content
[{:keys [id type coll] :as props}]
(let [opts (mf/deref opts-iref)]
[:*
[:& menu {:opts opts :coll coll}]
[:section.dashboard-grid.library
(when coll
[:& grid-header {:coll coll}])
@ -425,15 +367,13 @@
:else (first colls))
id (:id selected-coll)]
(mf/use-effect #(st/emit! di/fetch-collections))
(mf/use-effect {:fn #(st/emit! di/initialize
(di/fetch-icons id))
:deps #js [id type]})
(mf/use-effect {:fn #(st/emit! (di/fetch-icons id))
:deps #js [(str id)]})
[:section.dashboard-content
[:& nav {:type type
:id id
:colls colls
:selected-coll selected-coll}]
:colls colls}]
[:& content {:type type
:id id
:coll selected-coll}]]))

View file

@ -26,30 +26,6 @@
[uxbox.util.router :as rt]
[uxbox.util.time :as dt]))
;; --- Helpers & Constants
(def +ordering-options+
{:name "ds.ordering.by-name"
:created "ds.ordering.by-creation-date"})
(defn- sort-images-by
[ordering images]
(case ordering
:name (sort-by :name images)
:created (reverse (sort-by :created-at images))
images))
(defn- contains-term?
[phrase term]
(let [term (name term)]
(str/includes? (str/lower phrase) (str/trim (str/lower term)))))
(defn- filter-images-by
[term images]
(if (str/blank? term)
images
(filter #(contains-term? (:name %) term) images)))
;; --- Refs
(def collections-iref
@ -80,61 +56,41 @@
;; --- Nav
(defn- make-num-images-iref
[id]
(letfn [(selector [images]
(->> (vals images)
(filter #(= id (:collection-id %)))
(count)))]
(-> (comp (l/key :images)
(l/lens selector))
(l/derive st/state))))
(mf/defc nav-item
[{:keys [coll selected?] :as props}]
(let [local (mf/use-state {})
{:keys [id type name num-images]} coll
;; TODO: recalculate the num-images on crud operations for
;; avod doing this on UI.
num-images-iref (mf/use-memo #(make-num-images-iref (:id coll)) #js [id])
num-images (mf/deref num-images-iref)
editable? (= type :own)]
(letfn [(on-click [event]
(let [type (or type :own)]
(st/emit! (rt/nav :dashboard/images {} {:type type :id id}))))
(on-input-change [event]
(-> (dom/get-target event)
(dom/get-value)
(swap! local assoc :name)))
(on-cancel [event]
(swap! local dissoc :name :edit))
(on-double-click [event]
(when editable?
(swap! local assoc :edit true)))
(on-input-keyup [event]
(when (kbd/enter? event)
(let [value (-> (dom/get-target event) (dom/get-value))]
(st/emit! (di/rename-collection id (str/trim (:name @local))))
(swap! local assoc :edit false))))]
[:li {:on-click on-click
:on-double-click on-double-click
:class-name (when selected? "current")}
(if (:edit @local)
[:div
[:input.element-title {:value (if (:name @local)
(:name @local)
(if id name "Storage"))
:on-change on-input-change
:on-key-down on-input-keyup}]
[:span.close {:on-click on-cancel} i/close]]
[:span.element-title (if id name "Storage")])
[:span.element-subtitle (tr "ds.num-elements" (t/c num-images))]])))
editable? (= type :own)
on-click
(fn [event]
(let [type (or type :own)]
(st/emit! (rt/nav :dashboard-images {} {:type type :id id}))))
on-cancel-edition #(swap! local dissoc :edit)
on-double-click #(when editable? (swap! local assoc :edit true))
on-input-keyup
(fn [event]
(when (kbd/enter? event)
(let [value (-> (dom/get-target event)
(dom/get-value)
(str/trim))]
(st/emit! (di/rename-collection id value))
(swap! local assoc :edit false))))]
[:li {:on-click on-click
:on-double-click on-double-click
:class-name (when selected? "current")}
(if (:edit @local)
[:div
[:input.element-title {:default-value name
:on-key-down on-input-keyup}]
[:span.close {:on-click on-cancel-edition} i/close]]
[:span.element-title (if id name "Storage")])]))
(mf/defc nav
[{:keys [id type colls selected-coll] :as props}]
[{:keys [id type colls] :as props}]
(let [own? (= type :own)
builtin? (= type :builtin)
select-tab #(st/emit! (rt/nav :dashboard/images nil {:type %}))]
select-tab #(st/emit! (rt/nav :dashboard-images nil {:type %}))]
[:div.library-bar
[:div.library-bar-inside
[:ul.library-tabs
@ -148,7 +104,7 @@
[:ul.library-elements
(when own?
[:li
[:a.btn-primary {:on-click #(st/emit! (di/create-collection))}
[:a.btn-primary {:on-click #(st/emit! di/create-collection)}
(tr "ds.images-collection.new")]])
(when own?
[:& nav-item {:selected? (nil? id)}])
@ -168,7 +124,7 @@
colls (->> (vals colls)
(filter #(= :own (:type %)))
(remove #(= selected (:id %)))
(sort-by :name colls))
#_(sort-by :name colls))
on-select (fn [event id]
(dom/prevent-default event)
(dom/stop-propagation event)
@ -306,20 +262,21 @@
;; --- Grid
(defn- make-images-iref
[id]
(-> (comp (l/key :images)
(l/lens (fn [images]
(->> (vals images)
(filter #(= id (:collection-id %)))))))
(l/derive st/state)))
[id type]
(letfn [(selector-fn [state]
(let [images (vals (:images state))]
(filterv #(= id (:collection-id %)) images)))]
(-> (l/lens selector-fn)
(l/derive st/state))))
(mf/defc grid
[{:keys [id type coll opts] :as props}]
(let [editable? (or (= type :own) (nil? id))
images-iref (mf/use-memo #(make-images-iref id) #js [id])
images-iref (mf/use-memo
{:fn #(make-images-iref id type)
:deps (mf/deps id type)})
images (->> (mf/deref images-iref)
(filter-images-by (:filter opts ""))
(sort-images-by (:order opts :name)))]
(sort-by :created-at))]
[:div.dashboard-grid-content
[:div.dashboard-grid-row
(when editable?
@ -332,53 +289,53 @@
;; --- Menu
(mf/defc menu
[{:keys [opts coll] :as props}]
(let [ordering (:order opts :name)
filtering (:filter opts "")
icount (count (:images coll))]
(letfn [(on-term-change [event]
(let [term (-> (dom/get-target event)
(dom/get-value))]
(st/emit! (di/update-opts :filter term))))
(on-ordering-change [event]
(let [value (dom/event->value event)
value (read-string value)]
(st/emit! (di/update-opts :order value))))
(on-clear [event]
(st/emit! (di/update-opts :filter "")))]
[:section.dashboard-bar.library-gap
[:div.dashboard-info
;; (mf/defc menu
;; [{:keys [opts coll] :as props}]
;; (let [ordering (:order opts :name)
;; filtering (:filter opts "")
;; icount (count (:images coll))]
;; (letfn [(on-term-change [event]
;; (let [term (-> (dom/get-target event)
;; (dom/get-value))]
;; (st/emit! (di/update-opts :filter term))))
;; (on-ordering-change [event]
;; (let [value (dom/event->value event)
;; value (read-string value)]
;; (st/emit! (di/update-opts :order value))))
;; (on-clear [event]
;; (st/emit! (di/update-opts :filter "")))]
;; [:section.dashboard-bar.library-gap
;; [:div.dashboard-info
;; Counter
[:span.dashboard-images (tr "ds.num-images" (t/c icount))]
;; ;; Counter
;; [:span.dashboard-images (tr "ds.num-images" (t/c icount))]
;; Sorting
[:div
[:span (tr "ds.ordering")]
[:select.input-select {:on-change on-ordering-change
:value (pr-str ordering)}
(for [[key value] (seq +ordering-options+)]
[:option {:key key :value (pr-str key)} (tr value)])]]
;; ;; Sorting
;; [:div
;; [:span (tr "ds.ordering")]
;; [:select.input-select {:on-change on-ordering-change
;; :value (pr-str ordering)}
;; (for [[key value] (seq +ordering-options+)]
;; [:option {:key key :value (pr-str key)} (tr value)])]]
;; Search
[:form.dashboard-search
[:input.input-text {:key :images-search-box
:type "text"
:on-change on-term-change
:auto-focus true
:placeholder (tr "ds.search.placeholder")
:value filtering}]
[:div.clear-search {:on-click on-clear} i/close]]]])))
;; ;; Search
;; [:form.dashboard-search
;; [:input.input-text {:key :images-search-box
;; :type "text"
;; :on-change on-term-change
;; :auto-focus true
;; :placeholder (tr "ds.search.placeholder")
;; :value filtering}]
;; [:div.clear-search {:on-click on-clear} i/close]]]])))
(mf/defc content
[{:keys [id type coll] :as props}]
(let [opts (mf/deref opts-iref)]
[:*
[:& menu {:opts opts :coll coll}]
[:section.dashboard-grid.library
(when coll
[:& grid-header {:coll coll}])
[:& grid {:id id
:type type
:coll coll
@ -390,28 +347,26 @@
(mf/defc images-page
[{:keys [id type] :as props}]
(let [type (or type :own)
colls (mf/deref collections-iref)
(let [colls (mf/deref collections-iref)
colls (cond->> (vals colls)
(= type :own) (filter #(= :own (:type %)))
(= type :builtin) (filter #(= :builtin (:type %)))
true (sort-by :created-at))
selected-coll (cond
(and (= type :own) (nil? id)) nil
(uuid? id) (seek #(= id (:id %)) colls)
:else (first colls))
id (:id selected-coll)]
(mf/use-effect #(st/emit! (di/fetch-collections)))
(mf/use-effect {:fn #(st/emit! (di/initialize)
(di/fetch-images id))
:deps #js [id type]})
coll (cond
(and (= type :own) (nil? id)) nil
(uuid? id) (seek #(= id (:id %)) colls)
:else (first colls))
id (:id coll)]
(mf/use-effect #(st/emit! di/fetch-collections))
(mf/use-effect {:fn #(st/emit! (di/fetch-images (:id coll)))
:deps #js [(str (:id coll))]})
[:section.dashboard-content
[:& nav {:type type
:id id
:colls colls
:selected-coll selected-coll}]
:colls colls}]
[:& content {:type type
:id id
:coll selected-coll}]]))
:coll coll}]]))

View file

@ -64,59 +64,6 @@
files
(filter #(contains-term? (:name %) term) files)))
;; --- Menu (Filter & Sort)
(mf/defc menu
[{:keys [id opts files] :as props}]
(let [ordering (:order opts :modified)
filtering (:filter opts "")
on-term-change
(fn [event]
(let [term (-> (dom/get-target event)
(dom/get-value))]
(st/emit! (udp/update-opts :filter term))))
on-order-change
(fn [event]
(let [value (dom/event->value event)
value (read-string value)]
(st/emit! (udp/update-opts :order value))))
on-clear
(fn [event]
(st/emit! (udp/update-opts :filter "")))]
[:section.dashboard-bar.library-gap
[:div.dashboard-info
;; Counter
[:span.dashboard-images (tr "ds.num-files" (t/c (count files)))]
[:div
;; Sorting
;; TODO: convert to separate component?
(when id
[:*
[:span (tr "ds.ordering")]
[:select.input-select {:on-change on-order-change
:value (pr-str ordering)}
(for [[key value] (seq +ordering-options+)]
(let [key (pr-str key)]
[:option {:key key :value key} (tr value)]))]])]
;; Search
;; TODO: convert to separate component?
[:form.dashboard-search
[:input.input-text
{:key :images-search-box
:type "text"
:on-change on-term-change
:auto-focus true
:placeholder (tr "ds.search.placeholder")
:value (or filtering "")}]
[:div.clear-search {:on-click on-clear} i/close]]]]))
;; --- Grid Item Thumbnail
(mf/defc grid-item-thumbnail
@ -160,12 +107,12 @@
(str (tr "ds.updated-at" (dt/timeago (:modified-at file))))]]
[:div.project-th-actions
[:div.project-th-icon.pages
i/page
#_[:span (:total-pages project)]]
#_[:div.project-th-icon.comments
i/chat
[:span "0"]]
;; [:div.project-th-icon.pages
;; i/page
;; #_[:span (:total-pages project)]]
;; [:div.project-th-icon.comments
;; i/chat
;; [:span "0"]]
[:div.project-th-icon.edit
{:on-click on-edit}
i/pencil]
@ -176,7 +123,7 @@
;; --- Grid
(mf/defc grid
[{:keys [opts files] :as props}]
[{:keys [id opts files] :as props}]
(let [order (:order opts :modified)
filter (:filter opts "")
files (->> files
@ -184,15 +131,13 @@
(sort-by order))
on-click #(do
(dom/prevent-default %)
#_(modal/show! create-project-dialog {})
#_(udl/open! :create-project))
]
(st/emit! (udp/create-file {:project-id id})))]
[:section.dashboard-grid
[:h2 (tr "ds.projects.file-name")]
[:div.dashboard-grid-content
[:div.dashboard-grid-row
[:div.grid-item.add-project #_{:on-click on-click}
[:span (tr "ds.project-file")]]
(when id
[:div.grid-item.add-project {:on-click on-click}
[:span (tr "ds.new-file")]])
(for [item files]
[:& grid-item {:file item :key (:id item)}])]]]))
@ -236,16 +181,23 @@
(sort-by :created-at))]
[:div.library-bar
[:div.library-bar-inside
[:form.dashboard-search
[:input.input-text
{:key :images-search-box
:type "text"
:auto-focus true
:placeholder (tr "ds.search.placeholder")}]
[:div.clear-search i/close]]
[:ul.library-elements
[:li
[:a.btn-primary #_{:on-click #(st/emit! di/create-collection)}
"new project +"]]
[:li {:style {:marginBottom "20px"}
:on-click #(st/emit! (udp/go-to-project nil))
[:li.recent-projects {:on-click #(st/emit! (udp/go-to-project nil))
:class-name (when (nil? id) "current")}
[:span.element-title "Recent"]]
[:div.projects-row
[:span "PROJECTS"]
[:a.add-project #_{:on-click #(st/emit! di/create-collection)}
i/close]]
(for [item projects]
[:& nav-item {:id (:id item)
:key (:id item)
@ -268,10 +220,8 @@
[{:keys [id] :as props}]
(let [opts (mf/deref opts-iref)
files (mf/deref files-ref)]
[:*
[:& menu {:id id :opts opts :files files}]
[:section.dashboard-grid.library
[:& grid {:id id :opts opts :files files}]]]))
[:section.dashboard-grid.library
[:& grid {:id id :opts opts :files files}]]))
;; --- Projects Page

View file

@ -4,7 +4,43 @@
;;
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.shapes.attrs)
(ns uxbox.main.ui.shapes.attrs
(:require [cuerdas.core :as str]))
;; (defn camel-case
;; "Returns camel case version of the key, e.g. :http-equiv becomes :httpEquiv."
;; [k]
;; (if (or (keyword? k)
;; (string? k)
;; (symbol? k))
;; (let [[first-word & words] (str/split (name k) #"-")]
;; (if (or (empty? words)
;; (= "aria" first-word)
;; (= "data" first-word))
;; k
;; (-> (map str/capital words)
;; (conj first-word)
;; str/join
;; keyword)))
;; k))
(defn- process-key
[k]
(if (keyword? k)
(cond
(keyword-identical? k :stroke-color) :stroke
(keyword-identical? k :fill-color) :fill
(str/includes? (name k) "-") (str/camel k)
:else k)))
(defn- process-attrs
[m]
(persistent!
(reduce-kv (fn [m k v]
(assoc! m (process-key k) v))
(transient {})
m)))
(def shape-style-attrs
#{:fill-color
@ -17,12 +53,6 @@
:rx
:ry})
(def shape-default-attrs
{:stroke-color "#000000"
:stroke-opacity 1
:fill-color "#000000"
:fill-opacity 1})
(defn- stroke-type->dasharray
[style]
(case style
@ -30,24 +60,23 @@
:dotted "5,5"
:dashed "10,10"))
(defn- rename-attr
[[key value :as pair]]
(case key
:stroke-color [:stroke value]
:fill-color [:fill value]
pair))
;; (defn- rename-attr
;; [[key value :as pair]]
;; (case key
;; :stroke-color [:stroke value]
;; :fill-color [:fill value]
;; pair))
(defn- rename-attrs
[attrs]
(into {} (map rename-attr) attrs))
;; (defn- rename-attrs
;; [attrs]
;; (into {} (map rename-attr) attrs))
(defn- transform-stroke-attrs
[{:keys [stroke-style] :or {stroke-style :none} :as attrs}]
(case stroke-style
:none (dissoc attrs :stroke-style :stroke-width :stroke-opacity :stroke-color)
:solid (-> (merge shape-default-attrs attrs)
(dissoc :stroke-style))
(-> (merge shape-default-attrs attrs)
:solid (dissoc attrs :stroke-style)
(-> attrs
(assoc :stroke-dasharray (stroke-type->dasharray stroke-style))
(dissoc :stroke-style))))
@ -56,4 +85,4 @@
[shape]
(-> (select-keys shape shape-style-attrs)
(transform-stroke-attrs)
(rename-attrs)))
(process-attrs)))

View file

@ -47,18 +47,19 @@
(mf/defc image-shape
[{:keys [shape image] :as props}]
(let [{:keys [id x1 y1 width height modifier-mtx]} (geom/size shape)
(let [{:keys [id x y width height modifier-mtx]} shape
moving? (boolean modifier-mtx)
transform (when (gmt/matrix? modifier-mtx)
(str modifier-mtx))
props {:x x1 :y y1
:id (str "shape-" id)
:preserve-aspect-ratio "none"
:class (classnames :move-cursor moving?)
:xlink-href (:url image)
:transform transform
:width width
:height height}
attrs (merge props (attrs/extract-style-attrs shape))]
[:> :image (normalize-props attrs)]))
props (-> (attrs/extract-style-attrs shape)
(assoc :x x
:y y
:id (str "shape-" id)
:preserveAspectRatio "none"
:class (classnames :move-cursor moving?)
:xlinkHref (:url image)
:transform transform
:width width
:height height))]
[:& "image" props]))

View file

@ -7,6 +7,7 @@
(ns uxbox.main.ui.shapes.rect
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[uxbox.main.geom :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.ui.shapes.attrs :as attrs]
@ -46,18 +47,19 @@
(gmt/matrix? modifier-mtx) (geom/transform shape modifier-mtx)
:else shape)
{:keys [x1 y1 width height] :as shape} (geom/size shape)
{:keys [x y width height]} shape
transform (when (pos? rotation)
(str (rotate (gmt/matrix) shape)))
moving? (boolean modifier-mtx)
props {:x x1 :y y1
:id (str "shape-" id)
:className (classnames :move-cursor moving?)
:width width
:height height
:transform transform}
attrs (merge (attrs/extract-style-attrs shape) props)]
[:& "rect" attrs]))
props (-> (attrs/extract-style-attrs shape)
(assoc :x x
:y y
:id (str "shape-" id)
:className (classnames :move-cursor moving?)
:width width
:height height
:transform transform))]
[:& "rect" props]))

View file

@ -122,7 +122,7 @@
style (make-style shape)
on-input (fn [ev]
(let [content (dom/event->inner-text ev)]
(st/emit! (udw/update-shape-attrs id {:content content}))))]
#_(st/emit! (udw/update-shape-attrs id {:content content}))))]
[:foreignObject {:x x1 :y y1 :width width :height height}
[:div {:style (normalize-props style)
:ref (::container own)

View file

@ -12,9 +12,9 @@
[rumext.alpha :as mf]
[uxbox.main.constants :as c]
[uxbox.main.data.history :as udh]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.undo :as udu]
[uxbox.main.data.workspace :as udw]
[uxbox.main.data.workspace :as dw]
[uxbox.main.data.workspace-websocket :as dws]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.confirm]
@ -55,12 +55,12 @@
(dom/prevent-default event)
(dom/stop-propagation event)
(if (pos? (.-deltaY event))
(st/emit! (udw/decrease-zoom))
(st/emit! (udw/increase-zoom)))
(st/emit! (dw/decrease-zoom))
(st/emit! (dw/increase-zoom)))
(scroll/scroll-to-point dom mouse-point scroll-position))))
(mf/defc workspace-content
[{:keys [layout page file] :as params}]
[{:keys [layout page file flags] :as params}]
(let [canvas (mf/use-ref nil)
left-sidebar? (not (empty? (keep layout [:layers :sitemap
:document-history])))
@ -93,29 +93,50 @@
(when right-sidebar?
[:& right-sidebar {:page page :layout layout}])]))
(mf/defc workspace
[{:keys [file-id page-id] :as props}]
(mf/defc workspace-page
[{:keys [file-id page-id layout file flags] :as props}]
(mf/use-effect
{:deps #js [file-id page-id]
:fn (fn []
(let [sub (shortcuts/init)]
(st/emit! (udw/initialize file-id page-id))
#(rx/cancel! sub)))})
(let [layout (mf/deref refs/workspace-layout)
file (mf/deref refs/workspace-file)
page (mf/deref refs/workspace-page)
flags (mf/deref refs/selected-flags)]
{:deps (mf/deps file-id page-id)
:fn #(st/emit! (dw/initialize-page page-id))})
(let [page (mf/deref refs/workspace-page)]
[:> rdnd/provider {:backend rdnd/html5}
[:& messages-widget]
[:& header {:page page :layout layout :flags flags}]
(when (:colorpalette flags)
(when (:colorpalette layout)
[:& colorpalette])
(when (and layout page)
[:& workspace-content {:layout layout
:flags flags
:file file
:page page}])]))
(mf/defc workspace
[{:keys [file-id page-id] :as props}]
(mf/use-effect
{:deps (mf/deps file-id)
:fn (fn []
(st/emit! (dw/initialize file-id))
#(st/emit! (dw/finalize file-id)))})
(mf/use-effect
{:deps (mf/deps file-id page-id)
:fn (fn []
(let [sub (shortcuts/init)]
#(rx/cancel! sub)))})
(let [layout (mf/deref refs/workspace-layout)
file (mf/deref refs/workspace-file)
flags (mf/deref refs/selected-flags)]
;; TODO: maybe loading state?
(when file
[:& workspace-page {:layout layout
:file file
:flags flags
:page-id page-id
:file-id file-id}])))

View file

@ -30,10 +30,9 @@
(mf/defc palette-item
[{:keys [color] :as props}]
(letfn [(select-color [event]
(let [attrs (if (kbd/shift? event)
{:stroke-color color}
{:fill-color color})]
(st/emit! (udw/update-selected-shapes-attrs attrs))))]
(if (kbd/shift? event)
(st/emit! (udw/update-selected-shapes :stroke-color color))
(st/emit! (udw/update-selected-shapes :fill-color color))))]
(let [rgb-vec (hex->rgb color)
rgb-color (apply str "" (interpose ", " rgb-vec))]
[:div.color-cell {:key (str color)

View file

@ -75,7 +75,7 @@
(let [event (js/MouseEvent. "click")
link (.createElement js/document "a")
now (dt/now)
stream (->> (rx/from-coll (generate-files pages))
stream (->> (rx/from (generate-files pages))
(rx/reduce conj [])
(rx/mapcat zip/build)
(rx/map blob/create-uri)

View file

@ -51,8 +51,7 @@
:fill-opacity 0
:segments []}
{:type :canvas
:name "Canvas"
:stroke-color "#000000"}
:name "Canvas"}
{:type :curve
:name "Path"
:stroke-style :solid
@ -109,18 +108,17 @@
(def handle-drawing-generic
(letfn [(initialize-drawing [state point]
(let [shape (get-in state [:workspace-local :drawing])
shape (geom/setup shape {:x1 (:x point)
:y1 (:y point)
:x2 (+ (:x point) 2)
:y2 (+ (:y point) 2)})]
shape (geom/setup shape {:x (:x point)
:y (:y point)
:width 2
:height 2})]
(assoc-in state [:workspace-local :drawing] (assoc shape ::initialized? true))))
(resize-shape [shape point lock?]
(let [shape (-> (geom/shape->rect-shape shape)
(geom/size))
result (geom/resize-shape :bottom-right shape point lock?)
scale (geom/calculate-scale-ratio shape result)
mtx (geom/generate-resize-matrix :bottom-right shape scale)]
(let [shape' (geom/shape->rect-shape shape)
result (geom/resize-shape :bottom-right shape' point lock?)
scale (geom/calculate-scale-ratio shape' result)
mtx (geom/generate-resize-matrix :bottom-right shape' scale)]
(assoc shape :modifier-mtx mtx)))
(update-drawing [state point lock?]
@ -150,13 +148,13 @@
(def handle-drawing-path
(letfn [(stoper-event? [{:keys [type shift] :as event}]
(or (= event :interrupt)
(and (uws/mouse-event? event)
(or (and (= type :double-click) shift)
(= type :context-menu)))
(and (uws/keyboard-event? event)
(= type :down)
(= 13 (:key event)))))
(or (= event ::end-path-drawing)
(and (uws/mouse-event? event)
(or (and (= type :double-click) shift)
(= type :context-menu)))
(and (uws/keyboard-event? event)
(= type :down)
(= 13 (:key event)))))
(initialize-drawing [state point]
(-> state
@ -238,8 +236,7 @@
(def handle-drawing-curve
(letfn [(stoper-event? [{:keys [type shift] :as event}]
(or (= event :interrupt)
(and (uws/mouse-event? event) (= type :up))))
(uws/mouse-event? event) (= type :up))
(initialize-drawing [state]
(assoc-in state [:workspace-local :drawing ::initialized?] true))
@ -280,9 +277,11 @@
(geom/transform shape modifier-mtx)
shape)
shape (dissoc shape ::initialized? :modifier-mtx)]
;; Add & select the cred shape to the workspace
(rx/of (dw/add-shape shape)
dw/select-first-shape))))))))
;; Add & select the created shape to the workspace
(rx/of dw/deselect-all
(if (= :canvas (:type shape))
(dw/add-canvas shape)
(dw/add-shape shape))))))))))
(def close-drawing-path
(ptk/reify ::close-drawing-path
@ -322,7 +321,8 @@
(dom/stop-propagation event)
(st/emit! (dw/set-tooltip nil)
close-drawing-path
:interrupt))
::end-path-drawing))
(on-mouse-enter [event]
(st/emit! (dw/set-tooltip "Click to close the path")))
(on-mouse-leave [event]

View file

@ -7,23 +7,19 @@
(ns uxbox.main.ui.workspace.header
(:require
[rumext.core :as mx]
[lentes.core :as l]
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.config :as cfg]
[uxbox.main.data.history :as udh]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.undo :as udu]
[uxbox.main.data.workspace :as dw]
[uxbox.main.ui.workspace.images :refer [import-image-modal]]
[uxbox.main.ui.modal :as modal]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.modal :as modal]
[uxbox.main.ui.users :refer [user]]
[uxbox.main.ui.workspace.clipboard]
[uxbox.util.data :refer [index-of]]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.geom.point :as gpt]
[uxbox.main.ui.workspace.images :refer [import-image-modal]]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.math :as mth]
[uxbox.util.router :as rt]))
@ -40,100 +36,154 @@
[:span {} (str (mth/round (* 100 zoom)) "%")]
[:span.remove-zoom {:on-click increase} "+"]]]))
;; --- Header Users
(mf/defc user-widget
[{:keys [user self?] :as props}]
[:li.tooltip.tooltip-bottom
{:alt (:fullname user)
:on-click (when self?
#(st/emit! (rt/navigate :settings/profile)))}
[:img {:style {:border-color (:color user)}
:src (if self? "/images/avatar.jpg" "/images/avatar-red.jpg")}]])
(mf/defc active-users
[props]
(let [profile (mf/deref refs/profile)
users (mf/deref refs/workspace-users)]
[:ul.user-multi
[:& user-widget {:user profile :self? true}]
(for [id (->> (:active users)
(remove #(= % (:id profile))))]
[:& user-widget {:user (get-in users [:by-id id])
:key id}])]))
;; --- Header Component
(mf/defc header
[{:keys [page layout flags] :as props}]
(let [toggle #(st/emit! (dw/toggle-flag %))
toggle-layout #(st/emit! (dw/toggle-layout-flag %))
on-undo #(st/emit! (udu/undo))
on-redo #(st/emit! (udu/redo))
on-image #(modal/show! import-image-modal {})
;;on-download #(udl/open! :download)
]
file (mf/deref refs/workspace-file)
selected-drawtool (mf/deref refs/selected-drawing-tool)
select-drawtool #(st/emit! :interrupt
(dw/deactivate-ruler)
(dw/select-for-drawing %))]
[:header#workspace-bar.workspace-bar
[:div.main-icon
[:a {:on-click #(st/emit! (rt/nav :dashboard-projects))} i/logo-icon]]
[:div.project-tree-btn
{:alt (tr "header.sitemap")
:class (when (contains? layout :sitemap) "selected")
:on-click #(st/emit! (dw/toggle-layout-flag :sitemap))}
i/project-tree
[:span {} (:name page)]]
[:span (:project-name file) " / " (:name file)]]
[:& active-users]
[:div.workspace-options
[:ul.options-btn
[:li.tooltip.tooltip-bottom
{:alt (tr "header.draw-tools")
:class (when (contains? layout :drawtools) "selected")
:on-click #(st/emit! (dw/toggle-layout-flag :drawtools))}
i/shapes]
{:alt (tr "workspace.header.canvas")
:class (when (= selected-drawtool :canvas) "selected")
:on-click (partial select-drawtool :canvas)}
i/artboard]
[:li.tooltip.tooltip-bottom
{:alt (tr "header.color-palette")
{:alt (tr "workspace.header.rect")
:class (when (= selected-drawtool :rect) "selected")
:on-click (partial select-drawtool :rect)}
i/box]
[:li.tooltip.tooltip-bottom
{:alt (tr "workspace.header.circle")
:class (when (= selected-drawtool :circle) "selected")
:on-click (partial select-drawtool :circle)}
i/circle]
[:li.tooltip.tooltip-bottom
{:alt (tr "workspace.header.text")
:class (when (= selected-drawtool :text) "selected")
:on-click (partial select-drawtool :text)}
i/text]
[:li.tooltip.tooltip-bottom
{:alt (tr "workspace.header.path")
:class (when (= selected-drawtool :path) "selected")
:on-click (partial select-drawtool :path)}
i/curve]
[:li.tooltip.tooltip-bottom
{:alt (tr "workspace.header.curve")
:class (when (= selected-drawtool :curve) "selected")
:on-click (partial select-drawtool :curve)}
i/pencil]
[:li.tooltip.tooltip-bottom
{:alt (tr "workspace.header.color-palette")
:class (when (contains? layout :colorpalette) "selected")
:on-click #(st/emit! (dw/toggle-layout-flag :colorpalette))}
i/palette]
[:li.tooltip.tooltip-bottom
{:alt (tr "header.icons")
{:alt (tr "workspace.header.icons")
:class (when (contains? layout :icons) "selected")
:on-click #(st/emit! (dw/toggle-layout-flag :icons))}
i/icon-set]
;; [:li.tooltip.tooltip-bottom
;; {:alt (tr "header.layers")
;; :class (when (contains? layout :layers) "selected")
;; :on-click #(st/emit! (dw/toggle-layout-flag :layers))}
;; i/layers]
;; [:li.tooltip.tooltip-bottom
;; {:alt (tr "header.element-options")
;; :class (when (contains? layout :element-options) "selected")
;; :on-click #(st/emit! (dw/toggle-layout-flag :element-options))}
;; i/options]
[:li.tooltip.tooltip-bottom
{:alt (tr "header.layers")
:class (when (contains? layout :layers) "selected")
:on-click #(st/emit! (dw/toggle-layout-flag :layers))}
i/layers]
[:li.tooltip.tooltip-bottom
{:alt (tr "header.element-options")
:class (when (contains? layout :element-options) "selected")
:on-click #(st/emit! (dw/toggle-layout-flag :element-options))}
i/options]
[:li.tooltip.tooltip-bottom
{:alt (tr "header.document-history")
{:alt (tr "workspace.header.document-history")
:class (when (contains? layout :document-history) "selected")
:on-click #(st/emit! (dw/toggle-layout-flag :document-history))}
i/undo-history]]
[:ul.options-btn
i/undo-history]
;; [:li.tooltip.tooltip-bottom
;; {:alt (tr "header.undo")
;; :on-click on-undo}
;; i/undo]
;; [:li.tooltip.tooltip-bottom
;; {:alt (tr "header.redo")
;; :on-click on-redo}
;; i/redo]
[:li.tooltip.tooltip-bottom
{:alt (tr "header.undo")
:on-click on-undo}
i/undo]
[:li.tooltip.tooltip-bottom
{:alt (tr "header.redo")
:on-click on-redo}
i/redo]]
[:ul.options-btn
[:li.tooltip.tooltip-bottom
{:alt (tr "header.download")
{:alt (tr "workspace.header.download")
;; :on-click on-download
}
i/download]
[:li.tooltip.tooltip-bottom
{:alt (tr "header.image")
{:alt (tr "workspace.header.image")
:on-click on-image}
i/image]]
[:ul.options-btn
i/image]
[:li.tooltip.tooltip-bottom
{:alt (tr "header.rules")
:class (when (contains? flags :rules) "selected")
:on-click (partial toggle :rules)}
{:alt (tr "workspace.header.rules")
:class (when (contains? layout :rules) "selected")
:on-click (partial toggle-layout :rules)}
i/ruler]
[:li.tooltip.tooltip-bottom
{:alt (tr "header.grid")
{:alt (tr "workspace.header.grid")
:class (when (contains? flags :grid) "selected")
:on-click (partial toggle :grid)}
i/grid]
[:li.tooltip.tooltip-bottom
{:alt (tr "header.grid-snap")
{:alt (tr "workspace.header.grid-snap")
:class (when (contains? flags :grid-snap) "selected")
:on-click (partial toggle :grid-snap)}
i/grid-snap]]
;; [:li.tooltip.tooltip-bottom
;; {:alt (tr "header.align")}
;; i/alignment]]
[:ul.options-btn
[:li.tooltip.tooltip-bottom.view-mode
{:alt (tr "header.view-mode")
i/grid-snap]]]
;; [:li.tooltip.tooltip-bottom
;; {:alt (tr "header.align")}
;; i/alignment]]
;; [:& user]
[:div.secondary-options
[:& zoom-widget]
[:a.tooltip.tooltip-bottom.view-mode
{:alt (tr "workspace.header.view-mode")
;; :on-click #(st/emit! (dw/->OpenView (:id page)))
}
i/play]]
[:& zoom-widget]]
[:& user]]))
i/play]]
]))

View file

@ -129,13 +129,9 @@
(read-string)
(swap! local assoc :id))]
(mf/use-effect
{:fn #(do (st/emit! (udi/fetch-collections))
(st/emit! (udi/fetch-images nil)))})
(mf/use-effect
{:deps #js [type id]
:fn #(st/emit! (udi/fetch-images id))})
(mf/use-effect #(st/emit! udi/fetch-collections))
(mf/use-effect {:deps #js [(str id)]
:fn #(st/emit! (udi/fetch-images id))})
[:div.lightbox-body.big-lightbox
[:h3 (tr "image.import-library")]

View file

@ -26,10 +26,10 @@
(def ^:private +circle-props+
{:r 6
:style {:fillOpacity "1"
:strokeWidth "1px"
:strokeWidth "2px"
:vectorEffect "non-scaling-stroke"}
:fill "#31e6e0"
:stroke "#28c4d4"})
:fill "rgba(49,239,184,.7)"
:stroke "#31EFB8"})
;; --- Resize Implementation
@ -90,63 +90,63 @@
:on-mouse-down on-click
:r r
:style {:fillOpacity "1"
:strokeWidth "1px"
:strokeWidth "2px"
:vectorEffect "non-scaling-stroke"}
:fill "#31e6e0"
:stroke "#28c4d4"
:fill "rgba(49,239,184,.7)"
:stroke "#31EFB8"
:cx cx
:cy cy}])
(mf/defc controls
[{:keys [shape zoom on-click] :as props}]
(let [{:keys [x1 y1 width height]} shape
(let [{:keys [x y width height]} shape
radius (if (> (max width height) handler-size-threshold) 6.0 4.0)]
[:g.controls
[:rect.main {:x x1 :y y1
[:rect.main {:x x :y y
:width width
:height height
:stroke-dasharray (str (/ 5.0 zoom) "," (/ 5 zoom))
:style {:stroke "#333" :fill "transparent"
:stroke-dasharray (str (/ 8.0 zoom) "," (/ 5 zoom))
:style {:stroke "#31EFB8" :fill "transparent"
:stroke-opacity "1"}}]
[:& control-item {:class "top"
:on-click #(on-click :top %)
:r (/ radius zoom)
:cx (+ x1 (/ width 2))
:cy (- y1 2)}]
:cx (+ x (/ width 2))
:cy (- y 2)}]
[:& control-item {:on-click #(on-click :right %)
:r (/ radius zoom)
:cy (+ y1 (/ height 2))
:cx (+ x1 width 1)
:cy (+ y (/ height 2))
:cx (+ x width 1)
:class "right"}]
[:& control-item {:on-click #(on-click :bottom %)
:r (/ radius zoom)
:cx (+ x1 (/ width 2))
:cy (+ y1 height 2)
:cx (+ x (/ width 2))
:cy (+ y height 2)
:class "bottom"}]
[:& control-item {:on-click #(on-click :left %)
:r (/ radius zoom)
:cy (+ y1 (/ height 2))
:cx (- x1 3)
:cy (+ y (/ height 2))
:cx (- x 3)
:class "left"}]
[:& control-item {:on-click #(on-click :top-left %)
:r (/ radius zoom)
:cx x1
:cy y1
:cx x
:cy y
:class "top-left"}]
[:& control-item {:on-click #(on-click :top-right %)
:r (/ radius zoom)
:cx (+ x1 width)
:cy y1
:cx (+ x width)
:cy y
:class "top-right"}]
[:& control-item {:on-click #(on-click :bottom-left %)
:r (/ radius zoom)
:cx x1
:cy (+ y1 height)
:cx x
:cy (+ y height)
:class "bottom-left"}]
[:& control-item {:on-click #(on-click :bottom-right %)
:r (/ radius zoom)
:cx (+ x1 width)
:cy (+ y1 height)
:cx (+ x width)
:cy (+ y height)
:class "bottom-right"}]]))
;; --- Selection Handlers (Component)
@ -183,8 +183,8 @@
:r (/ 6.0 zoom)
:key index
:on-mouse-down #(on-mouse-down % index)
:fill "#31e6e0"
:stroke "#28c4d4"
:fill "rgba(49,239,184,.7)"
:stroke "#31EFB8"
:style {:cursor "pointer"}}])])))
;; TODO: add specs for clarity
@ -203,15 +203,15 @@
(mf/defc text-edition-selection-handlers
[{:keys [shape zoom] :as props}]
(let [{:keys [x1 y1 width height] :as shape} (geom/selection-rect shape)]
(let [{:keys [x y width height] :as shape} (geom/selection-rect shape)]
[:g.controls
[:rect.main {:x x1 :y y1
[:rect.main {:x x :y y
:width width
:height height
;; :stroke-dasharray (str (/ 5.0 zoom) "," (/ 5 zoom))
:style {:stroke "#333"
:style {:stroke "#31EFB8"
:stroke-width "0.5"
:stroke-opacity "0.5"
:stroke-opacity "1"
:fill "transparent"}}]]))
(mf/defc single-selection-handlers

View file

@ -40,7 +40,7 @@
: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))
:esc #(st/emit! dw/deselect-all)
:esc #(st/emit! :interrupt dw/deselect-all)
:delete #(st/emit! dw/delete-selected)
:ctrl+up #(st/emit! (dw/order-selected-shapes :up))
:ctrl+down #(st/emit! (dw/order-selected-shapes :down))

View file

@ -8,7 +8,6 @@
(ns uxbox.main.ui.workspace.sidebar
(:require
[rumext.alpha :as mf]
[uxbox.main.ui.workspace.sidebar.drawtools :refer [draw-toolbox]]
[uxbox.main.ui.workspace.sidebar.history :refer [history-toolbox]]
[uxbox.main.ui.workspace.sidebar.icons :refer [icons-toolbox]]
[uxbox.main.ui.workspace.sidebar.layers :refer [layers-toolbox]]
@ -36,8 +35,6 @@
[{:keys [layout page] :as props}]
[:aside#settings-bar.settings-bar
[:div.settings-bar-inside
(when (contains? layout :drawtools)
[:& draw-toolbox {:layout layout}])
(when (contains? layout :element-options)
[:& options-toolbox {:page page}])
#_(when (contains? layout :icons)

View file

@ -1,85 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.sidebar.drawtools
(:require
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.main.data.workspace :as dw]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.uuid :as uuid]))
;; --- Constants
(def +draw-tools+
[{:icon i/box
:help "ds.help.rect"
:type :rect
:priority 1}
{:icon i/circle
:help "ds.help.circle"
:type :circle
:priority 2}
{:icon i/text
:help "ds.help.text"
:type :text
:priority 4}
{:icon i/curve
:help "ds.help.path"
:type :path
:priority 5}
{:icon i/pencil
:help "ds.help.curve"
:type :curve
:priority 6}
;; TODO: we need an icon for canvas creation
{:icon i/box
:help "ds.help.canvas"
:type :canvas
:priority 7}])
;; --- Draw Toolbox (Component)
(mf/defc draw-toolbox
{:wrap [mf/wrap-memo]}
[{:keys [flags] :as props}]
(letfn [(close [event]
(st/emit! (dw/toggle-layout-flag :drawtools)))
(select [event tool]
(st/emit! :interrupt
(dw/deactivate-ruler)
(dw/select-for-drawing tool)))
(toggle-ruler [event]
(st/emit! (dw/select-for-drawing nil)
dw/deselect-all
(dw/toggle-ruler)))]
(let [selected (mf/deref refs/selected-drawing-tool)
tools (sort-by (comp :priority second) +draw-tools+)]
[:div.tool-window.drawing-tools
[:div.tool-window-bar
[:div.tool-window-icon i/window]
[:span (tr "ds.settings.draw-tools")]
[:div.tool-window-close {:on-click close} i/close]]
[:div.tool-window-content
(for [item tools]
(let [selected? (= (:type item) selected)]
[:div.tool-btn.tooltip.tooltip-hover
{:alt (tr (:help item))
:class (when selected? "selected")
:key (:type item)
:on-click #(select % (:type item))}
(:icon item)]))
#_[:div.tool-btn.tooltip.tooltip-hover
{:alt (tr "ds.help.ruler")
:on-click toggle-ruler
:class (when (contains? flags :ruler) "selected")}
i/ruler-tool]]])))

View file

@ -10,7 +10,6 @@
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.main.data.history :as udh]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.workspace :as dw]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]

View file

@ -10,7 +10,6 @@
[lentes.core :as l]
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.workspace :as dw]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
@ -206,7 +205,7 @@
:class (when-not collapsed? "inverse")}
i/arrow-slide]]
[:ul
(for [[index shape] shapes]
(for [[index shape] (reverse shapes)]
[:& layer-item {:shape shape
:selected selected
:index index
@ -260,7 +259,8 @@
[:div.tool-window-bar
[:div.tool-window-icon i/layers]
[:span (tr "ds.settings.layers")]
[:div.tool-window-close {:on-click on-click} i/close]]
;; [:div.tool-window-close {:on-click on-click} i/close]
]
[:div.tool-window-content
[:& canvas-list {:canvas canvas
:shapes all-shapes

View file

@ -9,103 +9,44 @@
(:require
[lentes.core :as l]
[rumext.alpha :as mf]
[rumext.core :as mx]
[uxbox.builtins.icons :as i]
[uxbox.main.data.workspace :as udw]
[uxbox.main.geom :as geom]
[uxbox.main.store :as st]
[uxbox.main.refs :as refs]
[uxbox.main.ui.shapes.attrs :refer [shape-default-attrs]]
[uxbox.main.ui.workspace.sidebar.options.circle-measures :as options-circlem]
[uxbox.main.ui.workspace.sidebar.options.fill :as options-fill]
[uxbox.main.ui.workspace.sidebar.options.icon-measures :as options-iconm]
[uxbox.main.ui.workspace.sidebar.options.image-measures :as options-imagem]
[uxbox.main.ui.workspace.sidebar.options.interactions :as options-interactions]
[uxbox.main.ui.workspace.sidebar.options.page :as options-page]
[uxbox.main.ui.workspace.sidebar.options.rect-measures :as options-rectm]
[uxbox.main.ui.workspace.sidebar.options.stroke :as options-stroke]
[uxbox.main.ui.workspace.sidebar.options.text :as options-text]
[uxbox.util.data :as data]
[uxbox.util.dom :as dom]
[uxbox.main.ui.workspace.sidebar.options.rect :as rect]
[uxbox.main.ui.workspace.sidebar.options.circle :as circle]
[uxbox.main.ui.workspace.sidebar.options.path :as path]
[uxbox.main.ui.workspace.sidebar.options.image :as image]
[uxbox.main.ui.workspace.sidebar.options.page :as page]
[uxbox.util.i18n :refer [tr]]))
;; --- Constants
(def ^:private +menus-map+
{:icon [::icon-measures ::fill ::stroke]
:rect [::rect-measures ::fill ::stroke]
:path [::fill ::stroke ::interactions]
:circle [::circle-measures ::fill ::stroke]
:text [::fill ::text]
:image [::image-measures]
::page [::page-measures ::page-grid-options]})
(def ^:private +menus+
[{:name "element.measures"
:id ::icon-measures
:icon i/infocard
:comp options-iconm/icon-measures-menu}
{:name "element.measures"
:id ::image-measures
:icon i/infocard
:comp options-imagem/image-measures-menu}
{:name "element.measures"
:id ::rect-measures
:icon i/infocard
:comp options-rectm/rect-measures-menu}
{:name "element.measures"
:id ::circle-measures
:icon i/infocard
:comp options-circlem/circle-measures-menu}
{:name "element.fill"
:id ::fill
:icon i/fill
:comp options-fill/fill-menu}
{:name "element.stroke"
:id ::stroke
:icon i/stroke
:comp options-stroke/stroke-menu}
{:name "element.text"
:id ::text
:icon i/text
:comp options-text/text-menu}
{:name "element.interactions"
:id ::interactions
:icon i/action
:comp options-interactions/interactions-menu}
{:name "element.page-measures"
:id ::page-measures
:icon i/page
:comp options-page/measures-menu}
{:name "element.page-grid-options"
:id ::page-grid-options
:icon i/grid
:comp options-page/grid-options-menu}])
(def ^:private +menus-by-id+
(data/index-by-id +menus+))
;; (def ^:private +menus-map+
;; {:icon [::icon-measures ::fill ::stroke]
;; :rect [::rect-measures ::fill ::stroke]
;; :path [::fill ::stroke ::interactions]
;; :circle [::circle-measures ::fill ::stroke]
;; :text [::fill ::text]
;; :image [::image-measures]
;; ::page [::page-measures ::page-grid-options]})
;; --- Options
(mf/defc shape-options
[{:keys [shape-id] :as props}]
(let [shape-iref (mf/use-memo {:deps #js [shape-id]
(let [shape-iref (mf/use-memo {:deps #js [(str shape-id)]
:fn #(-> (l/in [:workspace-data :shapes-by-id shape-id])
(l/derive st/state))})
shape (mf/deref shape-iref)
menus (get +menus-map+ (:type shape))]
shape (mf/deref shape-iref)]
[:div
(for [mid menus]
(let [{:keys [comp] :as menu} (get +menus-by-id+ mid)]
[:& comp {:menu menu :shape shape :key mid}]))]))
(mf/defc page-options
[{:keys [page] :as props}]
(let [menus (get +menus-map+ ::page)]
[:div
(for [mid menus]
(let [{:keys [comp] :as menu} (get +menus-by-id+ mid)]
[:& comp {:menu menu :page page :key mid}]))]))
(case (:type shape)
:rect [:& rect/options {:shape shape}]
:circle [:& circle/options {:shape shape}]
:path [:& path/options {:shape shape}]
:curve [:& path/options {:shape shape}]
:image [:& image/options {:shape shape}]
nil)]))
(mf/defc options-toolbox
{:wrap [mf/wrap-memo]}
@ -113,12 +54,12 @@
(let [close #(st/emit! (udw/toggle-layout-flag :element-options))
selected (mf/deref refs/selected-shapes)]
[:div.elementa-options.tool-window
[:div.tool-window-bar
[:div.tool-window-icon i/options]
[:span (tr "ds.settings.element-options")]
[:div.tool-window-close {:on-click close} i/close]]
;; [:div.tool-window-bar
;; [:div.tool-window-icon i/options]
;; [:span (tr "ds.settings.element-options")]
;; [:div.tool-window-close {:on-click close} i/close]]
[:div.tool-window-content
[:div.element-options
(if (= (count selected) 1)
[:& shape-options {:shape-id (first selected)}]
[:& page-options {:page page}])]]]))
[:& page/options {:page page}])]]]))

View file

@ -0,0 +1,132 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.sidebar.options.circle
(:require
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.builtins.icons :as i]
[uxbox.main.data.workspace :as udw]
[uxbox.main.geom :as geom]
[uxbox.main.store :as st]
[uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]]
[uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.math :as math :refer (precision-or-0)]))
(mf/defc measures-menu
[{:keys [shape] :as props}]
(let [on-size-change
(fn [event attr]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-integer 0))]
(st/emit! (udw/update-dimensions (:id shape) {attr value}))))
on-proportion-lock-change
(fn [event]
(st/emit! (udw/toggle-shape-proportion-lock (:id shape))))
on-size-rx-change #(on-size-change % :rx)
on-size-ry-change #(on-size-change % :ry)
on-position-change
(fn [event attr]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-integer))
point (gpt/point {attr value})]
(st/emit! (udw/update-position (:id shape) point))))
on-pos-cx-change #(on-position-change % :x)
on-pos-cy-change #(on-position-change % :y)
on-rotation-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-integer 0))]
(st/emit! (udw/update-shape (:id shape) {:rotation value}))))
on-radius-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-double 0))]
(st/emit! (udw/update-shape (:id shape) {:rx value :ry value}))))]
[:div.element-set
[:div.element-set-title (tr "workspace.options.measures")]
[:div.element-set-content
;; SIZE
[:span (tr "workspace.options.size")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text {:type "number"
:min "0"
:on-change on-size-rx-change
:value (-> (:rx shape)
(math/precision 2)
(d/coalesce-str "0"))}]]
[:div.lock-size {:class (when (:proportion-lock shape) "selected")
:on-click on-proportion-lock-change}
(if (:proportion-lock shape)
i/lock
i/unlock)]
[:div.input-element.pixels
[:input.input-text {:type "number"
:min "0"
:on-change on-size-ry-change
:value (-> (:ry shape)
(math/precision 2)
(d/coalesce-str "0"))}]]]
;; POSITION
[:span (tr "workspace.options.position")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text {:type "number"
:on-change on-pos-cx-change
:value (-> (:cx shape)
(math/precision 2)
(d/coalesce-str "0"))}]]
[:div.input-element.pixels
[:input.input-text {:type "number"
:on-change on-pos-cy-change
:value (-> (:cy shape)
(math/precision 2)
(d/coalesce-str "0"))}]]]
;; ROTATION & RADIUS
[:span (tr "workspace.options.rotation-radius")]
[:div.row-flex
[:div.input-element.degrees
[:input.input-text {:placeholder ""
:type "number"
:min 0
:max 360
:on-change on-rotation-change
:value (-> (:rotation shape)
(math/precision 2)
(d/coalesce-str "0"))}]]
[:div.input-element.pixels
[:input.input-text {:type "number"
:on-change on-radius-change
:value (-> (:rx shape)
(math/precision 2)
(d/coalesce-str "0"))}]]]]]))
(mf/defc options
[{:keys [shape] :as props}]
[:div
[:& measures-menu {:shape shape}]
[:& fill-menu {:shape shape}]
[:& stroke-menu {:shape shape}]])

View file

@ -1,117 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.sidebar.options.circle-measures
(:require
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.main.data.workspace :as udw]
[uxbox.main.geom :as geom]
[uxbox.main.store :as st]
[uxbox.util.data :refer (parse-int parse-float read-string)]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.math :refer (precision-or-0)]))
(declare on-size-change)
(declare on-rotation-change)
(declare on-position-change)
(declare on-proportion-lock-change)
(mf/defc circle-measures-menu
[{:keys [menu shape] :as props}]
[:div.element-set {:key (str (:id menu))}
[:div.element-set-title (:name menu)]
[:div.element-set-content
;; SLIDEBAR FOR ROTATION AND OPACITY
[:span (tr "ds.size")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder (tr "ds.width")
:type "number"
:min "0"
:value (precision-or-0 (:rx shape 0) 2)
:on-change #(on-size-change % shape :rx)}]]
[:div.lock-size {:class (when (:proportion-lock shape) "selected")
:on-click #(on-proportion-lock-change % shape)}
(if (:proportion-lock shape) i/lock i/unlock)]
[:div.input-element.pixels
[:input.input-text
{:placeholder (tr "ds.height")
:type "number"
:min "0"
:value (precision-or-0 (:ry shape 0) 2)
:on-change #(on-size-change % shape :ry)}]]]
[:span (tr "ds.position")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder "cx"
:type "number"
:value (precision-or-0 (:cx shape 0) 2)
:on-change #(on-position-change % shape :x)}]]
[:div.input-element.pixels
[:input.input-text
{:placeholder "cy"
:type "number"
:value (precision-or-0 (:cy shape 0) 2)
:on-change #(on-position-change % shape :y)}]]]
[:span (tr "ds.rotation")]
[:div.row-flex
[:input.slidebar
{:type "range"
:min 0
:max 360
:value (:rotation shape 0)
:on-change #(on-rotation-change % shape)}]]
[:div.row-flex
[:div.input-element.degrees
[:input.input-text
{:placeholder ""
:type "number"
:min 0
:max 360
:value (precision-or-0 (:rotation shape 0) 2)
:on-change #(on-rotation-change % shape)}]]
[:input.input-text
{:style {:visibility "hidden"}}]]]])
(defn- on-size-change
[event shape attr]
(let [value (dom/event->value event)
value (parse-int value 0)
sid (:id shape)
props {attr value}]
(st/emit! (udw/update-dimensions sid props))))
(defn- on-rotation-change
[event shape]
(let [value (dom/event->value event)
value (parse-int value 0)
sid (:id shape)]
(st/emit! (udw/update-shape-attrs sid {:rotation value}))))
(defn- on-position-change
[event shape attr]
(let [value (dom/event->value event)
value (parse-int value nil)
sid (:id shape)
point (gpt/point {attr value})]
(st/emit! (udw/update-position sid point))))
(defn- on-proportion-lock-change
[event shape]
(if (:proportion-lock shape)
(st/emit! (udw/unlock-proportions (:id shape)))
(st/emit! (udw/lock-proportions (:id shape)))))

View file

@ -9,43 +9,45 @@
(:require
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.common.data :as d]
[uxbox.main.data.workspace :as udw]
[uxbox.main.store :as st]
[uxbox.main.ui.modal :as modal]
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
[uxbox.util.data :refer [parse-float]]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :refer [tr]]))
(mf/defc fill-menu
[{:keys [menu shape]}]
(letfn [(change-attrs [attrs]
(st/emit! (udw/update-shape-attrs (:id shape) attrs)))
[{:keys [shape] :as props}]
(letfn [(update-shape! [attr value]
(st/emit! (udw/update-shape (:id shape) {attr value})))
(on-color-change [event]
(let [value (dom/event->value event)]
(change-attrs {:fill-color value})))
(let [value (-> (dom/get-target event)
(dom/get-value))]
(update-shape! :fill-color value)))
(on-opacity-change [event]
(let [value (dom/event->value event)
value (parse-float value 1)
value (/ value 10000)]
(change-attrs {:fill-opacity value})))
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-double 1)
(/ 10000))]
(update-shape! :fill-opacity value)))
(show-color-picker [event]
(let [x (.-clientX event)
y (.-clientY event)
props {:x x :y y
:on-change #(change-attrs {:fill-color %})
:on-change #(update-shape! :fill-color %)
:default "#ffffff"
:value (:fill-color shape)
:transparent? true}]
(modal/show! colorpicker-modal props)))]
[:div.element-set
[:div.element-set-title (:name menu)]
[:div.element-set-title (tr "element.fill")]
[:div.element-set-content
[:span (tr "ds.color")]
[:span (tr "workspace.options.color")]
[:div.row-flex.color-data
[:span.color-th
{:style {:background-color (:fill-color shape)}
{:style {:background-color (:fill-color shape "#000000")}
:on-click show-color-picker}]
[:div.color-info
[:input
@ -53,7 +55,7 @@
:value (:fill-color shape "")}]]]
;; SLIDEBAR FOR ROTATION AND OPACITY
[:span (tr "ds.opacity")]
[:span (tr "workspace.options.opacity")]
[:div.row-flex
[:input.slidebar
{:type "range"

View file

@ -95,7 +95,7 @@
[event shape]
(let [value (dom/event->value event)
value (parse-int value 0)]
(st/emit! (udw/update-shape-attrs (:id shape) {:rotation value}))))
#_(st/emit! (udw/update-shape-attrs (:id shape) {:rotation value}))))
(defn- on-position-change
[event shape attr]
@ -107,7 +107,7 @@
(defn- on-proportion-lock-change
[event shape]
(if (:proportion-lock shape)
#_(if (:proportion-lock shape)
(st/emit! (udw/unlock-proportions (:id shape)))
(st/emit! (udw/lock-proportions (:id shape)))))

View file

@ -1,134 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.sidebar.options.image-measures
(:require
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.main.data.workspace :as dw]
[uxbox.main.geom :as geom]
[uxbox.main.store :as st]
[uxbox.util.data :refer (parse-int parse-float read-string)]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.math :refer (precision-or-0)]))
(declare on-size-change)
(declare on-rotation-change)
(declare on-opacity-change)
(declare on-position-change)
(declare on-proportion-lock-change)
(mf/defc image-measures-menu
[{:keys [menu shape] :as props}]
(let [size (geom/size shape)]
[:div.element-set
[:div.element-set-title (:name menu)]
[:div.element-set-content
;; SLIDEBAR FOR ROTATION AND OPACITY
[:span (tr "ds.size")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder (tr "ds.width")
:type "number"
:min "0"
:value (precision-or-0 (:width size) 2)
:on-change #(on-size-change % shape :width)}]]
[:div.lock-size
{:class (when (:proportion-lock shape) "selected")
:on-click #(on-proportion-lock-change % shape)}
(if (:proportion-lock shape) i/lock i/unlock)]
[:div.input-element.pixels
[:input.input-text
{:placeholder (tr "ds.height")
:type "number"
:min "0"
:value (precision-or-0 (:height size) 2)
:on-change #(on-size-change % shape :height)}]]]
[:span (tr "ds.position")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder "X"
:type "number"
:value (precision-or-0 (:x1 shape 0) 2)
:on-change #(on-position-change % shape :x)}]]
[:div.input-element.pixels
[:input.input-text
{:placeholder "Y"
:type "number"
:value (precision-or-0 (:y1 shape 0) 2)
:on-change #(on-position-change % shape :y)}]]]
;; [:span (tr "ds.rotation")]
;; [:div.row-flex
;; [:input.slidebar
;; {:type "range"
;; :min 0
;; :max 360
;; :value (:rotation shape 0)
;; :on-change on-rotation-change}]]
;; [:div.row-flex
;; [:div.input-element.degrees
;; [:input.input-text
;; {:placeholder ""
;; :type "number"
;; :min 0
;; :max 360
;; :value (precision-or-0 (:rotation shape 0) 2)
;; :on-change on-rotation-change
;; }]]
;; [:input.input-text
;; {:style {:visibility "hidden"}}]]
[:span (tr "ds.opacity")]
[:div.row-flex
[:input.slidebar
{:type "range"
:min "0"
:max "10000"
:value (* 10000 (:opacity shape 1))
:step "1"
:on-change #(on-opacity-change % shape)}]]]]))
(defn- on-size-change
[event shape attr]
(let [value (dom/event->value event)
value (parse-int value 0)
props {attr value}]
(st/emit! (dw/update-dimensions (:id shape) props))))
(defn- on-rotation-change
[event shape]
(let [value (dom/event->value event)
value (parse-int value 0)]
(st/emit! (dw/update-shape-attrs (:id shape) {:rotation value}))))
(defn- on-opacity-change
[event shape]
(let [value (dom/event->value event)
value (parse-float value 1)
value (/ value 10000)]
(st/emit! (dw/update-shape-attrs (:id shape) {:opacity value}))))
(defn- on-position-change
[event shape attr]
(let [value (dom/event->value event)
value (parse-int value nil)
point (gpt/point {attr value})]
(st/emit! (dw/update-position (:id shape) point))))
(defn- on-proportion-lock-change
[event shape]
(if (:proportion-lock shape)
(st/emit! (dw/unlock-proportions (:id shape)))
(st/emit! (dw/lock-proportions (:id shape)))))

View file

@ -22,452 +22,452 @@
;; --- Helpers
(defn- on-change
([form attr event]
(dom/prevent-default event)
(let [value (dom/event->value event)
value (read-string value)]
(swap! form assoc attr value)))
([form attr keep event]
(let [data (select-keys @form keep)]
(reset! form data)
(on-change form attr event))))
;; (defn- on-change
;; ([form attr event]
;; (dom/prevent-default event)
;; (let [value (dom/event->value event)
;; value (read-string value)]
;; (swap! form assoc attr value)))
;; ([form attr keep event]
;; (let [data (select-keys @form keep)]
;; (reset! form data)
;; (on-change form attr event))))
;; --- Interactions List
;; ;; --- Interactions List
(defn- translate-trigger-name
[trigger]
(case trigger
:click "Click"
:doubleclick "Double Click"
:rightclick "Right Click"
:hover "Hover"
:mousein "Mouse In"
:mouseout "Mouse Out"
;; :swiperight "Swipe Right"
;; :swipeleft "Swipe Left"
;; :swipedown "Swipe Down"
;; :touchandhold "Touch and Hold"
;; :holdrelease "Hold release"
(pr-str trigger)))
;; (defn- translate-trigger-name
;; [trigger]
;; (case trigger
;; :click "Click"
;; :doubleclick "Double Click"
;; :rightclick "Right Click"
;; :hover "Hover"
;; :mousein "Mouse In"
;; :mouseout "Mouse Out"
;; ;; :swiperight "Swipe Right"
;; ;; :swipeleft "Swipe Left"
;; ;; :swipedown "Swipe Down"
;; ;; :touchandhold "Touch and Hold"
;; ;; :holdrelease "Hold release"
;; (pr-str trigger)))
(mf/defc interactions-list
[{:keys [shape form] :as props}]
(letfn [(on-edit [item event]
(dom/prevent-default event)
(reset! form item))
(delete [item]
(let [sid (:id shape)
id (:id item)]
(st/emit! (dw/delete-interaction sid id))))
(on-delete [item event]
(dom/prevent-default event)
(let [delete (partial delete item)]
(udl/open! :confirm {:on-accept delete})))]
[:ul.element-list
(for [item (vals (:interactions shape))
:let [key (pr-str (:id item))]]
[:li {:key key}
[:div.list-icon i/action]
[:span (translate-trigger-name (:trigger item))]
[:div.list-actions
[:a {:on-click (partial on-edit item)} i/pencil]
[:a {:on-click (partial on-delete item)} i/trash]]])]))
;; (mf/defc interactions-list
;; [{:keys [shape form] :as props}]
;; (letfn [(on-edit [item event]
;; (dom/prevent-default event)
;; (reset! form item))
;; (delete [item]
;; (let [sid (:id shape)
;; id (:id item)]
;; (st/emit! (dw/delete-interaction sid id))))
;; (on-delete [item event]
;; (dom/prevent-default event)
;; (let [delete (partial delete item)]
;; (udl/open! :confirm {:on-accept delete})))]
;; [:ul.element-list
;; (for [item (vals (:interactions shape))
;; :let [key (pr-str (:id item))]]
;; [:li {:key key}
;; [:div.list-icon i/action]
;; [:span (translate-trigger-name (:trigger item))]
;; [:div.list-actions
;; [:a {:on-click (partial on-edit item)} i/pencil]
;; [:a {:on-click (partial on-delete item)} i/trash]]])]))
;; --- Trigger Input
;; ;; --- Trigger Input
(mf/defc trigger-input
[{:keys [form] :as props}]
;; (mf/use-effect
;; {:init #(when-not (:trigger @form) (swap! form assoc :trigger :click))
;; :deps true})
[:div
[:span "Trigger"]
[:div.row-flex
[:select.input-select {:placeholder "Choose a trigger"
:on-change (partial on-change form :trigger)
:value (pr-str (:trigger @form))}
[:option {:value ":click"} "Click"]
[:option {:value ":doubleclick"} "Double-click"]
[:option {:value ":rightclick"} "Right-click"]
[:option {:value ":hover"} "Hover"]
[:option {:value ":mousein"} "Mouse in"]
[:option {:value ":mouseout"} "Mouse out"]
#_[:option {:value ":swiperight"} "Swipe right"]
#_[:option {:value ":swipeleft"} "Swipe left"]
#_[:option {:value ":swipedown"} "Swipe dpwn"]
#_[:option {:value ":touchandhold"} "Touch and hold"]
#_[:option {:value ":holdrelease"} "Hold release"]
#_[:option {:value ":keypress"} "Key press"]
#_[:option {:value ":pageisloaded"} "Page is loaded"]
#_[:option {:value ":windowscroll"} "Window is scrolled to"]]]])
;; (mf/defc trigger-input
;; [{:keys [form] :as props}]
;; ;; (mf/use-effect
;; ;; {:init #(when-not (:trigger @form) (swap! form assoc :trigger :click))
;; ;; :deps true})
;; [:div
;; [:span "Trigger"]
;; [:div.row-flex
;; [:select.input-select {:placeholder "Choose a trigger"
;; :on-change (partial on-change form :trigger)
;; :value (pr-str (:trigger @form))}
;; [:option {:value ":click"} "Click"]
;; [:option {:value ":doubleclick"} "Double-click"]
;; [:option {:value ":rightclick"} "Right-click"]
;; [:option {:value ":hover"} "Hover"]
;; [:option {:value ":mousein"} "Mouse in"]
;; [:option {:value ":mouseout"} "Mouse out"]
;; #_[:option {:value ":swiperight"} "Swipe right"]
;; #_[:option {:value ":swipeleft"} "Swipe left"]
;; #_[:option {:value ":swipedown"} "Swipe dpwn"]
;; #_[:option {:value ":touchandhold"} "Touch and hold"]
;; #_[:option {:value ":holdrelease"} "Hold release"]
;; #_[:option {:value ":keypress"} "Key press"]
;; #_[:option {:value ":pageisloaded"} "Page is loaded"]
;; #_[:option {:value ":windowscroll"} "Window is scrolled to"]]]])
;; --- URL Input
;; ;; --- URL Input
(mf/defc url-input
[form]
[:div
[:span "Url"]
[:div.row-flex
[:input.input-text
{:placeholder "http://"
:on-change (partial on-change form :url)
:value (:url @form "")
:type "url"}]]])
;; --- Elements Input
(defn- collect-shapes
[state page]
(let [shapes-by-id (:shapes state)
shapes (get-in state [:pages page :shapes])]
(letfn [(resolve-shape [acc id]
(let [shape (get shapes-by-id id)]
(if (= (:type shape) :group)
(reduce resolve-shape (conj acc shape) (:items shape))
(conj acc shape))))]
(reduce resolve-shape [] shapes))))
(mf/defc elements-input
[{:keys [page-id form] :as props}]
(let [shapes (collect-shapes @st/state page-id)]
[:div
[:span "Element"]
[:div.row-flex
[:select.input-select
{:placeholder "Choose an element"
:on-change (partial on-change form :element)
:value (pr-str (:element @form))}
[:option {:value "nil"} "---"]
(for [shape shapes
:let [key (pr-str (:id shape))]]
[:option {:key key :value key} (:name shape)])]]]))
;; --- Page Input
(mf/defc pages-input
[form-ref path]
;; FIXME: react on ref
#_(let [pages (mx/react refs/selected-project-pages)]
(when (and (not (:page @form-ref))
(pos? (count pages)))
(swap! form-ref assoc :page (:id (first pages))))
[:div
[:span "Page"]
[:div.row-flex
[:select.input-select {:placeholder "Choose a page"
:on-change (partial on-change form-ref :page)
:value (pr-str (:page @form-ref))}
(for [page pages
:let [key (pr-str (:id page))]]
[:option {:key key :value key} (:name page)])]]]))
;; --- Animation
(mf/defc animation-input
[{:keys [form] :as props}]
(when-not (:action @form)
(swap! form assoc :animation :none))
[:div
[:span "Animation"]
[:div.row-flex
[:select.input-select
{:placeholder "Animation"
:on-change (partial on-change form :animation)
:value (pr-str (:animation @form))}
[:option {:value ":none"} "None"]
[:option {:value ":fade"} "Fade"]
[:option {:value ":slide"} "Slide"]]]])
;; --- MoveTo Input
(mf/defc moveto-input
[{:keys [form] :as props}]
(when-not (:moveto-x @form)
(swap! form assoc :moveto-x 0))
(when-not (:moveto-y @form)
(swap! form assoc :moveto-y 0))
[:div
[:span "Move to position"]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder "X"
:on-change (partial on-change form :moveto-x)
:type "number"
:value (:moveto-x @form "")}]]
[:div.input-element.pixels
[:input.input-text
{:placeholder "Y"
:on-change (partial on-change form :moveto-y)
:type "number"
:value (:moveto-y @form "")}]]]])
;; --- MoveBy Input
(mf/defc moveby-input
[{:keys [form] :as props}]
(when-not (:moveby-x @form)
(swap! form assoc :moveby-x 0))
(when-not (:moveby-y @form)
(swap! form assoc :moveby-y 0))
[:div
[:span "Move to position"]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder "X"
:on-change (partial on-change form :moveby-x)
:type "number"
:value (:moveby-x @form "")}]]
[:div.input-element.pixels
[:input.input-text
{:placeholder "Y"
:on-change (partial on-change form :moveby-y)
:type "number"
:value (:moveby-y @form "")}]]]])
;; --- Opacity Input
(mf/defc opacity-input
[{:keys [form] :as props}]
(when-not (:opacity @form)
(swap! form assoc :opacity 100))
[:div
[:span "Opacity"]
[:div.row-flex
[:div.input-element.percentail
[:input.input-text
{:placeholder "%"
:on-change (partial on-change form :opacity)
:min "0"
:max "100"
:type "number"
:value (:opacity @form "")}]]]])
;; --- Rotate Input
;; (mx/defc rotate-input
;; (mf/defc url-input
;; [form]
;; [:div
;; [:span "Rotate (dg)"]
;; [:span "Url"]
;; [:div.row-flex
;; [:div.input-element.degrees
;; [:input.input-text
;; {:placeholder "http://"
;; :on-change (partial on-change form :url)
;; :value (:url @form "")
;; :type "url"}]]])
;; ;; --- Elements Input
;; (defn- collect-shapes
;; [state page]
;; (let [shapes-by-id (:shapes state)
;; shapes (get-in state [:pages page :shapes])]
;; (letfn [(resolve-shape [acc id]
;; (let [shape (get shapes-by-id id)]
;; (if (= (:type shape) :group)
;; (reduce resolve-shape (conj acc shape) (:items shape))
;; (conj acc shape))))]
;; (reduce resolve-shape [] shapes))))
;; (mf/defc elements-input
;; [{:keys [page-id form] :as props}]
;; (let [shapes (collect-shapes @st/state page-id)]
;; [:div
;; [:span "Element"]
;; [:div.row-flex
;; [:select.input-select
;; {:placeholder "Choose an element"
;; :on-change (partial on-change form :element)
;; :value (pr-str (:element @form))}
;; [:option {:value "nil"} "---"]
;; (for [shape shapes
;; :let [key (pr-str (:id shape))]]
;; [:option {:key key :value key} (:name shape)])]]]))
;; ;; --- Page Input
;; (mf/defc pages-input
;; [form-ref path]
;; ;; FIXME: react on ref
;; #_(let [pages (mx/react refs/selected-project-pages)]
;; (when (and (not (:page @form-ref))
;; (pos? (count pages)))
;; (swap! form-ref assoc :page (:id (first pages))))
;; [:div
;; [:span "Page"]
;; [:div.row-flex
;; [:select.input-select {:placeholder "Choose a page"
;; :on-change (partial on-change form-ref :page)
;; :value (pr-str (:page @form-ref))}
;; (for [page pages
;; :let [key (pr-str (:id page))]]
;; [:option {:key key :value key} (:name page)])]]]))
;; ;; --- Animation
;; (mf/defc animation-input
;; [{:keys [form] :as props}]
;; (when-not (:action @form)
;; (swap! form assoc :animation :none))
;; [:div
;; [:span "Animation"]
;; [:div.row-flex
;; [:select.input-select
;; {:placeholder "Animation"
;; :on-change (partial on-change form :animation)
;; :value (pr-str (:animation @form))}
;; [:option {:value ":none"} "None"]
;; [:option {:value ":fade"} "Fade"]
;; [:option {:value ":slide"} "Slide"]]]])
;; ;; --- MoveTo Input
;; (mf/defc moveto-input
;; [{:keys [form] :as props}]
;; (when-not (:moveto-x @form)
;; (swap! form assoc :moveto-x 0))
;; (when-not (:moveto-y @form)
;; (swap! form assoc :moveto-y 0))
;; [:div
;; [:span "Move to position"]
;; [:div.row-flex
;; [:div.input-element.pixels
;; [:input.input-text
;; {:placeholder "dg"
;; :on-change (partial on-change form :rotation)
;; {:placeholder "X"
;; :on-change (partial on-change form :moveto-x)
;; :type "number"
;; :value (:rotation @form "")}]]]])
;; :value (:moveto-x @form "")}]]
;; [:div.input-element.pixels
;; [:input.input-text
;; {:placeholder "Y"
;; :on-change (partial on-change form :moveto-y)
;; :type "number"
;; :value (:moveto-y @form "")}]]]])
;; --- Resize Input
;; ;; --- MoveBy Input
(mf/defc resize-input
[{:keys [form] :as props}]
[:div
[:span "Resize"]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder "Width"
:on-change (partial on-change form :resize-width)
:type "number"
:value (:resize-width @form "")}]]
[:div.input-element.pixels
[:input.input-text
{:placeholder "Height"
:on-change (partial on-change form :resize-height)
:type "number"
:value (:resize-height @form "")}]]]])
;; (mf/defc moveby-input
;; [{:keys [form] :as props}]
;; (when-not (:moveby-x @form)
;; (swap! form assoc :moveby-x 0))
;; (when-not (:moveby-y @form)
;; (swap! form assoc :moveby-y 0))
;; [:div
;; [:span "Move to position"]
;; [:div.row-flex
;; [:div.input-element.pixels
;; [:input.input-text
;; {:placeholder "X"
;; :on-change (partial on-change form :moveby-x)
;; :type "number"
;; :value (:moveby-x @form "")}]]
;; [:div.input-element.pixels
;; [:input.input-text
;; {:placeholder "Y"
;; :on-change (partial on-change form :moveby-y)
;; :type "number"
;; :value (:moveby-y @form "")}]]]])
;; --- Color Input
;; ;; --- Opacity Input
(mf/defc colorpicker
[{:keys [x y on-change value]}]
(let [left (- x 260)
top (- y 50)]
[:div.colorpicker-tooltip
{:style {:left (str left "px")
:top (str top "px")}}
;; (mf/defc opacity-input
;; [{:keys [form] :as props}]
;; (when-not (:opacity @form)
;; (swap! form assoc :opacity 100))
;; [:div
;; [:span "Opacity"]
;; [:div.row-flex
;; [:div.input-element.percentail
;; [:input.input-text
;; {:placeholder "%"
;; :on-change (partial on-change form :opacity)
;; :min "0"
;; :max "100"
;; :type "number"
;; :value (:opacity @form "")}]]]])
(cp/colorpicker
:theme :small
:value value
:on-change on-change)]))
;; ;; --- Rotate Input
(defmethod lbx/render-lightbox :interactions/colorpicker
[params]
(colorpicker params))
;; ;; (mx/defc rotate-input
;; ;; [form]
;; ;; [:div
;; ;; [:span "Rotate (dg)"]
;; ;; [:div.row-flex
;; ;; [:div.input-element.degrees
;; ;; [:input.input-text
;; ;; {:placeholder "dg"
;; ;; :on-change (partial on-change form :rotation)
;; ;; :type "number"
;; ;; :value (:rotation @form "")}]]]])
(mf/defc color-input
[{:keys [form] :as props}]
(when-not (:fill-color @form)
(swap! form assoc :fill-color "#000000"))
(when-not (:stroke-color @form)
(swap! form assoc :stroke-color "#000000"))
(letfn [(on-change [attr color]
(swap! form assoc attr color))
(on-change-fill-color [event]
(let [value (dom/event->value event)]
(when (color? value)
(on-change :fill-color value))))
(on-change-stroke-color [event]
(let [value (dom/event->value event)]
(when (color? value)
(on-change :stroke-color value))))
(show-picker [attr event]
(let [x (.-clientX event)
y (.-clientY event)
opts {:x x :y y
:on-change (partial on-change attr)
:value (get @form attr)
:transparent? true}]
(udl/open! :interactions/colorpicker opts)))]
(let [stroke-color (:stroke-color @form)
fill-color (:fill-color @form)]
[:div
[:div.row-flex
[:div.column-half
[:span "Fill"]
[:div.color-data
[:span.color-th
{:style {:background-color fill-color}
:on-click (partial show-picker :fill-color)}]
[:div.color-info
[:input
{:on-change on-change-fill-color
:value fill-color}]]]]
[:div.column-half
[:span "Stroke"]
[:div.color-data
[:span.color-th
{:style {:background-color stroke-color}
:on-click (partial show-picker :stroke-color)}]
[:div.color-info
[:input
{:on-change on-change-stroke-color
:value stroke-color}]]]]]])))
;; ;; --- Resize Input
;; --- Easing Input
;; (mf/defc resize-input
;; [{:keys [form] :as props}]
;; [:div
;; [:span "Resize"]
;; [:div.row-flex
;; [:div.input-element.pixels
;; [:input.input-text
;; {:placeholder "Width"
;; :on-change (partial on-change form :resize-width)
;; :type "number"
;; :value (:resize-width @form "")}]]
;; [:div.input-element.pixels
;; [:input.input-text
;; {:placeholder "Height"
;; :on-change (partial on-change form :resize-height)
;; :type "number"
;; :value (:resize-height @form "")}]]]])
(mf/defc easing-input
[{:keys [form] :as props}]
(when-not (:easing @form)
(swap! form assoc :easing :linear))
[:div
[:span "Easing"]
[:div.row-flex
[:select.input-select
{:placeholder "Easing"
:on-change (partial on-change form :easing)
:value (pr-str (:easing @form))}
[:option {:value ":linear"} "Linear"]
[:option {:value ":easein"} "Ease in"]
[:option {:value ":easeout"} "Ease out"]
[:option {:value ":easeinout"} "Ease in out"]]]])
;; ;; --- Color Input
;; --- Duration Input
;; (mf/defc colorpicker
;; [{:keys [x y on-change value]}]
;; (let [left (- x 260)
;; top (- y 50)]
;; [:div.colorpicker-tooltip
;; {:style {:left (str left "px")
;; :top (str top "px")}}
(mf/defc duration-input
[{:keys [form] :as props}]
(when-not (:duration @form)
(swap! form assoc :duration 300))
(when-not (:delay @form)
(swap! form assoc :delay 0))
[:div
[:span "Duration | Delay"]
[:div.row-flex
[:div.input-element.miliseconds
[:input.input-text
{:placeholder "Duration"
:type "number"
:on-change (partial on-change form :duration)
:value (pr-str (:duration @form))}]]
[:div.input-element.miliseconds
[:input.input-text {:placeholder "Delay"
:type "number"
:on-change (partial on-change form :delay)
:value (pr-str (:delay @form))}]]]])
;; (cp/colorpicker
;; :theme :small
;; :value value
;; :on-change on-change)]))
;; --- Action Input
;; (defmethod lbx/render-lightbox :interactions/colorpicker
;; [params]
;; (colorpicker params))
(mf/defc action-input
[{:keys [shape form] :as props}]
;; (when-not (:action @form)
;; (swap! form assoc :action :show))
(let [form-data (deref form)
simple? #{:gotourl :gotopage}
elements? (complement simple?)
animation? #{:show :hide :toggle}
only-easing? (complement animation?)]
[:div
[:span "Action"]
[:div.row-flex
[:select.input-select
{:placeholder "Choose an action"
:on-change (partial on-change form :action [:trigger])
:value (pr-str (:action form-data))}
[:option {:value ":show"} "Show"]
[:option {:value ":hide"} "Hide"]
[:option {:value ":toggle"} "Toggle"]
;; [:option {:value ":moveto"} "Move to"]
[:option {:value ":moveby"} "Move by"]
[:option {:value ":opacity"} "Opacity"]
[:option {:value ":size"} "Size"]
[:option {:value ":color"} "Color"]
;; [:option {:value ":rotate"} "Rotate"]
[:option {:value ":gotopage"} "Go to page"]
[:option {:value ":gotourl"} "Go to URL"]
#_[:option {:value ":goback"} "Go back"]
[:option {:value ":scrolltoelement"} "Scroll to element"]]]
;; (mf/defc color-input
;; [{:keys [form] :as props}]
;; (when-not (:fill-color @form)
;; (swap! form assoc :fill-color "#000000"))
;; (when-not (:stroke-color @form)
;; (swap! form assoc :stroke-color "#000000"))
;; (letfn [(on-change [attr color]
;; (swap! form assoc attr color))
;; (on-change-fill-color [event]
;; (let [value (dom/event->value event)]
;; (when (color? value)
;; (on-change :fill-color value))))
;; (on-change-stroke-color [event]
;; (let [value (dom/event->value event)]
;; (when (color? value)
;; (on-change :stroke-color value))))
;; (show-picker [attr event]
;; (let [x (.-clientX event)
;; y (.-clientY event)
;; opts {:x x :y y
;; :on-change (partial on-change attr)
;; :value (get @form attr)
;; :transparent? true}]
;; (udl/open! :interactions/colorpicker opts)))]
;; (let [stroke-color (:stroke-color @form)
;; fill-color (:fill-color @form)]
;; [:div
;; [:div.row-flex
;; [:div.column-half
;; [:span "Fill"]
;; [:div.color-data
;; [:span.color-th
;; {:style {:background-color fill-color}
;; :on-click (partial show-picker :fill-color)}]
;; [:div.color-info
;; [:input
;; {:on-change on-change-fill-color
;; :value fill-color}]]]]
;; [:div.column-half
;; [:span "Stroke"]
;; [:div.color-data
;; [:span.color-th
;; {:style {:background-color stroke-color}
;; :on-click (partial show-picker :stroke-color)}]
;; [:div.color-info
;; [:input
;; {:on-change on-change-stroke-color
;; :value stroke-color}]]]]]])))
(case (:action form-data)
:gotourl [:& url-input {:form form}]
;; :gotopage (pages-input form)
:color [:& color-input {:form form}]
;; :rotate (rotate-input form)
:size [:& resize-input {:form form}]
:moveto [:& moveto-input {:form form}]
:moveby [:& moveby-input {:form form}]
:opacity [:& opacity-input {:form form}]
nil)
;; ;; --- Easing Input
(when (elements? (:action form-data))
[:& elements-input {:page-id (:page shape)
:form form}])
;; (mf/defc easing-input
;; [{:keys [form] :as props}]
;; (when-not (:easing @form)
;; (swap! form assoc :easing :linear))
;; [:div
;; [:span "Easing"]
;; [:div.row-flex
;; [:select.input-select
;; {:placeholder "Easing"
;; :on-change (partial on-change form :easing)
;; :value (pr-str (:easing @form))}
;; [:option {:value ":linear"} "Linear"]
;; [:option {:value ":easein"} "Ease in"]
;; [:option {:value ":easeout"} "Ease out"]
;; [:option {:value ":easeinout"} "Ease in out"]]]])
(when (and (animation? (:action form-data))
(:element form-data))
[:& animation-input {:form form}])
;; ;; --- Duration Input
(when (or (not= (:animation form-data :none) :none)
(and (only-easing? (:action form-data))
(:element form-data)))
[:*
[:& easing-input {:form form}]
[:& duration-input {:form form}]])]))
;; (mf/defc duration-input
;; [{:keys [form] :as props}]
;; (when-not (:duration @form)
;; (swap! form assoc :duration 300))
;; (when-not (:delay @form)
;; (swap! form assoc :delay 0))
;; [:div
;; [:span "Duration | Delay"]
;; [:div.row-flex
;; [:div.input-element.miliseconds
;; [:input.input-text
;; {:placeholder "Duration"
;; :type "number"
;; :on-change (partial on-change form :duration)
;; :value (pr-str (:duration @form))}]]
;; [:div.input-element.miliseconds
;; [:input.input-text {:placeholder "Delay"
;; :type "number"
;; :on-change (partial on-change form :delay)
;; :value (pr-str (:delay @form))}]]]])
;; ;; --- Action Input
;; (mf/defc action-input
;; [{:keys [shape form] :as props}]
;; ;; (when-not (:action @form)
;; ;; (swap! form assoc :action :show))
;; (let [form-data (deref form)
;; simple? #{:gotourl :gotopage}
;; elements? (complement simple?)
;; animation? #{:show :hide :toggle}
;; only-easing? (complement animation?)]
;; [:div
;; [:span "Action"]
;; [:div.row-flex
;; [:select.input-select
;; {:placeholder "Choose an action"
;; :on-change (partial on-change form :action [:trigger])
;; :value (pr-str (:action form-data))}
;; [:option {:value ":show"} "Show"]
;; [:option {:value ":hide"} "Hide"]
;; [:option {:value ":toggle"} "Toggle"]
;; ;; [:option {:value ":moveto"} "Move to"]
;; [:option {:value ":moveby"} "Move by"]
;; [:option {:value ":opacity"} "Opacity"]
;; [:option {:value ":size"} "Size"]
;; [:option {:value ":color"} "Color"]
;; ;; [:option {:value ":rotate"} "Rotate"]
;; [:option {:value ":gotopage"} "Go to page"]
;; [:option {:value ":gotourl"} "Go to URL"]
;; #_[:option {:value ":goback"} "Go back"]
;; [:option {:value ":scrolltoelement"} "Scroll to element"]]]
;; (case (:action form-data)
;; :gotourl [:& url-input {:form form}]
;; ;; :gotopage (pages-input form)
;; :color [:& color-input {:form form}]
;; ;; :rotate (rotate-input form)
;; :size [:& resize-input {:form form}]
;; :moveto [:& moveto-input {:form form}]
;; :moveby [:& moveby-input {:form form}]
;; :opacity [:& opacity-input {:form form}]
;; nil)
;; (when (elements? (:action form-data))
;; [:& elements-input {:page-id (:page shape)
;; :form form}])
;; (when (and (animation? (:action form-data))
;; (:element form-data))
;; [:& animation-input {:form form}])
;; (when (or (not= (:animation form-data :none) :none)
;; (and (only-easing? (:action form-data))
;; (:element form-data)))
;; [:*
;; [:& easing-input {:form form}]
;; [:& duration-input {:form form}]])]))
;; --- Form
;; ;; --- Form
(mf/defc interactions-form
[{:keys [shape form] :as props}]
(letfn [(on-submit [event]
(dom/prevent-default event)
(let [sid (:id shape)
data (deref form)]
(st/emit! (dw/update-interaction sid data))
(reset! form nil)))
(on-cancel [event]
(dom/prevent-default event)
(reset! form nil))]
[:form {:on-submit on-submit}
[:& trigger-input {:form form}]
[:& action-input {:shape shape :form form}]
[:div.row-flex
[:input.btn-primary.btn-small.save-btn
{:value "Save" :type "submit"}]
[:a.cancel-btn {:on-click on-cancel}
"Cancel"]]]))
;; (mf/defc interactions-form
;; [{:keys [shape form] :as props}]
;; (letfn [(on-submit [event]
;; (dom/prevent-default event)
;; (let [sid (:id shape)
;; data (deref form)]
;; (st/emit! (dw/update-interaction sid data))
;; (reset! form nil)))
;; (on-cancel [event]
;; (dom/prevent-default event)
;; (reset! form nil))]
;; [:form {:on-submit on-submit}
;; [:& trigger-input {:form form}]
;; [:& action-input {:shape shape :form form}]
;; [:div.row-flex
;; [:input.btn-primary.btn-small.save-btn
;; {:value "Save" :type "submit"}]
;; [:a.cancel-btn {:on-click on-cancel}
;; "Cancel"]]]))
;; --- Interactions Menu
@ -477,7 +477,7 @@
(mf/defc interactions-menu
[{:keys [menu shape] :as props}]
(let [form (mf/use-state nil)
#_(let [form (mf/use-state nil)
interactions (:interactions shape)]
[:div.element-set {:key (str (:id menu))}
[:div.element-set-title (:name menu)]

View file

@ -12,7 +12,6 @@
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.main.constants :as c]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.workspace :as udw]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
@ -23,94 +22,59 @@
[uxbox.util.i18n :refer [tr]]
[uxbox.util.spec :refer [color?]]))
(mf/defc measures-menu
[{:keys [menu page] :as props}]
(mf/defc metadata-options
[{:keys [page] :as props}]
(let [metadata (:metadata page)
metadata (merge c/page-metadata metadata)]
(letfn [(on-size-change [event attr]
(let [value (-> (dom/event->value event)
(parse-int nil))]
(st/emit! (->> (assoc metadata attr value)
(udp/update-metadata (:id page))))))
change-color
(fn [color]
#_(st/emit! (->> (assoc metadata :background color)
(udp/update-metadata (:id page)))))
on-color-change
(fn [event]
(let [value (dom/event->value event)]
(change-color value)))
(change-color [color]
(st/emit! (->> (assoc metadata :background color)
(udp/update-metadata (:id page)))))
show-color-picker
(fn [event]
(let [x (.-clientX event)
y (.-clientY event)
props {:x x :y y
:default "#ffffff"
:value (:background metadata)
:transparent? true
:on-change change-color}]
(modal/show! colorpicker-modal props)))]
(on-color-change [event]
(let [value (dom/event->value event)]
(change-color value)))
[:div.element-set
[:div.element-set-title (tr "workspace.options.page-measures")]
[:div.element-set-content
[:span (tr "workspace.options.background-color")]
[:div.row-flex.color-data
[:span.color-th
{:style {:background-color (:background metadata "#ffffff")}
:on-click show-color-picker}]
[:div.color-info
[:input
{:on-change on-color-change
:value (:background metadata "#ffffff")}]]]]]))
(on-name-change [event]
(let [value (-> (dom/event->value event)
(str/trim))]
(st/emit! (-> (assoc page :name value)
(udp/update-page-attrs)))))
(show-color-picker [event]
(let [x (.-clientX event)
y (.-clientY event)
props {:x x :y y
:default "#ffffff"
:value (:background metadata)
:transparent? true
:on-change change-color}]
(modal/show! colorpicker-modal props)))]
[:div.element-set
[:div.element-set-title (tr (:name menu))]
[:div.element-set-content
[:span (tr "ds.name")]
[:div.row-flex
[:div.input-element
[:input.input-text
{:type "text"
:on-change on-name-change
:value (str (:name page))
:placeholder "page name"}]]]
[:span (tr "ds.size")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:type "number"
:on-change #(on-size-change % :width)
:value (str (:width metadata))
:placeholder (tr "ds.width")}]]
[:div.input-element.pixels
[:input.input-text
{:type "number"
:on-change #(on-size-change % :height)
:value (str (:height metadata))
:placeholder (tr "ds.height")}]]]
[:span (tr "ds.background-color")]
[:div.row-flex.color-data
[:span.color-th
{:style {:background-color (:background metadata)}
:on-click show-color-picker}]
[:div.color-info
[:input
{:on-change on-color-change
:value (:background metadata)}]]]]])))
(mf/defc grid-options-menu
[{:keys [menu page] :as props}]
(mf/defc grid-options
[{:keys [page] :as props}]
(let [metadata (:metadata page)
metadata (merge c/page-metadata metadata)]
(letfn [(on-x-change [event]
(let [value (-> (dom/event->value event)
#_(let [value (-> (dom/event->value event)
(parse-int nil))]
(st/emit! (->> (assoc metadata :grid-x-axis value)
(udp/update-metadata (:id page))))))
(on-y-change [event]
(let [value (-> (dom/event->value event)
#_(let [value (-> (dom/event->value event)
(parse-int nil))]
(st/emit! (->> (assoc metadata :grid-y-axis value)
(udp/update-metadata (:id page))))))
(change-color [color]
(st/emit! (->> (assoc metadata :grid-color color)
#_(st/emit! (->> (assoc metadata :grid-color color)
(udp/update-metadata (:id page)))))
(on-color-change [event]
(let [value (dom/event->value event)]
@ -126,9 +90,9 @@
:on-change change-color}]
(modal/show! colorpicker-modal props)))]
[:div.element-set
[:div.element-set-title (tr (:name menu))]
[:div.element-set-title (tr "element.page-grid-options")]
[:div.element-set-content
[:span (tr "ds.size")]
[:span (tr "workspace.options.size")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
@ -142,7 +106,7 @@
:value (:grid-y-axis metadata)
:on-change on-y-change
:placeholder "y"}]]]
[:span (tr "ds.color")]
[:span (tr "workspace.options.color")]
[:div.row-flex.color-data
[:span.color-th
{:style {:background-color (:grid-color metadata)}
@ -151,3 +115,10 @@
[:input
{:on-change on-color-change
:value (:grid-color metadata "#cccccc")}]]]]])))
(mf/defc options
[{:keys [page] :as props}]
[:div
[:& metadata-options {:page page}]
[:& grid-options {:page page}]])

View file

@ -0,0 +1,137 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.sidebar.options.rect
(:require
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.builtins.icons :as i]
[uxbox.main.data.workspace :as udw]
[uxbox.main.geom :as geom]
[uxbox.main.store :as st]
[uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]]
[uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.math :as math]))
(mf/defc measures-menu
[{:keys [shape] :as props}]
(let [on-size-change
(fn [event attr]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-integer 0))]
(st/emit! (udw/update-dimensions (:id shape) {attr value}))))
on-proportion-lock-change
(fn [event]
(st/emit! (udw/toggle-shape-proportion-lock (:id shape))))
on-position-change
(fn [event attr]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-integer))
point (gpt/point {attr value})]
(st/emit! (udw/update-position (:id shape) point))))
on-rotation-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-integer 0))]
(st/emit! (udw/update-shape (:id shape) {:rotation value}))))
on-radius-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-double 0))]
(st/emit! (udw/update-shape (:id shape) {:rx value :ry value}))))
on-width-change #(on-size-change % :width)
on-height-change #(on-size-change % :height)
on-pos-x-change #(on-position-change % :x)
on-pos-y-change #(on-position-change % :y)]
[:div.element-set
[:div.element-set-title (tr "workspace.options.measures")]
[:div.element-set-content
[:span (tr "workspace.options.size")]
;; WIDTH & HEIGHT
[:div.row-flex
[:div.input-element.pixels
[:input.input-text {:type "number"
:min "0"
:on-change on-width-change
:value (-> (:width shape)
(math/precision 2)
(d/coalesce-str "0"))}]]
[:div.lock-size {:class (when (:proportion-lock shape) "selected")
:on-click on-proportion-lock-change}
(if (:proportion-lock shape)
i/lock
i/unlock)]
[:div.input-element.pixels
[:input.input-text {:type "number"
:min "0"
:on-change on-height-change
:value (-> (:height shape)
(math/precision 2)
(d/coalesce-str "0"))}]]]
;; POSITION
[:span (tr "workspace.options.position")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text {:placeholder "x"
:type "number"
:on-change on-pos-x-change
:value (-> (:x shape)
(math/precision 2)
(d/coalesce-str "0"))}]]
[:div.input-element.pixels
[:input.input-text {:placeholder "y"
:type "number"
:on-change on-pos-y-change
:value (-> (:y shape)
(math/precision 2)
(d/coalesce-str "0"))}]]]
[:span (tr "workspace.options.rotation-radius")]
[:div.row-flex
[:div.input-element.degrees
[:input.input-text {:placeholder ""
:type "number"
:min 0
:max 360
:on-change on-rotation-change
:value (-> (:rotation shape 0)
(math/precision 2)
(d/coalesce-str "0"))}]]
[:div.input-element.pixels
[:input.input-text
{:placeholder "rx"
:type "number"
:on-change on-radius-change
:value (-> (:rx shape)
(math/precision 2)
(d/coalesce-str "0"))}]]]]]))
(mf/defc options
[{:keys [shape] :as props}]
[:div
[:& measures-menu {:shape shape}]
[:& fill-menu {:shape shape}]
[:& stroke-menu {:shape shape}]])

View file

@ -1,108 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.sidebar.options.rect-measures
(:require
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.main.data.workspace :as udw]
[uxbox.main.geom :as geom]
[uxbox.main.store :as st]
[uxbox.util.data :refer [parse-int parse-float read-string]]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.math :refer [precision-or-0]]))
(declare on-size-change)
(declare on-rotation-change)
(declare on-position-change)
(declare on-proportion-lock-change)
(mf/defc rect-measures-menu
[{:keys [menu shape] :as props}]
(let [size (geom/size shape)]
[:div.element-set
[:div.element-set-title (:name menu)]
[:div.element-set-content
;; SLIDEBAR FOR ROTATION AND OPACITY
[:span (tr "ds.size")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text {:placeholder (tr "ds.width")
:type "number"
:min "0"
:value (precision-or-0 (:width size) 2)
:on-change #(on-size-change % shape :width)}]]
[:div.lock-size {:class (when (:proportion-lock shape) "selected")
:on-click #(on-proportion-lock-change % shape)}
(if (:proportion-lock shape) i/lock i/unlock)]
[:div.input-element.pixels
[:input.input-text {:placeholder (tr "ds.height")
:type "number"
:min "0"
:value (precision-or-0 (:height size) 2)
:on-change #(on-size-change % shape :height)}]]]
[:span (tr "ds.position")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text {:placeholder "x"
:type "number"
:value (precision-or-0 (:x1 shape 0) 2)
:on-change #(on-position-change % shape :x)}]]
[:div.input-element.pixels
[:input.input-text {:placeholder "y"
:type "number"
:value (precision-or-0 (:y1 shape 0) 2)
:on-change #(on-position-change % shape :y)}]]]
[:span (tr "ds.rotation")]
[:div.row-flex
[:input.slidebar {:type "range"
:min 0
:max 360
:value (:rotation shape 0)
:on-change #(on-rotation-change % shape)}]]
[:div.row-flex
[:div.input-element.degrees
[:input.input-text {:placeholder ""
:type "number"
:min 0
:max 360
:value (precision-or-0 (:rotation shape "0") 2)
:on-change #(on-rotation-change % shape)}]]
[:input.input-text {:style {:visibility "hidden"}}]]]]))
(defn- on-size-change
[event shape attr]
(let [value (-> (dom/event->value event)
(parse-int 0))]
(st/emit! (udw/update-dimensions (:id shape) {attr value}))))
(defn- on-rotation-change
[event shape]
(let [value (dom/event->value event)
value (parse-int value 0)]
(st/emit! (udw/update-shape-attrs (:id shape) {:rotation value}))))
(defn- on-position-change
[event shape attr]
(let [value (-> (dom/event->value event)
(parse-int nil))
point (gpt/point {attr value})]
(st/emit! (udw/update-position (:id shape) point))))
(defn- on-proportion-lock-change
[event shape]
(if (:proportion-lock shape)
(st/emit! (udw/unlock-proportions (:id shape)))
(st/emit! (udw/lock-proportions (:id shape)))))

View file

@ -8,6 +8,7 @@
(ns uxbox.main.ui.workspace.sidebar.options.stroke
(:require
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.builtins.icons :as i]
[uxbox.main.data.workspace :as udw]
[uxbox.main.store :as st]
@ -16,126 +17,82 @@
[uxbox.util.data :refer [parse-int parse-float read-string]]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.math :refer [precision-or-0]]))
(declare on-width-change)
(declare on-opacity-change)
(declare on-stroke-style-change)
(declare on-stroke-color-change)
(declare on-border-change)
(declare show-color-picker)
[uxbox.util.math :as math]))
(mf/defc stroke-menu
[{:keys [menu shape] :as props}]
(let [local (mf/use-state {})
on-border-lock #(swap! local update :border-lock not)
on-stroke-style-change #(on-stroke-style-change % shape)
on-width-change #(on-width-change % shape)
on-stroke-color-change #(on-stroke-color-change % shape)
on-border-change-rx #(on-border-change % shape local :rx)
on-border-change-ry #(on-border-change % shape local :ry)
on-opacity-change #(on-opacity-change % shape)
show-color-picker #(show-color-picker % shape)]
[{:keys [shape] :as props}]
(let [on-stroke-style-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/read-string))]
(st/emit! (udw/update-shape (:id shape) {:stroke-style value}))))
on-stroke-width-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-double 1))]
(st/emit! (udw/update-shape (:id shape) {:stroke-width value}))))
on-stroke-opacity-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-double 1)
(/ 10000))]
(st/emit! (udw/update-shape (:id shape) {:stroke-opacity value}))))
show-color-picker
(fn [event]
(let [x (.-clientX event)
y (.-clientY event)
props {:x x :y y
:default "#ffffff"
:value (:stroke-color shape)
:on-change #(st/emit! (udw/update-shape (:id shape) {:stroke-color %}))
:transparent? true}]
(modal/show! colorpicker-modal props)))]
[:div.element-set
[:div.element-set-title (:name menu)]
[:div.element-set-title (tr "workspace.options.stroke")]
[:div.element-set-content
[:span (tr "ds.style")]
;; Stroke Style & Width
[:span (tr "workspace.options.stroke.style")]
[:div.row-flex
[:select#style.input-select {:placeholder (tr "ds.style")
:value (pr-str (:stroke-style shape))
[:select#style.input-select {:value (pr-str (:stroke-style shape))
:on-change on-stroke-style-change}
[:option {:value ":none"} (tr "ds.none")]
[:option {:value ":solid"} (tr "ds.solid")]
[:option {:value ":dotted"} (tr "ds.dotted")]
[:option {:value ":dashed"} (tr "ds.dashed")]
[:option {:value ":mixed"} (tr "ds.mixed")]]
[:div.input-element.pixels
[:input.input-text
{:placeholder (tr "ds.width")
:type "number"
:min "0"
:value (precision-or-0 (:stroke-width shape 1) 2)
:on-change on-width-change}]]]
[:option {:value ":none"} (tr "workspace.options.stroke.none")]
[:option {:value ":solid"} (tr "workspace.options.stroke.solid")]
[:option {:value ":dotted"} (tr "workspace.options.stroke.dotted")]
[:option {:value ":dashed"} (tr "workspace.options.stroke.dashed")]
[:option {:value ":mixed"} (tr "workspace.options.stroke.mixed")]]
[:span (tr "ds.color")]
[:div.input-element.pixels
[:input.input-text {:type "number"
:min "0"
:value (-> (:stroke-width shape)
(math/precision 2)
(d/coalesce-str "1"))
:on-change on-stroke-width-change}]]]
;; Stroke Color
[:span (tr "workspace.options.color")]
[:div.row-flex.color-data
[:span.color-th
{:style {:background-color (:stroke-color shape)}
:on-click show-color-picker}]
[:span.color-th {:style {:background-color (:stroke-color shape)}
:on-click show-color-picker}]
[:div.color-info
[:input
{:on-change on-stroke-color-change
:value (:stroke-color shape "")}]]]
[:input {:read-only true
:default-value (:stroke-color shape "")}]]]
[:span (tr "ds.radius")]
[:span (tr "workspace.options.opacity")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder "rx"
:type "number"
:value (precision-or-0 (:rx shape 0) 2)
:on-change on-border-change-rx}]]
[:div.lock-size
{:class (when (:border-lock @local) "selected")
:on-click on-border-lock}
i/lock]
[:div.input-element.pixels
[:input.input-text
{:placeholder "ry"
:type "number"
:value (precision-or-0 (:ry shape 0) 2)
:on-change on-border-change-ry}]]]
[:span (tr "ds.opacity")]
[:div.row-flex
[:input.slidebar
{:type "range"
:min "0"
:max "10000"
:value (* 10000 (:stroke-opacity shape 1))
:step "1"
:on-change on-opacity-change}]]]]))
(defn- on-width-change
[event shape]
(let [value (-> (dom/event->value event)
(parse-float 1))]
(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-width value}))))
(defn- on-opacity-change
[event shape]
(let [value (-> (dom/event->value event)
(parse-float 1)
(/ 10000))]
(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-opacity value}))))
(defn- on-stroke-style-change
[event shape]
(let [value (-> (dom/event->value event)
(read-string))]
(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-style value}))))
(defn- on-stroke-color-change
[event shape]
(let [value (dom/event->value event)]
(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-color value}))))
(defn- on-border-change
[event shape local attr]
(let [value (-> (dom/event->value event)
(parse-int nil))
id (:id shape)]
(if (:border-lock @local)
(st/emit! (udw/update-shape-attrs id {:rx value :ry value}))
(st/emit! (udw/update-shape-attrs id {attr value})))))
(defn- show-color-picker
[event shape]
(let [x (.-clientX event)
y (.-clientY event)
props {:x x :y y
:default "#ffffff"
:value (:stroke-color shape)
:on-change #(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-color %}))
:transparent? true}]
(modal/show! colorpicker-modal props)))
[:input.slidebar {:type "range"
:min "0"
:max "10000"
:value (-> (:stroke-opacity shape 1)
(* 10000)
(d/coalesce-str "1"))
:step "1"
:on-change on-stroke-opacity-change}]]]]))

View file

@ -29,7 +29,7 @@
{:mixins [mx/static]}
[menu {:keys [id] :as shape}]
(letfn [(update-attrs [attrs]
(st/emit! (udw/update-shape-attrs id attrs)))
#_(st/emit! (udw/update-shape-attrs id attrs)))
(on-font-family-change [event]
(let [value (dom/event->value event)
attrs {:font-family (read-string value)

View file

@ -11,7 +11,6 @@
[lentes.core :as l]
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.projects :as dp]
[uxbox.main.data.workspace :as dw]
[uxbox.main.store :as st]
@ -30,7 +29,7 @@
(mf/defc page-item
[{:keys [page index deletable? selected?] :as props}]
(let [on-edit #(modal/show! page-form-dialog {:page page})
delete-fn #(st/emit! (udp/delete-page (:id page)))
delete-fn #(st/emit! (dp/delete-page (:id page)))
on-delete #(do
(dom/prevent-default %)
(dom/stop-propagation %)
@ -101,11 +100,7 @@
close-fn #(st/emit! (dw/toggle-layout-flag :sitemap))]
[:div.sitemap.tool-window
[:div.tool-window-bar
[:div.tool-window-icon i/project-tree]
[:span (tr "ds.settings.sitemap")]
[:div.tool-window-close {:on-click close-fn} i/close]]
[:div.add-page {:on-click create-fn} i/close]]
[:div.tool-window-content
[:div.project-title
#_[:span (:name project)]
[:div.add-page {:on-click create-fn} i/close]]
[:& pages-list {:file file :current-page page}]]]))

View file

@ -11,7 +11,7 @@
[cljs.spec.alpha :as s]
[uxbox.builtins.icons :as i]
[uxbox.main.constants :as c]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.projects :as dp]
[uxbox.main.data.workspace :as udw]
[uxbox.main.store :as st]
[uxbox.main.ui.modal :as modal]
@ -34,8 +34,8 @@
(modal/hide!)
(let [data (:clean-data form)]
(if (nil? (:id data))
(st/emit! (udp/create-page data))
(st/emit! (udp/rename-page data)))))
(st/emit! (dp/create-page data))
(st/emit! (dp/rename-page data)))))
(defn- initial-data
[page]

View file

@ -11,6 +11,7 @@
[goog.events :as events]
[potok.core :as ptk]
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.main.constants :as c]
[uxbox.main.data.workspace :as dw]
[uxbox.main.geom :as geom]
@ -72,14 +73,29 @@
;; --- Selection Rect
(defn- selection->rect
[data]
(let [start (:start data)
stop (:stop data)
start-x (min (:x start) (:x stop))
start-y (min (:y start) (:y stop))
end-x (max (:x start) (:x stop))
end-y (max (:y start) (:y stop))]
(assoc data
:type :rect
:x start-x
:y start-y
:width (- end-x start-x)
:height (- end-y start-y))))
(def ^:private handle-selrect
(letfn [(update-state [state position]
(let [selrect (get-in state [:workspace-local :selrect])]
(if selrect
(assoc-in state [:workspace-local :selrect]
(dw/selection->rect (assoc selrect :stop position)))
(selection->rect (assoc selrect :stop position)))
(assoc-in state [:workspace-local :selrect]
(dw/selection->rect {:start position :stop position})))))
(selection->rect {:start position :stop position})))))
(clear-state [state]
(update state :workspace-local dissoc :selrect))]
@ -99,13 +115,11 @@
{:wrap [mf/wrap-memo]}
[{:keys [data] :as props}]
(when data
(let [{:keys [x1 y1 width height]} (geom/size data)]
[:rect.selection-rect
{:x x1
:y y1
:width width
:height height}])))
[:rect.selection-rect
{:x (:x data)
:y (:y data)
:width (:width data)
:height (:height data)}]))
;; --- Viewport Positioning
@ -127,6 +141,8 @@
;; --- Viewport
(declare remote-user-cursors)
(mf/defc canvas-and-shapes
{:wrap [mf/wrap-memo]}
[props]
@ -137,7 +153,7 @@
[:*
(for [item canvas]
[:& shape-wrapper {:shape item :key (:id item)}])
(for [item (reverse shapes)]
(for [item shapes]
[:& shape-wrapper {:shape item :key (:id item)}])]))
(mf/defc viewport
@ -275,4 +291,53 @@
(when (contains? flags :ruler)
[:& ruler {:zoom zoom :ruler (:ruler local)}])
;; -- METER CURSOR MULTIUSUARIO
[:& remote-user-cursors {:page page}]
[:& selrect {:data (:selrect local)}]]])))
(mf/defc remote-user-cursor
[{:keys [pointer user] :as props}]
[:g.multiuser-cursor {:key (:user-id pointer)
:transform (str "translate(" (:x pointer) "," (:y pointer) ") scale(4)")}
[:path {:fill (:color user)
:d "M5.292 4.027L1.524.26l-.05-.01L0 0l.258 1.524 3.769 3.768zm-.45 0l-.313.314L1.139.95l.314-.314zm-.5.5l-.315.316-3.39-3.39.315-.315 3.39 3.39zM1.192.526l-.668.667L.431.646.64.43l.552.094z"
:font-family "sans-serif"}]
[:g {:transform "translate(0 -291.708)"}
[:rect {:width "21.415"
:height "5.292"
:x "6.849"
:y "291.755"
:fill (:color user)
:fill-opacity ".893"
:paint-order "stroke fill markers"
:rx ".794"
:ry ".794"}]
[:text {:x "9.811"
:y "295.216"
:fill "#fff"
:stroke-width ".265"
:font-family "Open Sans"
:font-size"2.91"
:font-weight "400"
:letter-spacing"0"
:style {:line-height "1.25"}
:word-spacing "0"
;; :style="line-height:1
}
(:fullname user)]]])
(mf/defc remote-user-cursors
[{:keys [page] :as props}]
(let [users (mf/deref refs/workspace-users)
pointers (->> (vals (:pointer users))
(remove #(not= (:id page) (:page-id %)))
(filter #((:active users) (:user-id %))))]
(for [pointer pointers]
(let [user (get-in users [:by-id (:user-id pointer)])]
[:& remote-user-cursor {:pointer pointer
:user user
:key (:user-id pointer)}]))))

View 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) 2015-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.websockets
"A interface to webworkers exposed functionality."
(:require
[cljs.spec.alpha :as s]
[goog.events :as ev]
[beicon.core :as rx]
[potok.core :as ptk]
[uxbox.util.spec :as us])
(:import
goog.net.WebSocket
goog.net.WebSocket.EventType))
(defprotocol IWebSocket
(-stream [_] "Retrienve the message stream")
(-send [_ message] "send a message")
(-close [_] "close websocket"))
(defn open
[uri]
(let [sb (rx/subject)
ws (WebSocket. #js {:autoReconnect true})
lk1 (ev/listen ws EventType.MESSAGE
#(rx/push! sb {:type :message :payload (.-message %)}))
lk2 (ev/listen ws EventType.ERROR
#(rx/push! sb {:type :error :payload %}))
lk3 (ev/listen ws EventType.OPENED
#(rx/push! sb {:type :opened :payload %}))]
(.open ws uri)
(reify
cljs.core/IDeref
(-deref [_] ws)
IWebSocket
(-stream [_] sb)
(-send [_ msg]
(when (.isOpen ws)
(.send ws msg)))
(-close [_]
(.close ws)
(rx/end! sb)
(ev/unlistenByKey lk1)
(ev/unlistenByKey lk2)
(ev/unlistenByKey lk3)))))

View file

@ -201,6 +201,14 @@
(str/camel (name key))))))
;; (defn coalesce
;; [^number v ^number n]
;; (if (.-toFixed v)
;; (js/parseFloat (.toFixed v n))
;; 0))
;; (defmacro mirror-map [& fields]
;; (let [keys# (map #(keyword (name %)) fields)
;; vals# fields]

View file

@ -81,7 +81,8 @@
(defn precision
[^number v ^number n]
(js/parseFloat (.toFixed v n)))
(when (and (number? v) (number? n))
(js/parseFloat (.toFixed v n))))
(defn precision-or-0
[^number v ^number n]

View file

@ -5,12 +5,15 @@
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.util.router
(:require [reitit.core :as r]
[cuerdas.core :as str]
[potok.core :as ptk]
[uxbox.util.html.history :as html-history])
(:import goog.Uri
goog.Uri.QueryData))
(:require
[reitit.core :as r]
[cuerdas.core :as str]
[potok.core :as ptk]
[uxbox.common.data :as d]
[uxbox.util.html.history :as html-history])
(:import
goog.Uri
goog.Uri.QueryData))
(defonce +router+ nil)
@ -42,7 +45,9 @@
(if (empty? qparams)
(r/match->path match)
(let [uri (.parse goog.Uri (r/match->path match))
qdt (.createFromMap QueryData (clj->js qparams))]
qdt (.createFromMap QueryData (-> qparams
(d/remove-nil-vals)
(clj->js)))]
(.setQueryData uri qdt)
(.toString uri))))))

View file

@ -12,20 +12,20 @@
(schedule 0 func))
([ms func]
(let [sem (js/setTimeout #(func) ms)]
(reify rx/ICancellable
(-cancel [_]
(reify rx/IDisposable
(-dispose [_]
(js/clearTimeout sem))))))
(defn interval
[ms func]
(let [sem (js/setInterval #(func) ms)]
(reify rx/ICancellable
(-cancel [_]
(reify rx/IDisposable
(-dispose [_]
(js/clearInterval sem)))))
(defn schedule-on-idle
[func]
(let [sem (js/requestIdleCallback #(func))]
(reify rx/ICancellable
(-cancel [_]
(reify rx/IDisposable
(-dispose [_]
(js/cancelIdleCallback sem)))))

View file

@ -16,4 +16,4 @@
(let [zobj (js/JSZip.)]
(run! (partial attach-file zobj) files)
(->> (.generateAsync zobj #js {:type "blob"})
(rx/from-promise)))))
(rx/from)))))

View file

@ -10,7 +10,6 @@
[uxbox.util.router :as rt]
[uxbox.util.data :refer (parse-int)]
[uxbox.main.repo :as rp]
[uxbox.main.data.pages :as udpg]
[uxbox.main.data.projects :as udpj]))
;; --- Initialize