0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-19 19:21:23 -05:00

♻️ Refactor: shape data structure, dashboard data loading...

This commit is contained in:
Andrey Antukh 2020-01-07 09:35:38 +01:00
parent 9f8936ea40
commit 1e058463b2
56 changed files with 1785 additions and 2316 deletions

View file

@ -56,7 +56,6 @@
(let [sql create-project-user-sql
project-id (mk-uuid "project" project-index user-index)
user-id (mk-uuid "user" (dec user-index))]
(println sql project-id user-id)
(db/query-one conn [sql project-id user-id])))
;; --- Projects creation
@ -114,10 +113,10 @@
(let [canvas {:id (mk-uuid "canvas" 1)
:name "Canvas-1"
:type :canvas
:x1 200
:y1 200
:x2 1224
:y2 968}
:x 200
:y 200
:width 1024
:height 768}
data {:shapes []
:canvas [(:id canvas)]
:shapes-by-id {(:id canvas) canvas}}

View file

@ -56,7 +56,7 @@
[conn {:keys [name] :as item}]
(log/info "Creating or updating icons collection:" name)
(let [id (uuid/namespaced +icons-uuid-ns+ name)
sql "insert into icons_collections (id, user_id, name)
sql "insert into icon_collections (id, user_id, name)
values ($1, '00000000-0000-0000-0000-000000000000'::uuid, $2)
on conflict (id)
do update set name = $2
@ -122,8 +122,8 @@
"Create or replace image collection by its name."
[conn {:keys [name] :as item}]
(log/info "Creating or updating image collection:" name)
(let [id (uuid/namespaced +icons-uuid-ns+ name)
sql "insert into images_collections (id, user_id, name)
(let [id (uuid/namespaced +images-uuid-ns+ name)
sql "insert into image_collections (id, user_id, name)
values ($1, '00000000-0000-0000-0000-000000000000'::uuid, $2)
on conflict (id)
do update set name = $2

View file

@ -45,7 +45,7 @@
(sm/defmutation ::create-icons-collection
[{:keys [id user name] :as params}]
(let [id (or id (uuid/next))
sql "insert into icons_collections (id, user_id, name)
sql "insert into icon_collections (id, user_id, name)
values ($1, $2, $3) returning *"]
(db/query-one db/pool [sql id user name])))
@ -56,7 +56,7 @@
(sm/defmutation ::update-icons-collection
[{:keys [id user name] :as params}]
(let [sql "update icons_collections
(let [sql "update icon_collections
set name = $3
where id = $1
and user_id = $2
@ -97,7 +97,7 @@
(sm/defmutation ::delete-icons-collection
[{:keys [user id] :as params}]
(let [sql "update icons_collections
(let [sql "update icon_collections
set deleted_at = clock_timestamp()
where id = $1
and user_id = $2

View file

@ -60,7 +60,7 @@
(sm/defmutation ::create-image-collection
[{:keys [id user name] :as params}]
(let [sql "insert into images_collections (id, user_id, name)
(let [sql "insert into image_collections (id, user_id, name)
values ($1, $2, $3) returning *;"]
(db/query-one db/pool [sql (or id (uuid/next)) user name])))
@ -71,7 +71,7 @@
(sm/defmutation ::update-images-collection
[{:keys [id user name] :as params}]
(let [sql "update images_collections
(let [sql "update image_collections
set name = $3
where id = $1
and user_id = $2
@ -85,7 +85,7 @@
(sm/defmutation ::delete-images-collection
[{:keys [id user] :as params}]
(let [sql "update images_collections
(let [sql "update image_collections
set deleted_at = clock_timestamp()
where id = $1
and user_id = $2
@ -102,13 +102,13 @@
(-> (ds/save storage filename path)
(su/handle-on-context))))
(def ^:private create-image-sql
(su/defstr sql:create-image
"insert into images (user_id, name, collection_id, path, width, height, mimetype)
values ($1, $2, $3, $4, $5, $6, $7) returning *")
(defn- store-image-in-db
[conn {:keys [id user name path collection-id height width mimetype]}]
(let [sqlv [create-image-sql user name collection-id
(let [sqlv [sql:create-image user name collection-id
path width height mimetype]]
(-> (db/query-one conn sqlv)
(p/then populate-thumbnail)

View file

@ -59,7 +59,7 @@
(ex/raise :type :validation
:code :not-authorized))))))
;; --- Mutation: Create Project
;; --- Mutation: Create Project File
(declare create-file)
(declare create-page)
@ -72,9 +72,9 @@
[{:keys [user project-id] :as params}]
(db/with-atomic [conn db/pool]
(proj/check-edition-permissions! conn user project-id)
(p/let [file (create-file conn params)]
(create-page conn (assoc params :file-id (:id file)))
file)))
(p/let [file (create-file conn params)
page (create-page conn (assoc params :file-id (:id file)))]
(assoc file :pages [(:id page)]))))
(defn create-file
[conn {:keys [id user name project-id] :as params}]
@ -88,7 +88,10 @@
[conn {:keys [user file-id] :as params}]
(let [id (uuid/next)
name "Page 1"
data (blob/encode {})
data (blob/encode
{:shapes []
:canvas []
:shapes-by-id {}})
sql "insert into project_pages (id, user_id, file_id, name, version,
ordering, data)
values ($1, $2, $3, $4, 0, 1, $5) returning id"]

View file

@ -31,7 +31,7 @@
(def ^:private icons-collections-sql
"select *,
(select count(*) from icons where collection_id = ic.id) as num_icons
from icons_collections as ic
from icon_collections as ic
where (ic.user_id = $1 or
ic.user_id = '00000000-0000-0000-0000-000000000000'::uuid)
and ic.deleted_at is null

View file

@ -53,7 +53,7 @@
(def ^:private images-collections-sql
"select *,
(select count(*) from images where collection_id = ic.id) as num_images
from images_collections as ic
from image_collections as ic
where (ic.user_id = $1 or
ic.user_id = '00000000-0000-0000-0000-000000000000'::uuid)
and ic.deleted_at is null
@ -86,23 +86,32 @@
;; --- Query Images by Collection (id)
(def images-by-collection-sql
(su/defstr sql:images-by-collection
"select * from images
where (user_id = $1 or
user_id = '00000000-0000-0000-0000-000000000000'::uuid)
and deleted_at is null
and case when $2::uuid is null then collection_id is null
else collection_id = $2::uuid
end
order by created_at desc;")
order by created_at desc")
(s/def ::images-by-collection-query
(su/defstr sql:images-by-collection1
"with images as (~{sql:images-by-collection})
select im.* from images as im
where im.collection_id is null")
(su/defstr sql:images-by-collection2
"with images as (~{sql:images-by-collection})
select im.* from images as im
where im.collection_id = $2")
(s/def ::images-by-collection
(s/keys :req-un [::user]
:opt-un [::collection-id]))
(sq/defquery ::images-by-collection
[{:keys [user collection-id] :as params}]
(let [sqlv [images-by-collection-sql user collection-id]]
(let [sqlv (if (nil? collection-id)
[sql:images-by-collection1 user]
[sql:images-by-collection2 user collection-id])]
(-> (db/query db/pool sqlv)
(p/then populate-thumbnails)
(p/then #(mapv populate-urls %)))))

View file

@ -99,13 +99,13 @@
{:enter (fn [data]
(let [context (get-in data [:request ::vw/routing-context])
uploads (reduce (fn [acc ^FileUpload upload]
(assoc acc
(keyword (.name upload))
{:type :uploaded-file
:mtype (.contentType upload)
:path (.uploadedFileName upload)
:name (.fileName upload)
:size (.size upload)}))
(assoc! acc
(keyword (.name upload))
{:type :uploaded-file
:mtype (.contentType upload)
:path (.uploadedFileName upload)
:name (.fileName upload)
:size (.size upload)}))
(transient {})
(.fileUploads ^RoutingContext context))]
(update data :request assoc attr (persistent! uploads))))}))

View file

@ -7,7 +7,13 @@
(ns uxbox.common.data
"Data manipulation and query helper functions."
(:refer-clojure :exclude [concat])
(:require [clojure.set :as set]))
(:require [clojure.set :as set]
#?(:cljs [cljs.reader :as r]
:clj [clojure.edn :as r])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Structures Manipulation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn concat
[& colls]
@ -18,6 +24,18 @@
(rest colls))
result)))
(defn enumerate
([items] (enumerate items 0))
([items start]
(loop [idx start
items items
res []]
(if (empty? items)
res
(recur (inc idx)
(rest items)
(conj res [idx (first items)]))))))
(defn seek
([pred coll]
(seek pred coll nil))
@ -48,3 +66,75 @@
(recur (first r) (rest r) rs)
(recur (first r) (rest r) (conj rs [:mod k vmb]))))
rs)))))
(defn index-by
"Return a indexed map of the collection keyed by the result of
executing the getter over each element of the collection."
[getter coll]
(persistent!
(reduce #(assoc! %1 (getter %2) %2) (transient {}) coll)))
(defn remove-nil-vals
"Given a map, return a map removing key-value
pairs when value is `nil`."
[data]
(into {} (remove (comp nil? second) data)))
(defn without-keys
"Return a map without the keys provided
in the `keys` parameter."
[data keys]
(persistent!
(reduce #(dissoc! %1 %2) (transient data) keys)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Parsing / Conversion
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- nan?
[v]
(not= v v))
(defn- impl-parse-integer
[v]
#?(:cljs (js/parseInt v 10)
:clj (try
(Integer/parseInt v)
(catch Throwable e
nil))))
(defn- impl-parse-double
[v]
#?(:cljs (js/parseFloat v)
:clj (try
(Double/parseDouble v)
(catch Throwable e
nil))))
(defn parse-integer
([v]
(parse-integer v nil))
([v default]
(let [v (impl-parse-integer v)]
(if (or (nil? v) (nan? v))
default
v))))
(defn parse-double
([v]
(parse-double v nil))
([v default]
(let [v (impl-parse-double v)]
(if (or (nil? v) (nan? v))
default
v))))
(defn read-string
[v]
(r/read-string v))
(defn coalesce-str
[val default]
(if (or (nil? val) (nan? val))
default
(str val)))

View file

@ -16,13 +16,12 @@
(s/def ::background string?)
(s/def ::background-opacity number?)
;; Page related
(s/def ::file-id uuid?)
(s/def ::user uuid?)
(s/def ::created-at inst?)
(s/def ::modified-at inst?)
(s/def ::version number?)
(s/def ::ordering number?)
(s/def ::metadata
(s/keys :opt-un [::grid-y-axis
::grid-x-axis
::grid-color
::background
::background-opacity]))
;; Page Data related
(s/def ::shape
@ -35,19 +34,9 @@
(s/def ::shapes-by-id
(s/map-of uuid? ::shape))
;; Main
(s/def ::data
(s/keys :req-un [::shapes ::canvas ::shapes-by-id]))
(s/def ::metadata
(s/keys :opt-un [::grid-y-axis
::grid-x-axis
::grid-color
::background
::background-opacity]))
(s/def ::shape-change
(s/tuple #{:add :mod :del} keyword? any?))

View file

@ -3,15 +3,15 @@
org.clojure/clojure {:mvn/version "1.10.1"}
com.cognitect/transit-cljs {:mvn/version "0.8.256"}
cljsjs/react {:mvn/version "16.11.0-0"}
cljsjs/react-dom {:mvn/version "16.11.0-0"}
cljsjs/react-dom-server {:mvn/version "16.11.0-0"}
cljsjs/react {:mvn/version "16.12.0-1"}
cljsjs/react-dom {:mvn/version "16.12.0-1"}
cljsjs/react-dom-server {:mvn/version "16.12.0-1"}
environ/environ {:mvn/version "1.1.0"}
metosin/reitit-core {:mvn/version "0.3.10"}
expound/expound {:mvn/version "0.7.2"}
funcool/beicon {:mvn/version "5.1.0"}
funcool/beicon {:mvn/version "6.0.0-SNAPSHOT"}
funcool/cuerdas {:mvn/version "2.2.0"}
funcool/lentes {:mvn/version "1.3.3"}
funcool/potok {:mvn/version "2.8.0-SNAPSHOT"}

View file

@ -242,7 +242,7 @@
// IMAGES SECTION
&.images-th {
background-color: $primary-ui-bg;
border: 1px dashed $color-gray-light;
border-bottom: 2px solid lighten($color-gray-light, 12%);
&:hover {

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

@ -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

@ -10,8 +10,8 @@
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[potok.core :as ptk]
[uxbox.common.pages :as cp]
[uxbox.main.repo.core :as rp]
[uxbox.util.data :refer [index-by-id concatv]]
[uxbox.util.router :as rt]
[uxbox.util.spec :as us]
[uxbox.util.time :as dt]
@ -25,12 +25,15 @@
(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
@ -38,38 +41,18 @@
::created-at
::modified-at]))
(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 ::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 ::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
@ -77,23 +60,8 @@
::metadata
::data]))
(s/def ::pages
(s/every ::page :kind vector?))
;; --- 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 dissoc-project
"A reduce function for dissoc the project from the state map."
[state id]
(update state :projects dissoc id))
(defn unpack-page
[state {:keys [id data metadata] :as page}]
(-> state
@ -114,13 +82,10 @@
;; --- Initialize Dashboard
(declare fetch-projects)
(declare projects-fetched?)
(declare fetch-files)
(declare initialized)
;; TODO: rename to initialize dashboard
(defn initialize
[id]
(ptk/reify ::initialize
@ -156,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
@ -179,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)
@ -205,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)
@ -219,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
@ -243,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]
@ -265,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
@ -348,7 +311,7 @@
(defn pages-fetched
[pages]
(s/assert ::pages pages)
(s/assert (s/every ::page) pages)
(ptk/reify ::pages-fetched
IDeref
(-deref [_] pages)

View file

@ -50,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?)
@ -67,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
@ -91,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]))
@ -169,28 +170,6 @@
(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]
(update-in state [:workspace-users :by-id (:id user)] merge user))
state
users))))
;; --- Handle: Who
;; TODO: assign color
@ -256,66 +235,119 @@
(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)
(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
(dp/fetch-file file-id)
(dp/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? ::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 page-id)
#_(initialize-alignment page-id))))
(->> stream
(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 #(= ::stop-watcher %) stream)))))))
;; 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))))
#_(->> 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
@ -576,14 +608,22 @@
(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)))
@ -593,6 +633,32 @@
(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
(defn impl-duplicate-shape
@ -608,7 +674,7 @@
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)
@ -675,41 +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
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
@ -763,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
@ -923,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])
@ -1035,59 +1083,16 @@
(rx/map (constantly clear-drawing))
(rx/take-until stoper)))))))
;; --- Shape Proportions
;; (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])
;; (geom/size)
;; 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))
;; --- 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
@ -1098,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

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

@ -80,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"
@ -122,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

@ -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,60 +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
@ -164,9 +110,9 @@
;; [:div.project-th-icon.pages
;; i/page
;; #_[:span (:total-pages project)]]
#_[:div.project-th-icon.comments
i/chat
[:span "0"]]
;; [:div.project-th-icon.comments
;; i/chat
;; [:span "0"]]
[:div.project-th-icon.edit
{:on-click on-edit}
i/pencil]
@ -177,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
@ -185,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.new-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)}])]]]))
@ -245,10 +189,6 @@
: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.recent-projects {:on-click #(st/emit! (udp/go-to-project nil))
:class-name (when (nil? id) "current")}
[:span.element-title "Recent"]]
@ -280,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

@ -60,7 +60,7 @@
(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,28 +93,13 @@
(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 [(str file-id)]
:fn (fn []
(st/emit! (dw/initialize-ws file-id))
#(st/emit! (dw/finalize-ws file-id)))})
(mf/use-effect
{:deps #js [(str file-id)
(str page-id)]
:fn (fn []
(let [sub (shortcuts/init)]
(st/emit! (dw/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}]
@ -124,5 +109,34 @@
(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))
@ -282,7 +279,9 @@
shape (dissoc shape ::initialized? :modifier-mtx)]
;; Add & select the created shape to the workspace
(rx/of dw/deselect-all
(dw/add-shape shape)))))))))
(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,22 +7,19 @@
(ns uxbox.main.ui.workspace.header
(:require
[rumext.alpha :as mf]
[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.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]))
@ -66,6 +63,7 @@
(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 {})
@ -91,100 +89,100 @@
[:div.workspace-options
[:ul.options-btn
[:li.tooltip.tooltip-bottom
{:alt (tr "ds.help.canvas")
{: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 "ds.help.rect")
{: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 "ds.help.circle")
{: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 "ds.help.text")
{: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 "ds.help.path")
{: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 "ds.help.curve")
{: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 "header.color-palette")
{: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]
; [: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]
[: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]
[: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]]
;;[:& user]
;; [:li.tooltip.tooltip-bottom
;; {:alt (tr "header.align")}
;; i/alignment]]
;; [:& user]
[:div.secondary-options
[:& zoom-widget]
[:a.tooltip.tooltip-bottom.view-mode
{:alt (tr "header.view-mode")
{:alt (tr "workspace.header.view-mode")
;; :on-click #(st/emit! (dw/->OpenView (:id page)))
}
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

@ -99,10 +99,10 @@
(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 (/ 8.0 zoom) "," (/ 5 zoom))
@ -111,42 +111,42 @@
[:& 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)
@ -203,9 +203,9 @@
(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))

View file

@ -205,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

View file

@ -9,28 +9,15 @@
(: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.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.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.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.util.i18n :refer [tr]]))
;; --- Constants
@ -44,43 +31,6 @@
;; :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}])
;; (def ^:private +menus-by-id+
;; (data/index-by-id +menus+))
;; --- Options
(mf/defc shape-options
@ -93,6 +43,9 @@
(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
@ -102,9 +55,9 @@
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-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)

View file

@ -8,126 +8,125 @@
(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.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)]))
[uxbox.util.i18n :refer [tr]]
[uxbox.util.math :as math :refer (precision-or-0)]))
(mf/defc size-options
(mf/defc measures-menu
[{:keys [shape] :as props}]
[:*
[:span (tr "ds.size")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text {:placeholder (tr "ds.width")
:type "number"
:min "0"
;; :on-change #(on-size-change % shape :rx)
:value (precision-or-0 (:rx shape 0) 2)}]]
[:div.lock-size {:class (when (:proportion-lock shape) "selected")
;; :on-click #(on-proportion-lock-change % shape)
}
(if (:proportion-lock shape) i/lock i/unlock)]
(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}))))
[:div.input-element.pixels
[:input.input-text
{:placeholder (tr "ds.height")
:type "number"
:min "0"
;; :on-change #(on-size-change % shape :ry)
:value (precision-or-0 (:ry shape 0) 2)}]]]])
on-proportion-lock-change
(fn [event]
(st/emit! (udw/toggle-shape-proportion-lock (:id shape))))
(mf/defc position-options
[{:keys [shape] :as props}]
[:*
[:span (tr "ds.position")]
[:div.row-flex
[:div.input-element.pixels
[:input.input-text
{:placeholder "cx"
:type "number"
;; :on-change #(on-position-change % shape :x)
:value (precision-or-0 (:cx shape 0) 2)}]]
[:div.input-element.pixels
[:input.input-text
{:placeholder "cy"
:type "number"
;; :on-change #(on-position-change % shape :y)
:value (precision-or-0 (:cy shape 0) 2)}]]]])
on-size-rx-change #(on-size-change % :rx)
on-size-ry-change #(on-size-change % :ry)
(mf/defc rotation-options
[{:keys [shape] :as props}]
[:*
[:span (tr "ds.rotation")]
[:div.row-flex
[:input.slidebar
{:type "range"
:min 0
:max 360
;; :on-change #(on-rotation-change % shape)
:value (:rotation shape 0)}]]
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))))
[:div.row-flex
[:div.input-element.degrees
[:input.input-text
{:placeholder ""
:type "number"
:min 0
:max 360
;; :on-change #(on-rotation-change % shape)
:value (precision-or-0 (:rotation shape 0) 2)}]]
[:input.input-text
{:style {:visibility "hidden"}}]]])
on-pos-cx-change #(on-position-change % :x)
on-pos-cy-change #(on-position-change % :y)
(mf/defc measures-options
[{:keys [shape] :as props}]
[:div.element-set
[:div.element-set-title (tr "element.measures")]
[:div.element-set-content
[:& size-options {:shape shape}]
[:& position-options {:shape shape}]
[:& rotation-options {:shape shape}]]])
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}))))
;; (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))))
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}))))]
;; (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}))))
[:div.element-set
[:div.element-set-title (tr "workspace.options.measures")]
[:div.element-set-content
;; (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))))
;; 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)]
;; (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)))))
[: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-options {:shape shape}]
[:& 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,31 +9,33 @@
(: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 [shape] :as props}]
(letfn [(change-attrs [attrs]
(st/emit! (udw/update-shape-attrs (:id shape) attrs)))
(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}]
@ -42,10 +44,10 @@
[: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

@ -46,9 +46,9 @@
(modal/show! colorpicker-modal props)))]
[:div.element-set
[:div.element-set-title (tr "element.page-measures")]
[:div.element-set-title (tr "workspace.options.page-measures")]
[:div.element-set-content
[:span (tr "ds.background-color")]
[:span (tr "workspace.options.background-color")]
[:div.row-flex.color-data
[:span.color-th
{:style {:background-color (:background metadata "#ffffff")}
@ -92,7 +92,7 @@
[:div.element-set
[: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
@ -106,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)}

View file

@ -8,111 +8,130 @@
(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.util.data :refer [parse-int parse-float read-string]]
[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 :refer [precision-or-0]]))
[uxbox.util.math :as math]))
(declare on-size-change)
(declare on-rotation-change)
(declare on-position-change)
(declare on-proportion-lock-change)
(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)]
(mf/defc measures
[{:keys [menu shape] :as props}]
(let [size (geom/size shape)]
[:div.element-set
[:div.element-set-title (tr "element.measures")]
[:div.element-set-title (tr "workspace.options.measures")]
[:div.element-set-content
[:span (tr "ds.size")]
[:span (tr "workspace.options.size")]
;; WIDTH & HEIGHT
[:div.row-flex
[:div.input-element.pixels
[:input.input-text {:placeholder (tr "ds.width")
:type "number"
[:input.input-text {:type "number"
:min "0"
:value (precision-or-0 (:width size) 2)}]]
: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 % shape)}
(if (:proportion-lock shape) i/lock i/unlock)]
:on-click on-proportion-lock-change}
(if (:proportion-lock shape)
i/lock
i/unlock)]
[:div.input-element.pixels
[:input.input-text {:placeholder (tr "ds.height")
:type "number"
[:input.input-text {:type "number"
:min "0"
:value (precision-or-0 (:height size) 2)}]]]
:on-change on-height-change
:value (-> (:height shape)
(math/precision 2)
(d/coalesce-str "0"))}]]]
;; POSITION
[:span (tr "ds.position")]
[:span (tr "workspace.options.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-pos-x-change
:value (-> (:x shape)
(math/precision 2)
(d/coalesce-str "0"))}]]
[:div.input-element.pixels
[:input.input-text {:placeholder "y"
:type "number"
:value (precision-or-0 (:y1 shape 0) 2)}]]]
;; ROTATION
[:span (tr "ds.rotation")]
[:div.row-flex
[:input.slidebar {:type "range"
:min 0
:max 360
;; :on-change #(on-rotation-change % shape)
:value (:rotation shape 0)}]]
: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 % shape)
:value (precision-or-0 (:rotation shape "0") 2)}]]
[:input.input-text {:style {:visibility "hidden"}}]]]]))
:on-change on-rotation-change
:value (-> (:rotation shape 0)
(math/precision 2)
(d/coalesce-str "0"))}]]
;; (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}))))
[: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"))}]]]]]))
;; (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)))))
;; :rect [::rect-measures ::fill ::stroke]
(mf/defc options
[{:keys [shape] :as props}]
[:div
[:& measures {:shape shape}]
[:& 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 [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)]
(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 (tr "element.stroke")]
[: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

@ -82,11 +82,11 @@
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)))
: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]
@ -115,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
@ -155,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

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)))))