diff --git a/backend/bin/start-dev b/backend/bin/start-dev index 1ea95d260..dea5773d8 100755 --- a/backend/bin/start-dev +++ b/backend/bin/start-dev @@ -8,6 +8,6 @@ if [ ! -e ~/local/.fixtures-loaded ]; then touch ~/local/.fixtures-loaded fi -clojure ${CLOJURE_OPTS} -m uxbox.main +clojure -m uxbox.main diff --git a/backend/src/uxbox/fixtures.clj b/backend/src/uxbox/fixtures.clj index 1d7871059..7c8b74dfd 100644 --- a/backend/src/uxbox/fixtures.clj +++ b/backend/src/uxbox/fixtures.clj @@ -43,6 +43,21 @@ photo ""] (db/query-one conn [sql id fullname username email password photo]))) +;; --- Project User Relation Creation + +(def create-project-user-sql + "insert into project_users (project_id, user_id, can_edit) + values ($1, $2, true) + returning *") + +(defn create-additional-project-user + [conn [project-index user-index]] + (log/info "create project user" user-index project-index) + (let [sql create-project-user-sql + project-id (mk-uuid "project" project-index user-index) + user-id (mk-uuid "user" (dec user-index))] + (db/query-one conn [sql project-id user-id]))) + ;; --- Projects creation (def create-project-sql @@ -56,8 +71,12 @@ (let [sql create-project-sql id (mk-uuid "project" project-index user-index) user-id (mk-uuid "user" user-index) - name (str "sample project " project-index)] - (db/query-one conn [sql id user-id name]))) + name (str "project " project-index "," user-index)] + (p/do! (db/query-one conn [sql id user-id name]) + (when (and (= project-index 0) + (> user-index 0)) + (create-additional-project-user conn [project-index user-index]))))) + ;; --- Create Page Files @@ -72,7 +91,7 @@ id (mk-uuid "page-file" file-index project-index user-index) user-id (mk-uuid "user" user-index) project-id (mk-uuid "project" project-index user-index) - name (str "Sample file " file-index)] + name (str "file " file-index "," project-index "," user-index)] (db/query-one conn [sql id user-id project-id name]))) ;; --- Create Pages @@ -94,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}} diff --git a/backend/src/uxbox/http.clj b/backend/src/uxbox/http.clj index 7c78ecc17..ac80b9cdb 100644 --- a/backend/src/uxbox/http.clj +++ b/backend/src/uxbox/http.clj @@ -44,7 +44,7 @@ interceptors/format-response-body (vxi/errors errors/handle)] - routes [["/sub/:page-id" {:interceptors [(vxi/cookies) + routes [["/sub/:file-id" {:interceptors [(vxi/cookies) (vxi/cors cors-opts) (session/auth)] :get ws/handler}] diff --git a/backend/src/uxbox/http/interceptors.clj b/backend/src/uxbox/http/interceptors.clj index 4d27697eb..45a33aef3 100644 --- a/backend/src/uxbox/http/interceptors.clj +++ b/backend/src/uxbox/http/interceptors.clj @@ -7,7 +7,7 @@ (ns uxbox.http.interceptors (:require [vertx.web :as vw] - [uxbox.util.blob :as blob] + [uxbox.util.transit :as t] [uxbox.util.exceptions :as ex]) (:import io.vertx.ext.web.RoutingContext @@ -20,7 +20,7 @@ mtype (get-in request [:headers "content-type"])] (if (= "application/transit+json" mtype) (try - (let [params (blob/decode-from-json body)] + (let [params (t/decode (t/buffer->bytes body))] (update data :request assoc :body-params params)) (catch Exception e (ex/raise :type :parse @@ -35,7 +35,7 @@ (coll? body) (-> data (assoc-in [:response :body] - (blob/encode-with-json body true)) + (t/bytes->buffer (t/encode body))) (update-in [:response :headers] assoc "content-type" "application/transit+json")) diff --git a/backend/src/uxbox/http/session.clj b/backend/src/uxbox/http/session.clj index 7dad11bdb..f85b12867 100644 --- a/backend/src/uxbox/http/session.clj +++ b/backend/src/uxbox/http/session.clj @@ -56,7 +56,7 @@ (spx/terminate (assoc data ::unauthorized true))))) (vc/handle-on-context)))) :leave (fn [data] - (if (and (::unauthorized data) (:response data)) + (if (::unauthorized data) (update data :response assoc :status 403 :body {:type :authentication :code :unauthorized}) diff --git a/backend/src/uxbox/http/ws.clj b/backend/src/uxbox/http/ws.clj index 321278fa9..1ccc60e60 100644 --- a/backend/src/uxbox/http/ws.clj +++ b/backend/src/uxbox/http/ws.clj @@ -7,6 +7,7 @@ (ns uxbox.http.ws "Web Socket handlers" (:require + [clojure.tools.logging :as log] [promesa.core :as p] [uxbox.emails :as emails] [uxbox.http.session :as session] @@ -14,6 +15,7 @@ [uxbox.services.mutations :as sm] [uxbox.services.queries :as sq] [uxbox.util.uuid :as uuid] + [uxbox.util.transit :as t] [uxbox.util.blob :as blob] [vertx.http :as vh] [vertx.web :as vw] @@ -31,50 +33,89 @@ (declare ws-websocket) (declare ws-send!) -(declare ws-on-message!) -(declare ws-on-close!) -;; --- Public API +;; --- State Management -(declare on-message) -(declare on-close) -(declare on-eventbus-message) +(defonce state + (atom {})) -(def state (atom {})) +(defn send! + [ws message] + (ws-send! ws (-> (t/encode message) + (t/bytes->str)))) + +(defmulti handle-message + (fn [ws message] (:type message))) + +(defmethod handle-message :connect + [{:keys [file-id user-id] :as ws} message] + (let [local (swap! state assoc-in [file-id user-id] ws) + sessions (get local file-id) + message {:type :who :users (set (keys sessions))}] + (run! #(send! % message) (vals sessions)))) + +(defmethod handle-message :disconnect + [{:keys [user-id] :as ws} {:keys [file-id] :as message}] + (let [local (swap! state update file-id dissoc user-id) + sessions (get local file-id) + message {:type :who :users (set (keys sessions))}] + (run! #(send! % message) (vals sessions)))) + +(defmethod handle-message :who + [{:keys [file-id] :as ws} message] + (let [users (keys (get @state file-id))] + (send! ws {:type :who :users (set users)}))) + +(defmethod handle-message :pointer-update + [{:keys [user-id file-id] :as ws} message] + (let [sessions (->> (vals (get @state file-id)) + (remove #(= user-id (:user-id %)))) + message (assoc message :user-id user-id)] + (run! #(send! % message) sessions))) + +(defn- on-eventbus-message + [{:keys [file-id user-id] :as ws} {:keys [body] :as message}] + (send! ws body)) + +(defn- start-eventbus-consumer! + [vsm ws fid] + (let [topic (str "internal.uxbox.file." fid)] + (ve/consumer vsm topic #(on-eventbus-message ws %2)))) + +;; --- Handler (defn handler [{:keys [user] :as req}] (letfn [(on-init [ws] (let [vsm (::vw/execution-context req) - tpc "test.foobar" - pid (get-in req [:path-params :page-id]) - sem (ve/consumer vsm tpc #(on-eventbus-message ws %2))] - (swap! state update pid (fnil conj #{}) user) + fid (get-in req [:path-params :file-id]) + ws (assoc ws + :user-id user + :file-id fid) + sem (start-eventbus-consumer! vsm ws fid)] + (handle-message ws {:type :connect}) (assoc ws ::sem sem))) (on-message [ws message] - (let [pid (get-in req [:path-params :page-id])] - (ws-send! ws (str (::counter ws 0))) - (update ws ::counter (fnil inc 0)))) + (try + (->> (t/str->bytes message) + (t/decode) + (handle-message ws)) + (catch Throwable err + (log/error "Unexpected exception:\n" + (with-out-str + (.printStackTrace err (java.io.PrintWriter. *out*))))))) (on-close [ws] - (let [pid (get-in req [:path-params :page-id])] - (swap! state update pid disj user) + (let [fid (get-in req [:path-params :file-id])] + (handle-message ws {:type :disconnect :file-id fid}) (.unregister (::sem ws))))] - ;; (ws-websocket :on-init on-init - ;; :on-message on-message - ;; :on-close on-close))) - (-> (ws-websocket) (assoc :on-init on-init :on-message on-message :on-close on-close)))) -(defn- on-eventbus-message - [ws {:keys [body] :as message}] - (ws-send! ws body)) - ;; --- Internal (vertx api) (experimental) (defrecord WebSocket [on-init on-message on-close] diff --git a/backend/src/uxbox/media_loader.clj b/backend/src/uxbox/media_loader.clj index aea849529..d111ba88c 100644 --- a/backend/src/uxbox/media_loader.clj +++ b/backend/src/uxbox/media_loader.clj @@ -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 diff --git a/backend/src/uxbox/services/mutations/icons.clj b/backend/src/uxbox/services/mutations/icons.clj index edc1b0330..d9c306526 100644 --- a/backend/src/uxbox/services/mutations/icons.clj +++ b/backend/src/uxbox/services/mutations/icons.clj @@ -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 diff --git a/backend/src/uxbox/services/mutations/images.clj b/backend/src/uxbox/services/mutations/images.clj index 4b89383dd..b4270e0b4 100644 --- a/backend/src/uxbox/services/mutations/images.clj +++ b/backend/src/uxbox/services/mutations/images.clj @@ -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) diff --git a/backend/src/uxbox/services/mutations/project_files.clj b/backend/src/uxbox/services/mutations/project_files.clj index d3e7e294e..f2bfc6046 100644 --- a/backend/src/uxbox/services/mutations/project_files.clj +++ b/backend/src/uxbox/services/mutations/project_files.clj @@ -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"] diff --git a/backend/src/uxbox/services/mutations/project_pages.clj b/backend/src/uxbox/services/mutations/project_pages.clj index fe81faa7f..1e27df39b 100644 --- a/backend/src/uxbox/services/mutations/project_pages.clj +++ b/backend/src/uxbox/services/mutations/project_pages.clj @@ -8,17 +8,18 @@ (:require [clojure.spec.alpha :as s] [promesa.core :as p] + [uxbox.common.pages :as cp] [uxbox.db :as db] [uxbox.services.mutations :as sm] [uxbox.services.mutations.project-files :as files] [uxbox.services.queries.project-pages :refer [decode-row]] [uxbox.services.util :as su] - [uxbox.common.pages :as cp] - [uxbox.util.exceptions :as ex] [uxbox.util.blob :as blob] - [uxbox.util.sql :as sql] + [uxbox.util.exceptions :as ex] [uxbox.util.spec :as us] - [uxbox.util.uuid :as uuid])) + [uxbox.util.sql :as sql] + [uxbox.util.uuid :as uuid] + [vertx.eventbus :as ve])) ;; --- Helpers & Specs @@ -100,7 +101,7 @@ [conn {:keys [user-id id version data operations]}] (let [sql "insert into project_page_snapshots (user_id, page_id, version, data, operations) values ($1, $2, $3, $4, $5) - returning id, version, operations"] + returning id, page_id, user_id, version, operations"] (db/query-one conn [sql user-id id version data operations]))) ;; --- Mutation: Rename Page @@ -169,7 +170,14 @@ (-> (update-page-data conn page) (p/then (fn [_] (insert-page-snapshot conn page))) - (p/then (fn [s] (retrieve-lagged-operations conn s params)))))) + (p/then (fn [s] + (let [topic (str "internal.uxbox.file." (:file-id page))] + (p/do! (ve/publish! uxbox.core/system topic {:type :page-snapshot + :user-id (:user-id s) + :page-id (:page-id s) + :version (:version s) + :operations ops}) + (retrieve-lagged-operations conn s params)))))))) (su/defstr sql:lagged-snapshots "select s.id, s.operations @@ -182,7 +190,7 @@ (let [sql sql:lagged-snapshots] (-> (db/query conn [sql (:id params) (:version params) #_(:id snapshot)]) (p/then (fn [rows] - {:id (:id params) + {:page-id (:id params) :version (:version snapshot) :operations (into [] (comp (map decode-row) (map :operations) diff --git a/backend/src/uxbox/services/queries/icons.clj b/backend/src/uxbox/services/queries/icons.clj index 77428755c..eb7faecf1 100644 --- a/backend/src/uxbox/services/queries/icons.clj +++ b/backend/src/uxbox/services/queries/icons.clj @@ -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 diff --git a/backend/src/uxbox/services/queries/images.clj b/backend/src/uxbox/services/queries/images.clj index 93a52f0da..45168e8b1 100644 --- a/backend/src/uxbox/services/queries/images.clj +++ b/backend/src/uxbox/services/queries/images.clj @@ -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 %))))) diff --git a/backend/src/uxbox/services/queries/project_files.clj b/backend/src/uxbox/services/queries/project_files.clj index 06fa82135..21510c820 100644 --- a/backend/src/uxbox/services/queries/project_files.clj +++ b/backend/src/uxbox/services/queries/project_files.clj @@ -22,10 +22,13 @@ (s/def ::id ::us/uuid) (s/def ::name ::us/string) (s/def ::project-id ::us/uuid) +(s/def ::file-id ::us/uuid) (s/def ::user ::us/uuid) (su/defstr sql:generic-project-files - "select pf.*, + "select distinct on (pf.id, pf.created_at) + pf.*, + p.name as project_name, array_agg(pp.id) over pages_w as pages from project_files as pf inner join projects as p on (pf.project_id = p.id) @@ -91,6 +94,40 @@ (p/then' decode-row))) +;; --- Query: Users of the File + +(su/defstr sql:file-users + "select u.id, u.fullname, u.photo + from users as u + join project_file_users as pfu on (pfu.user_id = u.id) + where pfu.file_id = $1 + union all + select u.id, u.fullname, u.photo + from users as u + join project_users as pu on (pu.user_id = u.id) + where pu.project_id = $2") + +(declare retrieve-minimal-file) + +(su/defstr sql:minimal-file + "with files as (~{sql:generic-project-files}) + select id, project_id from files where id = $2") + +(s/def ::project-file-users + (s/keys :req-un [::user ::file-id])) + +(sq/defquery ::project-file-users + [{:keys [user file-id] :as params}] + (db/with-atomic [conn db/pool] + (-> (retrieve-minimal-file conn user file-id) + (p/then (fn [{:keys [id project-id]}] + (db/query conn [sql:file-users id project-id])))))) + +(defn- retrieve-minimal-file + [conn user-id file-id] + (-> (db/query-one conn [sql:minimal-file user-id file-id]) + (p/then' su/raise-not-found-if-nil))) + ;; --- Helpers (defn decode-row diff --git a/backend/src/uxbox/services/queries/project_pages.clj b/backend/src/uxbox/services/queries/project_pages.clj index decf5b74e..9d4310ee6 100644 --- a/backend/src/uxbox/services/queries/project_pages.clj +++ b/backend/src/uxbox/services/queries/project_pages.clj @@ -42,24 +42,6 @@ (str "with pages as (" sql:generic-project-pages ")" " select * from pages where file_id = $2")) -;; (defn project-pages-sql -;; [user] -;; (-> (sql/from ["project_pages" "pp"]) -;; (sql/join ["project_files" "pf"] "pf.id = pp.file_id") -;; (sql/join ["projects" "p"] "p.id = pf.project_id") -;; (sql/ljoin ["project_users", "pu"] "pu.project_id = p.id") -;; (sql/ljoin ["project_file_users", "pfu"] "pfu.file_id = pf.id") -;; (sql/select "pp.*") -;; (sql/where ["((pfu.user_id = ? and pfu.can_edit = true) or -;; (pu.user_id = ? and pu.can_edit = true))" user user]) -;; (sql/order "pp.created_at"))) - -;; (let [sql (-> (project-pages-sql user) -;; (sql/where ["pp.file_id = ?" file-id]) -;; (sql/fmt))] -;; (-> (db/query db/pool sql) -;; (p/then #(mapv decode-row %))))) - (s/def ::project-pages (s/keys :req-un [::user ::file-id])) diff --git a/backend/src/uxbox/services/queries/projects.clj b/backend/src/uxbox/services/queries/projects.clj index ffeb10ac9..c47c04479 100644 --- a/backend/src/uxbox/services/queries/projects.clj +++ b/backend/src/uxbox/services/queries/projects.clj @@ -23,6 +23,7 @@ (s/def ::token ::us/string) (s/def ::user ::us/uuid) + ;; --- Query: Projects (su/defstr sql:projects @@ -41,6 +42,7 @@ (-> (db/query db/pool [sql:projects user]) (p/then' (partial mapv decode-row)))) + ;; --- Helpers (defn decode-row diff --git a/backend/src/uxbox/util/blob.clj b/backend/src/uxbox/util/blob.clj index 6d467fdb9..1fcb0f023 100644 --- a/backend/src/uxbox/util/blob.clj +++ b/backend/src/uxbox/util/blob.clj @@ -29,38 +29,6 @@ String (->bytes [data] (.getBytes ^String data "UTF-8"))) -(defn str->bytes - "Convert string to byte array." - ([^String s] - (str->bytes s "UTF-8")) - ([^String s, ^String encoding] - (.getBytes s encoding))) - -(defn bytes->str - "Convert byte array to String." - ([^bytes data] - (bytes->str data "UTF-8")) - ([^bytes data, ^String encoding] - (String. data encoding))) - -(defn buffer - [^bytes data] - (Buffer/buffer data)) - -(defn encode-with-json - "A function used for encode data for transfer it to frontend." - ([data] (encode-with-json data false)) - ([data verbose?] - (let [type (if verbose? :json-verbose :json)] - (-> (t/encode data {:type type}) - (Buffer/buffer))))) - -(defn decode-from-json - "A function used for parse data coming from frontend." - [data] - (-> (->bytes data) - (t/decode {:type :json}))) - (defn encode "A function used for encode data for persist in the database." [data] @@ -73,7 +41,7 @@ (.writeInt dos (int data-len)) (.write dos ^bytes cdata (int 0) (alength cdata)) (-> (.toByteArray baos) - (buffer))))) + (t/bytes->buffer))))) (declare decode-v1) diff --git a/backend/src/uxbox/util/transit.clj b/backend/src/uxbox/util/transit.clj index f97bec799..bc76d4a3d 100644 --- a/backend/src/uxbox/util/transit.clj +++ b/backend/src/uxbox/util/transit.clj @@ -57,20 +57,8 @@ ([data] (decode data nil)) ([data opts] - (cond - (instance? Buffer data) - (decode (.getBytes ^Buffer data) opts) - - (bytes? data) - (with-open [input (ByteArrayInputStream. data)] - (read! (reader input opts))) - - (string? data) - (decode (.getBytes data "UTF-8") opts) - - :else - (with-open [input (io/input-stream data)] - (read! (reader input opts)))))) + (with-open [input (ByteArrayInputStream. ^bytes data)] + (read! (reader input opts))))) (defn encode ([data] @@ -80,3 +68,29 @@ (let [w (writer out opts)] (write! w data) (.toByteArray out))))) + +;; --- Helpers + +(defn str->bytes + "Convert string to byte array." + ([^String s] + (str->bytes s "UTF-8")) + ([^String s, ^String encoding] + (.getBytes s encoding))) + +(defn bytes->str + "Convert byte array to String." + ([^bytes data] + (bytes->str data "UTF-8")) + ([^bytes data, ^String encoding] + (String. data encoding))) + +(defn bytes->buffer + [^bytes data] + (Buffer/buffer data)) + +(defn buffer->bytes + [^Buffer data] + (.getBytes data)) + + diff --git a/backend/src/vertx/web/interceptors.clj b/backend/src/vertx/web/interceptors.clj index bca322513..eaad85d36 100644 --- a/backend/src/vertx/web/interceptors.clj +++ b/backend/src/vertx/web/interceptors.clj @@ -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))))})) diff --git a/common/uxbox/common/data.cljc b/common/uxbox/common/data.cljc index 1c2ee5c28..9fd9d2b20 100644 --- a/common/uxbox/common/data.cljc +++ b/common/uxbox/common/data.cljc @@ -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))) diff --git a/common/uxbox/common/pages.cljc b/common/uxbox/common/pages.cljc index 437b572d5..16aa9ca7b 100644 --- a/common/uxbox/common/pages.cljc +++ b/common/uxbox/common/pages.cljc @@ -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?)) diff --git a/docs/99-Collaborative-Edition.md b/docs/99-Collaborative-Edition.md new file mode 100644 index 000000000..3b05c4eed --- /dev/null +++ b/docs/99-Collaborative-Edition.md @@ -0,0 +1,123 @@ +# Collaborative Edition + +This is a collection of design notes for collaborative edition feature. + +## Persistence Ops + +This is a page data structure: + +``` +{:shapes [, ...] + :canvas [, ...] + :shapes-by-id { , ...}} +``` + +This is a potential list of persistent ops: + +``` +;; Generic (Shapes & Canvas) +[:mod-shape [:(mod|add|del) ], ...] ;; Persistent + +;; Example: +;; [:mod-shape 1 [:add :x 2] [:mod :y 3]] + +;; Specific +[:add-shape ] +[:add-canvas ] + +[:del-shape ] +[:del-canvas ] + +[:mov-canvas :after ] ;; null implies at first position +[:mov-shape :after ] +``` + +## Ephemeral communication (Websocket protocol) + + +### `join` ### + +Sent by clients for notify joining a concrete page-id inside a file. + +```clojure +{:type :join + :page-id + :version + } +``` + +Will cause: + +- A posible `:page-changes`. +- Broadcast `:joined` message to all users of the file. + + +### `who` ### + +Sent by clients for request the list of users in the channel. + +```clojure +{:type :who} +``` + +Will cause: + +- Reply to the client with the current users list: + +```clojure +{:type :who + :users #{,...}} +``` + +This will be sent all the time user joins or leaves the channel for +maintain the frontend updated with the lates participants. This +message is also sent at the beggining of connection from server to +client. + + +### `pointer-update` ### + +This is sent by client to server and then, broadcasted to the rest of +channel participants. + +```clojure +{:type :pointer-update + :page-id + :x + :y + } +``` + +The server broadcast message will look like: + +```clojure +{:type :pointer-update + :user-id + :page-id + :x + :y + } +``` + +### `:page-snapshot` ### + +A message that server sends to client for notify page changes. It can be sent +on `join` and when a page change is commited to the database. + +```clojure +{:type :page-snapshot + :user-id + :page-id + :version + :operations [, ...] + } +``` + +This message is only sent to users that does not perform this change. + + + + + + + diff --git a/frontend/deps.edn b/frontend/deps.edn index 5670a4d05..cedbc2a73 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -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"} diff --git a/frontend/resources/public/images/avatar-blue.jpg b/frontend/resources/public/images/avatar-blue.jpg new file mode 100644 index 000000000..947698852 Binary files /dev/null and b/frontend/resources/public/images/avatar-blue.jpg differ diff --git a/frontend/resources/public/images/avatar-orange.jpg b/frontend/resources/public/images/avatar-orange.jpg new file mode 100644 index 000000000..fcd45d1d7 Binary files /dev/null and b/frontend/resources/public/images/avatar-orange.jpg differ diff --git a/frontend/resources/public/images/avatar-pink.jpg b/frontend/resources/public/images/avatar-pink.jpg new file mode 100644 index 000000000..1738b21bb Binary files /dev/null and b/frontend/resources/public/images/avatar-pink.jpg differ diff --git a/frontend/resources/public/images/avatar-red.jpg b/frontend/resources/public/images/avatar-red.jpg new file mode 100644 index 000000000..f28234950 Binary files /dev/null and b/frontend/resources/public/images/avatar-red.jpg differ diff --git a/frontend/resources/public/images/svg/arrow-down-white.svg b/frontend/resources/public/images/svg/arrow-down-white.svg new file mode 100644 index 000000000..c47a7be72 --- /dev/null +++ b/frontend/resources/public/images/svg/arrow-down-white.svg @@ -0,0 +1,60 @@ + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/frontend/resources/styles/common/base.scss b/frontend/resources/styles/common/base.scss index fcd376d95..3e31e56a3 100644 --- a/frontend/resources/styles/common/base.scss +++ b/frontend/resources/styles/common/base.scss @@ -6,7 +6,7 @@ // Copyright (c) 2015-2016 Juan de la Cruz body { - background-color: $secondary-ui-bg; + background-color: lighten($dark-ui-bg, 5%); color: $medium-ui-text; display: flex; flex-direction: column; diff --git a/frontend/resources/styles/common/dependencies/colors-v2.scss b/frontend/resources/styles/common/dependencies/colors-v2.scss new file mode 100644 index 000000000..f9bbb6da0 --- /dev/null +++ b/frontend/resources/styles/common/dependencies/colors-v2.scss @@ -0,0 +1,75 @@ +// 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 Andrey Antukh +// Copyright (c) 2015-2016 Juan de la Cruz + +// Colors +$color-white: #ffffff; +$color-black: #000000; +$color-gray: #90969d; + +// Main color +$color-primary: #78dbbe; + +// Secondary colors +$color-success: #b6dd75; +$color-complete : #a599c6; +$color-warning: #e6a16f; +$color-danger: #de4762; +$color-info: #59b9e2; + +// Mixing Color varriable for creating both light and dark colors +$mix-percentage-dark: 81%; +$mix-percentage-darker: 60%; +$mix-percentage-light: 80%; +$mix-percentage-lighter: 20%; + +// Gray scale +$color-gray-light: mix($color-gray, $color-white, $mix-percentage-light); +$color-gray-lighter: mix($color-gray, $color-white, $mix-percentage-lighter); +$color-gray-dark: mix($color-gray, $color-black, $mix-percentage-dark); +$color-gray-darker: mix($color-gray, $color-black, $mix-percentage-darker); + +// Lighter colors +$color-success-light: mix($color-success, $color-white, $mix-percentage-light); +$color-success-lighter: mix($color-success, $color-white, $mix-percentage-lighter); + +$color-complete-light: mix($color-complete, $color-white, $mix-percentage-light); +$color-complete-lighter: mix($color-complete, $color-white, $mix-percentage-lighter); + +$color-primary-light: mix($color-primary, $color-white, $mix-percentage-light); +$color-primary-lighter: mix($color-primary, $color-white, $mix-percentage-lighter); + +$color-warning-light: mix($color-warning, $color-white, $mix-percentage-light); +$color-warning-lighter: mix($color-warning, $color-white, $mix-percentage-lighter); + +$color-danger-light: mix($color-danger, $color-white, $mix-percentage-light); +$color-danger-lighter: mix($color-danger, $color-white, $mix-percentage-lighter); + +$color-info-light: mix($color-info, $color-white, $mix-percentage-light); +$color-info-lighter: mix($color-info, $color-white, $mix-percentage-lighter); + +// Darker colors +$color-success-dark: mix($color-success, $color-black, $mix-percentage-dark); +$color-success-darker: mix($color-success, $color-black, $mix-percentage-darker); + +$color-complete-dark: mix($color-complete, $color-black, $mix-percentage-dark); +$color-complete-darker: mix($color-complete, $color-black, $mix-percentage-darker); + +$color-primary-dark: mix($color-primary, $color-black, $mix-percentage-dark); +$color-primary-darker: mix($color-primary, $color-black, $mix-percentage-darker); + +$color-warning-dark: mix($color-warning, $color-black, $mix-percentage-dark); +$color-warning-darker: mix($color-warning, $color-black, $mix-percentage-darker); + +$color-danger-dark: mix($color-danger, $color-black, $mix-percentage-dark); +$color-danger-darker: mix($color-danger, $color-black, $mix-percentage-darker); + +$color-info-dark: mix($color-info, $color-black, $mix-percentage-dark); +$color-info-darker: mix($color-info, $color-black, $mix-percentage-darker); + +// bg transparent +$color-dark-bg: rgba(0,0,0,.4); +$color-light-bg: rgba(255,255,255,.6); diff --git a/frontend/resources/styles/common/dependencies/colors.scss b/frontend/resources/styles/common/dependencies/colors.scss index f9bbb6da0..b156ce289 100644 --- a/frontend/resources/styles/common/dependencies/colors.scss +++ b/frontend/resources/styles/common/dependencies/colors.scss @@ -11,7 +11,7 @@ $color-black: #000000; $color-gray: #90969d; // Main color -$color-primary: #78dbbe; +$color-primary: #31EFB8; // Secondary colors $color-success: #b6dd75; @@ -31,6 +31,12 @@ $color-gray-light: mix($color-gray, $color-white, $mix-percentage-light); $color-gray-lighter: mix($color-gray, $color-white, $mix-percentage-lighter); $color-gray-dark: mix($color-gray, $color-black, $mix-percentage-dark); $color-gray-darker: mix($color-gray, $color-black, $mix-percentage-darker); +$color-gray-10: #E3E3E3; +$color-gray-20: #b1b2b5; +$color-gray-30: #7B7D85; +$color-gray-40: #64666A; +$color-gray-50: #303236; +$color-gray-60: #1F1F1F; // Lighter colors $color-success-light: mix($color-success, $color-white, $mix-percentage-light); diff --git a/frontend/resources/styles/common/dependencies/uxbox-light.scss b/frontend/resources/styles/common/dependencies/uxbox-light.scss index 9cc3dce49..e07c314df 100644 --- a/frontend/resources/styles/common/dependencies/uxbox-light.scss +++ b/frontend/resources/styles/common/dependencies/uxbox-light.scss @@ -19,28 +19,28 @@ $ui-flavour: $color-gray; // Change next colors for more customization // Background colors -$primary-ui-bg: $color-white; -$secondary-ui-bg: mix($ui-flavour, $color-white, $mix-percentage-lighter); -$dark-ui-bg: mix($ui-flavour, $color-white, $mix-percentage-light); +$primary-ui-bg: $color-gray-50; +$secondary-ui-bg: $color-gray-60; +$dark-ui-bg: $color-gray-10; // Border color -$intense-ui-border: $ui-flavour; -$medium-ui-border: mix($ui-flavour, $color-white, $mix-percentage-light); -$soft-ui-border: lighten($medium-ui-border, 18%); +$intense-ui-border: $color-gray-40; +$medium-ui-border: $color-gray-20; +$soft-ui-border: $color-gray-60; // Icon colors -$intense-ui-icons: mix($ui-flavour, $color-black, $mix-percentage-dark); -$medium-ui-icons: mix($ui-flavour, $color-white, $mix-percentage-light); -$soft-ui-icons: mix($ui-flavour, $color-white, $mix-percentage-lighter); +$intense-ui-icons: $color-gray-20; +$medium-ui-icons: $color-gray-30; +$soft-ui-icons: $color-gray-60; // Text colors -$intense-ui-text: mix($ui-flavour, $color-black, $mix-percentage-darker); -$medium-ui-text: $ui-flavour; -$soft-ui-text: mix($ui-flavour, $color-white, $mix-percentage-light); +$intense-ui-text: $color-gray-60; +$medium-ui-text: $color-gray-20; +$soft-ui-text: $color-gray-10; // Canvas colors $canvas-bg: mix($ui-flavour, $color-white, $mix-percentage-lighter); $scrollbar-bg: mix($ui-flavour, $color-white, $mix-percentage-light); // Input colors -$input-bg: $color-light-bg; +$input-bg: $primary-ui-bg; diff --git a/frontend/resources/styles/common/framework-v2.scss b/frontend/resources/styles/common/framework-v2.scss new file mode 100644 index 000000000..82e823d01 --- /dev/null +++ b/frontend/resources/styles/common/framework-v2.scss @@ -0,0 +1,1091 @@ +// 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 Andrey Antukh +// Copyright (c) 2015-2016 Juan de la Cruz + +// Buttons + +%btn { + appearance: none; + align-items: stretch; + border: none; + border-radius: 3px; + cursor: pointer; + display: flex; + font-size: $fs16; + font-weight: bold; + justify-content: center; + padding: 1rem; + transition: all .4s; + &.btn-big { + padding: 2rem 1rem; + } + &.btn-noshr { + flex-shrink: 0; + } + &.btn-small { + font-size: $fs14; + padding: .7rem 1rem; + line-height: 1.15; + } +} + +.btn-primary { + @extend %btn; + align-items: center; + background: $main-ui-color; + color: $color-white; + justify-content: center; + display: flex; + padding: 1rem; + &:hover { + background: $color-primary-dark; + color: $color-white; + } +} + +.btn-success { + @extend %btn; + background: $color-success; + color: $color-white; + &:hover { + background: $color-success-dark; + color: $color-white; + } +} + +.btn-delete { + @extend %btn; + background: $color-danger; + color: $color-white; + &:hover { + background: $color-danger-dark; + color: $color-white; + } +} + +.btn-gray { + @extend %btn; + background: $color-gray; + color: $color-white; + &:hover { + background: $color-gray-dark; + } +} + +.btn-complete { + @extend %btn; + background: $color-complete; + color: $color-white; + &:hover { + background: $color-complete-dark; + color: $color-white; + } +} + +.btn-transparent { + @extend %btn; + background: transparent; + border: 2px solid $color-white; + color: $color-white; + &:hover { + background: $color-white; + color: $color-info-dark; + } +} +.btn-large { + flex-grow: 3; + max-width: 400px; + flex-basis: 60%; + width: 100%; +} + +.btn-option { + display: flex; + a { + margin-right: .5rem; + &:last-child { + margin-right: 0; + } + } + &.column { + flex-direction: column; + a { + margin-bottom: .5rem; + &:last-child { + margin-bottom: 0; + } + } + } + &.mr-small { + margin: 1rem 0; + } + &.btn-option-400 { + margin: 0 auto; + max-width: 400px; + } + &.mb { + margin-bottom: .5rem; + } +} + +input[type=button][disabled], +.btn-disabled { + opacity: .5; + pointer-events: none; +} + +// Text color + +.text-error { + background-color: $color-danger; + border-radius: 3px; + color: $color-danger-lighter; + padding: 3px 6px; +} + +.text-success { + background-color: $color-success; + border-radius: 3px; + color: $color-white; + padding: 3px 6px; +} + +.text-warning { + background-color: $color-warning; + border-radius: 3px; + color: $color-white; + padding: 3px 6px; +} + +.text-info { + background-color: $color-complete; + border-radius: 3px; + color: $color-white; + padding: 3px 6px; +} + +// Slider dots + +ul.slider-dots { + align-items: center; + display: flex; + + li { + background-color: transparent; + border-radius: 50%; + border: 2px solid $color-white; + cursor: pointer; + height: 12px; + flex-shrink: 0; + margin: 6px; + width: 12px; + + &.current, + &:hover { + background-color: $dark-ui-bg; + } + + } + + &.dots-purple { + + li { + border-color: $color-complete; + + &.current, + &:hover { + background-color: $color-complete; + } + + } + + } + +} + +// Doted list + +.doted-list { + + li { + align-items: center; + display: flex; + padding: $small 0; + + &::before { + background-color: $color-complete; + border-radius: 50%; + content: ""; + flex-shrink: 0; + height: 10px; + margin-right: 6px; + width: 10px; + } + + &.not-included { + text-decoration: line-through; + } + + } + +} + +// Tags + +.tags { + display: flex; + flex-wrap: wrap; + + &:last-child { + margin-right: 0; + } + + .tag { + background-color: $color-gray-light; + border-radius: 3px; + color: $color-white; + cursor: pointer; + font-size: $fs14; + font-weight: bold; + margin: 0 $small $small 0; + padding: 4px 8px; + text-transform: uppercase; + + &:hover { + background-color: $color-gray-dark; + } + + &.tag-primary { + background-color: $color-primary; + color: $color-white; + + &:hover { + background-color: $color-primary-dark; + } + + } + + &.tag-green { + background-color: $color-success; + color: $color-white; + + &:hover { + background-color: $color-success-dark; + } + + } + + &.tag-purple { + background-color: $color-complete; + color: $color-white; + + &:hover { + background-color: $color-complete-dark; + } + + } + + &.tag-orange { + background-color: $color-warning; + color: $color-white; + + &:hover { + background-color: $color-warning-dark; + } + + } + + &.tag-red { + background-color: $color-danger; + color: $color-white; + + &:hover { + background-color: $color-danger-dark; + } + + } + + } + +} + +// Input elements +.input-element { + display: flex; + position: relative; + width: 100%; + + &::after { + color: $soft-ui-text; + font-size: $fs12; + height: 20px; + position: absolute; + right: 3px; + top: 26%; + width: 20px; + } + + // Input amounts + + &.pixels { + + &::after { + content: "px"; + } + + } + + &.percentail { + + &::after { + content: "%"; + } + + } + + &.miliseconds { + + &::after { + content: "ms"; + } + + } + + &.degrees { + + &::after { + content: "dg"; + } + + } + +} + +input, +select { + background-color: $input-bg; + box-sizing: border-box; + color: $intense-ui-text; + font-family: "sourcesanspro", sans-serif; + font-size: $fs16; + margin-bottom: $medium; + -webkit-appearance: none; + -moz-appearance: none; +} + +input[type="radio"], +input[type="checkbox"] { + box-sizing: border-box; + cursor: pointer; + line-height: normal; + margin-top: 1px 0 0; +} + +input:focus, +select:focus, +textarea:focus, +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + box-shadow: none; + outline: none; +} + +.form-errors { + color: $color-danger; +} + +// Input text + +.input-text { + border: none; + border-bottom: 1px solid $soft-ui-border; + background-color: $input-bg; + box-shadow: none; + outline: none; + padding: $small $big $small $small; + position: relative; + + @include placeholder { + transition: all .3s ease; + } + + &:focus { + border-color: $intense-ui-border; + box-shadow: none; + + @include placeholder { + opacity: 0; + transform: translateY(-20px); + transition: all .3s ease; + } + + } + + &.success { + background-color: $color-success-lighter; + border-color: $color-success; + color: $color-success-dark; + } + + &.error { + background-color: $color-danger-lighter; + border-color: $color-danger; + color: $color-danger-dark; + } + +} + +// Element-name + +input.element-name { + border: 1px solid $soft-ui-border; + border-radius: $br-small; + font-size: $fs13; + margin: 0px; + padding: 3px; + width: 100%; +} + +// Input select + +.input-select { + @extend .input-text; + background-image: url("/images/svg/arrow-down.svg"); + background-repeat: no-repeat; + background-position: 95% 48%; + background-size: 10px; + cursor: pointer; + + &.small { + padding: $x-small $big $x-small $x-small; + } + +} + + +// Input radio + +.input-radio, +.input-checkbox { + align-items: center; + display: flex; + margin-bottom: 10px; + margin-top: 10px; + padding-left: 0px; + + label{ + align-items: center; + cursor: pointer; + display: flex; + margin-right: 15px; + font-size: 13px; + + &:before{ + content:""; + width: 20px; + height: 20px; + margin-right: 10px; + background-color: $primary-ui-bg; + border: 1px solid $soft-ui-border; + box-shadow: inset 0 0 0 0 $main-ui-color ; + box-sizing: border-box; + } + + } + + &.column { + align-items: flex-start; + flex-direction: column; + } + +} + +.input-radio { + + label { + margin-bottom: 6px; + + &:before { + border-radius: 99px; + transition: box-shadow 0.2s linear 0s, color 0.2s linear 0s; + } + + } + + input[type=radio]:checked { + + & + label { + + &:before { + box-shadow: inset 0 0 0 5px $color-gray-light ; + } + + } + + } + + input[type=radio] { + display: none; + } + + input[type=radio][disabled] { + + & + label { + opacity: 0.65; + } + + } + +} +input[type=radio]:checked + label:before{ + + .input-radio.radio-success &{ + box-shadow: inset 0 0 0 5px $color-success; + } + + .input-radio.radio-primary &{ + box-shadow: inset 0 0 0 5px $color-primary; + } + + .input-radio.radio-info &{ + box-shadow: inset 0 0 0 5px $color-info; + } + + .input-radio.radio-warning &{ + box-shadow: inset 0 0 0 5px $color-warning; + } + + .input-radio.radio-danger &{ + box-shadow: inset 0 0 0 5px $color-danger; + } + + .input-radio.radio-complete &{ + box-shadow: inset 0 0 0 5px $color-complete; + } + +} + +// Input checkbox + +.input-checkbox { + + input[type=radio][disabled] { + + & + label { + + &:after { + background-color: $color-gray-lighter; + } + + } + + } + + label { + transition: border 0.2s linear 0s, color 0.2s linear 0s; + white-space: nowrap; + position: relative; + + &:before { + top: 1.4px; + border-radius: 3px; + transition: border 0.2s linear 0s, color 0.2s linear 0s; + } + + &::after { + display: inline-block; + width: 16px; + height: 16px; + position: absolute; + left: 3.2px; + top: 0px; + font-size: 11px; + transition: border 0.2s linear 0s, color 0.2s linear 0s; + } + + &:after { + border-radius: 3px; + } + + } + + input[type=checkbox] { + display: none; + } + + &.checkbox-circle { + + label { + + &:after { + border-radius: 99px; + } + + &:before { + border-radius: 99px; + } + + } + + } + + input[type=checkbox]:checked { + + & + label { + + &:before { + border-width: 10px; + } + + &::after { + content:"✓"; + color: #fff; + font-size: 16px; + } + + } + + } + + input[type=checkbox][disabled] { + + & + label { + opacity: 0.65; + + &:before { + background-color: #eceff3; + } + + } + + } + + &.right { + + label { + margin-right: 35px; + padding-left:0 !important; + + &:before { + right:-35px; + left:auto; + } + + } + + input[type=checkbox]:checked { + + & + label { + position: relative; + + &::after { + content:"✓"; + position: absolute; + right: -27px; + left: auto; + } + + } + + } + + } + +} + +input[type=checkbox]:checked + label{ + + .input-checkbox.check-success &:before{ + border-color: $color-success; + } + + .input-checkbox.check-primary &:before{ + border-color: $color-primary; + } + + .input-checkbox.check-complete &:before{ + border-color: $color-complete; + } + + .input-checkbox.check-warning &:before{ + border-color: $color-warning; + } + + .input-checkbox.check-danger &:before{ + border-color: $color-danger; + } + + .input-checkbox.check-info &:before{ + border-color: $color-info; + } + + .input-checkbox.check-success &::after, + .input-checkbox.check-primary &::after, + .input-checkbox.check-complete &::after, + .input-checkbox.check-warning &::after, + .input-checkbox.check-danger &::after, + .input-checkbox.check-info &::after { + color: $color-white; + } + +} + +// Input slidebar + +input[type=range] { + background-color: transparent; + -webkit-appearance: none; + margin: 10px 0; + width: 100%; +} +input[type=range]:focus { + outline: none; +} +input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 8px; + cursor: pointer; + animate: 0.2s; + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + background: $secondary-ui-bg; + border-radius: 25px; + border: 0px solid #000101; +} +input[type=range]::-webkit-slider-thumb { + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + border: 0px solid #000000; + height: 24px; + width: 8px; + border-radius: 7px; + background: $intense-ui-icons; + cursor: pointer; + -webkit-appearance: none; + margin-top: -8px; +} +input[type=range]:focus::-webkit-slider-runnable-track { + background: $secondary-ui-bg; +} +input[type=range]::-moz-range-track { + width: 100%; + height: 8px; + cursor: pointer; + animate: 0.2s; + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + background: $secondary-ui-bg; + border-radius: 25px; + border: 0px solid #000101; +} +input[type=range]::-moz-range-thumb { + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + border: 0px solid #000000; + height: 24px; + width: 8px; + border-radius: 7px; + background: $intense-ui-icons; + cursor: pointer; +} +input[type=range]::-ms-track { + width: 100%; + height: 8px; + cursor: pointer; + animate: 0.2s; + background: transparent; + border-color: transparent; + border-width: 39px 0; + color: transparent; +} +input[type=range]::-ms-fill-lower { + background: $secondary-ui-bg; + border: 0px solid #000101; + border-radius: 50px; + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; +} +input[type=range]::-ms-fill-upper { + background: $secondary-ui-bg; + border: 0px solid #000101; + border-radius: 50px; + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; +} +input[type=range]::-ms-thumb { + box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; + border: 0px solid #000000; + height: 24px; + width: 8px; + border-radius: 7px; + background: $intense-ui-icons; + cursor: pointer; +} +input[type=range]:focus::-ms-fill-lower { + background: $secondary-ui-bg; +} +input[type=range]:focus::-ms-fill-upper { + background: $secondary-ui-bg; +} + +// Scroll bar (chrome) + +::-webkit-scrollbar { + background-color: transparent; + cursor: pointer; + height: 8px; + width: 8px; +} +::-webkit-scrollbar-track { + background-color: transparent; +} +::-webkit-scrollbar-thumb { + background-color: $scrollbar-bg; + + &:hover { + background-color: darken($scrollbar-bg, 14%); + outline: 2px solid $main-ui-color; + } + +} + +// Tooltip + +.tooltip { + position: relative; + + &:hover { + + &::after { + background-color: $primary-ui-bg; + border-radius: $br-small; + color: $medium-ui-text; + content: attr(alt); + font-size: $fs11; + font-weight: bold; + padding: $x-small; + position: absolute; + left: 130%; + text-align: center; + top: 0; + white-space: nowrap; + z-index: 20; + @include animation(.3s,.6s,fadeIn); + } + + } + + &.tooltip-bottom { + + &:hover { + + &::after { + left: -100%; + top: 130%; + } + + } + + } + + &.tooltip-top { + + &:hover { + + &::after { + top: -165%; + left: -60%; + } + + } + + } + + &.tooltip-hover { + + &:hover { + + &::after { + align-items: center; + background-color: $input-bg; + box-sizing: border-box; + border-radius: 0; + color: $intense-ui-text; + display: flex; + height: 100%; + justify-content: center; + left: 0; + top: 0; + white-space: normal; + width: 100%; + } + + } + + } + +} + +// Messages +.message { + position: fixed; + top: 0; + left: 0px; + width: 100%; + z-index: 12; + @include animation(0, 1s, fadeInDown); + + .message-body { + align-items: center; + border-radius: $br-small; + border-bottom: 3px solid transparent; + color: $color-white; + display: flex; + flex-wrap: wrap; + justify-content: center; + margin: 0 auto; + padding: $medium $big; + position: relative; + width: 100%; + + span { + font-size: $fs18; + max-width: 60%; + text-align: center; + width: 100%; + } + + .close { + cursor: pointer; + position: absolute; + top: 10px; + right: 10px; + opacity: .3; + width: 18px; + + svg { + fill: $color-black; + height: 18px; + width: 18px; + transform: rotate(45deg); + } + + &:hover { + opacity: .8; + } + + } + + .message-action { + align-items: center; + display: flex; + justify-content: space-around; + margin-top: $medium; + max-width: 60%; + width: 100%; + } + + } + + &.error { + + .message-body { + background-color: $color-danger; + border-color: $color-danger-dark; + } + + } + + &.success { + + .message-body { + background-color: $color-success; + border-color: $color-success-dark; + } + + } + + &.info { + + .message-body { + background-color: $color-info; + border-color: $color-info-dark; + } + + } + + &.quick { + + .message-body { + + .close { + display: none; + } + + } + + } + + &.hide-message { + @include animation(0, .6s, fadeOutUp); + } + +} + +.message-version { + align-items: center; + background-color: rgba(27, 170, 214, .6); + border-radius: $br-small; + color: $color-white; + display: flex; + flex-direction: column; + justify-content: center; + padding: $small; + position: absolute; + right: 250px; + top: 40px; + @include animation(0, 1s, fadeInDown); + + span { + font-size: $fs13; + font-weight: bold; + } + + .message-action { + align-items: center; + display: flex; + justify-content: space-around; + margin-top: $small; + width: 100%; + + .btn-transparent { + font-size: $fs12; + padding: .3rem .5rem; + } + + } + + &.hide-message { + @include animation(0, .6s, fadeOutUp); + } + +} + +.close-bezier { + fill: $color-danger; + stroke: $color-danger-dark; + stroke-width: 2px; + cursor: pointer; + &:hover { + fill: $color-white; + } +} + +.message-inline { + background-color: $color-info; + color: $color-info-darker; + margin-bottom: 1.2rem; + padding: .8rem; + text-align: center; + p { + margin: 0; + } +} diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss index 82e823d01..78d698c2f 100644 --- a/frontend/resources/styles/common/framework.scss +++ b/frontend/resources/styles/common/framework.scss @@ -319,7 +319,7 @@ ul.slider-dots { width: 100%; &::after { - color: $soft-ui-text; + color: $medium-ui-text; font-size: $fs12; height: 20px; position: absolute; @@ -442,8 +442,10 @@ input[type="checkbox"]:focus { // Element-name input.element-name { - border: 1px solid $soft-ui-border; + background-color: $dark-ui-bg; + border: 1px solid $intense-ui-border; border-radius: $br-small; + color: $intense-ui-text; font-size: $fs13; margin: 0px; padding: 3px; @@ -454,7 +456,7 @@ input.element-name { .input-select { @extend .input-text; - background-image: url("/images/svg/arrow-down.svg"); + background-image: url("/images/svg/arrow-down-white.svg"); background-repeat: no-repeat; background-position: 95% 48%; background-size: 10px; @@ -855,9 +857,9 @@ input[type=range]:focus::-ms-fill-upper { &:hover { &::after { - background-color: $primary-ui-bg; + background-color: $color-white; border-radius: $br-small; - color: $medium-ui-text; + color: $intense-ui-text; content: attr(alt); font-size: $fs11; font-weight: bold; @@ -905,7 +907,7 @@ input[type=range]:focus::-ms-fill-upper { &::after { align-items: center; - background-color: $input-bg; + background-color: $color-white; box-sizing: border-box; border-radius: 0; color: $intense-ui-text; diff --git a/frontend/resources/styles/main/layouts/main-layout.scss b/frontend/resources/styles/main/layouts/main-layout.scss index 24ad47943..bb7b7ee50 100644 --- a/frontend/resources/styles/main/layouts/main-layout.scss +++ b/frontend/resources/styles/main/layouts/main-layout.scss @@ -12,10 +12,10 @@ } .dashboard-content { - background-image: url("../images/dashboard-img.svg"); - background-position: bottom center; - background-repeat: no-repeat; - background-size: 100%; + // background-image: url("../images/dashboard-img.svg"); + // background-position: bottom center; + // background-repeat: no-repeat; + // background-size: 100%; display: flex; flex-direction: column; height: calc(100vh - 60px); diff --git a/frontend/resources/styles/main/partials/colorpicker.scss b/frontend/resources/styles/main/partials/colorpicker.scss index 94202077c..89f5ffc77 100644 --- a/frontend/resources/styles/main/partials/colorpicker.scss +++ b/frontend/resources/styles/main/partials/colorpicker.scss @@ -154,12 +154,17 @@ .color-info { input { - border: 1px solid $soft-ui-border; + border: 1px solid $intense-ui-border; border-radius: $br-small; + color: $soft-ui-text; margin: 3px 0 0 $x-small; padding: 0 $x-small; width: 58px; font-size: $fs13; + + &:focus { + border-color: $soft-ui-text; + } } } diff --git a/frontend/resources/styles/main/partials/dashboard-bar-v2.scss b/frontend/resources/styles/main/partials/dashboard-bar-v2.scss new file mode 100644 index 000000000..b2d32457d --- /dev/null +++ b/frontend/resources/styles/main/partials/dashboard-bar-v2.scss @@ -0,0 +1,91 @@ +// 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 Andrey Antukh +// Copyright (c) 2015-2016 Juan de la Cruz + +.dashboard-bar { + align-items: center; + display: flex; + font-size: $fs14; + padding: $small $medium $small $x-big*2; + + .dashboard-images { + flex-basis: 70px; + } + + .dashboard-info { + align-items: center; + display: flex; + width: 500px; + justify-content: space-around; + + .dashboard-projects { + font-weight: bold; + margin-right: $medium; + } + + .sort-by { + margin-left: $small; + } + + .input-select { + background-color: transparent; + border-color: $soft-ui-border; + font-size: $fs14; + margin-bottom: 0; + margin-left: $medium; + padding: 3px 25px 3px 3px; + + option { + color: $intense-ui-text; + background: $secondary-ui-bg; + } + } + + } + + .dashboard-search { + align-items: center; + display: flex; + margin-left: $small; + + .input-text { + background: $input-bg; + border: 0; + color: $intense-ui-text; + padding: 4px 8px; + margin: 0; + max-width: 160px; + } + + .clear-search { + align-items: center; + background: $input-bg; + cursor: pointer; + display: flex; + height: 28px; + padding: 0 5px; + + svg { + fill: $medium-ui-icons; + height: 15px; + transform: rotate(45deg); + width: 15px; + + &:hover { + fill: $color-danger; + } + + } + + } + + } + + &.library-gap { + padding: $small $medium $small 270px; + } + +} diff --git a/frontend/resources/styles/main/partials/dashboard-grid-v2.scss b/frontend/resources/styles/main/partials/dashboard-grid-v2.scss new file mode 100644 index 000000000..3d0cb407c --- /dev/null +++ b/frontend/resources/styles/main/partials/dashboard-grid-v2.scss @@ -0,0 +1,419 @@ +// 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 Andrey Antukh +// Copyright (c) 2015-2016 Juan de la Cruz + +.dashboard-grid { + align-items: center; + display: flex; + flex-direction: column; + font-size: $fs14; + height: 100%; + padding: $medium; + // ACTIVITY BAR PADDING + //padding: $medium 250px $medium $medium; + + .dashboard-title { + margin: $medium 0; + position: relative; + width: 100%; + + h2 { + text-align: center; + width: 100%; + .edit { + padding: 5px 10px; + background: $primary-ui-bg; + border: none; + height: 100%; + } + .close { + padding: 5px 10px; + background: $primary-ui-bg; + cursor: pointer; + svg { + transform: rotate(45deg); + fill: $color-gray; + height: 20px; + width: 20px; + } + } + } + + .edition { + align-items: center; + display: flex; + position: absolute; + right: 40px; + top: 0; + + span { + cursor: pointer; + + svg { + fill: $color-gray; + height: 20px; + margin: 0 10px; + width: 20px; + } + + &:hover { + + svg { + fill: $color-gray-darker; + } + + } + + } + + } + + } + + .dashboard-grid-content { + display: flex; + height: 100%; + min-height: 60vh; + overflow: scroll; + width: 100%; + + .dashboard-grid-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + margin: auto; + width: 100%; + } + + .grid-item { + align-items: center; + border-radius: $br-medium; + cursor: pointer; + display: flex; + flex-direction: column; + flex-shrink: 0; + height: 200px; + margin: $medium $medium 0 $medium; + max-width: 300px; + min-width: 260px; + position: relative; + text-align: center; + width: 18%; + + &.small-item { + max-width: 12%; + min-width: 190px; + padding: $medium; + justify-content: center; + } + + .grid-item-icon { + width:90px; + height:90px; + } + + .item-info { + bottom: 0; + display: flex; + flex-direction: column; + left: 0; + padding: $small; + position: absolute; + text-align: left; + width: 100%; + + h3 { + color: $intense-ui-text; + font-size: $fs16; + font-weight: 400; + overflow: hidden; + padding: 0; + padding-right: $small; + text-overflow: ellipsis; + width: 100%; + white-space: nowrap; + } + + span.date { + color: $soft-ui-text; + font-size: $fs12; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + } + + } + + &.add-project { + border: 2px dashed $color-gray-light; + box-shadow: inset 0 0 0 transparent; + justify-content: center; + + span { + background-color: $color-primary-light; + border-radius: 5px; + color: $color-white; + font-size: $fs24; + padding: $small $medium; + } + + &:hover { + background-color: $color-primary-light; + border: 2px solid $color-white; + + span { + color: $color-white; + } + + } + + } + + // PROJECTS, ELEMENTS & ICONS GRID + &.project-th { + background-color: $primary-ui-bg; + border-bottom: 2px solid lighten($soft-ui-border, 12%); + + &:hover { + border-color: $main-ui-color; + + h3 { + color: $main-ui-color; + } + + } + + .project-th-actions { + align-items: center; + bottom: 0; + display: flex; + left: 0; + justify-content: flex-end; + padding: $small; + position: absolute; + width: 100%; + + svg { + fill: $color-gray-light; + height: 14px; + margin-right: $x-small; + width: 14px; + } + + span { + color: $color-gray-light; + } + + .project-th-icon { + align-items: center; + display: flex; + margin-right: $small; + + &.delete { + margin-right: 0; + + svg { + fill: $medium-ui-icons; + margin-right: 0; + } + + &:hover { + transform: scale(1.4); + + svg { + fill: $color-danger; + } + + } + + } + + &.edit { + margin-right: 0; + + svg { + fill: $medium-ui-icons; + } + + &:hover { + transform: scale(1.4); + + svg { + fill: $color-primary; + } + + } + + } + + } + + } + + } + + // IMAGES SECTION + &.images-th { + background-color: $primary-ui-bg; + border-bottom: 2px solid lighten($color-gray-light, 12%); + + &:hover { + border-color: $main-ui-color; + } + + } + + .grid-item-image { + + svg { + max-height: 100px; + max-width: 100px; + min-height: 40px; + min-width: 40px; + width: 8vw; + } + + } + + .color-swatch { + border-top-left-radius: $br-medium; + border-top-right-radius: $br-medium; + height: 25%; + left: 0; + position: absolute; + top: 0; + width: 100%; + } + + .color-data { + color: $color-gray; + margin-top: 15px; + } + + .input-checkbox { + margin: 0; + position: absolute; + top: 10px; + right: 5px; + + label { + margin: 0; + } + + } + + } + + } + + // STYLES FOR LIBRARIES + &.library { + padding: $medium $medium $medium 250px; + } + +} + +.grid-item-th { + background-position: center; + background-size: auto 80%; + background-repeat: no-repeat; + border-top-left-radius: $br-medium; + border-top-right-radius: $br-medium; + height: 70%; + overflow: hidden; + position: relative; + width: 100%; + + .img-th { + height: auto; + width: 100%; + } + +} + +// MULTISELECT OPTIONS BAR +.multiselect-bar { + @include animation(0,.5s,fadeInUp); + align-items: center; + background-color: $primary-ui-bg; + display: flex; + left: 0; + justify-content: center; + padding: $medium; + position: absolute; + width: 100%; + bottom: 0; + + .multiselect-nav { + align-items: center; + display: flex; + justify-content: center; + margin-left: 10%; + width: 110px; + + span { + margin-right: 1.5rem; + &:last-child { + margin-right: 0; + } + } + + svg { + cursor: pointer; + fill: $medium-ui-icons; + height: 20px; + width: 20px; + + &:hover { + fill: $intense-ui-icons; + } + + } + + span.delete { + + &:hover { + + svg{ + fill: $color-danger-light; + } + + } + + } + + } + +} + +.move-item { + position: relative; + + .move-list { + background-color: $dark-ui-bg; + border-radius: $br-small; + bottom: 30px; + display: flex; + flex-direction: column; + left: -30px; + max-height: 260px; + overflow-y: scroll; + padding: $medium; + position: absolute; + width: 260px; + + li { + padding-bottom: $medium; + + &.title { + color: $color-gray-darker; + } + + } + + } + +} diff --git a/frontend/resources/styles/main/partials/dashboard-grid.scss b/frontend/resources/styles/main/partials/dashboard-grid.scss index 3d0cb407c..175551b77 100644 --- a/frontend/resources/styles/main/partials/dashboard-grid.scss +++ b/frontend/resources/styles/main/partials/dashboard-grid.scss @@ -83,14 +83,13 @@ .dashboard-grid-row { display: flex; flex-wrap: wrap; - justify-content: center; - margin: auto; + margin-bottom: auto; width: 100%; } .grid-item { align-items: center; - border-radius: $br-medium; + border-radius: $br-small; cursor: pointer; display: flex; flex-direction: column; @@ -127,7 +126,7 @@ h3 { color: $intense-ui-text; - font-size: $fs16; + font-size: $fs15; font-weight: 400; overflow: hidden; padding: 0; @@ -138,7 +137,7 @@ } span.date { - color: $soft-ui-text; + color: $medium-ui-text; font-size: $fs12; overflow: hidden; text-overflow: ellipsis; @@ -148,42 +147,28 @@ } &.add-project { - border: 2px dashed $color-gray-light; - box-shadow: inset 0 0 0 transparent; + border: 1px dashed $color-gray-light; justify-content: center; span { - background-color: $color-primary-light; - border-radius: 5px; - color: $color-white; - font-size: $fs24; - padding: $small $medium; + color: $intense-ui-text; + font-size: $fs15; } &:hover { - background-color: $color-primary-light; - border: 2px solid $color-white; - - span { - color: $color-white; - } - + background-color: $color-white; + border: 2px solid $main-ui-color; } } // PROJECTS, ELEMENTS & ICONS GRID &.project-th { - background-color: $primary-ui-bg; - border-bottom: 2px solid lighten($soft-ui-border, 12%); + background-color: $color-white; + border: 2px solid $dark-ui-bg; &:hover { border-color: $main-ui-color; - - h3 { - color: $main-ui-color; - } - } .project-th-actions { @@ -204,7 +189,7 @@ } span { - color: $color-gray-light; + color: $color-black; } .project-th-icon { @@ -242,7 +227,7 @@ transform: scale(1.4); svg { - fill: $color-primary; + fill: $soft-ui-icons; } } @@ -257,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 { @@ -311,7 +296,7 @@ // STYLES FOR LIBRARIES &.library { - padding: $medium $medium $medium 250px; + padding: $medium $medium $medium 230px; } } @@ -320,8 +305,8 @@ background-position: center; background-size: auto 80%; background-repeat: no-repeat; - border-top-left-radius: $br-medium; - border-top-right-radius: $br-medium; + border-top-left-radius: $br-small; + border-top-right-radius: $br-small; height: 70%; overflow: hidden; position: relative; diff --git a/frontend/resources/styles/main/partials/library-bar-v2.scss b/frontend/resources/styles/main/partials/library-bar-v2.scss new file mode 100644 index 000000000..650a270ee --- /dev/null +++ b/frontend/resources/styles/main/partials/library-bar-v2.scss @@ -0,0 +1,123 @@ +// 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 Andrey Antukh +// Copyright (c) 2015-2016 Juan de la Cruz + +.library-bar { + background-color: $primary-ui-bg; + bottom: 0; + height: 100%; + left: 0; + position: fixed; + width: 250px; + + .library-bar-inside { + display: flex; + flex-direction: column; + height: 100%; + padding-top: 60px; + + .library-tabs { + align-items: center; + background-color: $secondary-ui-bg; + display: flex; + justify-content: space-around; + margin: 0; + padding-top: 12px; + + li { + background-color: darken($secondary-ui-bg, 10%); + border-top-left-radius: 3px; + border-top-right-radius: 3px; + color: $color-gray; + cursor: pointer; + font-weight: bold; + font-size: $fs14; + padding: .6rem; + text-align: center; + width: 118px; + + &:hover { + color: $color-white; + } + + &.current { + background-color: $primary-ui-bg; + color: $main-ui-color; + } + + } + + } + + .library-elements { + display: flex; + flex-direction: column; + height: calc(95% - 1rem); + margin-bottom: 1rem; + overflow-y: auto; + padding-bottom: 20px; + + li { + border-bottom: 1px solid $secondary-ui-bg; + cursor: pointer; + display: flex; + flex-direction: column; + flex-shrink: 0; + padding: 10px; + + span.element-title { + color: $color-gray-dark; + font-weight: bold; + margin-bottom: 5px; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + input.element-title { + border: 0; + height: 30px; + padding: 5px; + } + + .close { + background: $primary-ui-bg; + cursor: pointer; + padding: 5px 10px; + + svg { + fill: $color-gray; + height: 20px; + transform: rotate(45deg) translateY(7px); + width: 20px; + } + } + + .element-subtitle { + color: $color-gray-light; + font-style: italic; + } + + &:hover, + &.current { + background-color: $main-ui-color; + + span.element-title, + .element-subtitle { + color: $color-white; + } + input.element-title { + color: $color-gray-dark; + } + } + + } + + } + + } + +} diff --git a/frontend/resources/styles/main/partials/library-bar.scss b/frontend/resources/styles/main/partials/library-bar.scss index 650a270ee..6d8079ee3 100644 --- a/frontend/resources/styles/main/partials/library-bar.scss +++ b/frontend/resources/styles/main/partials/library-bar.scss @@ -6,18 +6,18 @@ // Copyright (c) 2015-2016 Juan de la Cruz .library-bar { - background-color: $primary-ui-bg; + background-color: $color-white; bottom: 0; height: 100%; left: 0; position: fixed; - width: 250px; + width: 230px; .library-bar-inside { display: flex; flex-direction: column; height: 100%; - padding-top: 60px; + padding-top: 40px; .library-tabs { align-items: center; @@ -61,21 +61,24 @@ padding-bottom: 20px; li { - border-bottom: 1px solid $secondary-ui-bg; cursor: pointer; display: flex; flex-direction: column; flex-shrink: 0; - padding: 10px; + padding: $small $medium; span.element-title { - color: $color-gray-dark; - font-weight: bold; - margin-bottom: 5px; + color: $intense-ui-text; + font-size: $fs14; overflow-x: hidden; text-overflow: ellipsis; white-space: nowrap; } + + &.recent-projects { + border-top: 1px solid $dark-ui-bg; + border-bottom: 1px solid $dark-ui-bg; + } input.element-title { border: 0; @@ -101,13 +104,17 @@ font-style: italic; } - &:hover, - &.current { + &:hover { background-color: $main-ui-color; + color: $intense-ui-text; + } + + &.current { span.element-title, .element-subtitle { - color: $color-white; + color: $intense-ui-text; + font-weight: bold; } input.element-title { color: $color-gray-dark; @@ -121,3 +128,80 @@ } } + +.projects-row { + align-items: center; + display: flex; + padding: $medium; + + span { + color: $color-gray-30; + font-size: $fs14; + } + + .add-project { + align-items: center; + background-color: $dark-ui-bg; + border-radius: $br-small; + border: 1px solid transparent; + cursor: pointer; + display: flex; + justify-content: center; + margin-left: auto; + padding: $x-small; + + svg { + fill: $soft-ui-icons; + height: 16px; + width: 16px; + } + + &:hover { + background-color: $main-ui-color; + + svg { + fill: $soft-ui-icons; + } + } + } +} + +.dashboard-search { + align-items: center; + border: 1px solid $color-gray-10; + display: flex; + margin: $medium; + + .input-text { + background: $color-white; + border: 0; + color: $intense-ui-text; + font-size: $fs14; + padding: 4px 8px; + margin: 0; + max-width: 170px; + width: 100%; + } + + .clear-search { + align-items: center; + cursor: pointer; + display: flex; + height: 28px; + padding: 0 5px; + + svg { + fill: $medium-ui-icons; + height: 15px; + transform: rotate(45deg); + width: 15px; + + &:hover { + fill: $color-danger; + } + + } + + } + +} \ No newline at end of file diff --git a/frontend/resources/styles/main/partials/main-bar-v2.scss b/frontend/resources/styles/main/partials/main-bar-v2.scss new file mode 100644 index 000000000..387f7af7e --- /dev/null +++ b/frontend/resources/styles/main/partials/main-bar-v2.scss @@ -0,0 +1,121 @@ +// 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 Andrey Antukh +// Copyright (c) 2015-2016 Juan de la Cruz + +.main-bar { + align-items: center; + background-color: $primary-ui-bg; + display: flex; + height: 60px; + padding: $x-small $medium; + position: relative; + z-index: 10; + + .main-logo { + + svg { + fill: $medium-ui-icons; + height: 40px; + width: 120px; + } + + } + +} + +.main-nav { + align-items: center; + display: flex; + height: 50px; + margin: 0 0 0 120px; + + li { + + a { + color: $soft-ui-text; + padding: 1rem; + + &:hover { + color: $intense-ui-text; + } + + } + + &.current { + + a { + color: $intense-ui-text; + } + + } + + } + +} + +.user-zone { + align-items: center; + cursor: pointer; + display: flex; + margin-left: auto; + position: relative; + + span { + color: $medium-ui-text; + margin: $small; + } + + img { + border-radius: 50%; + flex-shrink: 0; + height: 32px; + width: 32px; + } + + .dropdown { + background-color: $secondary-ui-bg; + border-radius: $br-small; + border: 1px solid $soft-ui-border; + min-width: 150px; + padding: 0 $small; + position: absolute; + top: 0; + right: 0; + width: 100%; + z-index: 12; + @include animation(0,.2s,fadeInDown); + + li { + font-size: $fs13; + padding: $small 0; + + svg { + fill: $medium-ui-text; + height: 12px; + width: 12px; + } + + span { + color: $medium-ui-text; + } + + &:hover { + + span { + color: $intense-ui-text; + } + + svg { + fill: $intense-ui-text; + } + + } + + } + + } + +} diff --git a/frontend/resources/styles/main/partials/main-bar.scss b/frontend/resources/styles/main/partials/main-bar.scss index 387f7af7e..acea41083 100644 --- a/frontend/resources/styles/main/partials/main-bar.scss +++ b/frontend/resources/styles/main/partials/main-bar.scss @@ -9,17 +9,18 @@ align-items: center; background-color: $primary-ui-bg; display: flex; - height: 60px; + height: 40px; padding: $x-small $medium; position: relative; z-index: 10; .main-logo { + padding-top: $x-small; svg { - fill: $medium-ui-icons; - height: 40px; - width: 120px; + fill: $intense-ui-icons; + height: 30px; + width: 100px; } } @@ -29,17 +30,19 @@ .main-nav { align-items: center; display: flex; - height: 50px; + font-size: $fs15; + height: 35px; margin: 0 0 0 120px; li { a { + border-bottom: 2px solid transparent; color: $soft-ui-text; - padding: 1rem; + margin: $x-small $big; &:hover { - color: $intense-ui-text; + border-color: $main-ui-color; } } @@ -47,7 +50,7 @@ &.current { a { - color: $intense-ui-text; + border-color: $main-ui-color; } } @@ -71,14 +74,14 @@ img { border-radius: 50%; flex-shrink: 0; - height: 32px; - width: 32px; + height: 25px; + width: 25px; } .dropdown { - background-color: $secondary-ui-bg; + background-color: $color-white; border-radius: $br-small; - border: 1px solid $soft-ui-border; + border: 1px solid $color-gray-10; min-width: 150px; padding: 0 $small; position: absolute; @@ -93,13 +96,13 @@ padding: $small 0; svg { - fill: $medium-ui-text; + fill: $color-gray-40; height: 12px; width: 12px; } span { - color: $medium-ui-text; + color: $color-gray-40; } &:hover { diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index ef725f4b9..0c29c61bf 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -62,14 +62,15 @@ } .element-set { + border-bottom: 1px solid $soft-ui-border; color: $medium-ui-text; margin: 0 $x-small; .element-set-title { - border-bottom: 1px dashed $soft-ui-border; - color: $medium-ui-text; - font-weight: bold; - padding: 2px $x-small; + color: $soft-ui-text; + font-size: $fs14; + margin-top: $x-small; + padding: $x-small; width: 100%; } @@ -185,36 +186,36 @@ .input-text { background-color: $input-bg; - border-color: $soft-ui-border; - color: $intense-ui-text; + border-color: $intense-ui-border; + color: $soft-ui-text; font-size: $fs13; margin: $x-small; padding: $x-small; width: 100%; &:focus { - color: darken($intense-ui-text, 8%); - border-color: $intense-ui-border; + color: lighten($soft-ui-text, 8%); + border-color: $medium-ui-border; } } .input-select { - color: $intense-ui-text; + color: $soft-ui-text; &:focus { - color: darken($intense-ui-text, 8%); + color: lighten($soft-ui-text, 8%); } option { color: $intense-ui-text; - background: $secondary-ui-bg; + background: $color-white; font-size: $fs12; } } span { - color: $medium-ui-text; + color: $soft-ui-text; font-size: $fs12; } @@ -286,7 +287,7 @@ .color-th { background-color: $color-gray-lighter; border: 1px solid $intense-ui-border; - border-radius: 50%; + border-radius: $br-small; cursor: pointer; flex-shrink: 0; height: 25px; diff --git a/frontend/resources/styles/main/partials/sidebar-layers-v2.scss b/frontend/resources/styles/main/partials/sidebar-layers-v2.scss new file mode 100644 index 000000000..065147f67 --- /dev/null +++ b/frontend/resources/styles/main/partials/sidebar-layers-v2.scss @@ -0,0 +1,265 @@ +// 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 Andrey Antukh +// Copyright (c) 2015-2016 Juan de la Cruz + +.layers-tools { + border-top: 1px solid $medium-ui-border; + bottom: 0; + display: flex; + height: 30px; + justify-content: center; + position: absolute; + width: 100%; + + .layers-tools-content { + align-items: center; + display: flex; + justify-content: space-between; + margin: 0; + width: 60%; + + li { + cursor: pointer; + + svg { + fill: $medium-ui-icons; + height: 14px; + width: 14px; + + &:hover { + fill: $intense-ui-text; + } + + } + + &.disable { + pointer-events: none; + + svg { + cursor: auto; + fill: $soft-ui-icons; + } + + } + + &.delete-layer { + + svg { + + &:hover { + fill: $color-danger; + } + + } + + } + + &.layer-up { + + svg { + transform: rotate(270deg); + } + + } + + &.layer-down { + + svg { + transform: rotate(90deg); + } + + } + + &.layer-top { + + svg { + transform: rotate(180deg); + } + + } + + } + + } + +} + +.element-list { + margin: 0; + width: 100%; + + ul { + border-left: 6px solid $intense-ui-border; + margin: 0; + } + + li { + cursor: pointer; + display: flex; + flex-direction: column; + width: 100%; + + &.dragging-TODO { + background-color: #eee; + } + + &.open { + + ul { + + li { + + .element-list-body { + border-style: dashed; + } + + } + + } + + } + + .element-list-body { + align-items: center; + border-bottom: 1px solid $soft-ui-border; + display: flex; + padding: $small; + transition: none; + width: 100%; + + svg { + fill: $soft-ui-icons; + height: 13px; + margin-right: 8px; + width: 13px; + } + + .element-actions { + align-items: center; + display: flex; + flex-shrink: 0; + width: 62px; + } + + .element-icon { + + svg { + fill: $medium-ui-icons; + } + + } + + .toggle-content { + margin-left: auto; + width: 12px; + + svg { + fill: $intense-ui-icons; + transform: rotate(90deg); + width: 10px; + } + + &.inverse { + svg { transform: rotate(270deg); } + } + + &:hover { + + svg { + fill: $medium-ui-icons; + } + + } + + } + + &.group { + + &.open { + + .toggle-content { + flex-shrink: 0; + + svg { + transform: rotate(270deg); + } + + } + + } + + } + + span.element-name { + min-width: 40px; + min-height: 16px; + display: block; + + color: $medium-ui-text; + font-size: $fs13; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &.selected { + + .element-icon { + + svg { + fill: $main-ui-color; + } + + } + + span { + color: $main-ui-color; + } + + } + + .selected { + + svg { + fill: $intense-ui-icons; + } + + } + + &:hover { + border-color: $main-ui-color; + + .element-icon { + + svg { + fill: $intense-ui-icons; + } + + } + + span { + color: $intense-ui-text; + } + + } + + &.drag-top { + border-top: 40px solid $soft-ui-border !important; + } + + &.drag-bottom { + border-bottom: 40px solid $soft-ui-border !important; + } + + &.drag-inside { + border: 2px solid $main-ui-color !important; + } + + } + + } + +} diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss index 065147f67..b7084f5ee 100644 --- a/frontend/resources/styles/main/partials/sidebar-layers.scss +++ b/frontend/resources/styles/main/partials/sidebar-layers.scss @@ -124,9 +124,8 @@ .element-list-body { align-items: center; - border-bottom: 1px solid $soft-ui-border; display: flex; - padding: $small; + padding: $x-small $small; transition: none; width: 100%; @@ -141,7 +140,14 @@ align-items: center; display: flex; flex-shrink: 0; - width: 62px; + width: 55px; + + .block-element { + + svg { + fill: $medium-ui-icons; + } + } } .element-icon { @@ -157,7 +163,7 @@ width: 12px; svg { - fill: $intense-ui-icons; + fill: $medium-ui-icons; transform: rotate(90deg); width: 10px; } @@ -169,7 +175,7 @@ &:hover { svg { - fill: $medium-ui-icons; + fill: $intense-ui-icons; } } @@ -206,18 +212,32 @@ } &.selected { - + .element-icon { - + svg { fill: $main-ui-color; } - + } - + span { color: $main-ui-color; } + + &:hover { + background-color: $main-ui-color; + + .element-icon, + .element-actions { + svg { + fill: $soft-ui-icons; + } + } + .element-name { + color: $intense-ui-text; + } + } } @@ -230,12 +250,13 @@ } &:hover { - border-color: $main-ui-color; + background-color: $main-ui-color; - .element-icon { + .element-icon, + .element-actions { svg { - fill: $intense-ui-icons; + fill: $soft-ui-icons; } } diff --git a/frontend/resources/styles/main/partials/sidebar-sitemap-v2.scss b/frontend/resources/styles/main/partials/sidebar-sitemap-v2.scss new file mode 100644 index 000000000..2d1ecf5b2 --- /dev/null +++ b/frontend/resources/styles/main/partials/sidebar-sitemap-v2.scss @@ -0,0 +1,279 @@ +// 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 Andrey Antukh +// Copyright (c) 2015-2016 Juan de la Cruz + +.sitemap { + + .project-title { + align-items: center; + border-bottom: 1px solid $soft-ui-border; + display: flex; + padding: $x-small; + width: 100%; + + span { + color: $intense-ui-text; + font-size: $fs14; + font-weight: bold; + max-width: 80%; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .add-page { + align-items: center; + background-color: $soft-ui-icons; + border-radius: $br-small; + border: 1px solid transparent; + cursor: pointer; + display: flex; + justify-content: center; + margin-left: auto; + padding: $x-small; + + svg { + fill: $intense-ui-icons; + height: 16px; + width: 16px; + } + + &:hover { + background-color: $dark-ui-bg; + border-color: $soft-ui-border; + } + + } + + } + + .element-list { + + li { + align-items: center; + display: flex; + flex-direction: row; + width: 100%; + + .page-icon { + + svg { + fill: $medium-ui-icons; + height: 15px; + margin-right: $x-small; + width: 15px; + } + + } + + span { + color: $medium-ui-text; + font-size: $fs14; + max-width: 75%; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .page-actions { + align-items: center; + display: none; + margin-left: auto; + + a { + + svg { + fill: $soft-ui-icons; + height: 15px; + margin-left: $x-small; + width: 15px; + + &:hover { + fill: $intense-ui-icons; + } + + } + + } + + } + + &:hover { + + .page-icon { + + svg { + fill: $main-ui-color; + } + + } + + span { + color: $main-ui-color; + } + + } + + &.selected { + + .page-icon { + + svg { + fill: $main-ui-color; + } + + } + + span { + color: $main-ui-color; + font-weight: bold; + } + + } + + } + + &:hover { + + .page-actions { + display: flex; + @include animation(0s,.3s,fadeIn); + } + + } + + .element-list-body { + align-items: center; + border-bottom: 1px solid $soft-ui-border; + display: flex; + padding: $small; + transition: none; + width: 100%; + + svg { + fill: $soft-ui-icons; + height: 13px; + margin-right: 8px; + width: 13px; + } + + .element-actions { + align-items: center; + display: flex; + flex-shrink: 0; + width: 62px; + } + + .element-icon { + + svg { + fill: $medium-ui-icons; + } + + } + + .toggle-content { + margin-left: auto; + width: 12px; + + svg { + fill: $intense-ui-icons; + transform: rotate(90deg); + width: 10px; + } + + &.inverse { + svg { transform: rotate(270deg); } + } + + &:hover { + + svg { + fill: $medium-ui-icons; + } + + } + + } + + &.group { + + &.open { + + .toggle-content { + flex-shrink: 0; + + svg { + transform: rotate(270deg); + } + + } + + } + + } + + span.element-name { + min-width: 40px; + min-height: 16px; + display: block; + + color: $medium-ui-text; + font-size: $fs13; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &.selected { + + .element-icon { + + svg { + fill: $main-ui-color; + } + + } + + span { + color: $main-ui-color; + } + + } + + .selected { + + svg { + fill: $intense-ui-icons; + } + + } + + &:hover { + border-color: $main-ui-color; + + .element-icon { + + svg { + fill: $intense-ui-icons; + } + + } + + span { + color: $intense-ui-text; + } + + } + + &.dragging { + // TODO: revisit this + background-color: #eee; + } + } + } + +} diff --git a/frontend/resources/styles/main/partials/sidebar-sitemap.scss b/frontend/resources/styles/main/partials/sidebar-sitemap.scss index 2d1ecf5b2..3727cf685 100644 --- a/frontend/resources/styles/main/partials/sidebar-sitemap.scss +++ b/frontend/resources/styles/main/partials/sidebar-sitemap.scss @@ -6,23 +6,9 @@ // Copyright (c) 2015-2016 Juan de la Cruz .sitemap { - - .project-title { - align-items: center; - border-bottom: 1px solid $soft-ui-border; - display: flex; - padding: $x-small; - width: 100%; - - span { - color: $intense-ui-text; - font-size: $fs14; - font-weight: bold; - max-width: 80%; - overflow-x: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } + max-height: 180px; + + .tool-window-bar { .add-page { align-items: center; @@ -42,12 +28,13 @@ } &:hover { - background-color: $dark-ui-bg; - border-color: $soft-ui-border; + background-color: $main-ui-color; + + svg { + fill: $soft-ui-icons; + } } - } - } .element-list { @@ -62,16 +49,16 @@ svg { fill: $medium-ui-icons; - height: 15px; - margin-right: $x-small; - width: 15px; + height: 13px; + margin-right: $small; + width: 13px; } } span { color: $medium-ui-text; - font-size: $fs14; + font-size: $fs13; max-width: 75%; overflow-x: hidden; text-overflow: ellipsis; @@ -89,10 +76,11 @@ fill: $soft-ui-icons; height: 15px; margin-left: $x-small; + opacity: .6; width: 15px; &:hover { - fill: $intense-ui-icons; + opacity: 1; } } @@ -102,17 +90,23 @@ } &:hover { + background-color: $main-ui-color; .page-icon { svg { - fill: $main-ui-color; + fill: $soft-ui-icons; + } + + span { + color: $intense-ui-text; } } - span { - color: $main-ui-color; + .page-actions { + display: flex; + @include animation(0s,.3s,fadeIn); } } @@ -129,27 +123,31 @@ span { color: $main-ui-color; - font-weight: bold; } } - } + &:hover { - &:hover { + .page-icon { - .page-actions { - display: flex; - @include animation(0s,.3s,fadeIn); + svg { + fill: $soft-ui-icons; + } + } + + span { + color: $intense-ui-text; + } } } + .element-list-body { align-items: center; - border-bottom: 1px solid $soft-ui-border; display: flex; - padding: $small; + padding: $x-small $small; transition: none; width: 100%; @@ -167,111 +165,9 @@ width: 62px; } - .element-icon { - - svg { - fill: $medium-ui-icons; - } - - } - - .toggle-content { - margin-left: auto; - width: 12px; - - svg { - fill: $intense-ui-icons; - transform: rotate(90deg); - width: 10px; - } - - &.inverse { - svg { transform: rotate(270deg); } - } - - &:hover { - - svg { - fill: $medium-ui-icons; - } - - } - - } - - &.group { - - &.open { - - .toggle-content { - flex-shrink: 0; - - svg { - transform: rotate(270deg); - } - - } - - } - - } - - span.element-name { - min-width: 40px; - min-height: 16px; - display: block; - - color: $medium-ui-text; - font-size: $fs13; - overflow-x: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - &.selected { - - .element-icon { - - svg { - fill: $main-ui-color; - } - - } - - span { - color: $main-ui-color; - } - - } - - .selected { - - svg { - fill: $intense-ui-icons; - } - - } - - &:hover { - border-color: $main-ui-color; - - .element-icon { - - svg { - fill: $intense-ui-icons; - } - - } - - span { - color: $intense-ui-text; - } - - } - &.dragging { // TODO: revisit this - background-color: #eee; + background-color: $dark-ui-bg; } } } diff --git a/frontend/resources/styles/main/partials/sidebar-v2.scss b/frontend/resources/styles/main/partials/sidebar-v2.scss new file mode 100644 index 000000000..13a6e046a --- /dev/null +++ b/frontend/resources/styles/main/partials/sidebar-v2.scss @@ -0,0 +1,91 @@ +// 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 Andrey Antukh +// Copyright (c) 2015-2016 Juan de la Cruz + +.settings-bar { + background-color: $primary-ui-bg; + bottom: 0; + height: 100%; + position: fixed; + right: 0; + width: 230px; + z-index: 10; + + &.settings-bar-left { + left: 0 + } + + .settings-bar-inside { + align-items: center; + display: flex; + flex-direction: column; + overflow-y: auto; + overflow-x: hidden; + padding-top: 50px; + height: 100%; + + .tool-window { + border-bottom: 1px solid $medium-ui-border; + display: flex; + flex-direction: column; + flex: 1; + width: 100%; + + .tool-window-bar { + align-items: center; + border-bottom: 1px solid $medium-ui-border; + display: flex; + flex-shrink: 0; + padding: 2px $x-small; + + svg { + fill: $intense-ui-icons; + height: 12px; + width: 12px; + } + + span { + color: $intense-ui-text; + font-weight: bold; + } + + .tool-window-icon { + margin-right: $small; + } + + .tool-window-close { + cursor: pointer; + margin-left: auto; + transform: rotate(45deg); + + &:hover { + + svg { + fill: $color-danger; + } + + } + + } + + } + + .tool-window-content { + display: flex; + flex-wrap: wrap; + overflow-y: auto; + padding-bottom: $medium; + } + + &#layers { + padding-bottom: 30px; + } + + } + + } + +} diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index 13a6e046a..f858cdf30 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -24,11 +24,11 @@ flex-direction: column; overflow-y: auto; overflow-x: hidden; - padding-top: 50px; + padding-top: 40px; height: 100%; .tool-window { - border-bottom: 1px solid $medium-ui-border; + border-bottom: 1px solid $soft-ui-border; display: flex; flex-direction: column; flex: 1; @@ -36,10 +36,9 @@ .tool-window-bar { align-items: center; - border-bottom: 1px solid $medium-ui-border; display: flex; flex-shrink: 0; - padding: 2px $x-small; + padding: $small; svg { fill: $intense-ui-icons; @@ -48,12 +47,13 @@ } span { - color: $intense-ui-text; - font-weight: bold; + color: $soft-ui-text; + font-size: $fs14; } .tool-window-icon { margin-right: $small; + display: none; } .tool-window-close { diff --git a/frontend/resources/styles/main/partials/workspace-bar-v2.scss b/frontend/resources/styles/main/partials/workspace-bar-v2.scss new file mode 100644 index 000000000..4a4ae7b6c --- /dev/null +++ b/frontend/resources/styles/main/partials/workspace-bar-v2.scss @@ -0,0 +1,292 @@ +// 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 Andrey Antukh +// Copyright (c) 2015-2016 Juan de la Cruz + +.workspace-bar { + align-items: center; + background-color: $primary-ui-bg; + border-bottom: 1px solid $soft-ui-border; + display: flex; + height: 50px; + padding: $x-small $medium $x-small 65px; + position: relative; + z-index: 11; + + .main-icon { + align-items: center; + background-color: $secondary-ui-bg; + cursor: pointer; + display: flex; + height: 100%; + justify-content: center; + left: 0; + position: absolute; + top: 0; + width: 50px; + + a { + height: 35px; + + svg { + fill: $medium-ui-icons; + height: 35px; + width: 35px; + + } + + &:hover { + + svg { + fill: $main-ui-color; + } + + } + + } + + } + + .project-tree-btn { + align-items: center; + background-color: $secondary-ui-bg; + border-radius: $br-small; + border: 1px solid transparent; + cursor: pointer; + display: flex; + padding: $x-small $x-small $x-small $medium; + width: 164px; + + svg { + fill: $intense-ui-icons; + height: 20px; + margin-right: $small; + width: 20px; + } + + span { + color: $intense-ui-text; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &:hover { + background-color: $dark-ui-bg; + border-color: $soft-ui-border; + } + + &.selected { + background-color: $main-ui-color; + + svg { + fill: $color-white; + } + + span { + color: $color-white; + } + + } + } + + .workspace-options { + display: flex; + + .options-btn { + align-items: center; + border-right: 4px double $soft-ui-border; + display: flex; + margin: 0; + + &:last-child { + border: none; + } + + li { + align-items: center; + background-color: $secondary-ui-bg; + border: 1px solid transparent; + border-radius: $br-small; + cursor: pointer; + display: flex; + flex-shrink: 0; + height: 30px; + justify-content: center; + margin: 0 $small; + position: relative; + width: 30px; + + a { + padding-top: 6px; + } + + svg { + fill: $intense-ui-icons; + height: 18px; + width: 18px; + } + + &:hover { + background-color: $dark-ui-bg; + border-color: $soft-ui-border; + } + + &.selected { + background-color: $main-ui-color; + + svg { + fill: $color-white; + } + + } + + &.view-mode { + background-color: $intense-ui-icons; + + svg { + fill: $secondary-ui-bg; + } + + &:hover { + background-color: $color-white; + border-color: $soft-ui-border; + + svg { + fill: $intense-ui-icons; + } + + } + + } + + } + + } + + .options-view { + align-items: center; + display: flex; + justify-content: content; + margin: 0; + + li { + align-items: center; + display: flex; + flex-shrink: 0; + height: 30px; + margin: 0 $small; + position: relative; + width: 60px; + + &.zoom-input { + width: 85px; + padding: 0 25px; + position: relative; + + .add-zoom, + .remove-zoom { + align-items: center; + border-radius: 50%; + border: 1px solid $intense-ui-border; + cursor: pointer; + color: $intense-ui-border; + display: none; + flex-shrink: 0; + font-size: $fs20; + font-weight: bold; + height: 20px; + justify-content: center; + position: absolute; + top: 5px; + width: 20px; + + &:hover { + border-color: $color-primary; + color: $color-primary; + } + + } + + .add-zoom { + left: -5px; + } + + .remove-zoom { + padding-top: 4px; + right: -5px; + } + + &:hover { + + .add-zoom, + .remove-zoom { + display: flex; + @include animation(0s,.3s,fadeIn); + } + + } + + } + + } + + } + + } + +} + +.options-btn { + align-items: center; + border-right: 4px double $soft-ui-border; + display: flex; + margin: 0; + + &:last-child { + border: none; + } + + li { + align-items: center; + background-color: $secondary-ui-bg; + border: 1px solid transparent; + border-radius: $br-small; + cursor: pointer; + display: flex; + flex-shrink: 0; + height: 30px; + justify-content: center; + margin: 0 $small; + position: relative; + width: 30px; + + a { + padding-top: 6px; + } + + svg { + fill: $intense-ui-icons; + height: 18px; + width: 18px; + } + + &:hover { + background-color: $dark-ui-bg; + border-color: $soft-ui-border; + } + + &.selected { + background-color: $main-ui-color; + + svg { + fill: $color-white; + } + + } + + } + +} diff --git a/frontend/resources/styles/main/partials/workspace-bar.scss b/frontend/resources/styles/main/partials/workspace-bar.scss index 4a4ae7b6c..46b796284 100644 --- a/frontend/resources/styles/main/partials/workspace-bar.scss +++ b/frontend/resources/styles/main/partials/workspace-bar.scss @@ -10,7 +10,7 @@ background-color: $primary-ui-bg; border-bottom: 1px solid $soft-ui-border; display: flex; - height: 50px; + height: 40px; padding: $x-small $medium $x-small 65px; position: relative; z-index: 11; @@ -28,12 +28,12 @@ width: 50px; a { - height: 35px; + height: 30px; svg { fill: $medium-ui-icons; - height: 35px; - width: 35px; + height: 30px; + width: 30px; } @@ -51,13 +51,9 @@ .project-tree-btn { align-items: center; - background-color: $secondary-ui-bg; - border-radius: $br-small; - border: 1px solid transparent; cursor: pointer; display: flex; - padding: $x-small $x-small $x-small $medium; - width: 164px; + padding: $x-small; svg { fill: $intense-ui-icons; @@ -67,33 +63,18 @@ } span { - color: $intense-ui-text; + color: $soft-ui-text; + font-size: $fs14; overflow-x: hidden; text-overflow: ellipsis; white-space: nowrap; } - &:hover { - background-color: $dark-ui-bg; - border-color: $soft-ui-border; - } - - &.selected { - background-color: $main-ui-color; - - svg { - fill: $color-white; - } - - span { - color: $color-white; - } - - } } .workspace-options { display: flex; + margin: auto; .options-btn { align-items: center; @@ -107,7 +88,7 @@ li { align-items: center; - background-color: $secondary-ui-bg; + background-color: transparent; border: 1px solid transparent; border-radius: $br-small; cursor: pointer; @@ -130,34 +111,25 @@ } &:hover { - background-color: $dark-ui-bg; - border-color: $soft-ui-border; + background-color: $color-primary; + + svg { + fill: $color-gray-50; + } + } &.selected { - background-color: $main-ui-color; svg { - fill: $color-white; - } - - } - - &.view-mode { - background-color: $intense-ui-icons; - - svg { - fill: $secondary-ui-bg; + fill: $color-primary; } &:hover { - background-color: $color-white; - border-color: $soft-ui-border; svg { - fill: $intense-ui-icons; + fill: $soft-ui-icons; } - } } @@ -166,67 +138,72 @@ } - .options-view { + } + + .options-view { + align-items: center; + display: flex; + justify-content: content; + margin: 0; + + li { align-items: center; display: flex; - justify-content: content; - margin: 0; + flex-shrink: 0; + height: 30px; + margin: 0 $small; + position: relative; + width: 60px; - li { - align-items: center; - display: flex; - flex-shrink: 0; - height: 30px; - margin: 0 $small; + &.zoom-input { + width: 85px; + padding: 0 25px; position: relative; - width: 60px; - &.zoom-input { - width: 85px; - padding: 0 25px; - position: relative; + span { + color: $medium-ui-text; + font-size: $fs15; + } + + .add-zoom, + .remove-zoom { + align-items: center; + border-radius: 50%; + border: 1px solid $intense-ui-border; + cursor: pointer; + color: $intense-ui-border; + display: none; + flex-shrink: 0; + font-size: $fs20; + font-weight: bold; + height: 20px; + justify-content: center; + position: absolute; + top: 5px; + width: 20px; + + &:hover { + border-color: $color-primary; + color: $color-primary; + } + + } + + .add-zoom { + left: -5px; + } + + .remove-zoom { + padding-top: 4px; + right: -5px; + } + + &:hover { .add-zoom, .remove-zoom { - align-items: center; - border-radius: 50%; - border: 1px solid $intense-ui-border; - cursor: pointer; - color: $intense-ui-border; - display: none; - flex-shrink: 0; - font-size: $fs20; - font-weight: bold; - height: 20px; - justify-content: center; - position: absolute; - top: 5px; - width: 20px; - - &:hover { - border-color: $color-primary; - color: $color-primary; - } - - } - - .add-zoom { - left: -5px; - } - - .remove-zoom { - padding-top: 4px; - right: -5px; - } - - &:hover { - - .add-zoom, - .remove-zoom { - display: flex; - @include animation(0s,.3s,fadeIn); - } - + display: flex; + @include animation(0s,.3s,fadeIn); } } @@ -290,3 +267,87 @@ } } + +.secondary-options { + align-items: center; + display: flex; + + .view-mode { + background-color: $intense-ui-icons; + align-items: center; + border-radius: $br-small; + cursor: pointer; + display: flex; + flex-shrink: 0; + height: 30px; + justify-content: center; + margin: 0 $small; + position: relative; + width: 30px; + + a { + padding-top: 6px; + } + + svg { + fill: $secondary-ui-bg; + height: 18px; + width: 18px; + } + + &:hover { + background-color: $main-ui-color; + + svg { + fill: $soft-ui-icons; + } + + } + + } + +} + +.user-multi { + align-items: center; + cursor: pointer; + display: flex; + margin: 0; + + li { + margin-left: $small; + position: relative; + + img { + border: 3px solid #f3dd14; + border-radius: 50%; + flex-shrink: 0; + height: 25px; + width: 25px; + } + } +} + +.multiuser-cursor { + align-items: center; + display: flex; + left: 0; + position: absolute; + top: 0; + z-index: 10000; + + svg { + height: 15px; + fill: #f3dd14; + width: 15px; + } + + span { + background-color: #f3dd14; + border-radius: $br-small; + color: $color-black; + font-size: $fs12; + margin-left: $small; + padding: $x-small; + } +} \ No newline at end of file diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index 397ce7cf0..aa56e0363 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -70,7 +70,7 @@ } .workspace-viewport { - height: calc(95% - 20px); + height: calc(100% - 40px); overflow: scroll; transition: none; width: 100%; diff --git a/frontend/src/uxbox/builtins/icons.cljs b/frontend/src/uxbox/builtins/icons.cljs index 73b90c61f..ae5e0ea3f 100644 --- a/frontend/src/uxbox/builtins/icons.cljs +++ b/frontend/src/uxbox/builtins/icons.cljs @@ -68,6 +68,17 @@ {:d "M298.766 343.507C295.152 347.12 371.3 500 381.92 500c10.62 0 104.594-152.013 102.007-156.493-2.583-4.474-63.805-.626-63.805-.626s26.42-111.48-50.044-195.794c-87.817-96.833-193.242-82.57-193.242-82.57s6.47-60.767 0-64.432C171.166-3.13 15.942 87.23 16.02 96.417c.08 9.187 149.695 93.815 156.386 90.08 6.692-3.738 1.877-63.18 1.877-63.18s77.484-9.466 147.628 61.927c63.27 64.394 36.283 158.888 36.283 158.888s-55.935-4.117-59.427-.625z"}]]])) +(def artboard + (html + [:svg + {:viewBox "0 0 500 500" + :height "500" + :width "500"} + [:g + [:path + {:d + "M0 0v134.00391L134.00586 0H0zm166.32227 0v44.453125h289.22461V455.54688H44.453125V164.07617H0V500h500V0H166.32227z"}]]])) + (def chat (html [:svg#svg2 diff --git a/frontend/src/uxbox/main/data/auth.cljs b/frontend/src/uxbox/main/data/auth.cljs index 1433eb69c..8506e9b14 100644 --- a/frontend/src/uxbox/main/data/auth.cljs +++ b/frontend/src/uxbox/main/data/auth.cljs @@ -38,10 +38,6 @@ (rx/of du/fetch-profile (rt/navigate :dashboard-projects))))) -(defn logged-in? - [v] - (= (ptk/type v) ::logged-in)) - ;; --- Login (s/def ::login-params @@ -50,7 +46,7 @@ (defn login [{:keys [username password] :as data}] (s/assert ::login-params data) - (reify + (ptk/reify ::login ptk/UpdateEvent (update [_ state] (merge state (dissoc initial-state :route :router))) diff --git a/frontend/src/uxbox/main/data/colors.cljs b/frontend/src/uxbox/main/data/colors.cljs index 8b2785539..871492be5 100644 --- a/frontend/src/uxbox/main/data/colors.cljs +++ b/frontend/src/uxbox/main/data/colors.cljs @@ -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 diff --git a/frontend/src/uxbox/main/data/history.cljs b/frontend/src/uxbox/main/data/history.cljs index b44e4914e..011b3570a 100644 --- a/frontend/src/uxbox/main/data/history.cljs +++ b/frontend/src/uxbox/main/data/history.cljs @@ -9,7 +9,7 @@ [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk] - [uxbox.main.data.pages :as udp] + [uxbox.main.data.projects :as dp] [uxbox.main.repo :as rp] [uxbox.util.data :refer [replace-by-id index-by]] [uxbox.util.spec :as us])) @@ -26,7 +26,7 @@ (s/def ::user ::us/uuid) (s/def ::shapes - (s/every ::udp/minimal-shape :kind vector?)) + (s/every ::dp/minimal-shape :kind vector?)) (s/def ::data (s/keys :req-un [::shapes])) @@ -77,7 +77,7 @@ (watch [_ state stream] #_(let [stopper (rx/filter #(= % ::stop-page-watcher) stream)] (->> stream - (rx/filter udp/page-persisted?) + (rx/filter dp/page-persisted?) (rx/debounce 1000) (rx/flat-map #(rx/of (fetch-history id) (fetch-pinned-history id))) @@ -192,7 +192,7 @@ (assoc :history true :data (:data item)))] (-> state - (udp/unpack-page page) + (dp/unpack-page page) (assoc-in [:workspace pid :history :selected] version)))))) ;; --- Apply Selected History @@ -209,7 +209,7 @@ ptk/WatchEvent (watch [_ state s] #_(let [pid (get-in state [:workspace :current])] - (rx/of (udp/persist-page pid)))))) + (rx/of (dp/persist-page pid)))))) ;; --- Deselect Page History @@ -219,7 +219,7 @@ (update [_ state] #_(let [pid (get-in state [:workspace :current]) packed (get-in state [:packed-pages pid])] - (-> (udp/unpack-page state packed) + (-> (dp/unpack-page state packed) (assoc-in [:workspace pid :history :selected] nil)))))) ;; --- Refresh Page History diff --git a/frontend/src/uxbox/main/data/icons.cljs b/frontend/src/uxbox/main/data/icons.cljs index 136a5c5e3..202e3a9cb 100644 --- a/frontend/src/uxbox/main/data/icons.cljs +++ b/frontend/src/uxbox/main/data/icons.cljs @@ -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 diff --git a/frontend/src/uxbox/main/data/images.cljs b/frontend/src/uxbox/main/data/images.cljs index 39181633b..51802dc1d 100644 --- a/frontend/src/uxbox/main/data/images.cljs +++ b/frontend/src/uxbox/main/data/images.cljs @@ -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 diff --git a/frontend/src/uxbox/main/data/pages.cljs b/frontend/src/uxbox/main/data/pages.cljs deleted file mode 100644 index fc7093981..000000000 --- a/frontend/src/uxbox/main/data/pages.cljs +++ /dev/null @@ -1,325 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2015-2017 Andrey Antukh - -(ns uxbox.main.data.pages - "Page related events (for workspace mainly)." - (:require - [cljs.spec.alpha :as s] - [beicon.core :as rx] - [cuerdas.core :as str] - [potok.core :as ptk] - [uxbox.main.repo.core :as rp] - [uxbox.main.data.projects :as dp] - [uxbox.util.data :refer [index-by-id concatv]] - [uxbox.util.spec :as us] - [uxbox.util.timers :as ts] - [uxbox.util.uuid :as uuid])) - -;; --- Struct - -(s/def ::id ::us/uuid) -(s/def ::name ::us/string) -(s/def ::inst ::us/inst) -(s/def ::type ::us/keyword) -(s/def ::file-id ::us/uuid) -(s/def ::created-at ::us/inst) -(s/def ::modified-at ::us/inst) -(s/def ::version ::us/number) -(s/def ::width (s/and ::us/number ::us/positive)) -(s/def ::height (s/and ::us/number ::us/positive)) - -(s/def ::grid-x-axis ::us/number) -(s/def ::grid-y-axis ::us/number) -(s/def ::grid-color ::us/string) -(s/def ::background ::us/string) -(s/def ::background-opacity ::us/number) - -(s/def ::ordering ::us/number) -(s/def ::user ::us/uuid) - -(s/def ::metadata - (s/keys :opt-un [::grid-y-axis - ::grid-x-axis - ::grid-color - ::background - ::background-opacity])) - -;; TODO: start using uxbox.common.pagedata/data spec ... - -(s/def ::minimal-shape - (s/keys :req-un [::type ::name] - :opt-un [::id])) - -(s/def ::shapes (s/coll-of ::us/uuid :kind vector?)) -(s/def ::canvas (s/coll-of ::us/uuid :kind vector?)) - -(s/def ::shapes-by-id - (s/map-of ::us/uuid ::minimal-shape)) - -(s/def ::data - (s/keys :req-un [::shapes ::canvas ::shapes-by-id])) - -(s/def ::page - (s/keys :req-un [::id - ::name - ::file-id - ::created-at - ::modified-at - ::user-id - ::ordering - ::metadata - ::data])) - -(s/def ::pages - (s/every ::page :kind vector?)) - -;; --- Protocols - -(defprotocol IPageDataUpdate - "A marker protocol for mark events that alters the - page and is subject to perform a backend synchronization.") - -(defprotocol IPagePersistentOps - (-persistent-ops [o] "Get a list of ops for the event.")) - -(defn page-update? - [o] - (or (satisfies? IPageDataUpdate o) - (= ::page-data-update o))) - -;; --- Helpers - -;; (defn pack-page -;; "Return a packed version of page object ready -;; for send to remore storage service." -;; [state id] -;; (letfn [(pack-shapes [ids] -;; (mapv #(get-in state [:shapes %]) ids))] -;; (let [page (get-in state [:pages id]) -;; data {:shapes (pack-shapes (concatv (:canvas page) -;; (:shapes page)))}] -;; (-> page -;; (assoc :data data) -;; (dissoc :shapes))))) - -(defn unpack-page - [state {:keys [id data metadata] :as page}] - (-> state - (update :pages assoc id (dissoc page :data)) - (update :pages-data assoc id data))) - -(defn purge-page - "Remove page and all related stuff from the state." - [state id] - (if-let [file-id (get-in state [:pages id :file-id])] - (-> state - (update-in [:files file-id :pages] #(filterv (partial not= id) %)) - (update-in [:workspace-file :pages] #(filterv (partial not= id) %)) - (update :pages dissoc id) - (update :pages-data dissoc id)) - state)) - -;; --- Fetch Pages (by File ID) - -(declare pages-fetched) - -(defn fetch-pages - [file-id] - (s/assert ::us/uuid file-id) - (reify - ptk/WatchEvent - (watch [_ state s] - (->> (rp/query :project-pages {:file-id file-id}) - (rx/map pages-fetched))))) - -;; --- Pages Fetched - -(defn pages-fetched - [pages] - (s/assert ::pages pages) - (ptk/reify ::pages-fetched - IDeref - (-deref [_] pages) - - ptk/UpdateEvent - (update [_ state] - (reduce unpack-page state pages)))) - -;; --- Fetch Page (By ID) - -(declare page-fetched) - -(defn fetch-page - "Fetch page by id." - [id] - (s/assert ::us/uuid id) - (reify - ptk/WatchEvent - (watch [_ state s] - (->> (rp/query :project-page {:id id}) - (rx/map page-fetched))))) - -;; --- Page Fetched - -(defn page-fetched - [data] - (s/assert ::page data) - (ptk/reify ::page-fetched - IDeref - (-deref [_] data) - - ptk/UpdateEvent - (update [_ state] - (unpack-page state data)))) - -;; --- Create Page - -(declare page-created) - -(s/def ::create-page - (s/keys :req-un [::name ::file-id])) - -(defn create-page - [{:keys [file-id name] :as data}] - (s/assert ::create-page data) - (ptk/reify ::create-page - ptk/WatchEvent - (watch [this state s] - (let [ordering (count (get-in state [:files file-id :pages])) - params {:name name - :file-id file-id - :ordering ordering - :data {:shapes [] - :canvas [] - :shapes-by-id {}} - :metadata {}}] - (->> (rp/mutation :create-project-page params) - (rx/map page-created)))))) - -;; --- Page Created - -(defn page-created - [{:keys [id file-id] :as page}] - (s/assert ::page page) - (ptk/reify ::page-created - cljs.core/IDeref - (-deref [_] page) - - ptk/UpdateEvent - (update [_ state] - (let [data (:data page) - page (dissoc page :data)] - (-> state - (update-in [:workspace-file :pages] (fnil conj []) id) - (update :pages assoc id page) - (update :pages-data assoc id data)))) - - ptk/WatchEvent - (watch [_ state stream] - (rx/of (uxbox.main.data.projects/fetch-file file-id))))) - -;; --- Rename Page - -(s/def ::rename-page - (s/keys :req-un [::id ::name])) - -(defn rename-page - [{:keys [id name] :as data}] - (s/assert ::rename-page data) - (ptk/reify ::rename-page - ptk/UpdateEvent - (update [_ state] - (let [pid (get-in state [:workspace-page :id]) - state (assoc-in state [:pages id :name] name)] - (cond-> state - (= pid id) (assoc-in [:workspace-page :name] name)))) - - ptk/WatchEvent - (watch [_ state stream] - (let [params {:id id :name name}] - (->> (rp/mutation :rename-page params) - (rx/map #(ptk/data-event ::page-renamed data))))))) - -;; --- Delete Page (by ID) - -(defn delete-page - [id] - {:pre [(uuid? id)]} - (reify - ptk/UpdateEvent - (update [_ state] - (purge-page state id)) - - ptk/WatchEvent - (watch [_ state s] - (let [page (:workspace-page state)] - (rx/merge - (->> (rp/mutation :delete-project-page {:id id}) - (rx/flat-map (fn [_] - (if (= id (:id page)) - (rx/of (dp/go-to (:file-id page))) - (rx/empty)))))))))) - -;; --- Persist Page - -(declare page-persisted) - -(def persist-current-page - (ptk/reify ::persist-page - ptk/WatchEvent - (watch [this state s] - (let [local (:workspace-local state) - page (:workspace-page state) - data (:workspace-data state)] - (if (:history local) - (rx/empty) - (let [page (assoc page :data data)] - (->> (rp/mutation :update-project-page-data page) - (rx/map (fn [res] (merge page res))) - (rx/map page-persisted) - (rx/catch (fn [err] (rx/of ::page-persist-error)))))))))) - -;; --- Page Persisted - -(defn page-persisted - [{:keys [id] :as page}] - (s/assert ::page page) - (ptk/reify ::page-persisted - cljs.core/IDeref - (-deref [_] page) - - ptk/UpdateEvent - (update [_ state] - (let [data (:data page) - page (dissoc page :data)] - (-> state - (assoc :workspace-data data) - (assoc :workspace-page page) - (update :pages assoc id page) - (update :pages-data assoc id data)))))) - -;; --- Update Page - -;; TODO: deprecated, need refactor (this is used on page options) -(defn update-page-attrs - [{:keys [id] :as data}] - (s/assert ::page data) - (ptk/reify ::update-page-attrs - ptk/UpdateEvent - (update [_ state] - (update state :workspace-page merge (dissoc data :id :version))))) - -;; --- Update Page Metadata - -;; TODO: deprecated, need refactor (this is used on page options) -(defn update-metadata - [id metadata] - (s/assert ::id id) - (s/assert ::metadata metadata) - (reify - ptk/UpdateEvent - (update [this state] - (assoc-in state [:pages id :metadata] metadata)))) diff --git a/frontend/src/uxbox/main/data/projects.cljs b/frontend/src/uxbox/main/data/projects.cljs index fa8d8d990..c6c39bc3b 100644 --- a/frontend/src/uxbox/main/data/projects.cljs +++ b/frontend/src/uxbox/main/data/projects.cljs @@ -6,26 +6,34 @@ (ns uxbox.main.data.projects (:require + [beicon.core :as rx] [cljs.spec.alpha :as s] [cuerdas.core :as str] - [beicon.core :as rx] [potok.core :as ptk] + [uxbox.common.pages :as cp] [uxbox.main.repo.core :as rp] - [uxbox.util.uuid :as uuid] + [uxbox.util.router :as rt] [uxbox.util.spec :as us] [uxbox.util.time :as dt] - [uxbox.util.router :as rt])) + [uxbox.util.timers :as ts] + [uxbox.util.uuid :as uuid])) ;; --- Specs -(s/def ::id uuid?) -(s/def ::name string?) -(s/def ::version integer?) -(s/def ::user-id uuid?) -(s/def ::created-at inst?) -(s/def ::modified-at inst?) +(s/def ::id ::us/uuid) +(s/def ::name ::us/string) +(s/def ::user ::us/uuid) +(s/def ::type ::us/keyword) +(s/def ::file-id ::us/uuid) +(s/def ::project-id ::us/uuid) +(s/def ::created-at ::us/inst) +(s/def ::modified-at ::us/inst) +(s/def ::version ::us/number) +(s/def ::ordering ::us/number) +(s/def ::metadata (s/nilable ::cp/metadata)) +(s/def ::data ::cp/data) -(s/def ::project-entity +(s/def ::project (s/keys ::req-un [::id ::name ::version @@ -33,23 +41,47 @@ ::created-at ::modified-at])) -(declare fetch-projects) -(declare projects-fetched?) +(s/def ::file + (s/keys :req-un [::id + ::name + ::created-at + ::modified-at + ::project-id])) + +(s/def ::page + (s/keys :req-un [::id + ::name + ::file-id + ::version + ::created-at + ::modified-at + ::user-id + ::ordering + ::metadata + ::data])) ;; --- Helpers -(defn assoc-project - "A reduce function for assoc the project to the state map." - [state {:keys [id] :as project}] - (s/assert ::project-entity project) - (update-in state [:projects id] merge project)) +(defn unpack-page + [state {:keys [id data metadata] :as page}] + (-> state + (update :pages assoc id (dissoc page :data)) + (update :pages-data assoc id data))) -(defn dissoc-project - "A reduce function for dissoc the project from the state map." +(defn purge-page + "Remove page and all related stuff from the state." [state id] - (update state :projects dissoc id)) + (if-let [file-id (get-in state [:pages id :file-id])] + (-> state + (update-in [:files file-id :pages] #(filterv (partial not= id) %)) + (update-in [:workspace-file :pages] #(filterv (partial not= id) %)) + (update :pages dissoc id) + (update :pages-data dissoc id)) + state)) -;; --- Initialize +;; --- Initialize Dashboard + +(declare fetch-projects) (declare fetch-files) (declare initialized) @@ -89,21 +121,8 @@ (when order {:order order}) (when filter {:filter filter}))))) -;; --- Projects Fetched - -(defn projects-fetched - [projects] - (s/assert (s/every ::project-entity) projects) - (ptk/reify ::projects-fetched - ptk/UpdateEvent - (update [_ state] - (reduce assoc-project state projects)))) - -(defn projects-fetched? - [v] - (= ::projects-fetched (ptk/type v))) - ;; --- Fetch Projects +(declare projects-fetched) (def fetch-projects (ptk/reify ::fetch-projects @@ -112,6 +131,17 @@ (->> (rp/query :projects) (rx/map projects-fetched))))) +;; --- Projects Fetched + +(defn projects-fetched + [projects] + (s/assert (s/every ::project) projects) + (ptk/reify ::projects-fetched + ptk/UpdateEvent + (update [_ state] + (let [assoc-project #(update-in %1 [:projects (:id %2)] merge %2)] + (reduce assoc-project state projects))))) + ;; --- Fetch Files (declare files-fetched) @@ -138,11 +168,9 @@ ;; --- Files Fetched -(s/def ::files any?) - (defn files-fetched [files] - (s/assert ::files files) + (s/assert (s/every ::file) files) (ptk/reify ::files-fetched cljs.core/IDeref (-deref [_] files) @@ -152,6 +180,34 @@ (let [assoc-file #(assoc-in %1 [:files (:id %2)] %2)] (reduce assoc-file state files))))) +;; --- Create Project + +(declare project-created) + +(def create-project + (ptk/reify ::create-project + ptk/WatchEvent + (watch [this state stream] + (let [name (str "Project Name " (gensym "p"))] + (->> (rp/mutation! :create-project {:name name}) + (rx/map (fn [data] + (projects-fetched [data])))))))) + +;; --- Create File + +(defn create-file + [{:keys [project-id] :as params}] + (ptk/reify ::create-file + ptk/WatchEvent + (watch [this state stream] + (let [name (str "File Name " (gensym "p")) + params {:name name :project-id project-id}] + (->> (rp/mutation! :create-project-file params) + (rx/mapcat + (fn [data] + (rx/of (files-fetched [data]) + #(update-in % [:dashboard-projects :files project-id] conj (:id data)))))))))) + ;; --- Rename Project (defn rename-project @@ -176,7 +232,7 @@ (ptk/reify ::delete-project ptk/UpdateEvent (update [_ state] - (dissoc-project state id)) + (update state :projects dissoc id)) ptk/WatchEvent (watch [_ state s] @@ -198,32 +254,6 @@ (->> (rp/mutation :delete-project-file {:id id}) (rx/ignore))))) -;; --- Create Project - -(declare project-created) - -(s/def ::create-project - (s/keys :req-un [::name])) - -(defn create-project - [{:keys [name] :as params}] - (s/assert ::create-project params) - (ptk/reify ::create-project - ptk/WatchEvent - (watch [this state stream] - (->> (rp/mutation :create-project {:name name}) - (rx/map project-created))))) - -;; --- Project Created - -(defn project-created - [data] - (ptk/reify ::project-created - ptk/UpdateEvent - (update [_ state] - (assoc-project state data)))) - - ;; --- Rename Project (defn rename-file @@ -262,3 +292,206 @@ (if (nil? id) (rx/of (rt/nav :dashboard-projects {} {})) (rx/of (rt/nav :dashboard-projects {} {:project-id (str id)})))))) + + +;; --- Fetch Pages (by File ID) + +(declare pages-fetched) + +(defn fetch-pages + [file-id] + (s/assert ::us/uuid file-id) + (reify + ptk/WatchEvent + (watch [_ state s] + (->> (rp/query :project-pages {:file-id file-id}) + (rx/map pages-fetched))))) + +;; --- Pages Fetched + +(defn pages-fetched + [pages] + (s/assert (s/every ::page) pages) + (ptk/reify ::pages-fetched + IDeref + (-deref [_] pages) + + ptk/UpdateEvent + (update [_ state] + (reduce unpack-page state pages)))) + +;; --- Fetch Page (By ID) + +(declare page-fetched) + +(defn fetch-page + "Fetch page by id." + [id] + (s/assert ::us/uuid id) + (reify + ptk/WatchEvent + (watch [_ state s] + (->> (rp/query :project-page {:id id}) + (rx/map page-fetched))))) + +;; --- Page Fetched + +(defn page-fetched + [data] + (s/assert ::page data) + (ptk/reify ::page-fetched + IDeref + (-deref [_] data) + + ptk/UpdateEvent + (update [_ state] + (unpack-page state data)))) + +;; --- Create Page + +(declare page-created) + +(s/def ::create-page + (s/keys :req-un [::name ::file-id])) + +(defn create-page + [{:keys [file-id name] :as data}] + (s/assert ::create-page data) + (ptk/reify ::create-page + ptk/WatchEvent + (watch [this state s] + (let [ordering (count (get-in state [:files file-id :pages])) + params {:name name + :file-id file-id + :ordering ordering + :data {:shapes [] + :canvas [] + :shapes-by-id {}} + :metadata {}}] + (->> (rp/mutation :create-project-page params) + (rx/map page-created)))))) + +;; --- Page Created + +(defn page-created + [{:keys [id file-id] :as page}] + (s/assert ::page page) + (ptk/reify ::page-created + cljs.core/IDeref + (-deref [_] page) + + ptk/UpdateEvent + (update [_ state] + (let [data (:data page) + page (dissoc page :data)] + (-> state + (update-in [:workspace-file :pages] (fnil conj []) id) + (update :pages assoc id page) + (update :pages-data assoc id data)))) + + ptk/WatchEvent + (watch [_ state stream] + (rx/of (uxbox.main.data.projects/fetch-file file-id))))) + +;; --- Rename Page + +(s/def ::rename-page + (s/keys :req-un [::id ::name])) + +(defn rename-page + [{:keys [id name] :as data}] + (s/assert ::rename-page data) + (ptk/reify ::rename-page + ptk/UpdateEvent + (update [_ state] + (let [pid (get-in state [:workspace-page :id]) + state (assoc-in state [:pages id :name] name)] + (cond-> state + (= pid id) (assoc-in [:workspace-page :name] name)))) + + ptk/WatchEvent + (watch [_ state stream] + (let [params {:id id :name name}] + (->> (rp/mutation :rename-page params) + (rx/map #(ptk/data-event ::page-renamed data))))))) + +;; --- Delete Page (by ID) + +(defn delete-page + [id] + {:pre [(uuid? id)]} + (reify + ptk/UpdateEvent + (update [_ state] + (purge-page state id)) + + ptk/WatchEvent + (watch [_ state s] + (let [page (:workspace-page state)] + (rx/merge + (->> (rp/mutation :delete-project-page {:id id}) + (rx/flat-map (fn [_] + (if (= id (:id page)) + (rx/of (go-to (:file-id page))) + (rx/empty)))))))))) + +;; --- Persist Page + +(declare page-persisted) + +(def persist-current-page + (ptk/reify ::persist-page + ptk/WatchEvent + (watch [this state s] + (let [local (:workspace-local state) + page (:workspace-page state) + data (:workspace-data state)] + (if (:history local) + (rx/empty) + (let [page (assoc page :data data)] + (->> (rp/mutation :update-project-page-data page) + (rx/map (fn [res] (merge page res))) + (rx/map page-persisted) + (rx/catch (fn [err] (rx/of ::page-persist-error)))))))))) + +;; --- Page Persisted + +(defn page-persisted + [{:keys [id] :as page}] + (s/assert ::page page) + (ptk/reify ::page-persisted + cljs.core/IDeref + (-deref [_] page) + + ptk/UpdateEvent + (update [_ state] + (let [data (:data page) + page (dissoc page :data)] + (-> state + (assoc :workspace-data data) + (assoc :workspace-page page) + (update :pages assoc id page) + (update :pages-data assoc id data)))))) + +;; --- Update Page + +;; TODO: deprecated, need refactor (this is used on page options) +(defn update-page-attrs + [{:keys [id] :as data}] + (s/assert ::page data) + (ptk/reify ::update-page-attrs + ptk/UpdateEvent + (update [_ state] + (update state :workspace-page merge (dissoc data :id :version))))) + +;; --- Update Page Metadata + +;; TODO: deprecated, need refactor (this is used on page options) +(defn update-metadata + [id metadata] + (s/assert ::id id) + (s/assert ::metadata metadata) + (reify + ptk/UpdateEvent + (update [this state] + (assoc-in state [:pages id :metadata] metadata)))) diff --git a/frontend/src/uxbox/main/data/undo.cljs b/frontend/src/uxbox/main/data/undo.cljs index 0047db0fe..34b56e4fe 100644 --- a/frontend/src/uxbox/main/data/undo.cljs +++ b/frontend/src/uxbox/main/data/undo.cljs @@ -9,7 +9,7 @@ [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk] - [uxbox.main.data.pages :as udp] + [uxbox.main.data.projects :as dp] [uxbox.main.store :as st] [uxbox.util.spec :as us])) @@ -28,9 +28,9 @@ (ptk/reify ::watch-page-changes ptk/WatchEvent (watch [_ state stream] - #_(let [stopper (rx/filter #(= % ::udp/stop-page-watcher) stream)] + #_(let [stopper (rx/filter #(= % ::dp/stop-page-watcher) stream)] (->> stream - (rx/filter udp/page-update?) + (rx/filter dp/page-update?) (rx/filter #(not (undo? %))) (rx/filter #(not (redo? %))) (rx/map #(save-undo-entry id)) @@ -49,7 +49,7 @@ (ptk/reify ::save-undo-entry ptk/UpdateEvent (update [_ state] - #_(let [page (udp/pack-page state id) + #_(let [page (dp/pack-page state id) undo {:data (:data page) :metadata (:metadata page)}] (-> state @@ -82,7 +82,7 @@ ;; (pp/pprint packed) (-> state - (udp/unpack-page packed) + (dp/unpack-page packed) (assoc-in [:undo pid :selected] pointer)))))))) (defn undo? @@ -113,7 +113,7 @@ ;; (pp/pprint packed) (-> state - (udp/unpack-page packed) + (dp/unpack-page packed) (assoc-in [:undo pid :selected] pointer)))))))) (defn redo? diff --git a/frontend/src/uxbox/main/data/users.cljs b/frontend/src/uxbox/main/data/users.cljs index 854e40651..3785731d0 100644 --- a/frontend/src/uxbox/main/data/users.cljs +++ b/frontend/src/uxbox/main/data/users.cljs @@ -8,6 +8,7 @@ (:require [cljs.spec.alpha :as s] [beicon.core :as rx] + [cuerdas.core :as str] [potok.core :as ptk] [uxbox.main.repo.core :as rp] [uxbox.util.i18n :as i18n :refer [tr]] @@ -31,7 +32,7 @@ ;; --- Profile Fetched -(s/def ::profile-fetched-params +(s/def ::profile-fetched (s/keys :req-un [::id ::username ::fullname @@ -41,8 +42,8 @@ (defn profile-fetched [data] - (s/assert ::profile-fetched-params data) - (reify + (s/assert ::profile-fetched data) + (ptk/reify ::profile-fetched ptk/UpdateEvent (update [_ state] (assoc state :profile data)) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 14d3654db..79ffa3b77 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -12,9 +12,9 @@ [uxbox.config :as cfg] [uxbox.common.data :as d] [uxbox.common.pages :as cp] + [uxbox.main.websockets :as ws] [uxbox.main.constants :as c] [uxbox.main.data.icons :as udi] - [uxbox.main.data.pages :as udp] [uxbox.main.data.projects :as dp] [uxbox.main.geom :as geom] [uxbox.main.refs :as refs] @@ -28,15 +28,16 @@ [uxbox.util.perf :as perf] [uxbox.util.router :as rt] [uxbox.util.spec :as us] + [uxbox.util.transit :as t] [uxbox.util.time :as dt] - [uxbox.util.uuid :as uuid])) + [uxbox.util.uuid :as uuid] + [vendor.randomcolor])) + ;; TODO: temporal workaround (def clear-ruler nil) (def start-ruler nil) -(declare shapes-overlaps?) - ;; --- Specs (s/def ::id ::us/uuid) @@ -49,7 +50,6 @@ (s/def ::font-size number?) (s/def ::font-style string?) (s/def ::font-weight string?) -(s/def ::height number?) (s/def ::hidden boolean?) (s/def ::id uuid?) (s/def ::letter-spacing number?) @@ -66,12 +66,13 @@ (s/def ::stroke-style #{:none :solid :dotted :dashed :mixed}) (s/def ::stroke-width number?) (s/def ::text-align #{"left" "right" "center" "justify"}) -(s/def ::type #{:rect :path :circle :image :text}) +(s/def ::type #{:rect :path :circle :image :text :canvas}) +(s/def ::x number?) +(s/def ::y number?) +(s/def ::cx number?) +(s/def ::cy number?) (s/def ::width number?) -(s/def ::x1 number?) -(s/def ::x2 number?) -(s/def ::y1 number?) -(s/def ::y2 number?) +(s/def ::height number?) (s/def ::attributes (s/keys :opt-un [::blocked @@ -90,13 +91,14 @@ ::proportion ::proportion-lock ::rx ::ry + ::cx ::cy + ::x ::y ::stroke-color ::stroke-opacity ::stroke-style ::stroke-width ::text-align - ::x1 ::x2 - ::y1 ::y2])) + ::width ::height])) (s/def ::minimal-shape (s/keys :req-un [::id ::page ::type ::name])) @@ -114,6 +116,103 @@ (defn interrupt? [e] (= e :interrupt)) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Websockets Events +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; --- Initialize WebSocket + +(declare fetch-users) +(declare handle-who) +(declare handle-pointer-update) +(declare handle-page-snapshot) +(declare shapes-changes-commited) + +(s/def ::type keyword?) +(s/def ::message + (s/keys :req-un [::type])) + +(defn initialize-ws + [file-id] + (ptk/reify ::initialize + ptk/UpdateEvent + (update [_ state] + (let [uri (str "ws://localhost:6060/sub/" file-id)] + (assoc-in state [:ws file-id] (ws/open uri)))) + + ptk/WatchEvent + (watch [_ state stream] + (let [wsession (get-in state [:ws file-id])] + (->> (rx/merge + (rx/of (fetch-users file-id)) + (->> (ws/-stream wsession) + (rx/filter #(= :message (:type %))) + (rx/map (comp t/decode :payload)) + (rx/filter #(s/valid? ::message %)) + (rx/map (fn [{:keys [type] :as msg}] + (case type + :who (handle-who msg) + :pointer-update (handle-pointer-update msg) + :page-snapshot (handle-page-snapshot msg) + ::unknown))))) + + + (rx/take-until + (rx/filter #(= ::finalize %) stream))))))) + +;; --- Finalize Websocket + +(defn finalize-ws + [file-id] + (ptk/reify ::finalize + ptk/WatchEvent + (watch [_ state stream] + (ws/-close (get-in state [:ws file-id])) + (rx/of ::finalize)))) + +;; --- Handle: Who + +;; TODO: assign color + +(defn- assign-user-color + [state user-id] + (let [user (get-in state [:workspace-users :by-id user-id]) + color (js/randomcolor) + user (if (string? (:color user)) + user + (assoc user :color color))] + (assoc-in state [:workspace-users :by-id user-id] user))) + +(defn handle-who + [{:keys [users] :as msg}] + (s/assert set? users) + (ptk/reify ::handle-who + ptk/UpdateEvent + (update [_ state] + (as-> state $$ + (assoc-in $$ [:workspace-users :active] users) + (reduce assign-user-color $$ users))))) + +(defn handle-pointer-update + [{:keys [user-id page-id x y] :as msg}] + (ptk/reify ::handle-pointer-update + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-users :pointer user-id] + {:page-id page-id + :user-id user-id + :x x + :y y})))) + +(defn handle-page-snapshot + [{:keys [user-id page-id version operations] :as msg}] + (ptk/reify ::handle-page-snapshot + ptk/WatchEvent + (watch [_ state stream] + (let [local (:workspace-local state)] + (when (= (:page-id local) page-id) + (rx/of (shapes-changes-commited msg))))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; General workspace events ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -122,7 +221,7 @@ (declare initialize-alignment) -(def default-layout #{:sitemap :drawtools :layers :element-options :rules}) +(def default-layout #{:sitemap :layers :element-options :rules}) (def workspace-default {:zoom 1 @@ -133,72 +232,122 @@ :tooltip nil}) (declare initialized) -(declare watch-page-changes) -;; (declare watch-events) (defn initialize "Initialize the workspace state." - [file-id page-id] + [file-id] (s/assert ::us/uuid file-id) - (s/assert ::us/uuid page-id) (ptk/reify ::initialize ptk/UpdateEvent (update [_ state] (let [local (assoc workspace-default - :file-id file-id - :page-id page-id)] + :file-id file-id) + ;; :page-id page-id) + ;; TODO: this need to be parametrized + uri (str "ws://localhost:6060/sub/" file-id)] (-> state (assoc :workspace-layout default-layout) - ;; (update :workspace-layout - ;; (fn [data] - ;; (if (nil? data) default-layout data))) - (assoc :workspace-local local)))) + (assoc :workspace-local local) + (assoc-in [:ws file-id] (ws/open uri))))) ptk/WatchEvent (watch [_ state stream] - #_(when-not (get-in state [:pages page-id]) - (reset! st/loader true)) + (let [wsession (get-in state [:ws file-id])] + (rx/merge + ;; Stop possible previous watchers and re-fetch the main page + ;; and all project related pages. + (rx/of ::stop-watcher + (dp/fetch-file file-id) + (dp/fetch-pages file-id) + (fetch-users file-id)) - (rx/merge - ;; Stop possible previous watchers and re-fetch the main page - ;; and all project related pages. - (rx/of ::stop-watcher - (udp/fetch-page page-id) - (dp/fetch-file file-id) - (udp/fetch-pages file-id)) + ;; When main page is fetched, schedule the main initialization. + (->> (rx/zip (rx/filter (ptk/type? ::dp/pages-fetched) stream) + (rx/filter (ptk/type? ::dp/files-fetched) stream)) + (rx/take 1) + (rx/do #(reset! st/loader false)) + (rx/mapcat #(rx/of (initialized file-id) + #_(initialize-alignment page-id)))) - ;; When main page is fetched, schedule the main initialization. - (->> (rx/zip (rx/filter (ptk/type? ::udp/page-fetched) stream) - (rx/filter (ptk/type? ::dp/files-fetched) stream)) - (rx/take 1) - (rx/do #(reset! st/loader false)) - (rx/mapcat #(rx/of (initialized file-id page-id) - #_(initialize-alignment page-id)))) + ;; WebSocket Incoming Messages Handling + (->> (ws/-stream wsession) + (rx/filter #(= :message (:type %))) + (rx/map (comp t/decode :payload)) + (rx/filter #(s/valid? ::message %)) + (rx/map (fn [{:keys [type] :as msg}] + (case type + :who (handle-who msg) + :pointer-update (handle-pointer-update msg) + :page-snapshot (handle-page-snapshot msg) + ::unknown)))) - ;; When workspace is initialized, run the event watchers. - (->> (rx/filter (ptk/type? ::initialized) stream) - (rx/take 1) - (rx/mapcat #(rx/of watch-page-changes))))) - ptk/EffectEvent - (effect [_ state stream] - ;; Optimistic prefetch of projects if them are not already fetched - #_(when-not (seq (:projects state)) - (st/emit! (dp/fetch-projects)))))) + #_(->> stream + ;; TODO: this need to be rethinked + (rx/filter uxbox.main.ui.workspace.streams/pointer-event?) + (rx/sample 150) + (rx/tap (fn [{:keys [pt] :as event}] + (let [msg {:type :pointer-update + :page-id page-id + :x (:x pt) + :y (:y pt)}] + (ws/-send (get-in state [:ws file-id]) (t/encode msg))))) + (rx/ignore) + (rx/take-until (rx/filter #(= ::finalize %) stream)))))))) (defn- initialized - [file-id page-id] + [file-id] (s/assert ::us/uuid file-id) - (s/assert ::us/uuid page-id) (ptk/reify ::initialized ptk/UpdateEvent (update [_ state] - (let [file (get-in state [:files file-id]) - page (get-in state [:pages page-id]) + (let [file (get-in state [:files file-id])] + (assoc state :workspace-file file))))) + +(defn finalize + [file-id] + (ptk/reify ::finalize + cljs.core/IDeref + (-deref [_] file-id) + + ptk/EffectEvent + (effect [_ state stream] + (ws/-close (get-in state [:ws file-id]))))) + +(defn initialize-page + [page-id] + (ptk/reify ::initialize-page + ptk/UpdateEvent + (update [_ state] + (let [page (get-in state [:pages page-id]) data (get-in state [:pages-data page-id])] (assoc state - :workspace-file file :workspace-data data - :workspace-page page))))) + :workspace-page page))) + + ptk/EffectEvent + (effect [_ state stream]))) + +;; --- Fetch Workspace Users + +(declare users-fetched) + +(defn fetch-users + [file-id] + (ptk/reify ::fetch-users + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :project-file-users {:file-id file-id}) + (rx/map users-fetched))))) + +(defn users-fetched + [users] + (ptk/reify ::users-fetched + ptk/UpdateEvent + (update [_ state] + (reduce (fn [state user] + (update-in state [:workspace-users :by-id (:id user)] merge user)) + state + users)))) ;; --- Toggle layout flag @@ -214,7 +363,6 @@ (disj flags flag) (conj flags flag))))))) - ;; --- Workspace Flags (defn activate-flag @@ -237,7 +385,6 @@ (update [_ state] (update-in state [:workspace-local :flags] disj flag)))) - (defn toggle-flag [flag] (s/assert keyword? flag) @@ -458,23 +605,59 @@ (assoc-in $ [:workspace-data :shapes-by-id id] shape)))) (declare commit-shapes-changes) +(declare select-shape) (declare recalculate-shape-canvas-relation) +(def shape-default-attrs + {:stroke-color "#000000" + :stroke-opacity 1 + :fill-color "#000000" + :fill-opacity 1}) + (defn add-shape [data] + (s/assert ::attributes data) (let [id (uuid/random)] (ptk/reify ::add-shape ptk/UpdateEvent (update [_ state] (let [shape (-> (geom/setup-proportions data) (assoc :id id)) + shape (merge shape-default-attrs shape) shape (recalculate-shape-canvas-relation state shape)] (impl-assoc-shape state shape))) ptk/WatchEvent (watch [_ state stream] (let [shape (get-in state [:workspace-data :shapes-by-id id])] - (rx/of (commit-shapes-changes [[:add-shape id shape]]))))))) + (rx/of (commit-shapes-changes [[:add-shape id shape]]) + (select-shape id))))))) + +(def canvas-default-attrs + {:stroke-color "#000000" + :stroke-opacity 1 + :fill-color "#ffffff" + :fill-opacity 1}) + +(defn add-canvas + [data] + (s/assert ::attributes data) + (let [id (uuid/random)] + (ptk/reify ::add-canvas + ptk/UpdateEvent + (update [_ state] + (let [shape (-> (geom/setup-proportions data) + (assoc :id id)) + shape (merge canvas-default-attrs shape) + shape (recalculate-shape-canvas-relation state shape)] + (impl-assoc-shape state shape))) + + ptk/WatchEvent + (watch [_ state stream] + (let [shape (get-in state [:workspace-data :shapes-by-id id])] + (rx/of (commit-shapes-changes [[:add-canvas id shape]]) + (select-shape id))))))) + ;; --- Duplicate Selected @@ -491,13 +674,12 @@ duplicate (partial impl-duplicate-shape state) shapes (map duplicate selected)] (rx/merge - (rx/from-coll (map (fn [s] #(impl-assoc-shape % s)) shapes)) + (rx/from (map (fn [s] #(impl-assoc-shape % s)) shapes)) (rx/of (commit-shapes-changes (mapv #(vector :add-shape (:id %) %) shapes)))))))) ;; --- Toggle shape's selection status (selected or deselected) (defn select-shape - "Mark a shape selected for drawing." [id] (s/assert ::us/uuid id) (ptk/reify ::select-shape @@ -523,18 +705,6 @@ (assoc :selected #{}) (dissoc :selected-canvas)))))) -;; --- Select First Shape - -;; TODO: first??? - -(def select-first-shape - (ptk/reify ::select-first-shape - ptk/UpdateEvent - (update [_ state] - (let [pid (get-in state [:workspace-local :id]) - sid (first (get-in state [:workspace-data :shapes]))] - (assoc-in state [:workspace-local :selected] #{sid}))))) - ;; --- Select Shapes (By selrect) (defn- impl-try-match-shape @@ -571,46 +741,35 @@ ;; --- Update Shape Attrs -(defn update-shape-attrs - [id attrs] - (s/assert ::us/uuid id) - (let [atts (s/conform ::attributes attrs)] - (ptk/reify ::update-shape-attrs - ptk/UpdateEvent - (update [_ state] - (if (map? attrs) - (update-in state [:workspace-data :shapes-by-id id] merge attrs) - state))))) - (defn update-shape - [id & attrs] - (let [attrs' (->> (apply hash-map attrs) - (s/conform ::attributes))] - (ptk/reify ::update-shape - udp/IPagePersistentOps - (-persistent-ops [_] - (->> (partition-all 2 attrs) - (mapv (fn [[key val]] [:mod-shape id key val])))) - - ptk/UpdateEvent - (update [_ state] - (cond-> state - (not= attrs' ::s/invalid) - (update-in [:workspace-data :shapes-by-id id] merge attrs')))))) + [id attrs] + (s/assert ::attributes attrs) + (ptk/reify ::update-shape + ptk/UpdateEvent + (update [_ state] + (let [shape-old (get-in state [:workspace-data :shapes-by-id id]) + shape-new (merge shape-old attrs) + diff (d/diff-maps shape-old shape-new)] + (-> state + (assoc-in [:workspace-data :shapes-by-id id] shape-new) + (assoc ::tmp-change (into [:mod-shape id] diff))))) + ptk/WatchEvent + (watch [_ state stream] + (let [change (::tmp-change state)] + (prn "update-shape" change) + (rx/of (commit-shapes-changes [change]) + #(dissoc state ::tmp-change)))))) ;; --- Update Selected Shapes attrs -;; TODO: improve performance of this event - -(defn update-selected-shapes-attrs - [attrs] - (s/assert ::attributes attrs) +(defn update-selected-shapes + [& attrs] (ptk/reify ::update-selected-shapes-attrs ptk/WatchEvent (watch [_ state stream] (let [selected (get-in state [:workspace-local :selected])] - (rx/from-coll (map #(update-shape-attrs % attrs) selected)))))) + (rx/from (map #(apply update-shape % attrs) selected)))))) ;; --- Move Selected @@ -664,19 +823,6 @@ (rx/of (apply-temporal-displacement-in-bulk selected displacement)) (rx/of (materialize-temporal-modifier-in-bulk selected))))))) -;; --- Update Shape Position - -(deftype UpdateShapePosition [id point] - ptk/UpdateEvent - (update [_ state] - (update-in state [:shapes id] geom/absolute-move point))) - -(defn update-position - "Update the start position coordenate of the shape." - [id point] - {:pre [(uuid? id) (gpt/point? point)]} - (UpdateShapePosition. id point)) - ;; --- Delete Selected (defn impl-dissoc-shape @@ -726,7 +872,6 @@ [loc] (s/assert ::direction loc) (ptk/reify ::move-selected-layer - udp/IPageDataUpdate ptk/UpdateEvent (update [_ state] (let [id (first (get-in state [:workspace-local :selected])) @@ -825,9 +970,10 @@ (defn- recalculate-shape-canvas-relation [state shape] - (let [xfmt (comp (map #(get-in state [:workspace-data :shapes-by-id %])) + (let [shape' (geom/shape->rect-shape shape) + xfmt (comp (map #(get-in state [:workspace-data :shapes-by-id %])) (map geom/shape->rect-shape) - (filter #(geom/overlaps? % shape)) + (filter #(geom/overlaps? % shape')) (map :id)) id (->> (get-in state [:workspace-data :canvas]) @@ -859,8 +1005,6 @@ (rx/of (commit-shapes-changes changes) #(dissoc state ::tmp-changes))))))) -(declare shapes-changes-commited) - (defn commit-shapes-changes [operations] (s/assert ::cp/operations operations) @@ -878,31 +1022,22 @@ params {:id (:id page) :version (:version page) :operations operations}] - (prn "commit-shapes-changes" params) (->> (rp/mutation :update-project-page params) - ;; (rx/tap #(prn "foobar" %)) - (rx/map shapes-changes-commited)))) - - ;; ptk/EffectEvent - ;; (effect [_ state stream] - ;; (let [data {:shapes [] - ;; :shapes-by-id {}}] - ;; (prn "commit-shapes-changes$effect" (cp/process-ops data operations)))) - )) + (rx/map shapes-changes-commited)))))) (s/def ::shapes-changes-commited - (s/keys :req-un [::id ::version ::cp/operations])) + (s/keys :req-un [::page-id ::version ::cp/operations])) (defn shapes-changes-commited - [{:keys [id version operations] :as params}] + [{:keys [page-id version operations] :as params}] (s/assert ::shapes-changes-commited params) (ptk/reify ::shapes-changes-commited ptk/UpdateEvent (update [_ state] (-> state (assoc-in [:workspace-page :version] version) - (assoc-in [:pages id :version] version) - (update-in [:pages-data id] cp/process-ops operations) + (assoc-in [:pages page-id :version] version) + (update-in [:pages-data page-id] cp/process-ops operations) (update :workspace-data cp/process-ops operations))))) ;; --- Start shape "edition mode" @@ -936,52 +1071,28 @@ (ptk/reify ::select-for-drawing ptk/UpdateEvent (update [_ state] - (update state :workspace-local assoc :drawing-tool tool :drawing data))))) + (update state :workspace-local assoc :drawing-tool tool :drawing data)) -;; --- Shape Proportions - -;; TODO: revisit - -(deftype LockShapeProportions [id] - ptk/UpdateEvent - (update [_ state] - (let [[width height] (-> (get-in state [:shapes id]) - (geom/size) - (keep [:width :height])) - proportion (/ width height)] - (update-in state [:shapes id] assoc - :proportion proportion - :proportion-lock true)))) - -(defn lock-proportions - "Mark proportions of the shape locked and save the current - proportion as additional precalculated property." - [id] - {:pre [(uuid? id)]} - (LockShapeProportions. id)) - -;; TODO: revisit - -(deftype UnlockShapeProportions [id] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:shapes id :proportion-lock] false))) - -(defn unlock-proportions - [id] - {:pre [(uuid? id)]} - (UnlockShapeProportions. id)) + ptk/WatchEvent + (watch [_ state stream] + (let [cancel-event? (fn [event] + (interrupt? event)) + stoper (rx/filter (ptk/type? ::clear-drawing) stream)] + (->> (rx/filter cancel-event? stream) + (rx/take 1) + (rx/map (constantly clear-drawing)) + (rx/take-until stoper))))))) ;; --- Update Dimensions -;; TODO: revisit - -(s/def ::width (s/and ::us/number ::us/positive)) -(s/def ::height (s/and ::us/number ::us/positive)) +(s/def ::width ::us/number) +(s/def ::height ::us/number) (s/def ::update-dimensions (s/keys :opt-un [::width ::height])) +;; TODO: emit commit-changes + (defn update-dimensions "A helper event just for update the position of the shape using the width and height attrs @@ -992,35 +1103,31 @@ (ptk/reify ::update-dimensions ptk/UpdateEvent (update [_ state] - (update-in state [:shapes id] geom/resize-dim dimensions)))) + (update-in state [:workspace-data :shapes-by-id id] geom/resize-dim dimensions)))) -;; --- Update Interaction +;; --- Shape Proportions -;; TODO: revisit -(deftype UpdateInteraction [shape interaction] - ptk/UpdateEvent - (update [_ state] - (let [id (or (:id interaction) - (uuid/random)) - data (assoc interaction :id id)] - (assoc-in state [:shapes shape :interactions id] data)))) +(defn toggle-shape-proportion-lock + [id] + (ptk/reify ::toggle-shape-proportion-lock + ptk/UpdateEvent + (update [_ state] + (let [shape (get-in state [:workspace-data :shapes-by-id id])] + (if (:proportion-lock shape) + (assoc-in state [:workspace-data :shapes-by-id id :proportion-lock] false) + (->> (geom/assign-proportions (assoc shape :proportion-lock true)) + (assoc-in state [:workspace-data :shapes-by-id id]))))))) -(defn update-interaction - [shape interaction] - (UpdateInteraction. shape interaction)) +;; --- Update Shape Position -;; --- Delete Interaction - -;; TODO: revisit -(deftype DeleteInteracton [shape id] - ptk/UpdateEvent - (update [_ state] - (update-in state [:shapes shape :interactions] dissoc id))) - -(defn delete-interaction - [shape id] - {:pre [(uuid? id) (uuid? shape)]} - (DeleteInteracton. shape id)) +(defn update-position + [id point] + (s/assert ::us/uuid id) + (s/assert gpt/point? point) + (ptk/reify ::update-position + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-data :shapes-by-id id] geom/absolute-move point)))) ;; --- Path Modifications @@ -1141,46 +1248,6 @@ {:pre [(uuid? id)]} (UnlockShape. id)) -;; --- Recalculate Shapes relations (Shapes <-> Canvas) - -(def rehash-shapes-relationships - (letfn [(overlaps? [canvas shape] - (let [shape1 (geom/shape->rect-shape canvas) - shape2 (geom/shape->rect-shape shape)] - (geom/overlaps? shape1 shape2)))] - (ptk/reify ::rehash-shapes-relationships - ptk/UpdateEvent - (update [_ state] - (let [data (:workspace-data state) - canvas (map #(get-in data [:shapes-by-id %]) (:canvas data)) - shapes (map #(get-in data [:shapes-by-id %]) (:shapes data))] - (reduce (fn [state {:keys [id] :as shape}] - (let [canvas (first (filter #(overlaps? % shape) canvas))] - (update-in state [:workspace-data :shapes-by-id id] assoc :canvas (:id canvas)))) - state - shapes)))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Selection Rect IMPL -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; TODO: move to shapes impl maybe... - -(defn selection->rect - [data] - (let [start (:start data) - stop (:stop data) - start-x (min (:x start) (:x stop)) - start-y (min (:y start) (:y stop)) - end-x (max (:x start) (:x stop)) - end-y (max (:y start) (:y stop))] - (assoc data - :x1 start-x - :y1 start-y - :x2 end-x - :y2 end-y - :type :rect))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Canvas Interactions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1193,24 +1260,6 @@ (update [_ state] (update state :workspace-local assoc :selected-canvas id)))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Server Interactions DEPRECATED -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; --- Update Metadata - -;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event. - -(defn update-metadata - [id metadata] - (s/assert ::us/uuid id) - (s/assert ::udp/metadata metadata) - (ptk/reify ::update-metadata - ptk/WatchEvent - (watch [_ state s] - #_(rx/of (udp/update-metadata id metadata) - (initialize-alignment id))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Navigation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1254,66 +1303,3 @@ pages (vec (concat before [id] after))] (assoc-in state [:projects (:project-id page) :pages] pages))))) -;; -- Page Changes Watcher - -(def watch-page-changes - (ptk/reify ::watch-page-changes - ptk/WatchEvent - (watch [_ state stream] - (let [stopper (rx/filter #(= % ::stop-watcher) stream)] - (->> stream - (rx/filter udp/page-update?) - (rx/debounce 500) - (rx/mapcat #(rx/of rehash-shapes-relationships - udp/persist-current-page)) - (rx/take-until stopper)))))) - -;; (def watch-shapes-changes -;; (letfn [(look-for-changes [[old new]] -;; (reduce-kv (fn [acc k v] -;; (if (identical? v (get old k)) -;; acc -;; (conj acc k))) -;; #{} -;; new)) -;; (select-shapes [state] -;; (get-in state [:workspace-data :shapes-by-id])) -;; ] -;; (ptk/reify ::watch-page-changes -;; ptk/WatchEvent -;; (watch [_ state stream] -;; (let [stopper (rx/filter #(= % ::stop-page-watcher) stream)] -;; (->> stream -;; (rx/filter udp/page-update?) -;; (rx/debounce 1000) -;; (rx/mapcat #(rx/merge (rx/of persist-page -;; (->> (rx/filter page-persisted? stream) -;; (rx/timeout 1000 (rx/empty)) -;; (rx/take 1) -;; (rx/ignore))))) -;; (rx/take-until stopper)))))) - - - -;; (let [stoper (rx/filter #(= % ::stop-shapes-watcher) stream) -;; into' (fn [dst srcs] (reduce #(into %1 %2) dst srcs))] -;; (->> (rx/merge st/store (rx/of state)) -;; (rx/map #(get-in % [:workspace-data :shapes-by-id])) -;; (rx/buffer 2 1) -;; (rx/map look-for-changes) -;; (rx/buffer-time 300) -;; (rx/map #(into' #{} %)) -;; (rx/filter (complement empty?)) -;; ;; (rx/tap #(prn "changed" %)) -;; ;; (rx/mapcat (fn [items] (rx/from-coll -;; ;; (map rehash-shape-relationship items)))) -;; (rx/ignore) -;; (rx/take-until stoper))))))) - -(defn shapes-overlaps? - [canvas shape] - (let [shape1 (geom/shape->rect-shape canvas) - shape2 (geom/shape->rect-shape shape)] - (geom/overlaps? shape1 shape2))) - - diff --git a/frontend/src/uxbox/main/data/workspace_websocket.cljs b/frontend/src/uxbox/main/data/workspace_websocket.cljs new file mode 100644 index 000000000..353ddafa6 --- /dev/null +++ b/frontend/src/uxbox/main/data/workspace_websocket.cljs @@ -0,0 +1,127 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.main.data.workspace-websocket + (:require + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [potok.core :as ptk] + [uxbox.config :as cfg] + [uxbox.common.data :as d] + [uxbox.common.pages :as cp] + [uxbox.main.websockets :as ws] + [uxbox.main.data.icons :as udi] + [uxbox.main.data.projects :as dp] + [uxbox.main.repo.core :as rp] + [uxbox.main.store :as st] + [uxbox.util.transit :as t] + [vendor.randomcolor])) + +;; --- Initialize WebSocket + +(declare fetch-users) +(declare handle-who) +(declare handle-pointer-update) +(declare handle-page-snapshot) + +(s/def ::type keyword?) +(s/def ::message + (s/keys :req-un [::type])) + +(defn initialize + [file-id] + (ptk/reify ::initialize + ptk/UpdateEvent + (update [_ state] + (let [uri (str "ws://localhost:6060/sub/" file-id)] + (assoc-in state [:ws file-id] (ws/open uri)))) + + ptk/WatchEvent + (watch [_ state stream] + (let [wsession (get-in state [:ws file-id])] + (->> (rx/merge + (rx/of (fetch-users file-id)) + (->> (ws/-stream wsession) + (rx/filter #(= :message (:type %))) + (rx/map (comp t/decode :payload)) + (rx/filter #(s/valid? ::message %)) + (rx/map (fn [{:keys [type] :as msg}] + (case type + :who (handle-who msg) + :pointer-update (handle-pointer-update msg) + :page-snapshot (handle-page-snapshot msg) + ::unknown))))) + + + (rx/take-until + (rx/filter #(= ::finalize %) stream))))))) + +;; --- Finalize Websocket + +(defn finalize + [file-id] + (ptk/reify ::finalize + ptk/WatchEvent + (watch [_ state stream] + (ws/-close (get-in state [:ws file-id])) + (rx/of ::finalize)))) + +;; --- Fetch Workspace Users + +(declare users-fetched) + +(defn fetch-users + [file-id] + (ptk/reify ::fetch-users + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :project-file-users {:file-id file-id}) + (rx/map users-fetched))))) + +(defn users-fetched + [users] + (ptk/reify ::users-fetched + ptk/UpdateEvent + (update [_ state] + (reduce (fn [state user] + (assoc-in state [:workspace-users :by-id (:id user)] user)) + state + users)))) + + +;; --- Handle: Who + +;; TODO: assign color + +(defn handle-who + [{:keys [users] :as msg}] + (s/assert set? users) + (ptk/reify ::handle-who + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-users :active] users)))) + +(defn handle-pointer-update + [{:keys [user-id page-id x y] :as msg}] + (ptk/reify ::handle-pointer-update + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-users :pointer user-id] + {:page-id page-id + :user-id user-id + :x x + :y y})))) + +(defn handle-page-snapshot + [{:keys [user-id page-id version operations :as msg]}] + (ptk/reify ::handle-page-snapshot + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:workspace-page :version] version) + (assoc-in [:pages page-id :version] version) + (update-in [:pages-data page-id] cp/process-ops operations) + (update :workspace-data cp/process-ops operations))))) diff --git a/frontend/src/uxbox/main/geom.cljs b/frontend/src/uxbox/main/geom.cljs index 9195e725c..3f9a5497d 100644 --- a/frontend/src/uxbox/main/geom.cljs +++ b/frontend/src/uxbox/main/geom.cljs @@ -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) diff --git a/frontend/src/uxbox/main/locales/en.cljs b/frontend/src/uxbox/main/locales/en.cljs index 8aa9a3e7e..faf00994c 100644 --- a/frontend/src/uxbox/main/locales/en.cljs +++ b/frontend/src/uxbox/main/locales/en.cljs @@ -15,6 +15,7 @@ "ds.num-files" ["No files" "%s file" "%s files"] + "ds.new-file" "+ New File" "ds.project-title" "Your projects" "ds.project-new" "+ New project" "ds.project-thumbnail.alt" "Project title" @@ -79,33 +80,8 @@ "ds.project.placeholder" "New project name" "ds.project.new" "New project" - "ds.radius" "Radius" - "ds.size" "Size" - "ds.width" "Width" - "ds.height" "Height" - "ds.style" "Style" - "ds.none" "None" - "ds.solid" "Solid" - "ds.dotted" "Dotted" - "ds.dashed" "Dashed" - "ds.mixed" "Mixed" - "ds.position" "Position" - "ds.rotation" "Rotation" - "ds.opacity" "Opacity" - "ds.color" "Color" - "ds.background-color" "Background color" - "ds.font-family" "Font family" - "ds.size-weight" "Size and Weight" - "ds.font-size" "Font Size" - "ds.line-height-letter-spacing" "Line height and Letter spacing" - "ds.line-height" "Line height" - "ds.letter-spacing" "Letter spacing" - "ds.text-align" "Text align" - "ds.name" "Name" - "ds.go" "Go go go!" - - "ds.accept" "Accept" - "ds.cancel" "Cancel" + "common.accept" "Accept" + "common.cancel" "Cancel" "ds.settings.icons" "Icons" "ds.settings.element-options" "Element options" @@ -121,43 +97,69 @@ "ds.history.versions" "History" "ds.history.pinned" "Pinned" - "ds.help.rect" "Box (Ctrl + B)" - "ds.help.circle" "Circle (Ctrl + E)" - "ds.help.line" "Line (Ctrl + L)" - "ds.help.text" "Text" - "ds.help.path" "Path" - "ds.help.curve" "Curve" - "ds.help.ruler" "Ruler" - "ds.help.canvas" "Canvas" + "workspace.header.rect" "Box (Ctrl + B)" + "workspace.header.circle" "Circle (Ctrl + E)" + "workspace.header.line" "Line (Ctrl + L)" + "workspace.header.text" "Text" + "workspace.header.path" "Path" + "workspace.header.curve" "Curve" + "workspace.header.ruler" "Ruler" + "workspace.header.canvas" "Canvas" "ds.user.profile" "Profile" "ds.user.password" "Password" "ds.user.notifications" "Notifications" "ds.user.exit" "Exit" - "header.sitemap" "Sitemap (Ctrl + Shift + M)" - "header.draw-tools" "Draw tools (Ctrl + Shift + S)" - "header.color-palette" "Color Palette (---)" - "header.icons" "Icons (Ctrl + Shift + I)" - "header.layers" "Layers (Ctrl + Shift + L)" - "header.element-options" "Element options (Ctrl + Shift + O)" - "header.document-history" "History (Ctrl + Shift + H)" - "header.undo" "Undo (Ctrl + Z)" - "header.redo" "Redo (Ctrl + Shift + Z)" - "header.download" "Download (Ctrl + E)" - "header.image" "Image (Ctrl + I)" - "header.rules" "Rules" - "header.grid" "Grid (Ctrl + G)" - "header.grid-snap" "Snap to grid" - "header.align" "Align (Ctrl + A)" - "header.view-mode" "View mode (Ctrl + P)" + "workspace.header.sitemap" "Sitemap (Ctrl + Shift + M)" + "workspace.header.draw-tools" "Draw tools (Ctrl + Shift + S)" + "workspace.header.color-palette" "Color Palette (---)" + "workspace.header.icons" "Icons (Ctrl + Shift + I)" + "workspace.header.layers" "Layers (Ctrl + Shift + L)" + "workspace.header.element-options" "Element options (Ctrl + Shift + O)" + "workspace.header.document-history" "History (Ctrl + Shift + H)" + "workspace.header.undo" "Undo (Ctrl + Z)" + "workspace.header.redo" "Redo (Ctrl + Shift + Z)" + "workspace.header.download" "Download (Ctrl + E)" + "workspace.header.image" "Image (Ctrl + I)" + "workspace.header.rules" "Rules" + "workspace.header.grid" "Grid (Ctrl + G)" + "workspace.header.grid-snap" "Snap to grid" + "workspace.header.align" "Align (Ctrl + A)" + "workspace.header.view-mode" "View mode (Ctrl + P)" + + "workspace.options.radius" "Radius" + "workspace.options.size" "Size" + "workspace.options.width" "Width" + "workspace.options.height" "Height" + "workspace.options.stroke.style" "Style" + "workspace.options.stroke.none" "None" + "workspace.options.stroke.solid" "Solid" + "workspace.options.stroke.dotted" "Dotted" + "workspace.options.stroke.dashed" "Dashed" + "workspace.options.stroke.mixed" "Mixed" + "workspace.options.position" "Position" + "workspace.options.rotation" "Rotation" + "workspace.options.opacity" "Opacity" + "workspace.options.color" "Color" + "workspace.options.background-color" "Background color" + "workspace.options.font-family" "Font family" + "workspace.options.size-weight" "Size and Weight" + "workspace.options.font-size" "Font Size" + "workspace.options.line-height-letter-spacing" "Line height and Letter spacing" + "workspace.options.line-height" "Line height" + "workspace.options.letter-spacing" "Letter spacing" + "workspace.options.text-align" "Text align" + "workspace.options.name" "Name" + "workspace.options.go" "Go go go!" + "workspace.options.measures" "Size, position & rotation" + "workspace.options.rotation-radius" "Rotation & Radius" - "element.measures" "Size, position & rotation" "element.fill" "Fill" - "element.stroke" "Stroke" + "workspace.options.stroke" "Stroke" "element.text" "Text" "element.interactions" "Interactions" - "element.page-measures" "Page settings" + "workspace.options.page-measures" "Page settings" "element.page-grid-options" "Grid settings" "image.new" "New image" diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index 95c49a127..16ecfcefa 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -11,6 +11,11 @@ [uxbox.main.constants :as c] [uxbox.main.store :as st])) + +(def profile + (-> (l/key :profile) + (l/derive st/state))) + (def workspace (-> (l/key :workspace-local) (l/derive st/state))) @@ -31,6 +36,10 @@ (-> (l/key :workspace-file) (l/derive st/state))) +(def workspace-users + (-> (l/key :workspace-users) + (l/derive st/state))) + (def workspace-data (-> (l/key :workspace-data) (l/derive st/state))) diff --git a/frontend/src/uxbox/main/ui.cljs b/frontend/src/uxbox/main/ui.cljs index 07e77dabf..2016ece95 100644 --- a/frontend/src/uxbox/main/ui.cljs +++ b/frontend/src/uxbox/main/ui.cljs @@ -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"))))))) diff --git a/frontend/src/uxbox/main/ui/dashboard.cljs b/frontend/src/uxbox/main/ui/dashboard.cljs index 5d0524bed..18403ede4 100644 --- a/frontend/src/uxbox/main/ui/dashboard.cljs +++ b/frontend/src/uxbox/main/ui/dashboard.cljs @@ -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}])])) diff --git a/frontend/src/uxbox/main/ui/dashboard/colors.cljs b/frontend/src/uxbox/main/ui/dashboard/colors.cljs index 52c9ea673..6059542d9 100644 --- a/frontend/src/uxbox/main/ui/dashboard/colors.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/colors.cljs @@ -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}]])) diff --git a/frontend/src/uxbox/main/ui/dashboard/header.cljs b/frontend/src/uxbox/main/ui/dashboard/header.cljs index c32d885db..80c797031 100644 --- a/frontend/src/uxbox/main/ui/dashboard/header.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/header.cljs @@ -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]])) diff --git a/frontend/src/uxbox/main/ui/dashboard/icons.cljs b/frontend/src/uxbox/main/ui/dashboard/icons.cljs index c0a9091c4..1eb72fc35 100644 --- a/frontend/src/uxbox/main/ui/dashboard/icons.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/icons.cljs @@ -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}]])) diff --git a/frontend/src/uxbox/main/ui/dashboard/images.cljs b/frontend/src/uxbox/main/ui/dashboard/images.cljs index f1028ae63..424b509d4 100644 --- a/frontend/src/uxbox/main/ui/dashboard/images.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/images.cljs @@ -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}]])) diff --git a/frontend/src/uxbox/main/ui/dashboard/projects.cljs b/frontend/src/uxbox/main/ui/dashboard/projects.cljs index 1a136b356..e63cc3fcc 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects.cljs @@ -64,59 +64,6 @@ files (filter #(contains-term? (:name %) term) files))) -;; --- Menu (Filter & Sort) - -(mf/defc menu - [{:keys [id opts files] :as props}] - (let [ordering (:order opts :modified) - filtering (:filter opts "") - - on-term-change - (fn [event] - (let [term (-> (dom/get-target event) - (dom/get-value))] - (st/emit! (udp/update-opts :filter term)))) - - on-order-change - (fn [event] - (let [value (dom/event->value event) - value (read-string value)] - (st/emit! (udp/update-opts :order value)))) - - on-clear - (fn [event] - (st/emit! (udp/update-opts :filter "")))] - - [:section.dashboard-bar.library-gap - [:div.dashboard-info - - ;; Counter - [:span.dashboard-images (tr "ds.num-files" (t/c (count files)))] - - [:div - ;; Sorting - ;; TODO: convert to separate component? - (when id - [:* - [:span (tr "ds.ordering")] - [:select.input-select {:on-change on-order-change - :value (pr-str ordering)} - (for [[key value] (seq +ordering-options+)] - (let [key (pr-str key)] - [:option {:key key :value key} (tr value)]))]])] - - ;; Search - ;; TODO: convert to separate component? - [:form.dashboard-search - [:input.input-text - {:key :images-search-box - :type "text" - :on-change on-term-change - :auto-focus true - :placeholder (tr "ds.search.placeholder") - :value (or filtering "")}] - [:div.clear-search {:on-click on-clear} i/close]]]])) - ;; --- Grid Item Thumbnail (mf/defc grid-item-thumbnail @@ -160,12 +107,12 @@ (str (tr "ds.updated-at" (dt/timeago (:modified-at file))))]] [:div.project-th-actions - [:div.project-th-icon.pages - i/page - #_[:span (:total-pages project)]] - #_[:div.project-th-icon.comments - i/chat - [:span "0"]] + ;; [:div.project-th-icon.pages + ;; i/page + ;; #_[:span (:total-pages project)]] + ;; [:div.project-th-icon.comments + ;; i/chat + ;; [:span "0"]] [:div.project-th-icon.edit {:on-click on-edit} i/pencil] @@ -176,7 +123,7 @@ ;; --- Grid (mf/defc grid - [{:keys [opts files] :as props}] + [{:keys [id opts files] :as props}] (let [order (:order opts :modified) filter (:filter opts "") files (->> files @@ -184,15 +131,13 @@ (sort-by order)) on-click #(do (dom/prevent-default %) - #_(modal/show! create-project-dialog {}) - #_(udl/open! :create-project)) - ] + (st/emit! (udp/create-file {:project-id id})))] [:section.dashboard-grid - [:h2 (tr "ds.projects.file-name")] [:div.dashboard-grid-content [:div.dashboard-grid-row - [:div.grid-item.add-project #_{:on-click on-click} - [:span (tr "ds.project-file")]] + (when id + [:div.grid-item.add-project {:on-click on-click} + [:span (tr "ds.new-file")]]) (for [item files] [:& grid-item {:file item :key (:id item)}])]]])) @@ -236,16 +181,23 @@ (sort-by :created-at))] [:div.library-bar [:div.library-bar-inside + [:form.dashboard-search + [:input.input-text + {:key :images-search-box + :type "text" + :auto-focus true + :placeholder (tr "ds.search.placeholder")}] + [:div.clear-search i/close]] [:ul.library-elements - [:li - [:a.btn-primary #_{:on-click #(st/emit! di/create-collection)} - "new project +"]] - - [:li {:style {:marginBottom "20px"} - :on-click #(st/emit! (udp/go-to-project nil)) + [:li.recent-projects {:on-click #(st/emit! (udp/go-to-project nil)) :class-name (when (nil? id) "current")} [:span.element-title "Recent"]] + [:div.projects-row + [:span "PROJECTS"] + [:a.add-project #_{:on-click #(st/emit! di/create-collection)} + i/close]] + (for [item projects] [:& nav-item {:id (:id item) :key (:id item) @@ -268,10 +220,8 @@ [{:keys [id] :as props}] (let [opts (mf/deref opts-iref) files (mf/deref files-ref)] - [:* - [:& menu {:id id :opts opts :files files}] - [:section.dashboard-grid.library - [:& grid {:id id :opts opts :files files}]]])) + [:section.dashboard-grid.library + [:& grid {:id id :opts opts :files files}]])) ;; --- Projects Page diff --git a/frontend/src/uxbox/main/ui/shapes/attrs.cljs b/frontend/src/uxbox/main/ui/shapes/attrs.cljs index 6ad2d7303..4c3f56baf 100644 --- a/frontend/src/uxbox/main/ui/shapes/attrs.cljs +++ b/frontend/src/uxbox/main/ui/shapes/attrs.cljs @@ -4,7 +4,43 @@ ;; ;; Copyright (c) 2016-2017 Andrey Antukh -(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))) diff --git a/frontend/src/uxbox/main/ui/shapes/image.cljs b/frontend/src/uxbox/main/ui/shapes/image.cljs index 04136cd9a..bde963f92 100644 --- a/frontend/src/uxbox/main/ui/shapes/image.cljs +++ b/frontend/src/uxbox/main/ui/shapes/image.cljs @@ -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])) diff --git a/frontend/src/uxbox/main/ui/shapes/rect.cljs b/frontend/src/uxbox/main/ui/shapes/rect.cljs index f1f38928c..1b2184072 100644 --- a/frontend/src/uxbox/main/ui/shapes/rect.cljs +++ b/frontend/src/uxbox/main/ui/shapes/rect.cljs @@ -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])) diff --git a/frontend/src/uxbox/main/ui/shapes/text.cljs b/frontend/src/uxbox/main/ui/shapes/text.cljs index 834563b60..2ce42d6dd 100644 --- a/frontend/src/uxbox/main/ui/shapes/text.cljs +++ b/frontend/src/uxbox/main/ui/shapes/text.cljs @@ -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) diff --git a/frontend/src/uxbox/main/ui/workspace.cljs b/frontend/src/uxbox/main/ui/workspace.cljs index 9cc31a8e9..e60a9f317 100644 --- a/frontend/src/uxbox/main/ui/workspace.cljs +++ b/frontend/src/uxbox/main/ui/workspace.cljs @@ -12,9 +12,9 @@ [rumext.alpha :as mf] [uxbox.main.constants :as c] [uxbox.main.data.history :as udh] - [uxbox.main.data.pages :as udp] [uxbox.main.data.undo :as udu] - [uxbox.main.data.workspace :as udw] + [uxbox.main.data.workspace :as dw] + [uxbox.main.data.workspace-websocket :as dws] [uxbox.main.refs :as refs] [uxbox.main.store :as st] [uxbox.main.ui.confirm] @@ -55,12 +55,12 @@ (dom/prevent-default event) (dom/stop-propagation event) (if (pos? (.-deltaY event)) - (st/emit! (udw/decrease-zoom)) - (st/emit! (udw/increase-zoom))) + (st/emit! (dw/decrease-zoom)) + (st/emit! (dw/increase-zoom))) (scroll/scroll-to-point dom mouse-point scroll-position)))) (mf/defc workspace-content - [{:keys [layout page file] :as params}] + [{:keys [layout page file flags] :as params}] (let [canvas (mf/use-ref nil) left-sidebar? (not (empty? (keep layout [:layers :sitemap :document-history]))) @@ -93,29 +93,50 @@ (when right-sidebar? [:& right-sidebar {:page page :layout layout}])])) -(mf/defc workspace - [{:keys [file-id page-id] :as props}] - +(mf/defc workspace-page + [{:keys [file-id page-id layout file flags] :as props}] (mf/use-effect - {:deps #js [file-id page-id] - :fn (fn [] - (let [sub (shortcuts/init)] - (st/emit! (udw/initialize file-id page-id)) - #(rx/cancel! sub)))}) - - (let [layout (mf/deref refs/workspace-layout) - file (mf/deref refs/workspace-file) - page (mf/deref refs/workspace-page) - flags (mf/deref refs/selected-flags)] + {:deps (mf/deps file-id page-id) + :fn #(st/emit! (dw/initialize-page page-id))}) + (let [page (mf/deref refs/workspace-page)] [:> rdnd/provider {:backend rdnd/html5} [:& messages-widget] [:& header {:page page :layout layout :flags flags}] - (when (:colorpalette flags) + (when (:colorpalette layout) [:& colorpalette]) (when (and layout page) [:& workspace-content {:layout layout + :flags flags :file file :page page}])])) + + + +(mf/defc workspace + [{:keys [file-id page-id] :as props}] + (mf/use-effect + {:deps (mf/deps file-id) + :fn (fn [] + (st/emit! (dw/initialize file-id)) + #(st/emit! (dw/finalize file-id)))}) + + (mf/use-effect + {:deps (mf/deps file-id page-id) + :fn (fn [] + (let [sub (shortcuts/init)] + #(rx/cancel! sub)))}) + + (let [layout (mf/deref refs/workspace-layout) + file (mf/deref refs/workspace-file) + flags (mf/deref refs/selected-flags)] + + ;; TODO: maybe loading state? + (when file + [:& workspace-page {:layout layout + :file file + :flags flags + :page-id page-id + :file-id file-id}]))) diff --git a/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs b/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs index 15dcb1d04..83e52b472 100644 --- a/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/uxbox/main/ui/workspace/colorpalette.cljs @@ -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) diff --git a/frontend/src/uxbox/main/ui/workspace/download.cljs b/frontend/src/uxbox/main/ui/workspace/download.cljs index 25bdaa623..7d524c6fc 100644 --- a/frontend/src/uxbox/main/ui/workspace/download.cljs +++ b/frontend/src/uxbox/main/ui/workspace/download.cljs @@ -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) diff --git a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs index be260ce94..c9828b665 100644 --- a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs +++ b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs @@ -51,8 +51,7 @@ :fill-opacity 0 :segments []} {:type :canvas - :name "Canvas" - :stroke-color "#000000"} + :name "Canvas"} {:type :curve :name "Path" :stroke-style :solid @@ -109,18 +108,17 @@ (def handle-drawing-generic (letfn [(initialize-drawing [state point] (let [shape (get-in state [:workspace-local :drawing]) - shape (geom/setup shape {:x1 (:x point) - :y1 (:y point) - :x2 (+ (:x point) 2) - :y2 (+ (:y point) 2)})] + shape (geom/setup shape {:x (:x point) + :y (:y point) + :width 2 + :height 2})] (assoc-in state [:workspace-local :drawing] (assoc shape ::initialized? true)))) (resize-shape [shape point lock?] - (let [shape (-> (geom/shape->rect-shape shape) - (geom/size)) - result (geom/resize-shape :bottom-right shape point lock?) - scale (geom/calculate-scale-ratio shape result) - mtx (geom/generate-resize-matrix :bottom-right shape scale)] + (let [shape' (geom/shape->rect-shape shape) + result (geom/resize-shape :bottom-right shape' point lock?) + scale (geom/calculate-scale-ratio shape' result) + mtx (geom/generate-resize-matrix :bottom-right shape' scale)] (assoc shape :modifier-mtx mtx))) (update-drawing [state point lock?] @@ -150,13 +148,13 @@ (def handle-drawing-path (letfn [(stoper-event? [{:keys [type shift] :as event}] - (or (= event :interrupt) - (and (uws/mouse-event? event) - (or (and (= type :double-click) shift) - (= type :context-menu))) - (and (uws/keyboard-event? event) - (= type :down) - (= 13 (:key event))))) + (or (= event ::end-path-drawing) + (and (uws/mouse-event? event) + (or (and (= type :double-click) shift) + (= type :context-menu))) + (and (uws/keyboard-event? event) + (= type :down) + (= 13 (:key event))))) (initialize-drawing [state point] (-> state @@ -238,8 +236,7 @@ (def handle-drawing-curve (letfn [(stoper-event? [{:keys [type shift] :as event}] - (or (= event :interrupt) - (and (uws/mouse-event? event) (= type :up)))) + (uws/mouse-event? event) (= type :up)) (initialize-drawing [state] (assoc-in state [:workspace-local :drawing ::initialized?] true)) @@ -280,9 +277,11 @@ (geom/transform shape modifier-mtx) shape) shape (dissoc shape ::initialized? :modifier-mtx)] - ;; Add & select the cred shape to the workspace - (rx/of (dw/add-shape shape) - dw/select-first-shape)))))))) + ;; Add & select the created shape to the workspace + (rx/of dw/deselect-all + (if (= :canvas (:type shape)) + (dw/add-canvas shape) + (dw/add-shape shape)))))))))) (def close-drawing-path (ptk/reify ::close-drawing-path @@ -322,7 +321,8 @@ (dom/stop-propagation event) (st/emit! (dw/set-tooltip nil) close-drawing-path - :interrupt)) + ::end-path-drawing)) + (on-mouse-enter [event] (st/emit! (dw/set-tooltip "Click to close the path"))) (on-mouse-leave [event] diff --git a/frontend/src/uxbox/main/ui/workspace/header.cljs b/frontend/src/uxbox/main/ui/workspace/header.cljs index 575cdc655..579a7e997 100644 --- a/frontend/src/uxbox/main/ui/workspace/header.cljs +++ b/frontend/src/uxbox/main/ui/workspace/header.cljs @@ -7,23 +7,19 @@ (ns uxbox.main.ui.workspace.header (:require - [rumext.core :as mx] + [lentes.core :as l] [rumext.alpha :as mf] [uxbox.builtins.icons :as i] [uxbox.config :as cfg] [uxbox.main.data.history :as udh] - [uxbox.main.data.pages :as udp] [uxbox.main.data.undo :as udu] [uxbox.main.data.workspace :as dw] - [uxbox.main.ui.workspace.images :refer [import-image-modal]] - [uxbox.main.ui.modal :as modal] [uxbox.main.refs :as refs] [uxbox.main.store :as st] + [uxbox.main.ui.modal :as modal] [uxbox.main.ui.users :refer [user]] - [uxbox.main.ui.workspace.clipboard] - [uxbox.util.data :refer [index-of]] - [uxbox.util.i18n :refer (tr)] - [uxbox.util.geom.point :as gpt] + [uxbox.main.ui.workspace.images :refer [import-image-modal]] + [uxbox.util.i18n :refer [tr]] [uxbox.util.math :as mth] [uxbox.util.router :as rt])) @@ -40,100 +36,154 @@ [:span {} (str (mth/round (* 100 zoom)) "%")] [:span.remove-zoom {:on-click increase} "+"]]])) +;; --- Header Users + +(mf/defc user-widget + [{:keys [user self?] :as props}] + [:li.tooltip.tooltip-bottom + {:alt (:fullname user) + :on-click (when self? + #(st/emit! (rt/navigate :settings/profile)))} + [:img {:style {:border-color (:color user)} + :src (if self? "/images/avatar.jpg" "/images/avatar-red.jpg")}]]) + +(mf/defc active-users + [props] + (let [profile (mf/deref refs/profile) + users (mf/deref refs/workspace-users)] + [:ul.user-multi + [:& user-widget {:user profile :self? true}] + (for [id (->> (:active users) + (remove #(= % (:id profile))))] + [:& user-widget {:user (get-in users [:by-id id]) + :key id}])])) + ;; --- Header Component (mf/defc header [{:keys [page layout flags] :as props}] (let [toggle #(st/emit! (dw/toggle-flag %)) + toggle-layout #(st/emit! (dw/toggle-layout-flag %)) on-undo #(st/emit! (udu/undo)) on-redo #(st/emit! (udu/redo)) on-image #(modal/show! import-image-modal {}) ;;on-download #(udl/open! :download) - ] + file (mf/deref refs/workspace-file) + selected-drawtool (mf/deref refs/selected-drawing-tool) + select-drawtool #(st/emit! :interrupt + (dw/deactivate-ruler) + (dw/select-for-drawing %))] + [:header#workspace-bar.workspace-bar [:div.main-icon [:a {:on-click #(st/emit! (rt/nav :dashboard-projects))} i/logo-icon]] + [:div.project-tree-btn {:alt (tr "header.sitemap") :class (when (contains? layout :sitemap) "selected") :on-click #(st/emit! (dw/toggle-layout-flag :sitemap))} - i/project-tree - [:span {} (:name page)]] + [:span (:project-name file) " / " (:name file)]] + + [:& active-users] + [:div.workspace-options [:ul.options-btn [:li.tooltip.tooltip-bottom - {:alt (tr "header.draw-tools") - :class (when (contains? layout :drawtools) "selected") - :on-click #(st/emit! (dw/toggle-layout-flag :drawtools))} - i/shapes] + {:alt (tr "workspace.header.canvas") + :class (when (= selected-drawtool :canvas) "selected") + :on-click (partial select-drawtool :canvas)} + i/artboard] [:li.tooltip.tooltip-bottom - {:alt (tr "header.color-palette") + {:alt (tr "workspace.header.rect") + :class (when (= selected-drawtool :rect) "selected") + :on-click (partial select-drawtool :rect)} + i/box] + [:li.tooltip.tooltip-bottom + {:alt (tr "workspace.header.circle") + :class (when (= selected-drawtool :circle) "selected") + :on-click (partial select-drawtool :circle)} + i/circle] + [:li.tooltip.tooltip-bottom + {:alt (tr "workspace.header.text") + :class (when (= selected-drawtool :text) "selected") + :on-click (partial select-drawtool :text)} + i/text] + [:li.tooltip.tooltip-bottom + {:alt (tr "workspace.header.path") + :class (when (= selected-drawtool :path) "selected") + :on-click (partial select-drawtool :path)} + i/curve] + [:li.tooltip.tooltip-bottom + {:alt (tr "workspace.header.curve") + :class (when (= selected-drawtool :curve) "selected") + :on-click (partial select-drawtool :curve)} + i/pencil] + [:li.tooltip.tooltip-bottom + {:alt (tr "workspace.header.color-palette") :class (when (contains? layout :colorpalette) "selected") :on-click #(st/emit! (dw/toggle-layout-flag :colorpalette))} i/palette] [:li.tooltip.tooltip-bottom - {:alt (tr "header.icons") + {:alt (tr "workspace.header.icons") :class (when (contains? layout :icons) "selected") :on-click #(st/emit! (dw/toggle-layout-flag :icons))} i/icon-set] + ;; [:li.tooltip.tooltip-bottom + ;; {:alt (tr "header.layers") + ;; :class (when (contains? layout :layers) "selected") + ;; :on-click #(st/emit! (dw/toggle-layout-flag :layers))} + ;; i/layers] + ;; [:li.tooltip.tooltip-bottom + ;; {:alt (tr "header.element-options") + ;; :class (when (contains? layout :element-options) "selected") + ;; :on-click #(st/emit! (dw/toggle-layout-flag :element-options))} + ;; i/options] [:li.tooltip.tooltip-bottom - {:alt (tr "header.layers") - :class (when (contains? layout :layers) "selected") - :on-click #(st/emit! (dw/toggle-layout-flag :layers))} - i/layers] - [:li.tooltip.tooltip-bottom - {:alt (tr "header.element-options") - :class (when (contains? layout :element-options) "selected") - :on-click #(st/emit! (dw/toggle-layout-flag :element-options))} - i/options] - [:li.tooltip.tooltip-bottom - {:alt (tr "header.document-history") + {:alt (tr "workspace.header.document-history") :class (when (contains? layout :document-history) "selected") :on-click #(st/emit! (dw/toggle-layout-flag :document-history))} - i/undo-history]] - [:ul.options-btn + i/undo-history] + ;; [:li.tooltip.tooltip-bottom + ;; {:alt (tr "header.undo") + ;; :on-click on-undo} + ;; i/undo] + ;; [:li.tooltip.tooltip-bottom + ;; {:alt (tr "header.redo") + ;; :on-click on-redo} + ;; i/redo] [:li.tooltip.tooltip-bottom - {:alt (tr "header.undo") - :on-click on-undo} - i/undo] - [:li.tooltip.tooltip-bottom - {:alt (tr "header.redo") - :on-click on-redo} - i/redo]] - [:ul.options-btn - [:li.tooltip.tooltip-bottom - {:alt (tr "header.download") + {:alt (tr "workspace.header.download") ;; :on-click on-download } i/download] [:li.tooltip.tooltip-bottom - {:alt (tr "header.image") + {:alt (tr "workspace.header.image") :on-click on-image} - i/image]] - [:ul.options-btn + i/image] [:li.tooltip.tooltip-bottom - {:alt (tr "header.rules") - :class (when (contains? flags :rules) "selected") - :on-click (partial toggle :rules)} + {:alt (tr "workspace.header.rules") + :class (when (contains? layout :rules) "selected") + :on-click (partial toggle-layout :rules)} i/ruler] [:li.tooltip.tooltip-bottom - {:alt (tr "header.grid") + {:alt (tr "workspace.header.grid") :class (when (contains? flags :grid) "selected") :on-click (partial toggle :grid)} i/grid] [:li.tooltip.tooltip-bottom - {:alt (tr "header.grid-snap") + {:alt (tr "workspace.header.grid-snap") :class (when (contains? flags :grid-snap) "selected") :on-click (partial toggle :grid-snap)} - i/grid-snap]] - ;; [:li.tooltip.tooltip-bottom - ;; {:alt (tr "header.align")} - ;; i/alignment]] - [:ul.options-btn - [:li.tooltip.tooltip-bottom.view-mode - {:alt (tr "header.view-mode") + i/grid-snap]]] + ;; [:li.tooltip.tooltip-bottom + ;; {:alt (tr "header.align")} + ;; i/alignment]] + ;; [:& user] + [:div.secondary-options + [:& zoom-widget] + [:a.tooltip.tooltip-bottom.view-mode + {:alt (tr "workspace.header.view-mode") ;; :on-click #(st/emit! (dw/->OpenView (:id page))) } - i/play]] - [:& zoom-widget]] - [:& user]])) + i/play]] + ])) diff --git a/frontend/src/uxbox/main/ui/workspace/images.cljs b/frontend/src/uxbox/main/ui/workspace/images.cljs index c080f2ecb..0091eaf0c 100644 --- a/frontend/src/uxbox/main/ui/workspace/images.cljs +++ b/frontend/src/uxbox/main/ui/workspace/images.cljs @@ -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")] diff --git a/frontend/src/uxbox/main/ui/workspace/selection.cljs b/frontend/src/uxbox/main/ui/workspace/selection.cljs index 6e2819827..fc8ac7186 100644 --- a/frontend/src/uxbox/main/ui/workspace/selection.cljs +++ b/frontend/src/uxbox/main/ui/workspace/selection.cljs @@ -26,10 +26,10 @@ (def ^:private +circle-props+ {:r 6 :style {:fillOpacity "1" - :strokeWidth "1px" + :strokeWidth "2px" :vectorEffect "non-scaling-stroke"} - :fill "#31e6e0" - :stroke "#28c4d4"}) + :fill "rgba(49,239,184,.7)" + :stroke "#31EFB8"}) ;; --- Resize Implementation @@ -90,63 +90,63 @@ :on-mouse-down on-click :r r :style {:fillOpacity "1" - :strokeWidth "1px" + :strokeWidth "2px" :vectorEffect "non-scaling-stroke"} - :fill "#31e6e0" - :stroke "#28c4d4" + :fill "rgba(49,239,184,.7)" + :stroke "#31EFB8" :cx cx :cy cy}]) (mf/defc controls [{:keys [shape zoom on-click] :as props}] - (let [{:keys [x1 y1 width height]} shape + (let [{:keys [x y width height]} shape radius (if (> (max width height) handler-size-threshold) 6.0 4.0)] [:g.controls - [:rect.main {:x x1 :y y1 + [:rect.main {:x x :y y :width width :height height - :stroke-dasharray (str (/ 5.0 zoom) "," (/ 5 zoom)) - :style {:stroke "#333" :fill "transparent" + :stroke-dasharray (str (/ 8.0 zoom) "," (/ 5 zoom)) + :style {:stroke "#31EFB8" :fill "transparent" :stroke-opacity "1"}}] [:& control-item {:class "top" :on-click #(on-click :top %) :r (/ radius zoom) - :cx (+ x1 (/ width 2)) - :cy (- y1 2)}] + :cx (+ x (/ width 2)) + :cy (- y 2)}] [:& control-item {:on-click #(on-click :right %) :r (/ radius zoom) - :cy (+ y1 (/ height 2)) - :cx (+ x1 width 1) + :cy (+ y (/ height 2)) + :cx (+ x width 1) :class "right"}] [:& control-item {:on-click #(on-click :bottom %) :r (/ radius zoom) - :cx (+ x1 (/ width 2)) - :cy (+ y1 height 2) + :cx (+ x (/ width 2)) + :cy (+ y height 2) :class "bottom"}] [:& control-item {:on-click #(on-click :left %) :r (/ radius zoom) - :cy (+ y1 (/ height 2)) - :cx (- x1 3) + :cy (+ y (/ height 2)) + :cx (- x 3) :class "left"}] [:& control-item {:on-click #(on-click :top-left %) :r (/ radius zoom) - :cx x1 - :cy y1 + :cx x + :cy y :class "top-left"}] [:& control-item {:on-click #(on-click :top-right %) :r (/ radius zoom) - :cx (+ x1 width) - :cy y1 + :cx (+ x width) + :cy y :class "top-right"}] [:& control-item {:on-click #(on-click :bottom-left %) :r (/ radius zoom) - :cx x1 - :cy (+ y1 height) + :cx x + :cy (+ y height) :class "bottom-left"}] [:& control-item {:on-click #(on-click :bottom-right %) :r (/ radius zoom) - :cx (+ x1 width) - :cy (+ y1 height) + :cx (+ x width) + :cy (+ y height) :class "bottom-right"}]])) ;; --- Selection Handlers (Component) @@ -183,8 +183,8 @@ :r (/ 6.0 zoom) :key index :on-mouse-down #(on-mouse-down % index) - :fill "#31e6e0" - :stroke "#28c4d4" + :fill "rgba(49,239,184,.7)" + :stroke "#31EFB8" :style {:cursor "pointer"}}])]))) ;; TODO: add specs for clarity @@ -203,15 +203,15 @@ (mf/defc text-edition-selection-handlers [{:keys [shape zoom] :as props}] - (let [{:keys [x1 y1 width height] :as shape} (geom/selection-rect shape)] + (let [{:keys [x y width height] :as shape} (geom/selection-rect shape)] [:g.controls - [:rect.main {:x x1 :y y1 + [:rect.main {:x x :y y :width width :height height ;; :stroke-dasharray (str (/ 5.0 zoom) "," (/ 5 zoom)) - :style {:stroke "#333" + :style {:stroke "#31EFB8" :stroke-width "0.5" - :stroke-opacity "0.5" + :stroke-opacity "1" :fill "transparent"}}]])) (mf/defc single-selection-handlers diff --git a/frontend/src/uxbox/main/ui/workspace/shortcuts.cljs b/frontend/src/uxbox/main/ui/workspace/shortcuts.cljs index ec94ee7ef..bc8c7797a 100644 --- a/frontend/src/uxbox/main/ui/workspace/shortcuts.cljs +++ b/frontend/src/uxbox/main/ui/workspace/shortcuts.cljs @@ -40,7 +40,7 @@ :ctrl+b #(st/emit! (dw/select-for-drawing :rect)) :ctrl+e #(st/emit! (dw/select-for-drawing :circle)) :ctrl+t #(st/emit! (dw/select-for-drawing :text)) - :esc #(st/emit! dw/deselect-all) + :esc #(st/emit! :interrupt dw/deselect-all) :delete #(st/emit! dw/delete-selected) :ctrl+up #(st/emit! (dw/order-selected-shapes :up)) :ctrl+down #(st/emit! (dw/order-selected-shapes :down)) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs index bd8446f4c..bc26e9549 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs @@ -8,7 +8,6 @@ (ns uxbox.main.ui.workspace.sidebar (:require [rumext.alpha :as mf] - [uxbox.main.ui.workspace.sidebar.drawtools :refer [draw-toolbox]] [uxbox.main.ui.workspace.sidebar.history :refer [history-toolbox]] [uxbox.main.ui.workspace.sidebar.icons :refer [icons-toolbox]] [uxbox.main.ui.workspace.sidebar.layers :refer [layers-toolbox]] @@ -36,8 +35,6 @@ [{:keys [layout page] :as props}] [:aside#settings-bar.settings-bar [:div.settings-bar-inside - (when (contains? layout :drawtools) - [:& draw-toolbox {:layout layout}]) (when (contains? layout :element-options) [:& options-toolbox {:page page}]) #_(when (contains? layout :icons) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/drawtools.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/drawtools.cljs deleted file mode 100644 index c5ff877a5..000000000 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/drawtools.cljs +++ /dev/null @@ -1,85 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2015-2016 Juan de la Cruz -;; Copyright (c) 2015-2019 Andrey Antukh - -(ns uxbox.main.ui.workspace.sidebar.drawtools - (:require - [rumext.alpha :as mf] - [uxbox.builtins.icons :as i] - [uxbox.main.data.workspace :as dw] - [uxbox.main.refs :as refs] - [uxbox.main.store :as st] - [uxbox.util.i18n :refer [tr]] - [uxbox.util.uuid :as uuid])) - -;; --- Constants - -(def +draw-tools+ - [{:icon i/box - :help "ds.help.rect" - :type :rect - :priority 1} - {:icon i/circle - :help "ds.help.circle" - :type :circle - :priority 2} - {:icon i/text - :help "ds.help.text" - :type :text - :priority 4} - {:icon i/curve - :help "ds.help.path" - :type :path - :priority 5} - {:icon i/pencil - :help "ds.help.curve" - :type :curve - :priority 6} - ;; TODO: we need an icon for canvas creation - {:icon i/box - :help "ds.help.canvas" - :type :canvas - :priority 7}]) - -;; --- Draw Toolbox (Component) - -(mf/defc draw-toolbox - {:wrap [mf/wrap-memo]} - [{:keys [flags] :as props}] - (letfn [(close [event] - (st/emit! (dw/toggle-layout-flag :drawtools))) - (select [event tool] - (st/emit! :interrupt - (dw/deactivate-ruler) - (dw/select-for-drawing tool))) - (toggle-ruler [event] - (st/emit! (dw/select-for-drawing nil) - dw/deselect-all - (dw/toggle-ruler)))] - - (let [selected (mf/deref refs/selected-drawing-tool) - tools (sort-by (comp :priority second) +draw-tools+)] - [:div.tool-window.drawing-tools - [:div.tool-window-bar - [:div.tool-window-icon i/window] - [:span (tr "ds.settings.draw-tools")] - [:div.tool-window-close {:on-click close} i/close]] - [:div.tool-window-content - (for [item tools] - (let [selected? (= (:type item) selected)] - [:div.tool-btn.tooltip.tooltip-hover - {:alt (tr (:help item)) - :class (when selected? "selected") - :key (:type item) - :on-click #(select % (:type item))} - (:icon item)])) - - #_[:div.tool-btn.tooltip.tooltip-hover - {:alt (tr "ds.help.ruler") - :on-click toggle-ruler - :class (when (contains? flags :ruler) "selected")} - i/ruler-tool]]]))) - diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/history.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/history.cljs index 87aeb78a9..45eaeb94b 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/history.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/history.cljs @@ -10,7 +10,6 @@ [rumext.alpha :as mf] [uxbox.builtins.icons :as i] [uxbox.main.data.history :as udh] - [uxbox.main.data.pages :as udp] [uxbox.main.data.workspace :as dw] [uxbox.main.refs :as refs] [uxbox.main.store :as st] diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs index 066854cc4..28c92b326 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs @@ -10,7 +10,6 @@ [lentes.core :as l] [rumext.alpha :as mf] [uxbox.builtins.icons :as i] - [uxbox.main.data.pages :as udp] [uxbox.main.data.workspace :as dw] [uxbox.main.refs :as refs] [uxbox.main.store :as st] @@ -206,7 +205,7 @@ :class (when-not collapsed? "inverse")} i/arrow-slide]] [:ul - (for [[index shape] shapes] + (for [[index shape] (reverse shapes)] [:& layer-item {:shape shape :selected selected :index index @@ -260,7 +259,8 @@ [:div.tool-window-bar [:div.tool-window-icon i/layers] [:span (tr "ds.settings.layers")] - [:div.tool-window-close {:on-click on-click} i/close]] + ;; [:div.tool-window-close {:on-click on-click} i/close] + ] [:div.tool-window-content [:& canvas-list {:canvas canvas :shapes all-shapes diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs index 492e1bc42..8d0cd7dc6 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs @@ -9,103 +9,44 @@ (:require [lentes.core :as l] [rumext.alpha :as mf] - [rumext.core :as mx] [uxbox.builtins.icons :as i] [uxbox.main.data.workspace :as udw] - [uxbox.main.geom :as geom] [uxbox.main.store :as st] [uxbox.main.refs :as refs] - [uxbox.main.ui.shapes.attrs :refer [shape-default-attrs]] - [uxbox.main.ui.workspace.sidebar.options.circle-measures :as options-circlem] - [uxbox.main.ui.workspace.sidebar.options.fill :as options-fill] - [uxbox.main.ui.workspace.sidebar.options.icon-measures :as options-iconm] - [uxbox.main.ui.workspace.sidebar.options.image-measures :as options-imagem] - [uxbox.main.ui.workspace.sidebar.options.interactions :as options-interactions] - [uxbox.main.ui.workspace.sidebar.options.page :as options-page] - [uxbox.main.ui.workspace.sidebar.options.rect-measures :as options-rectm] - [uxbox.main.ui.workspace.sidebar.options.stroke :as options-stroke] - [uxbox.main.ui.workspace.sidebar.options.text :as options-text] - [uxbox.util.data :as data] - [uxbox.util.dom :as dom] + [uxbox.main.ui.workspace.sidebar.options.rect :as rect] + [uxbox.main.ui.workspace.sidebar.options.circle :as circle] + [uxbox.main.ui.workspace.sidebar.options.path :as path] + [uxbox.main.ui.workspace.sidebar.options.image :as image] + [uxbox.main.ui.workspace.sidebar.options.page :as page] [uxbox.util.i18n :refer [tr]])) ;; --- Constants -(def ^:private +menus-map+ - {:icon [::icon-measures ::fill ::stroke] - :rect [::rect-measures ::fill ::stroke] - :path [::fill ::stroke ::interactions] - :circle [::circle-measures ::fill ::stroke] - :text [::fill ::text] - :image [::image-measures] - ::page [::page-measures ::page-grid-options]}) - -(def ^:private +menus+ - [{:name "element.measures" - :id ::icon-measures - :icon i/infocard - :comp options-iconm/icon-measures-menu} - {:name "element.measures" - :id ::image-measures - :icon i/infocard - :comp options-imagem/image-measures-menu} - {:name "element.measures" - :id ::rect-measures - :icon i/infocard - :comp options-rectm/rect-measures-menu} - {:name "element.measures" - :id ::circle-measures - :icon i/infocard - :comp options-circlem/circle-measures-menu} - {:name "element.fill" - :id ::fill - :icon i/fill - :comp options-fill/fill-menu} - {:name "element.stroke" - :id ::stroke - :icon i/stroke - :comp options-stroke/stroke-menu} - {:name "element.text" - :id ::text - :icon i/text - :comp options-text/text-menu} - {:name "element.interactions" - :id ::interactions - :icon i/action - :comp options-interactions/interactions-menu} - {:name "element.page-measures" - :id ::page-measures - :icon i/page - :comp options-page/measures-menu} - {:name "element.page-grid-options" - :id ::page-grid-options - :icon i/grid - :comp options-page/grid-options-menu}]) - -(def ^:private +menus-by-id+ - (data/index-by-id +menus+)) +;; (def ^:private +menus-map+ +;; {:icon [::icon-measures ::fill ::stroke] +;; :rect [::rect-measures ::fill ::stroke] +;; :path [::fill ::stroke ::interactions] +;; :circle [::circle-measures ::fill ::stroke] +;; :text [::fill ::text] +;; :image [::image-measures] +;; ::page [::page-measures ::page-grid-options]}) ;; --- Options (mf/defc shape-options [{:keys [shape-id] :as props}] - (let [shape-iref (mf/use-memo {:deps #js [shape-id] + (let [shape-iref (mf/use-memo {:deps #js [(str shape-id)] :fn #(-> (l/in [:workspace-data :shapes-by-id shape-id]) (l/derive st/state))}) - shape (mf/deref shape-iref) - menus (get +menus-map+ (:type shape))] + shape (mf/deref shape-iref)] [:div - (for [mid menus] - (let [{:keys [comp] :as menu} (get +menus-by-id+ mid)] - [:& comp {:menu menu :shape shape :key mid}]))])) - -(mf/defc page-options - [{:keys [page] :as props}] - (let [menus (get +menus-map+ ::page)] - [:div - (for [mid menus] - (let [{:keys [comp] :as menu} (get +menus-by-id+ mid)] - [:& comp {:menu menu :page page :key mid}]))])) + (case (:type shape) + :rect [:& rect/options {:shape shape}] + :circle [:& circle/options {:shape shape}] + :path [:& path/options {:shape shape}] + :curve [:& path/options {:shape shape}] + :image [:& image/options {:shape shape}] + nil)])) (mf/defc options-toolbox {:wrap [mf/wrap-memo]} @@ -113,12 +54,12 @@ (let [close #(st/emit! (udw/toggle-layout-flag :element-options)) selected (mf/deref refs/selected-shapes)] [:div.elementa-options.tool-window - [:div.tool-window-bar - [:div.tool-window-icon i/options] - [:span (tr "ds.settings.element-options")] - [:div.tool-window-close {:on-click close} i/close]] + ;; [:div.tool-window-bar + ;; [:div.tool-window-icon i/options] + ;; [:span (tr "ds.settings.element-options")] + ;; [:div.tool-window-close {:on-click close} i/close]] [:div.tool-window-content [:div.element-options (if (= (count selected) 1) [:& shape-options {:shape-id (first selected)}] - [:& page-options {:page page}])]]])) + [:& page/options {:page page}])]]])) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle.cljs new file mode 100644 index 000000000..782eef4f2 --- /dev/null +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle.cljs @@ -0,0 +1,132 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2015-2016 Juan de la Cruz +;; Copyright (c) 2015-2019 Andrey Antukh + +(ns uxbox.main.ui.workspace.sidebar.options.circle + (:require + [rumext.alpha :as mf] + [uxbox.common.data :as d] + [uxbox.builtins.icons :as i] + [uxbox.main.data.workspace :as udw] + [uxbox.main.geom :as geom] + [uxbox.main.store :as st] + [uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]] + [uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]] + [uxbox.util.dom :as dom] + [uxbox.util.geom.point :as gpt] + [uxbox.util.i18n :refer [tr]] + [uxbox.util.math :as math :refer (precision-or-0)])) + +(mf/defc measures-menu + [{:keys [shape] :as props}] + (let [on-size-change + (fn [event attr] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer 0))] + (st/emit! (udw/update-dimensions (:id shape) {attr value})))) + + on-proportion-lock-change + (fn [event] + (st/emit! (udw/toggle-shape-proportion-lock (:id shape)))) + + on-size-rx-change #(on-size-change % :rx) + on-size-ry-change #(on-size-change % :ry) + + on-position-change + (fn [event attr] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer)) + point (gpt/point {attr value})] + (st/emit! (udw/update-position (:id shape) point)))) + + on-pos-cx-change #(on-position-change % :x) + on-pos-cy-change #(on-position-change % :y) + + on-rotation-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer 0))] + (st/emit! (udw/update-shape (:id shape) {:rotation value})))) + + on-radius-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-double 0))] + (st/emit! (udw/update-shape (:id shape) {:rx value :ry value}))))] + + [:div.element-set + [:div.element-set-title (tr "workspace.options.measures")] + [:div.element-set-content + + ;; SIZE + [:span (tr "workspace.options.size")] + [:div.row-flex + [:div.input-element.pixels + [:input.input-text {:type "number" + :min "0" + :on-change on-size-rx-change + :value (-> (:rx shape) + (math/precision 2) + (d/coalesce-str "0"))}]] + [:div.lock-size {:class (when (:proportion-lock shape) "selected") + :on-click on-proportion-lock-change} + (if (:proportion-lock shape) + i/lock + i/unlock)] + + [:div.input-element.pixels + [:input.input-text {:type "number" + :min "0" + :on-change on-size-ry-change + :value (-> (:ry shape) + (math/precision 2) + (d/coalesce-str "0"))}]]] + + ;; POSITION + [:span (tr "workspace.options.position")] + [:div.row-flex + [:div.input-element.pixels + [:input.input-text {:type "number" + :on-change on-pos-cx-change + :value (-> (:cx shape) + (math/precision 2) + (d/coalesce-str "0"))}]] + [:div.input-element.pixels + [:input.input-text {:type "number" + :on-change on-pos-cy-change + :value (-> (:cy shape) + (math/precision 2) + (d/coalesce-str "0"))}]]] + ;; ROTATION & RADIUS + [:span (tr "workspace.options.rotation-radius")] + [:div.row-flex + [:div.input-element.degrees + [:input.input-text {:placeholder "" + :type "number" + :min 0 + :max 360 + :on-change on-rotation-change + :value (-> (:rotation shape) + (math/precision 2) + (d/coalesce-str "0"))}]] + + [:div.input-element.pixels + [:input.input-text {:type "number" + :on-change on-radius-change + :value (-> (:rx shape) + (math/precision 2) + (d/coalesce-str "0"))}]]]]])) + +(mf/defc options + [{:keys [shape] :as props}] + [:div + [:& measures-menu {:shape shape}] + [:& fill-menu {:shape shape}] + [:& stroke-menu {:shape shape}]]) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle_measures.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle_measures.cljs deleted file mode 100644 index cc95ccf8c..000000000 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/circle_measures.cljs +++ /dev/null @@ -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 -;; Copyright (c) 2015-2019 Andrey Antukh - -(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))))) - diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/fill.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/fill.cljs index 488302d4a..c6de18aac 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/fill.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/fill.cljs @@ -9,43 +9,45 @@ (:require [rumext.alpha :as mf] [uxbox.builtins.icons :as i] + [uxbox.common.data :as d] [uxbox.main.data.workspace :as udw] [uxbox.main.store :as st] [uxbox.main.ui.modal :as modal] [uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]] - [uxbox.util.data :refer [parse-float]] [uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]])) (mf/defc fill-menu - [{:keys [menu shape]}] - (letfn [(change-attrs [attrs] - (st/emit! (udw/update-shape-attrs (:id shape) attrs))) + [{:keys [shape] :as props}] + (letfn [(update-shape! [attr value] + (st/emit! (udw/update-shape (:id shape) {attr value}))) (on-color-change [event] - (let [value (dom/event->value event)] - (change-attrs {:fill-color value}))) + (let [value (-> (dom/get-target event) + (dom/get-value))] + (update-shape! :fill-color value))) (on-opacity-change [event] - (let [value (dom/event->value event) - value (parse-float value 1) - value (/ value 10000)] - (change-attrs {:fill-opacity value}))) + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-double 1) + (/ 10000))] + (update-shape! :fill-opacity value))) (show-color-picker [event] (let [x (.-clientX event) y (.-clientY event) props {:x x :y y - :on-change #(change-attrs {:fill-color %}) + :on-change #(update-shape! :fill-color %) :default "#ffffff" :value (:fill-color shape) :transparent? true}] (modal/show! colorpicker-modal props)))] [:div.element-set - [:div.element-set-title (:name menu)] + [:div.element-set-title (tr "element.fill")] [:div.element-set-content - [:span (tr "ds.color")] + [:span (tr "workspace.options.color")] [:div.row-flex.color-data [:span.color-th - {:style {:background-color (:fill-color shape)} + {:style {:background-color (:fill-color shape "#000000")} :on-click show-color-picker}] [:div.color-info [:input @@ -53,7 +55,7 @@ :value (:fill-color shape "")}]]] ;; SLIDEBAR FOR ROTATION AND OPACITY - [:span (tr "ds.opacity")] + [:span (tr "workspace.options.opacity")] [:div.row-flex [:input.slidebar {:type "range" diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs index eb17169b1..881a63af0 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs @@ -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))))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/image_measures.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/image_measures.cljs deleted file mode 100644 index 4f75f4bc6..000000000 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/image_measures.cljs +++ /dev/null @@ -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 -;; Copyright (c) 2015-2017 Juan de la Cruz - -(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))))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs index 3ff8dc675..8df360ec0 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs @@ -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)] diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs index 3914db58b..9f5b6bab1 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/page.cljs @@ -12,7 +12,6 @@ [rumext.alpha :as mf] [uxbox.builtins.icons :as i] [uxbox.main.constants :as c] - [uxbox.main.data.pages :as udp] [uxbox.main.data.workspace :as udw] [uxbox.main.refs :as refs] [uxbox.main.store :as st] @@ -23,94 +22,59 @@ [uxbox.util.i18n :refer [tr]] [uxbox.util.spec :refer [color?]])) -(mf/defc measures-menu - [{:keys [menu page] :as props}] +(mf/defc metadata-options + [{:keys [page] :as props}] (let [metadata (:metadata page) - metadata (merge c/page-metadata metadata)] - (letfn [(on-size-change [event attr] - (let [value (-> (dom/event->value event) - (parse-int nil))] - (st/emit! (->> (assoc metadata attr value) - (udp/update-metadata (:id page)))))) + change-color + (fn [color] + #_(st/emit! (->> (assoc metadata :background color) + (udp/update-metadata (:id page))))) + on-color-change + (fn [event] + (let [value (dom/event->value event)] + (change-color value))) - (change-color [color] - (st/emit! (->> (assoc metadata :background color) - (udp/update-metadata (:id page))))) + show-color-picker + (fn [event] + (let [x (.-clientX event) + y (.-clientY event) + props {:x x :y y + :default "#ffffff" + :value (:background metadata) + :transparent? true + :on-change change-color}] + (modal/show! colorpicker-modal props)))] - (on-color-change [event] - (let [value (dom/event->value event)] - (change-color value))) + [:div.element-set + [:div.element-set-title (tr "workspace.options.page-measures")] + [:div.element-set-content + [:span (tr "workspace.options.background-color")] + [:div.row-flex.color-data + [:span.color-th + {:style {:background-color (:background metadata "#ffffff")} + :on-click show-color-picker}] + [:div.color-info + [:input + {:on-change on-color-change + :value (:background metadata "#ffffff")}]]]]])) - (on-name-change [event] - (let [value (-> (dom/event->value event) - (str/trim))] - (st/emit! (-> (assoc page :name value) - (udp/update-page-attrs))))) - - (show-color-picker [event] - (let [x (.-clientX event) - y (.-clientY event) - props {:x x :y y - :default "#ffffff" - :value (:background metadata) - :transparent? true - :on-change change-color}] - (modal/show! colorpicker-modal props)))] - - [:div.element-set - [:div.element-set-title (tr (:name menu))] - [:div.element-set-content - [:span (tr "ds.name")] - [:div.row-flex - [:div.input-element - [:input.input-text - {:type "text" - :on-change on-name-change - :value (str (:name page)) - :placeholder "page name"}]]] - - [:span (tr "ds.size")] - [:div.row-flex - [:div.input-element.pixels - [:input.input-text - {:type "number" - :on-change #(on-size-change % :width) - :value (str (:width metadata)) - :placeholder (tr "ds.width")}]] - [:div.input-element.pixels - [:input.input-text - {:type "number" - :on-change #(on-size-change % :height) - :value (str (:height metadata)) - :placeholder (tr "ds.height")}]]] - - [:span (tr "ds.background-color")] - [:div.row-flex.color-data - [:span.color-th - {:style {:background-color (:background metadata)} - :on-click show-color-picker}] - [:div.color-info - [:input - {:on-change on-color-change - :value (:background metadata)}]]]]]))) - -(mf/defc grid-options-menu - [{:keys [menu page] :as props}] +(mf/defc grid-options + [{:keys [page] :as props}] (let [metadata (:metadata page) metadata (merge c/page-metadata metadata)] (letfn [(on-x-change [event] - (let [value (-> (dom/event->value event) + #_(let [value (-> (dom/event->value event) (parse-int nil))] (st/emit! (->> (assoc metadata :grid-x-axis value) (udp/update-metadata (:id page)))))) (on-y-change [event] - (let [value (-> (dom/event->value event) + #_(let [value (-> (dom/event->value event) (parse-int nil))] (st/emit! (->> (assoc metadata :grid-y-axis value) (udp/update-metadata (:id page)))))) (change-color [color] - (st/emit! (->> (assoc metadata :grid-color color) + #_(st/emit! (->> (assoc metadata :grid-color color) (udp/update-metadata (:id page))))) (on-color-change [event] (let [value (dom/event->value event)] @@ -126,9 +90,9 @@ :on-change change-color}] (modal/show! colorpicker-modal props)))] [:div.element-set - [:div.element-set-title (tr (:name menu))] + [:div.element-set-title (tr "element.page-grid-options")] [:div.element-set-content - [:span (tr "ds.size")] + [:span (tr "workspace.options.size")] [:div.row-flex [:div.input-element.pixels [:input.input-text @@ -142,7 +106,7 @@ :value (:grid-y-axis metadata) :on-change on-y-change :placeholder "y"}]]] - [:span (tr "ds.color")] + [:span (tr "workspace.options.color")] [:div.row-flex.color-data [:span.color-th {:style {:background-color (:grid-color metadata)} @@ -151,3 +115,10 @@ [:input {:on-change on-color-change :value (:grid-color metadata "#cccccc")}]]]]]))) + +(mf/defc options + [{:keys [page] :as props}] + [:div + [:& metadata-options {:page page}] + [:& grid-options {:page page}]]) + diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect.cljs new file mode 100644 index 000000000..8b3c3e2b2 --- /dev/null +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect.cljs @@ -0,0 +1,137 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2015-2017 Andrey Antukh +;; Copyright (c) 2015-2017 Juan de la Cruz + +(ns uxbox.main.ui.workspace.sidebar.options.rect + (:require + [rumext.alpha :as mf] + [uxbox.common.data :as d] + [uxbox.builtins.icons :as i] + [uxbox.main.data.workspace :as udw] + [uxbox.main.geom :as geom] + [uxbox.main.store :as st] + [uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]] + [uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]] + [uxbox.util.dom :as dom] + [uxbox.util.geom.point :as gpt] + [uxbox.util.i18n :refer [tr]] + [uxbox.util.math :as math])) + +(mf/defc measures-menu + [{:keys [shape] :as props}] + (let [on-size-change + (fn [event attr] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer 0))] + (st/emit! (udw/update-dimensions (:id shape) {attr value})))) + + on-proportion-lock-change + (fn [event] + (st/emit! (udw/toggle-shape-proportion-lock (:id shape)))) + + on-position-change + (fn [event attr] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer)) + point (gpt/point {attr value})] + (st/emit! (udw/update-position (:id shape) point)))) + + on-rotation-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-integer 0))] + (st/emit! (udw/update-shape (:id shape) {:rotation value})))) + + on-radius-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-double 0))] + (st/emit! (udw/update-shape (:id shape) {:rx value :ry value})))) + + on-width-change #(on-size-change % :width) + on-height-change #(on-size-change % :height) + on-pos-x-change #(on-position-change % :x) + on-pos-y-change #(on-position-change % :y)] + + [:div.element-set + [:div.element-set-title (tr "workspace.options.measures")] + [:div.element-set-content + [:span (tr "workspace.options.size")] + + ;; WIDTH & HEIGHT + [:div.row-flex + [:div.input-element.pixels + [:input.input-text {:type "number" + :min "0" + :on-change on-width-change + :value (-> (:width shape) + (math/precision 2) + (d/coalesce-str "0"))}]] + + [:div.lock-size {:class (when (:proportion-lock shape) "selected") + :on-click on-proportion-lock-change} + (if (:proportion-lock shape) + i/lock + i/unlock)] + + [:div.input-element.pixels + [:input.input-text {:type "number" + :min "0" + :on-change on-height-change + :value (-> (:height shape) + (math/precision 2) + (d/coalesce-str "0"))}]]] + + ;; POSITION + [:span (tr "workspace.options.position")] + [:div.row-flex + [:div.input-element.pixels + [:input.input-text {:placeholder "x" + :type "number" + :on-change on-pos-x-change + :value (-> (:x shape) + (math/precision 2) + (d/coalesce-str "0"))}]] + [:div.input-element.pixels + [:input.input-text {:placeholder "y" + :type "number" + :on-change on-pos-y-change + :value (-> (:y shape) + (math/precision 2) + (d/coalesce-str "0"))}]]] + + [:span (tr "workspace.options.rotation-radius")] + [:div.row-flex + [:div.input-element.degrees + [:input.input-text {:placeholder "" + :type "number" + :min 0 + :max 360 + :on-change on-rotation-change + :value (-> (:rotation shape 0) + (math/precision 2) + (d/coalesce-str "0"))}]] + + [:div.input-element.pixels + [:input.input-text + {:placeholder "rx" + :type "number" + :on-change on-radius-change + :value (-> (:rx shape) + (math/precision 2) + (d/coalesce-str "0"))}]]]]])) + + +(mf/defc options + [{:keys [shape] :as props}] + [:div + [:& measures-menu {:shape shape}] + [:& fill-menu {:shape shape}] + [:& stroke-menu {:shape shape}]]) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect_measures.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect_measures.cljs deleted file mode 100644 index d4867c085..000000000 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rect_measures.cljs +++ /dev/null @@ -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 -;; Copyright (c) 2015-2017 Juan de la Cruz - -(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))))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs index d957dd15b..b64c8c821 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs @@ -8,6 +8,7 @@ (ns uxbox.main.ui.workspace.sidebar.options.stroke (:require [rumext.alpha :as mf] + [uxbox.common.data :as d] [uxbox.builtins.icons :as i] [uxbox.main.data.workspace :as udw] [uxbox.main.store :as st] @@ -16,126 +17,82 @@ [uxbox.util.data :refer [parse-int parse-float read-string]] [uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]] - [uxbox.util.math :refer [precision-or-0]])) - -(declare on-width-change) -(declare on-opacity-change) -(declare on-stroke-style-change) -(declare on-stroke-color-change) -(declare on-border-change) -(declare show-color-picker) + [uxbox.util.math :as math])) (mf/defc stroke-menu - [{:keys [menu shape] :as props}] - (let [local (mf/use-state {}) - on-border-lock #(swap! local update :border-lock not) - on-stroke-style-change #(on-stroke-style-change % shape) - on-width-change #(on-width-change % shape) - on-stroke-color-change #(on-stroke-color-change % shape) - on-border-change-rx #(on-border-change % shape local :rx) - on-border-change-ry #(on-border-change % shape local :ry) - on-opacity-change #(on-opacity-change % shape) - show-color-picker #(show-color-picker % shape)] + [{:keys [shape] :as props}] + (let [on-stroke-style-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/read-string))] + (st/emit! (udw/update-shape (:id shape) {:stroke-style value})))) + + on-stroke-width-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-double 1))] + (st/emit! (udw/update-shape (:id shape) {:stroke-width value})))) + + on-stroke-opacity-change + (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value) + (d/parse-double 1) + (/ 10000))] + (st/emit! (udw/update-shape (:id shape) {:stroke-opacity value})))) + + show-color-picker + (fn [event] + (let [x (.-clientX event) + y (.-clientY event) + props {:x x :y y + :default "#ffffff" + :value (:stroke-color shape) + :on-change #(st/emit! (udw/update-shape (:id shape) {:stroke-color %})) + :transparent? true}] + (modal/show! colorpicker-modal props)))] + [:div.element-set - [:div.element-set-title (:name menu)] + [:div.element-set-title (tr "workspace.options.stroke")] [:div.element-set-content - [:span (tr "ds.style")] + + ;; Stroke Style & Width + [:span (tr "workspace.options.stroke.style")] [:div.row-flex - [:select#style.input-select {:placeholder (tr "ds.style") - :value (pr-str (:stroke-style shape)) + [:select#style.input-select {:value (pr-str (:stroke-style shape)) :on-change on-stroke-style-change} - [:option {:value ":none"} (tr "ds.none")] - [:option {:value ":solid"} (tr "ds.solid")] - [:option {:value ":dotted"} (tr "ds.dotted")] - [:option {:value ":dashed"} (tr "ds.dashed")] - [:option {:value ":mixed"} (tr "ds.mixed")]] - [:div.input-element.pixels - [:input.input-text - {:placeholder (tr "ds.width") - :type "number" - :min "0" - :value (precision-or-0 (:stroke-width shape 1) 2) - :on-change on-width-change}]]] + [:option {:value ":none"} (tr "workspace.options.stroke.none")] + [:option {:value ":solid"} (tr "workspace.options.stroke.solid")] + [:option {:value ":dotted"} (tr "workspace.options.stroke.dotted")] + [:option {:value ":dashed"} (tr "workspace.options.stroke.dashed")] + [:option {:value ":mixed"} (tr "workspace.options.stroke.mixed")]] - [:span (tr "ds.color")] + [:div.input-element.pixels + [:input.input-text {:type "number" + :min "0" + :value (-> (:stroke-width shape) + (math/precision 2) + (d/coalesce-str "1")) + :on-change on-stroke-width-change}]]] + + ;; Stroke Color + [:span (tr "workspace.options.color")] [:div.row-flex.color-data - [:span.color-th - {:style {:background-color (:stroke-color shape)} - :on-click show-color-picker}] + [:span.color-th {:style {:background-color (:stroke-color shape)} + :on-click show-color-picker}] [:div.color-info - [:input - {:on-change on-stroke-color-change - :value (:stroke-color shape "")}]]] + [:input {:read-only true + :default-value (:stroke-color shape "")}]]] - [:span (tr "ds.radius")] + [:span (tr "workspace.options.opacity")] [:div.row-flex - [:div.input-element.pixels - [:input.input-text - {:placeholder "rx" - :type "number" - :value (precision-or-0 (:rx shape 0) 2) - :on-change on-border-change-rx}]] - [:div.lock-size - {:class (when (:border-lock @local) "selected") - :on-click on-border-lock} - i/lock] - [:div.input-element.pixels - [:input.input-text - {:placeholder "ry" - :type "number" - :value (precision-or-0 (:ry shape 0) 2) - :on-change on-border-change-ry}]]] - - [:span (tr "ds.opacity")] - [:div.row-flex - [:input.slidebar - {:type "range" - :min "0" - :max "10000" - :value (* 10000 (:stroke-opacity shape 1)) - :step "1" - :on-change on-opacity-change}]]]])) - -(defn- on-width-change - [event shape] - (let [value (-> (dom/event->value event) - (parse-float 1))] - (st/emit! (udw/update-shape-attrs (:id shape) {:stroke-width value})))) - -(defn- on-opacity-change - [event shape] - (let [value (-> (dom/event->value event) - (parse-float 1) - (/ 10000))] - (st/emit! (udw/update-shape-attrs (:id shape) {:stroke-opacity value})))) - -(defn- on-stroke-style-change - [event shape] - (let [value (-> (dom/event->value event) - (read-string))] - (st/emit! (udw/update-shape-attrs (:id shape) {:stroke-style value})))) - -(defn- on-stroke-color-change - [event shape] - (let [value (dom/event->value event)] - (st/emit! (udw/update-shape-attrs (:id shape) {:stroke-color value})))) - -(defn- on-border-change - [event shape local attr] - (let [value (-> (dom/event->value event) - (parse-int nil)) - id (:id shape)] - (if (:border-lock @local) - (st/emit! (udw/update-shape-attrs id {:rx value :ry value})) - (st/emit! (udw/update-shape-attrs id {attr value}))))) - -(defn- show-color-picker - [event shape] - (let [x (.-clientX event) - y (.-clientY event) - props {:x x :y y - :default "#ffffff" - :value (:stroke-color shape) - :on-change #(st/emit! (udw/update-shape-attrs (:id shape) {:stroke-color %})) - :transparent? true}] - (modal/show! colorpicker-modal props))) + [:input.slidebar {:type "range" + :min "0" + :max "10000" + :value (-> (:stroke-opacity shape 1) + (* 10000) + (d/coalesce-str "1")) + :step "1" + :on-change on-stroke-opacity-change}]]]])) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs index b236c4fe4..9e424149f 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/text.cljs @@ -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) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs index 499f09e22..6f54ec0cd 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs @@ -11,7 +11,6 @@ [lentes.core :as l] [rumext.alpha :as mf] [uxbox.builtins.icons :as i] - [uxbox.main.data.pages :as udp] [uxbox.main.data.projects :as dp] [uxbox.main.data.workspace :as dw] [uxbox.main.store :as st] @@ -30,7 +29,7 @@ (mf/defc page-item [{:keys [page index deletable? selected?] :as props}] (let [on-edit #(modal/show! page-form-dialog {:page page}) - delete-fn #(st/emit! (udp/delete-page (:id page))) + delete-fn #(st/emit! (dp/delete-page (:id page))) on-delete #(do (dom/prevent-default %) (dom/stop-propagation %) @@ -101,11 +100,7 @@ close-fn #(st/emit! (dw/toggle-layout-flag :sitemap))] [:div.sitemap.tool-window [:div.tool-window-bar - [:div.tool-window-icon i/project-tree] [:span (tr "ds.settings.sitemap")] - [:div.tool-window-close {:on-click close-fn} i/close]] + [:div.add-page {:on-click create-fn} i/close]] [:div.tool-window-content - [:div.project-title - #_[:span (:name project)] - [:div.add-page {:on-click create-fn} i/close]] [:& pages-list {:file file :current-page page}]]])) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs index 2d24b8806..05274c662 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs @@ -11,7 +11,7 @@ [cljs.spec.alpha :as s] [uxbox.builtins.icons :as i] [uxbox.main.constants :as c] - [uxbox.main.data.pages :as udp] + [uxbox.main.data.projects :as dp] [uxbox.main.data.workspace :as udw] [uxbox.main.store :as st] [uxbox.main.ui.modal :as modal] @@ -34,8 +34,8 @@ (modal/hide!) (let [data (:clean-data form)] (if (nil? (:id data)) - (st/emit! (udp/create-page data)) - (st/emit! (udp/rename-page data))))) + (st/emit! (dp/create-page data)) + (st/emit! (dp/rename-page data))))) (defn- initial-data [page] diff --git a/frontend/src/uxbox/main/ui/workspace/viewport.cljs b/frontend/src/uxbox/main/ui/workspace/viewport.cljs index ac9b909c8..fc8176bc7 100644 --- a/frontend/src/uxbox/main/ui/workspace/viewport.cljs +++ b/frontend/src/uxbox/main/ui/workspace/viewport.cljs @@ -11,6 +11,7 @@ [goog.events :as events] [potok.core :as ptk] [rumext.alpha :as mf] + [uxbox.builtins.icons :as i] [uxbox.main.constants :as c] [uxbox.main.data.workspace :as dw] [uxbox.main.geom :as geom] @@ -72,14 +73,29 @@ ;; --- Selection Rect +(defn- selection->rect + [data] + (let [start (:start data) + stop (:stop data) + start-x (min (:x start) (:x stop)) + start-y (min (:y start) (:y stop)) + end-x (max (:x start) (:x stop)) + end-y (max (:y start) (:y stop))] + (assoc data + :type :rect + :x start-x + :y start-y + :width (- end-x start-x) + :height (- end-y start-y)))) + (def ^:private handle-selrect (letfn [(update-state [state position] (let [selrect (get-in state [:workspace-local :selrect])] (if selrect (assoc-in state [:workspace-local :selrect] - (dw/selection->rect (assoc selrect :stop position))) + (selection->rect (assoc selrect :stop position))) (assoc-in state [:workspace-local :selrect] - (dw/selection->rect {:start position :stop position}))))) + (selection->rect {:start position :stop position}))))) (clear-state [state] (update state :workspace-local dissoc :selrect))] @@ -99,13 +115,11 @@ {:wrap [mf/wrap-memo]} [{:keys [data] :as props}] (when data - (let [{:keys [x1 y1 width height]} (geom/size data)] - [:rect.selection-rect - {:x x1 - :y y1 - :width width - :height height}]))) - + [:rect.selection-rect + {:x (:x data) + :y (:y data) + :width (:width data) + :height (:height data)}])) ;; --- Viewport Positioning @@ -127,6 +141,8 @@ ;; --- Viewport +(declare remote-user-cursors) + (mf/defc canvas-and-shapes {:wrap [mf/wrap-memo]} [props] @@ -137,7 +153,7 @@ [:* (for [item canvas] [:& shape-wrapper {:shape item :key (:id item)}]) - (for [item (reverse shapes)] + (for [item shapes] [:& shape-wrapper {:shape item :key (:id item)}])])) (mf/defc viewport @@ -275,4 +291,53 @@ (when (contains? flags :ruler) [:& ruler {:zoom zoom :ruler (:ruler local)}]) + + ;; -- METER CURSOR MULTIUSUARIO + [:& remote-user-cursors {:page page}] + [:& selrect {:data (:selrect local)}]]]))) + + +(mf/defc remote-user-cursor + [{:keys [pointer user] :as props}] + [:g.multiuser-cursor {:key (:user-id pointer) + :transform (str "translate(" (:x pointer) "," (:y pointer) ") scale(4)")} + [:path {:fill (:color user) + :d "M5.292 4.027L1.524.26l-.05-.01L0 0l.258 1.524 3.769 3.768zm-.45 0l-.313.314L1.139.95l.314-.314zm-.5.5l-.315.316-3.39-3.39.315-.315 3.39 3.39zM1.192.526l-.668.667L.431.646.64.43l.552.094z" + :font-family "sans-serif"}] + [:g {:transform "translate(0 -291.708)"} + [:rect {:width "21.415" + :height "5.292" + :x "6.849" + :y "291.755" + :fill (:color user) + :fill-opacity ".893" + :paint-order "stroke fill markers" + :rx ".794" + :ry ".794"}] + [:text {:x "9.811" + :y "295.216" + :fill "#fff" + :stroke-width ".265" + :font-family "Open Sans" + :font-size"2.91" + :font-weight "400" + :letter-spacing"0" + :style {:line-height "1.25"} + :word-spacing "0" + ;; :style="line-height:1 + } + (:fullname user)]]]) + +(mf/defc remote-user-cursors + [{:keys [page] :as props}] + (let [users (mf/deref refs/workspace-users) + pointers (->> (vals (:pointer users)) + (remove #(not= (:id page) (:page-id %))) + (filter #((:active users) (:user-id %))))] + (for [pointer pointers] + (let [user (get-in users [:by-id (:user-id pointer)])] + [:& remote-user-cursor {:pointer pointer + :user user + :key (:user-id pointer)}])))) + diff --git a/frontend/src/uxbox/main/websockets.cljs b/frontend/src/uxbox/main/websockets.cljs new file mode 100644 index 000000000..27a691b7a --- /dev/null +++ b/frontend/src/uxbox/main/websockets.cljs @@ -0,0 +1,50 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2015-2017 Andrey Antukh + +(ns uxbox.main.websockets + "A interface to webworkers exposed functionality." + (:require + [cljs.spec.alpha :as s] + [goog.events :as ev] + [beicon.core :as rx] + [potok.core :as ptk] + [uxbox.util.spec :as us]) + (:import + goog.net.WebSocket + goog.net.WebSocket.EventType)) + +(defprotocol IWebSocket + (-stream [_] "Retrienve the message stream") + (-send [_ message] "send a message") + (-close [_] "close websocket")) + + +(defn open + [uri] + (let [sb (rx/subject) + ws (WebSocket. #js {:autoReconnect true}) + lk1 (ev/listen ws EventType.MESSAGE + #(rx/push! sb {:type :message :payload (.-message %)})) + lk2 (ev/listen ws EventType.ERROR + #(rx/push! sb {:type :error :payload %})) + lk3 (ev/listen ws EventType.OPENED + #(rx/push! sb {:type :opened :payload %}))] + (.open ws uri) + (reify + cljs.core/IDeref + (-deref [_] ws) + + IWebSocket + (-stream [_] sb) + (-send [_ msg] + (when (.isOpen ws) + (.send ws msg))) + (-close [_] + (.close ws) + (rx/end! sb) + (ev/unlistenByKey lk1) + (ev/unlistenByKey lk2) + (ev/unlistenByKey lk3))))) diff --git a/frontend/src/uxbox/util/data.cljs b/frontend/src/uxbox/util/data.cljs index f26dcf61e..778fb4ea2 100644 --- a/frontend/src/uxbox/util/data.cljs +++ b/frontend/src/uxbox/util/data.cljs @@ -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] diff --git a/frontend/src/uxbox/util/math.cljs b/frontend/src/uxbox/util/math.cljs index ae5a83b0d..0810d4683 100644 --- a/frontend/src/uxbox/util/math.cljs +++ b/frontend/src/uxbox/util/math.cljs @@ -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] diff --git a/frontend/src/uxbox/util/router.cljs b/frontend/src/uxbox/util/router.cljs index 19b523179..299214559 100644 --- a/frontend/src/uxbox/util/router.cljs +++ b/frontend/src/uxbox/util/router.cljs @@ -5,12 +5,15 @@ ;; Copyright (c) 2015-2019 Andrey Antukh (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)))))) diff --git a/frontend/src/uxbox/util/timers.cljs b/frontend/src/uxbox/util/timers.cljs index 9e60b1d1e..fb91729fa 100644 --- a/frontend/src/uxbox/util/timers.cljs +++ b/frontend/src/uxbox/util/timers.cljs @@ -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))))) diff --git a/frontend/src/uxbox/util/zip.cljs b/frontend/src/uxbox/util/zip.cljs index 3373553c7..169845e8c 100644 --- a/frontend/src/uxbox/util/zip.cljs +++ b/frontend/src/uxbox/util/zip.cljs @@ -16,4 +16,4 @@ (let [zobj (js/JSZip.)] (run! (partial attach-file zobj) files) (->> (.generateAsync zobj #js {:type "blob"}) - (rx/from-promise))))) + (rx/from))))) diff --git a/frontend/src/uxbox/view/data/viewer.cljs b/frontend/src/uxbox/view/data/viewer.cljs index 7ae36890b..079e2ca77 100644 --- a/frontend/src/uxbox/view/data/viewer.cljs +++ b/frontend/src/uxbox/view/data/viewer.cljs @@ -10,7 +10,6 @@ [uxbox.util.router :as rt] [uxbox.util.data :refer (parse-int)] [uxbox.main.repo :as rp] - [uxbox.main.data.pages :as udpg] [uxbox.main.data.projects :as udpj])) ;; --- Initialize diff --git a/frontend/vendor/datefns/datefns.bundle.min.js b/frontend/vendor/datefns/datefns.bundle.min.js index b8a73aedd..57528cd85 100644 --- a/frontend/vendor/datefns/datefns.bundle.min.js +++ b/frontend/vendor/datefns/datefns.bundle.min.js @@ -1 +1 @@ -!function(){"use strict";var e="undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},t=function(e){var t=new Date(e.getTime()),n=t.getTimezoneOffset();return t.setSeconds(0,0),6e4*n+t.getTime()%6e4};var n=function(e){return e instanceof Date},r=36e5,a=6e4,o=2,u=/[T ]/,i=/:/,s=/^(\d{2})$/,f=[/^([+-]\d{2})$/,/^([+-]\d{3})$/,/^([+-]\d{4})$/],c=/^(\d{4})/,l=[/^([+-]\d{4})/,/^([+-]\d{5})/,/^([+-]\d{6})/],d=/^-(\d{2})$/,g=/^-?(\d{3})$/,h=/^-?(\d{2})-?(\d{2})$/,v=/^-?W(\d{2})$/,m=/^-?W(\d{2})-?(\d{1})$/,D=/^(\d{2}([.,]\d*)?)$/,M=/^(\d{2}):?(\d{2}([.,]\d*)?)$/,T=/^(\d{2}):?(\d{2}):?(\d{2}([.,]\d*)?)$/,p=/([Z+-].*)$/,y=/^(Z)$/,S=/^([+-])(\d{2})$/,x=/^([+-])(\d{2}):?(\d{2})$/;function Y(e,t,n){t=t||0,n=n||0;var r=new Date(0);r.setUTCFullYear(e,0,4);var a=7*t+n+1-(r.getUTCDay()||7);return r.setUTCDate(r.getUTCDate()+a),r}var w=function(e,w){if(n(e))return new Date(e.getTime());if("string"!=typeof e)return new Date(e);var b=(w||{}).additionalDigits;b=null==b?o:Number(b);var F=function(e){var t,n={},r=e.split(u);if(i.test(r[0])?(n.date=null,t=r[0]):(n.date=r[0],t=r[1]),t){var a=p.exec(t);a?(n.time=t.replace(a[1],""),n.timezone=a[1]):n.time=t}return n}(e),H=function(e,t){var n,r=f[t],a=l[t];if(n=c.exec(e)||a.exec(e)){var o=n[1];return{year:parseInt(o,10),restDateString:e.slice(o.length)}}if(n=s.exec(e)||r.exec(e)){var u=n[1];return{year:100*parseInt(u,10),restDateString:e.slice(u.length)}}return{year:null}}(F.date,b),I=H.year,X=function(e,t){if(null===t)return null;var n,r,a,o;if(0===e.length)return(r=new Date(0)).setUTCFullYear(t),r;if(n=d.exec(e))return r=new Date(0),a=parseInt(n[1],10)-1,r.setUTCFullYear(t,a),r;if(n=g.exec(e)){r=new Date(0);var u=parseInt(n[1],10);return r.setUTCFullYear(t,0,u),r}if(n=h.exec(e)){r=new Date(0),a=parseInt(n[1],10)-1;var i=parseInt(n[2],10);return r.setUTCFullYear(t,a,i),r}if(n=v.exec(e))return o=parseInt(n[1],10)-1,Y(t,o);if(n=m.exec(e)){o=parseInt(n[1],10)-1;var s=parseInt(n[2],10)-1;return Y(t,o,s)}return null}(H.restDateString,I);if(X){var W,$=X.getTime(),z=0;if(F.time&&(z=function(e){var t,n,o;if(t=D.exec(e))return(n=parseFloat(t[1].replace(",",".")))%24*r;if(t=M.exec(e))return n=parseInt(t[1],10),o=parseFloat(t[2].replace(",",".")),n%24*r+o*a;if(t=T.exec(e)){n=parseInt(t[1],10),o=parseInt(t[2],10);var u=parseFloat(t[3].replace(",","."));return n%24*r+o*a+1e3*u}return null}(F.time)),F.timezone)A=F.timezone,W=((C=y.exec(A))?0:(C=S.exec(A))?(U=60*parseInt(C[2],10),"+"===C[1]?-U:U):(C=x.exec(A))?(U=60*parseInt(C[2],10)+parseInt(C[3],10),"+"===C[1]?-U:U):0)*a;else{var G=$+z,O=new Date(G);W=t(O);var E=new Date(G);E.setDate(O.getDate()+1);var Z=t(E)-t(O);Z>0&&(W+=Z)}return new Date($+z+W)}var A,C,U;return new Date(e)};var b=function(e){var t=w(e),n=new Date(0);return n.setFullYear(t.getFullYear(),0,1),n.setHours(0,0,0,0),n};var F=function(e){var t=w(e);return t.setHours(0,0,0,0),t},H=6e4,I=864e5;var X=function(e,t){var n=F(e),r=F(t),a=n.getTime()-n.getTimezoneOffset()*H,o=r.getTime()-r.getTimezoneOffset()*H;return Math.round((a-o)/I)};var W=function(e){var t=w(e);return X(t,b(t))+1};var $=function(e,t){var n=t&&Number(t.weekStartsOn)||0,r=w(e),a=r.getDay(),o=(a=a.getTime()?n+1:t.getTime()>=u.getTime()?n:n-1};var O=function(e){var t=G(e),n=new Date(0);return n.setFullYear(t,0,4),n.setHours(0,0,0,0),z(n)},E=6048e5;var Z=function(e){var t=w(e),n=z(t).getTime()-O(t).getTime();return Math.round(n/E)+1};var A=function(e){if(n(e))return!isNaN(e);throw new TypeError(toString.call(e)+" is not an instance of Date")};var C=["M","MM","Q","D","DD","DDD","DDDD","d","E","W","WW","YY","YYYY","GG","GGGG","H","HH","h","hh","m","mm","s","ss","S","SS","SSS","Z","ZZ","X","x"];var U=function(e){var t=[];for(var n in e)e.hasOwnProperty(n)&&t.push(n);var r=C.concat(t).sort().reverse();return new RegExp("(\\[[^\\[]*\\])|(\\\\)?("+r.join("|")+"|.)","g")};var N=function(){var e=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],t=["January","February","March","April","May","June","July","August","September","October","November","December"],n=["Su","Mo","Tu","We","Th","Fr","Sa"],r=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],a=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],o=["AM","PM"],u=["am","pm"],i=["a.m.","p.m."],s={MMM:function(t){return e[t.getMonth()]},MMMM:function(e){return t[e.getMonth()]},dd:function(e){return n[e.getDay()]},ddd:function(e){return r[e.getDay()]},dddd:function(e){return a[e.getDay()]},A:function(e){return e.getHours()/12>=1?o[1]:o[0]},a:function(e){return e.getHours()/12>=1?u[1]:u[0]},aa:function(e){return e.getHours()/12>=1?i[1]:i[0]}};return["M","D","DDD","d","Q","W"].forEach(function(e){s[e+"o"]=function(t,n){return function(e){var t=e%100;if(t>20||t<10)switch(t%10){case 1:return e+"st";case 2:return e+"nd";case 3:return e+"rd"}return e+"th"}(n[e](t))}}),{formatters:s,formattingTokensRegExp:U(s)}},k={distanceInWords:function(){var e={lessThanXSeconds:{one:"less than a second",other:"less than {{count}} seconds"},xSeconds:{one:"1 second",other:"{{count}} seconds"},halfAMinute:"half a minute",lessThanXMinutes:{one:"less than a minute",other:"less than {{count}} minutes"},xMinutes:{one:"1 minute",other:"{{count}} minutes"},aboutXHours:{one:"about 1 hour",other:"about {{count}} hours"},xHours:{one:"1 hour",other:"{{count}} hours"},xDays:{one:"1 day",other:"{{count}} days"},aboutXMonths:{one:"about 1 month",other:"about {{count}} months"},xMonths:{one:"1 month",other:"{{count}} months"},aboutXYears:{one:"about 1 year",other:"about {{count}} years"},xYears:{one:"1 year",other:"{{count}} years"},overXYears:{one:"over 1 year",other:"over {{count}} years"},almostXYears:{one:"almost 1 year",other:"almost {{count}} years"}};return{localize:function(t,n,r){var a;return r=r||{},a="string"==typeof e[t]?e[t]:1===n?e[t].one:e[t].other.replace("{{count}}",n),r.addSuffix?r.comparison>0?"in "+a:a+" ago":a}}}(),format:N()};var J={M:function(e){return e.getMonth()+1},MM:function(e){return Q(e.getMonth()+1,2)},Q:function(e){return Math.ceil((e.getMonth()+1)/3)},D:function(e){return e.getDate()},DD:function(e){return Q(e.getDate(),2)},DDD:function(e){return W(e)},DDDD:function(e){return Q(W(e),3)},d:function(e){return e.getDay()},E:function(e){return e.getDay()||7},W:function(e){return Z(e)},WW:function(e){return Q(Z(e),2)},YY:function(e){return Q(e.getFullYear(),4).substr(2)},YYYY:function(e){return Q(e.getFullYear(),4)},GG:function(e){return String(G(e)).substr(2)},GGGG:function(e){return G(e)},H:function(e){return e.getHours()},HH:function(e){return Q(e.getHours(),2)},h:function(e){var t=e.getHours();return 0===t?12:t>12?t%12:t},hh:function(e){return Q(J.h(e),2)},m:function(e){return e.getMinutes()},mm:function(e){return Q(e.getMinutes(),2)},s:function(e){return e.getSeconds()},ss:function(e){return Q(e.getSeconds(),2)},S:function(e){return Math.floor(e.getMilliseconds()/100)},SS:function(e){return Q(Math.floor(e.getMilliseconds()/10),2)},SSS:function(e){return Q(e.getMilliseconds(),3)},Z:function(e){return R(e.getTimezoneOffset(),":")},ZZ:function(e){return R(e.getTimezoneOffset())},X:function(e){return Math.floor(e.getTime()/1e3)},x:function(e){return e.getTime()}};function R(e,t){t=t||"";var n=e>0?"-":"+",r=Math.abs(e),a=r%60;return n+Q(Math.floor(r/60),2)+t+Q(a,2)}function Q(e,t){for(var n=Math.abs(e).toString();n.lengthr?-1:n0?Math.floor(n):Math.ceil(n)};var K=function(e,t){var n=w(e),r=w(t);return 12*(n.getFullYear()-r.getFullYear())+(n.getMonth()-r.getMonth())};var L=function(e,t){var n=w(e).getTime(),r=w(t).getTime();return nr?1:0};var V=function(e,t){var n=w(e),r=w(t),a=L(n,r),o=Math.abs(K(n,r));return n.setMonth(n.getMonth()-a*o),a*(o-(L(n,r)===-a))},_=1440,ee=2520,te=43200,ne=86400;var re=function(e,t,n){var r=n||{},a=j(e,t),o=r.locale,u=k.distanceInWords.localize;o&&o.distanceInWords&&o.distanceInWords.localize&&(u=o.distanceInWords.localize);var i,s,f={addSuffix:Boolean(r.addSuffix),comparison:a};a>0?(i=w(e),s=w(t)):(i=w(t),s=w(e));var c,l=q(s,i),d=s.getTimezoneOffset()-i.getTimezoneOffset(),g=Math.round(l/60)-d;if(g<2)return r.includeSeconds?l<5?u("lessThanXSeconds",5,f):l<10?u("lessThanXSeconds",10,f):l<20?u("lessThanXSeconds",20,f):l<40?u("halfAMinute",null,f):u(l<60?"lessThanXMinutes":"xMinutes",1,f):0===g?u("lessThanXMinutes",1,f):u("xMinutes",g,f);if(g<45)return u("xMinutes",g,f);if(g<90)return u("aboutXHours",1,f);if(g<_)return u("aboutXHours",Math.round(g/60),f);if(g0&&(W+=Z)}return new Date($+z+W)}return new Date(e)};var b=function(e){var t=w(e),n=new Date(0);return n.setFullYear(t.getFullYear(),0,1),n.setHours(0,0,0,0),n};var F=function(e){var t=w(e);return t.setHours(0,0,0,0),t},H=6e4,I=864e5;var X=function(e,t){var n=F(e),r=F(t),a=n.getTime()-n.getTimezoneOffset()*H,o=r.getTime()-r.getTimezoneOffset()*H;return Math.round((a-o)/I)};var W=function(e){var t=w(e);return X(t,b(t))+1};var $=function(e,t){var n=t&&Number(t.weekStartsOn)||0,r=w(e),a=r.getDay(),o=(a=a.getTime()?n+1:t.getTime()>=u.getTime()?n:n-1};var O=function(e){var t=G(e),n=new Date(0);return n.setFullYear(t,0,4),n.setHours(0,0,0,0),z(n)},E=6048e5;var Z=function(e){var t=w(e),n=z(t).getTime()-O(t).getTime();return Math.round(n/E)+1};var A=function(e){if(n(e))return!isNaN(e);throw new TypeError(toString.call(e)+" is not an instance of Date")};var C=["M","MM","Q","D","DD","DDD","DDDD","d","E","W","WW","YY","YYYY","GG","GGGG","H","HH","h","hh","m","mm","s","ss","S","SS","SSS","Z","ZZ","X","x"];var U=function(e){var t=[];for(var n in e)e.hasOwnProperty(n)&&t.push(n);var r=C.concat(t).sort().reverse();return new RegExp("(\\[[^\\[]*\\])|(\\\\)?("+r.join("|")+"|.)","g")};var N=function(){var e=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],t=["January","February","March","April","May","June","July","August","September","October","November","December"],n=["Su","Mo","Tu","We","Th","Fr","Sa"],r=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],a=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],o=["AM","PM"],u=["am","pm"],i=["a.m.","p.m."],s={MMM:function(t){return e[t.getMonth()]},MMMM:function(e){return t[e.getMonth()]},dd:function(e){return n[e.getDay()]},ddd:function(e){return r[e.getDay()]},dddd:function(e){return a[e.getDay()]},A:function(e){return e.getHours()/12>=1?o[1]:o[0]},a:function(e){return e.getHours()/12>=1?u[1]:u[0]},aa:function(e){return e.getHours()/12>=1?i[1]:i[0]}};return["M","D","DDD","d","Q","W"].forEach((function(e){s[e+"o"]=function(t,n){return function(e){var t=e%100;if(t>20||t<10)switch(t%10){case 1:return e+"st";case 2:return e+"nd";case 3:return e+"rd"}return e+"th"}(n[e](t))}})),{formatters:s,formattingTokensRegExp:U(s)}},k={distanceInWords:function(){var e={lessThanXSeconds:{one:"less than a second",other:"less than {{count}} seconds"},xSeconds:{one:"1 second",other:"{{count}} seconds"},halfAMinute:"half a minute",lessThanXMinutes:{one:"less than a minute",other:"less than {{count}} minutes"},xMinutes:{one:"1 minute",other:"{{count}} minutes"},aboutXHours:{one:"about 1 hour",other:"about {{count}} hours"},xHours:{one:"1 hour",other:"{{count}} hours"},xDays:{one:"1 day",other:"{{count}} days"},aboutXMonths:{one:"about 1 month",other:"about {{count}} months"},xMonths:{one:"1 month",other:"{{count}} months"},aboutXYears:{one:"about 1 year",other:"about {{count}} years"},xYears:{one:"1 year",other:"{{count}} years"},overXYears:{one:"over 1 year",other:"over {{count}} years"},almostXYears:{one:"almost 1 year",other:"almost {{count}} years"}};return{localize:function(t,n,r){var a;return r=r||{},a="string"==typeof e[t]?e[t]:1===n?e[t].one:e[t].other.replace("{{count}}",n),r.addSuffix?r.comparison>0?"in "+a:a+" ago":a}}}(),format:N()};var J={M:function(e){return e.getMonth()+1},MM:function(e){return P(e.getMonth()+1,2)},Q:function(e){return Math.ceil((e.getMonth()+1)/3)},D:function(e){return e.getDate()},DD:function(e){return P(e.getDate(),2)},DDD:function(e){return W(e)},DDDD:function(e){return P(W(e),3)},d:function(e){return e.getDay()},E:function(e){return e.getDay()||7},W:function(e){return Z(e)},WW:function(e){return P(Z(e),2)},YY:function(e){return P(e.getFullYear(),4).substr(2)},YYYY:function(e){return P(e.getFullYear(),4)},GG:function(e){return String(G(e)).substr(2)},GGGG:function(e){return G(e)},H:function(e){return e.getHours()},HH:function(e){return P(e.getHours(),2)},h:function(e){var t=e.getHours();return 0===t?12:t>12?t%12:t},hh:function(e){return P(J.h(e),2)},m:function(e){return e.getMinutes()},mm:function(e){return P(e.getMinutes(),2)},s:function(e){return e.getSeconds()},ss:function(e){return P(e.getSeconds(),2)},S:function(e){return Math.floor(e.getMilliseconds()/100)},SS:function(e){return P(Math.floor(e.getMilliseconds()/10),2)},SSS:function(e){return P(e.getMilliseconds(),3)},Z:function(e){return Q(e.getTimezoneOffset(),":")},ZZ:function(e){return Q(e.getTimezoneOffset())},X:function(e){return Math.floor(e.getTime()/1e3)},x:function(e){return e.getTime()}};function R(e){return e.match(/\[[\s\S]/)?e.replace(/^\[|]$/g,""):e.replace(/\\/g,"")}function Q(e,t){t=t||"";var n=e>0?"-":"+",r=Math.abs(e),a=r%60;return n+P(Math.floor(r/60),2)+t+P(a,2)}function P(e,t){for(var n=Math.abs(e).toString();n.lengthr?-1:n0?Math.floor(n):Math.ceil(n)};var L=function(e,t){var n=w(e),r=w(t);return 12*(n.getFullYear()-r.getFullYear())+(n.getMonth()-r.getMonth())};var V=function(e,t){var n=w(e).getTime(),r=w(t).getTime();return nr?1:0};var _=function(e,t){var n=w(e),r=w(t),a=V(n,r),o=Math.abs(L(n,r));return n.setMonth(n.getMonth()-a*o),a*(o-(V(n,r)===-a))},ee=1440,te=2520,ne=43200,re=86400;var ae=function(e,t,n){var r=n||{},a=B(e,t),o=r.locale,u=k.distanceInWords.localize;o&&o.distanceInWords&&o.distanceInWords.localize&&(u=o.distanceInWords.localize);var i,s,f={addSuffix:Boolean(r.addSuffix),comparison:a};a>0?(i=w(e),s=w(t)):(i=w(t),s=w(e));var c,l=K(s,i),d=s.getTimezoneOffset()-i.getTimezoneOffset(),g=Math.round(l/60)-d;if(g<2)return r.includeSeconds?l<5?u("lessThanXSeconds",5,f):l<10?u("lessThanXSeconds",10,f):l<20?u("lessThanXSeconds",20,f):l<40?u("halfAMinute",null,f):u(l<60?"lessThanXMinutes":"xMinutes",1,f):0===g?u("lessThanXMinutes",1,f):u("xMinutes",g,f);if(g<45)return u("xMinutes",g,f);if(g<90)return u("aboutXHours",1,f);if(g colors.length) { + + // Since we're generating multiple colors, + // incremement the seed. Otherwise we'd just + // generate the same color each time... + if (seed && options.seed) options.seed += 1; + + colors.push(randomColor(options)); + } + + options.count = totalColors; + + return colors; + } + + // First we pick a hue (H) + H = pickHue(options); + + // Then use H to determine saturation (S) + S = pickSaturation(H, options); + + // Then use S and H to determine brightness (B). + B = pickBrightness(H, S, options); + + // Then we return the HSB color in the desired format + return setFormat([H,S,B], options); + }; + + function pickHue(options) { + if (colorRanges.length > 0) { + var hueRange = getRealHueRange(options.hue); + + var hue = randomWithin(hueRange); + + //Each of colorRanges.length ranges has a length equal approximatelly one step + var step = (hueRange[1] - hueRange[0]) / colorRanges.length; + + var j = parseInt((hue - hueRange[0]) / step); + + //Check if the range j is taken + if (colorRanges[j] === true) { + j = (j + 2) % colorRanges.length; + } + else { + colorRanges[j] = true; + } + + var min = (hueRange[0] + j * step) % 359, + max = (hueRange[0] + (j + 1) * step) % 359; + + hueRange = [min, max]; + + hue = randomWithin(hueRange); + + if (hue < 0) {hue = 360 + hue;} + return hue + } + else { + var hueRange = getHueRange(options.hue); + + hue = randomWithin(hueRange); + // Instead of storing red as two seperate ranges, + // we group them, using negative numbers + if (hue < 0) { + hue = 360 + hue; + } + + return hue; + } + } + + function pickSaturation (hue, options) { + + if (options.hue === 'monochrome') { + return 0; + } + + if (options.luminosity === 'random') { + return randomWithin([0,100]); + } + + var saturationRange = getSaturationRange(hue); + + var sMin = saturationRange[0], + sMax = saturationRange[1]; + + switch (options.luminosity) { + + case 'bright': + sMin = 55; + break; + + case 'dark': + sMin = sMax - 10; + break; + + case 'light': + sMax = 55; + break; + } + + return randomWithin([sMin, sMax]); + + } + + function pickBrightness (H, S, options) { + + var bMin = getMinimumBrightness(H, S), + bMax = 100; + + switch (options.luminosity) { + + case 'dark': + bMax = bMin + 20; + break; + + case 'light': + bMin = (bMax + bMin)/2; + break; + + case 'random': + bMin = 0; + bMax = 100; + break; + } + + return randomWithin([bMin, bMax]); + } + + function setFormat (hsv, options) { + + switch (options.format) { + + case 'hsvArray': + return hsv; + + case 'hslArray': + return HSVtoHSL(hsv); + + case 'hsl': + var hsl = HSVtoHSL(hsv); + return 'hsl('+hsl[0]+', '+hsl[1]+'%, '+hsl[2]+'%)'; + + case 'hsla': + var hslColor = HSVtoHSL(hsv); + var alpha = options.alpha || Math.random(); + return 'hsla('+hslColor[0]+', '+hslColor[1]+'%, '+hslColor[2]+'%, ' + alpha + ')'; + + case 'rgbArray': + return HSVtoRGB(hsv); + + case 'rgb': + var rgb = HSVtoRGB(hsv); + return 'rgb(' + rgb.join(', ') + ')'; + + case 'rgba': + var rgbColor = HSVtoRGB(hsv); + var alpha = options.alpha || Math.random(); + return 'rgba(' + rgbColor.join(', ') + ', ' + alpha + ')'; + + default: + return HSVtoHex(hsv); + } + + } + + function getMinimumBrightness(H, S) { + + var lowerBounds = getColorInfo(H).lowerBounds; + + for (var i = 0; i < lowerBounds.length - 1; i++) { + + var s1 = lowerBounds[i][0], + v1 = lowerBounds[i][1]; + + var s2 = lowerBounds[i+1][0], + v2 = lowerBounds[i+1][1]; + + if (S >= s1 && S <= s2) { + + var m = (v2 - v1)/(s2 - s1), + b = v1 - m*s1; + + return m*S + b; + } + + } + + return 0; + } + + function getHueRange (colorInput) { + + if (typeof parseInt(colorInput) === 'number') { + + var number = parseInt(colorInput); + + if (number < 360 && number > 0) { + return [number, number]; + } + + } + + if (typeof colorInput === 'string') { + + if (colorDictionary[colorInput]) { + var color = colorDictionary[colorInput]; + if (color.hueRange) {return color.hueRange;} + } else if (colorInput.match(/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i)) { + var hue = HexToHSB(colorInput)[0]; + return [ hue, hue ]; + } + } + + return [0,360]; + + } + + function getSaturationRange (hue) { + return getColorInfo(hue).saturationRange; + } + + function getColorInfo (hue) { + + // Maps red colors to make picking hue easier + if (hue >= 334 && hue <= 360) { + hue-= 360; + } + + for (var colorName in colorDictionary) { + var color = colorDictionary[colorName]; + if (color.hueRange && + hue >= color.hueRange[0] && + hue <= color.hueRange[1]) { + return colorDictionary[colorName]; + } + } return 'Color not found'; + } + + function randomWithin (range) { + if (seed === null) { + //generate random evenly destinct number from : https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ + var golden_ratio = 0.618033988749895; + var r=Math.random(); + r += golden_ratio; + r %= 1; + return Math.floor(range[0] + r*(range[1] + 1 - range[0])); + } else { + //Seeded random algorithm from http://indiegamr.com/generate-repeatable-random-numbers-in-js/ + var max = range[1] || 1; + var min = range[0] || 0; + seed = (seed * 9301 + 49297) % 233280; + var rnd = seed / 233280.0; + return Math.floor(min + rnd * (max - min)); +} + } + + function HSVtoHex (hsv){ + + var rgb = HSVtoRGB(hsv); + + function componentToHex(c) { + var hex = c.toString(16); + return hex.length == 1 ? '0' + hex : hex; + } + + var hex = '#' + componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]); + + return hex; + + } + + function defineColor (name, hueRange, lowerBounds) { + + var sMin = lowerBounds[0][0], + sMax = lowerBounds[lowerBounds.length - 1][0], + + bMin = lowerBounds[lowerBounds.length - 1][1], + bMax = lowerBounds[0][1]; + + colorDictionary[name] = { + hueRange: hueRange, + lowerBounds: lowerBounds, + saturationRange: [sMin, sMax], + brightnessRange: [bMin, bMax] + }; + + } + + function loadColorBounds () { + + defineColor( + 'monochrome', + null, + [[0,0],[100,0]] + ); + + defineColor( + 'red', + [-26,18], + [[20,100],[30,92],[40,89],[50,85],[60,78],[70,70],[80,60],[90,55],[100,50]] + ); + + defineColor( + 'orange', + [19,46], + [[20,100],[30,93],[40,88],[50,86],[60,85],[70,70],[100,70]] + ); + + defineColor( + 'yellow', + [47,62], + [[25,100],[40,94],[50,89],[60,86],[70,84],[80,82],[90,80],[100,75]] + ); + + defineColor( + 'green', + [63,178], + [[30,100],[40,90],[50,85],[60,81],[70,74],[80,64],[90,50],[100,40]] + ); + + defineColor( + 'blue', + [179, 257], + [[20,100],[30,86],[40,80],[50,74],[60,60],[70,52],[80,44],[90,39],[100,35]] + ); + + defineColor( + 'purple', + [258, 282], + [[20,100],[30,87],[40,79],[50,70],[60,65],[70,59],[80,52],[90,45],[100,42]] + ); + + defineColor( + 'pink', + [283, 334], + [[20,100],[30,90],[40,86],[60,84],[80,80],[90,75],[100,73]] + ); + + } + + function HSVtoRGB (hsv) { + + // this doesn't work for the values of 0 and 360 + // here's the hacky fix + var h = hsv[0]; + if (h === 0) {h = 1;} + if (h === 360) {h = 359;} + + // Rebase the h,s,v values + h = h/360; + var s = hsv[1]/100, + v = hsv[2]/100; + + var h_i = Math.floor(h*6), + f = h * 6 - h_i, + p = v * (1 - s), + q = v * (1 - f*s), + t = v * (1 - (1 - f)*s), + r = 256, + g = 256, + b = 256; + + switch(h_i) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + } + + var result = [Math.floor(r*255), Math.floor(g*255), Math.floor(b*255)]; + return result; + } + + function HexToHSB (hex) { + hex = hex.replace(/^#/, ''); + hex = hex.length === 3 ? hex.replace(/(.)/g, '$1$1') : hex; + + var red = parseInt(hex.substr(0, 2), 16) / 255, + green = parseInt(hex.substr(2, 2), 16) / 255, + blue = parseInt(hex.substr(4, 2), 16) / 255; + + var cMax = Math.max(red, green, blue), + delta = cMax - Math.min(red, green, blue), + saturation = cMax ? (delta / cMax) : 0; + + switch (cMax) { + case red: return [ 60 * (((green - blue) / delta) % 6) || 0, saturation, cMax ]; + case green: return [ 60 * (((blue - red) / delta) + 2) || 0, saturation, cMax ]; + case blue: return [ 60 * (((red - green) / delta) + 4) || 0, saturation, cMax ]; + } + } + + function HSVtoHSL (hsv) { + var h = hsv[0], + s = hsv[1]/100, + v = hsv[2]/100, + k = (2-s)*v; + + return [ + h, + Math.round(s*v / (k<1 ? k : 2-k) * 10000) / 100, + k/2 * 100 + ]; + } + + function stringToInteger (string) { + var total = 0; + for (var i = 0; i !== string.length; i++) { + if (total >= Number.MAX_SAFE_INTEGER) break; + total += string.charCodeAt(i); + } + return total + } + + // get The range of given hue when options.count!=0 + function getRealHueRange(colorHue) + { if (!isNaN(colorHue)) { + var number = parseInt(colorHue); + + if (number < 360 && number > 0) { + return getColorInfo(colorHue).hueRange + } + } + else if (typeof colorHue === 'string') { + + if (colorDictionary[colorHue]) { + var color = colorDictionary[colorHue]; + + if (color.hueRange) { + return color.hueRange + } + } else if (colorHue.match(/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i)) { + var hue = HexToHSB(colorHue)[0]; + return getColorInfo(hue).hueRange + } + } + + return [0,360] +} + return randomColor; +})); +}); +var randomColor_1 = randomColor.randomColor;if (typeof self !== "undefined") { + init(self); +} else if (typeof global$1 !== "undefined") { + init(global$1); +} else if (typeof window !== "undefined") { + init(window); +} else { + throw new Error("unsupported execution environment"); +} + +function init(g) { + g.randomcolor = randomColor; +}}()); \ No newline at end of file diff --git a/frontend/vendor/randomcolor/randomcolor.bundle.min.js b/frontend/vendor/randomcolor/randomcolor.bundle.min.js new file mode 100644 index 000000000..d7fe207e7 --- /dev/null +++ b/frontend/vendor/randomcolor/randomcolor.bundle.min.js @@ -0,0 +1 @@ +!function(){"use strict";var e="undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{};"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;var r=function(e,r){return e(r={exports:{}},r.exports),r.exports}((function(e,r){!function(n,t){var a=function(){var e=null,r={};u("monochrome",null,[[0,0],[100,0]]),u("red",[-26,18],[[20,100],[30,92],[40,89],[50,85],[60,78],[70,70],[80,60],[90,55],[100,50]]),u("orange",[19,46],[[20,100],[30,93],[40,88],[50,86],[60,85],[70,70],[100,70]]),u("yellow",[47,62],[[25,100],[40,94],[50,89],[60,86],[70,84],[80,82],[90,80],[100,75]]),u("green",[63,178],[[30,100],[40,90],[50,85],[60,81],[70,74],[80,64],[90,50],[100,40]]),u("blue",[179,257],[[20,100],[30,86],[40,80],[50,74],[60,60],[70,52],[80,44],[90,39],[100,35]]),u("purple",[258,282],[[20,100],[30,87],[40,79],[50,70],[60,65],[70,59],[80,52],[90,45],[100,42]]),u("pink",[283,334],[[20,100],[30,90],[40,86],[60,84],[80,80],[90,75],[100,73]]);var n=[],t=function(u){if(void 0!==(u=u||{}).seed&&null!==u.seed&&u.seed===parseInt(u.seed,10))e=u.seed;else if("string"==typeof u.seed)e=function(e){for(var r=0,n=0;n!==e.length&&!(r>=Number.MAX_SAFE_INTEGER);n++)r+=e.charCodeAt(n);return r}(u.seed);else{if(void 0!==u.seed&&null!==u.seed)throw new TypeError("The seed value must be an integer or string");e=null}var l,c;if(null!==u.count&&void 0!==u.count){for(var h=u.count,d=[],g=0;gd.length;)e&&u.seed&&(u.seed+=1),d.push(t(u));return u.count=h,d}return function(e,r){switch(r.format){case"hsvArray":return e;case"hslArray":return f(e);case"hsl":var n=f(e);return"hsl("+n[0]+", "+n[1]+"%, "+n[2]+"%)";case"hsla":var t=f(e),a=r.alpha||Math.random();return"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+a+")";case"rgbArray":return i(e);case"rgb":return"rgb("+i(e).join(", ")+")";case"rgba":var o=i(e);a=r.alpha||Math.random();return"rgba("+o.join(", ")+", "+a+")";default:return function(e){var r=i(e);function n(e){var r=e.toString(16);return 1==r.length?"0"+r:r}return"#"+n(r[0])+n(r[1])+n(r[2])}(e)}}([l=function(e){if(n.length>0){var t=o(c=function(e){if(isNaN(e)){if("string"==typeof e)if(r[e]){var n=r[e];if(n.hueRange)return n.hueRange}else if(e.match(/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i)){return a(s(e)[0]).hueRange}}else{var t=parseInt(e);if(t<360&&t>0)return a(e).hueRange}return[0,360]}(e.hue)),u=(c[1]-c[0])/n.length,i=parseInt((t-c[0])/u);!0===n[i]?i=(i+2)%n.length:n[i]=!0;var f=(c[0]+i*u)%359,l=(c[0]+(i+1)*u)%359;return(t=o(c=[f,l]))<0&&(t=360+t),t}var c=function(e){if("number"==typeof parseInt(e)){var n=parseInt(e);if(n<360&&n>0)return[n,n]}if("string"==typeof e)if(r[e]){var t=r[e];if(t.hueRange)return t.hueRange}else if(e.match(/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i)){var a=s(e)[0];return[a,a]}return[0,360]}(e.hue);return(t=o(c))<0&&(t=360+t),t}(u),c=function(e,r){if("monochrome"===r.hue)return 0;if("random"===r.luminosity)return o([0,100]);var n=function(e){return a(e).saturationRange}(e),t=n[0],u=n[1];switch(r.luminosity){case"bright":t=55;break;case"dark":t=u-10;break;case"light":u=55}return o([t,u])}(l,u),function(e,r,n){var t=function(e,r){for(var n=a(e).lowerBounds,t=0;t=o&&r<=i){var f=(s-u)/(i-o);return f*r+(u-f*o)}}return 0}(e,r),u=100;switch(n.luminosity){case"dark":u=t+20;break;case"light":t=(u+t)/2;break;case"random":t=0,u=100}return o([t,u])}(l,c,u)],u)};function a(e){for(var n in e>=334&&e<=360&&(e-=360),r){var t=r[n];if(t.hueRange&&e>=t.hueRange[0]&&e<=t.hueRange[1])return r[n]}return"Color not found"}function o(r){if(null===e){var n=Math.random();return n+=.618033988749895,n%=1,Math.floor(r[0]+n*(r[1]+1-r[0]))}var t=r[1]||1,a=r[0]||0,o=(e=(9301*e+49297)%233280)/233280;return Math.floor(a+o*(t-a))}function u(e,n,t){var a=t[0][0],o=t[t.length-1][0],u=t[t.length-1][1],i=t[0][1];r[e]={hueRange:n,lowerBounds:t,saturationRange:[a,o],brightnessRange:[u,i]}}function i(e){var r=e[0];0===r&&(r=1),360===r&&(r=359),r/=360;var n=e[1]/100,t=e[2]/100,a=Math.floor(6*r),o=6*r-a,u=t*(1-n),i=t*(1-o*n),s=t*(1-(1-o)*n),f=256,l=256,c=256;switch(a){case 0:f=t,l=s,c=u;break;case 1:f=i,l=t,c=u;break;case 2:f=u,l=t,c=s;break;case 3:f=u,l=i,c=t;break;case 4:f=s,l=u,c=t;break;case 5:f=t,l=u,c=i}return[Math.floor(255*f),Math.floor(255*l),Math.floor(255*c)]}function s(e){e=3===(e=e.replace(/^#/,"")).length?e.replace(/(.)/g,"$1$1"):e;var r=parseInt(e.substr(0,2),16)/255,n=parseInt(e.substr(2,2),16)/255,t=parseInt(e.substr(4,2),16)/255,a=Math.max(r,n,t),o=a-Math.min(r,n,t),u=a?o/a:0;switch(a){case r:return[(n-t)/o%6*60||0,u,a];case n:return[60*((t-r)/o+2)||0,u,a];case t:return[60*((r-n)/o+4)||0,u,a]}}function f(e){var r=e[0],n=e[1]/100,t=e[2]/100,a=(2-n)*t;return[r,Math.round(n*t/(a<1?a:2-a)*1e4)/100,a/2*100]}return t}();e&&e.exports&&(r=e.exports=a),r.randomColor=a}()}));r.randomColor;if("undefined"!=typeof self)n(self);else if(void 0!==e)n(e);else{if("undefined"==typeof window)throw new Error("unsupported execution environment");n(window)}function n(e){e.randomcolor=r}}(); \ No newline at end of file diff --git a/frontend/vendor/randomcolor/randomcolor.js b/frontend/vendor/randomcolor/randomcolor.js new file mode 100644 index 000000000..da9234c8d --- /dev/null +++ b/frontend/vendor/randomcolor/randomcolor.js @@ -0,0 +1,11 @@ +import randomcolor from "randomcolor"; + +if (typeof self !== "undefined") { init(self); } +else if (typeof global !== "undefined") { init(global); } +else if (typeof window !== "undefined") { init(window); } +else { throw new Error("unsupported execution environment"); } + +function init(g) { + g.randomcolor = randomcolor; +} + diff --git a/frontend/vendor/react-color/react-color.bundle.min.js b/frontend/vendor/react-color/react-color.bundle.min.js index db903b9e8..041fe7949 100644 --- a/frontend/vendor/react-color/react-color.bundle.min.js +++ b/frontend/vendor/react-color/react-color.bundle.min.js @@ -1 +1 @@ -!function(e){"use strict";e=e&&e.hasOwnProperty("default")?e.default:e;var t="undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},r="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function n(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function o(e,t){return e(t={exports:{}},t.exports),t.exports}var a="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED";function i(){}function u(){}u.resetWarningCache=i;var l=o(function(e){e.exports=function(){function e(e,t,r,n,o,i){if(i!==a){var u=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw u.name="Invariant Violation",u}}function t(){return e}e.isRequired=e;var r={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:u,resetWarningCache:i};return r.PropTypes=r,r}()}),s="object"==typeof r&&r&&r.Object===Object&&r,c="object"==typeof self&&self&&self.Object===Object&&self,f=s||c||Function("return this")(),p=f.Symbol,d=Object.prototype,h=d.hasOwnProperty,v=d.toString,b=p?p.toStringTag:void 0;var g=function(e){var t=h.call(e,b),r=e[b];try{e[b]=void 0;var n=!0}catch(e){}var o=v.call(e);return n&&(t?e[b]=r:delete e[b]),o},y=Object.prototype.toString;var _=function(e){return y.call(e)},m="[object Null]",w="[object Undefined]",x=p?p.toStringTag:void 0;var j=function(e){return null==e?void 0===e?w:m:x&&x in Object(e)?g(e):_(e)},O=Array.isArray;var S=function(e){return null!=e&&"object"==typeof e},C="[object String]";var k=function(e){return"string"==typeof e||!O(e)&&S(e)&&j(e)==C};var E=function(e){return function(t,r,n){for(var o=-1,a=Object(t),i=n(t),u=i.length;u--;){var l=i[e?u:++o];if(!1===r(a[l],l,a))break}return t}}();var A=function(e,t){for(var r=-1,n=Array(e);++r-1&&e%1==0&&e-1&&e%1==0&&e<=W},N={};N["[object Float32Array]"]=N["[object Float64Array]"]=N["[object Int8Array]"]=N["[object Int16Array]"]=N["[object Int32Array]"]=N["[object Uint8Array]"]=N["[object Uint8ClampedArray]"]=N["[object Uint16Array]"]=N["[object Uint32Array]"]=!0,N["[object Arguments]"]=N["[object Array]"]=N["[object ArrayBuffer]"]=N["[object Boolean]"]=N["[object DataView]"]=N["[object Date]"]=N["[object Error]"]=N["[object Function]"]=N["[object Map]"]=N["[object Number]"]=N["[object Object]"]=N["[object RegExp]"]=N["[object Set]"]=N["[object String]"]=N["[object WeakMap]"]=!1;var V=function(e){return S(e)&&I(e.length)&&!!N[j(e)]};var $=function(e){return function(t){return e(t)}},X=o(function(e,t){var r=t&&!t.nodeType&&t,n=r&&e&&!e.nodeType&&e,o=n&&n.exports===r&&s.process,a=function(){try{var e=n&&n.require&&n.require("util").types;return e||o&&o.binding&&o.binding("util")}catch(e){}}();e.exports=a}),q=X&&X.isTypedArray,Y=q?$(q):V,K=Object.prototype.hasOwnProperty;var G=function(e,t){var r=O(e),n=!r&&H(e),o=!r&&!n&&z(e),a=!r&&!n&&!o&&Y(e),i=r||n||o||a,u=i?A(e.length,String):[],l=u.length;for(var s in e)!t&&!K.call(e,s)||i&&("length"==s||o&&("offset"==s||"parent"==s)||a&&("buffer"==s||"byteLength"==s||"byteOffset"==s)||B(s,l))||u.push(s);return u},J=Object.prototype;var Q=function(e){var t=e&&e.constructor;return e===("function"==typeof t&&t.prototype||J)};var Z=function(e,t){return function(r){return e(t(r))}},ee=Z(Object.keys,Object),te=Object.prototype.hasOwnProperty;var re=function(e){if(!Q(e))return ee(e);var t=[];for(var r in Object(e))te.call(e,r)&&"constructor"!=r&&t.push(r);return t};var ne=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)},oe="[object AsyncFunction]",ae="[object Function]",ie="[object GeneratorFunction]",ue="[object Proxy]";var le=function(e){if(!ne(e))return!1;var t=j(e);return t==ae||t==ie||t==oe||t==ue};var se=function(e){return null!=e&&I(e.length)&&!le(e)};var ce=function(e){return se(e)?G(e):re(e)};var fe=function(e,t){return e&&E(e,t,ce)};var pe=function(e){return e};var de=function(e){return"function"==typeof e?e:pe};var he=function(e,t){return e&&fe(e,de(t))},ve=Z(Object.getPrototypeOf,Object),be="[object Object]",ge=Function.prototype,ye=Object.prototype,_e=ge.toString,me=ye.hasOwnProperty,we=_e.call(Object);var xe=function(e){if(!S(e)||j(e)!=be)return!1;var t=ve(e);if(null===t)return!0;var r=me.call(t,"constructor")&&t.constructor;return"function"==typeof r&&r instanceof r&&_e.call(r)==we};var je=function(e,t){for(var r=-1,n=null==e?0:e.length,o=Array(n);++r-1};var Pe=function(e,t){var r=this.__data__,n=Ce(r,e);return n<0?(++this.size,r.push([e,t])):r[n][1]=t,this};function Re(e){var t=-1,r=null==e?0:e.length;for(this.clear();++tu))return!1;var s=a.get(e);if(s&&a.get(t))return s==t;var c=-1,f=!0,p=r&Tt?new At:void 0;for(a.set(e,t),a.set(t,e);++c0&&void 0!==arguments[0]?arguments[0]:[],i=[];return(0,a.default)(t,function(t){Array.isArray(t)?e(t).map(function(e){return i.push(e)}):(0,o.default)(t)?(0,n.default)(t,function(e,t){!0===e&&i.push(t),i.push(t+"-"+e)}):(0,r.default)(t)&&i.push(t)}),i};t.default=u});n(yn);yn.flattenNames;var _n=function(e,t){for(var r=-1,n=null==e?0:e.length;++r1&&void 0!==arguments[1]?arguments[1]:[],a=e.default&&(0,n.default)(e.default)||{};return t.map(function(t){var n=e[t];return n&&(0,r.default)(n,function(e,t){a[t]||(a[t]={}),a[t]=o({},a[t],n[t])}),t}),a};t.default=i});n(zo);zo.mergeClasses;var Do=o(function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.autoprefix=void 0;var r,n=(r=he)&&r.__esModule?r:{default:r},o=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:"span";return function(r){function n(){var r,l,s;i(this,n);for(var c=arguments.length,f=Array(c),p=0;p1&&void 0!==arguments[1]?arguments[1]:"span";return function(r){function n(){var r,l,s;i(this,n);for(var c=arguments.length,f=Array(c),p=0;p1&&void 0!==arguments[1])||arguments[1];r[e]=t};return 0===e&&n("first-child"),e===t-1&&n("last-child"),(0===e||e%2==0)&&n("even"),1===Math.abs(e%2)&&n("odd"),n("nth-child",e),r}});n(Wo);var Io=o(function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ReactCSS=t.loop=t.handleActive=t.handleHover=t.hover=void 0;var r=l(yn),n=l(zo),o=l(Do),a=l(Uo),i=l(Bo),u=l(Wo);function l(e){return e&&e.__esModule?e:{default:e}}t.hover=a.default,t.handleHover=a.default,t.handleActive=i.default,t.loop=u.default;var s=t.ReactCSS=function(e){for(var t=arguments.length,a=Array(t>1?t-1:0),i=1;i0){if(++t>=ea)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}(Zo);var oa=function(e,t){return na(Jo(e,t,pe),e+"")};var aa=function(e,t,r){if(!ne(r))return!1;var n=typeof t;return!!("number"==n?se(r)&&B(t,r.length):"string"==n&&t in r)&&Se(r[t],e)};var ia=function(e){return oa(function(t,r){var n=-1,o=r.length,a=o>1?r[o-1]:void 0,i=o>2?r[2]:void 0;for(a=e.length>3&&"function"==typeof a?(o--,a):void 0,i&&aa(r[0],r[1],i)&&(a=o<3?void 0:a,o=1),t=Object(t);++ni?1:Math.round(100*c/i)/100,t.a!==f)return{h:t.h,s:t.s,l:t.l,a:f,source:"rgb"}}else{var p=void 0;if(n!==(p=s<0?0:s>a?1:Math.round(100*s/a)/100))return{h:t.h,s:t.s,l:t.l,a:p,source:"rgb"}}return null}});n(ua);ua.calculateChange;var la=o(function(e,t){Object.defineProperty(t,"__esModule",{value:!0});var r={},n=t.render=function(e,t,r,n){if("undefined"==typeof document&&!n)return null;var o=n?new n:document.createElement("canvas");o.width=2*r,o.height=2*r;var a=o.getContext("2d");return a?(a.fillStyle=e,a.fillRect(0,0,o.width,o.height),a.fillStyle=t,a.fillRect(0,0,r,r),a.translate(r,r),a.fillRect(0,0,r,r),o.toDataURL()):null};t.get=function(e,t,o,a){var i=e+"-"+t+"-"+o+(a?"-server":"");if(r[i])return r[i];var u=n(e,t,o,a);return r[i]=u,u}});n(la);la.render,la.get;var sa=o(function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.Checkboard=void 0;var n=i(e),o=i(Io),a=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}(la);function i(e){return e&&e.__esModule?e:{default:e}}var u=r.Checkboard=function(e){var t=e.white,r=e.grey,i=e.size,u=e.renderers,l=e.borderRadius,s=e.boxShadow,c=(0,o.default)({default:{grid:{borderRadius:l,boxShadow:s,absolute:"0px 0px 0px 0px",background:"url("+a.get(t,r,i,u.canvas)+") center left"}}});return n.default.createElement("div",{style:c.grid})};u.defaultProps={size:8,white:"transparent",grey:"rgba(0,0,0,.08)",renderers:{}},r.default=u});n(sa);sa.Checkboard;var ca=o(function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.Alpha=void 0;var n=Object.assign||function(e){for(var t=1;t-1},c=function(e){return Number(String(e).replace(/%/g,""))},f=r.EditableInput=function(t){function r(e){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,r);var t=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,(r.__proto__||Object.getPrototypeOf(r)).call(this));return t.handleBlur=function(){t.state.blurValue&&t.setState({value:t.state.blurValue,blurValue:null})},t.handleChange=function(e){t.setUpdatedValue(e.target.value,e)},t.handleKeyDown=function(e){var r=c(e.target.value);if(!isNaN(r)&&s(e.keyCode)){var n=t.getArrowOffset(),o=e.keyCode===u?r+n:r-n;t.setUpdatedValue(o,e)}},t.handleDrag=function(e){if(t.props.dragLabel){var r=Math.round(t.props.value+e.movementX);r>=0&&r<=t.props.dragMax&&t.props.onChange&&t.props.onChange(t.getValueObjectWithLabel(r),e)}},t.handleMouseDown=function(e){t.props.dragLabel&&(e.preventDefault(),t.handleDrag(e),window.addEventListener("mousemove",t.handleDrag),window.addEventListener("mouseup",t.handleMouseUp))},t.handleMouseUp=function(){t.unbindEventListeners()},t.unbindEventListeners=function(){window.removeEventListener("mousemove",t.handleDrag),window.removeEventListener("mouseup",t.handleMouseUp)},t.state={value:String(e.value).toUpperCase(),blurValue:String(e.value).toUpperCase()},t}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(r,e.PureComponent||e.Component),n(r,[{key:"componentWillReceiveProps",value:function(e){var t=this.input;e.value!==this.state.value&&(t===document.activeElement?this.setState({blurValue:String(e.value).toUpperCase()}):this.setState({value:String(e.value).toUpperCase(),blurValue:!this.state.blurValue&&String(e.value).toUpperCase()}))}},{key:"componentWillUnmount",value:function(){this.unbindEventListeners()}},{key:"getValueObjectWithLabel",value:function(e){return function(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}({},this.props.label,e)}},{key:"getArrowOffset",value:function(){return this.props.arrowOffset||1}},{key:"setUpdatedValue",value:function(e,t){var r=null!==this.props.label?this.getValueObjectWithLabel(e):e;this.props.onChange&&this.props.onChange(r,t);var n,o=function(e){return String(e).indexOf("%")>-1}(t.target.value);this.setState({value:o?(n=e,n+"%"):e})}},{key:"render",value:function(){var e=this,t=(0,a.default)({default:{wrap:{position:"relative"}},"user-override":{wrap:this.props.style&&this.props.style.wrap?this.props.style.wrap:{},input:this.props.style&&this.props.style.input?this.props.style.input:{},label:this.props.style&&this.props.style.label?this.props.style.label:{}},"dragLabel-true":{label:{cursor:"ew-resize"}}},{"user-override":!0},this.props);return o.default.createElement("div",{style:t.wrap},o.default.createElement("input",{style:t.input,ref:function(t){return e.input=t},value:this.state.value,onKeyDown:this.handleKeyDown,onChange:this.handleChange,onBlur:this.handleBlur,placeholder:this.props.placeholder,spellCheck:"false"}),this.props.label&&!this.props.hideLabel?o.default.createElement("span",{style:t.label,onMouseDown:this.handleMouseDown},this.props.label):null)}}]),r}();r.default=f});n(fa);fa.EditableInput;var pa=o(function(e,t){Object.defineProperty(t,"__esModule",{value:!0});t.calculateChange=function(e,t,r,n){var o=n.clientWidth,a=n.clientHeight,i="number"==typeof e.pageX?e.pageX:e.touches[0].pageX,u="number"==typeof e.pageY?e.pageY:e.touches[0].pageY,l=i-(n.getBoundingClientRect().left+window.pageXOffset),s=u-(n.getBoundingClientRect().top+window.pageYOffset);if("vertical"===t){var c=void 0;if(s<0)c=359;else if(s>a)c=0;else{c=360*(-100*s/a+100)/100}if(r.h!==c)return{h:c,s:r.s,l:r.l,a:r.a,source:"rgb"}}else{var f=void 0;if(l<0)f=0;else if(l>o)f=359;else{f=360*(100*l/o)/100}if(r.h!==f)return{h:f,s:r.s,l:r.l,a:r.a,source:"rgb"}}return null}});n(pa);pa.calculateChange;var da=o(function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.Hue=void 0;var n=function(){function e(e,t){for(var r=0;r=t||r<0||f&&e-s>=a}function v(){var e=va();if(h(e))return b(e);u=setTimeout(v,function(e){var r=t-(e-l);return f?Sa(r,a-(e-s)):r}(e))}function b(e){return u=void 0,p&&n?d(e):(n=o=void 0,i)}function g(){var e=va(),r=h(e);if(n=arguments,o=this,l=e,r){if(void 0===u)return function(e){return s=e,u=setTimeout(v,t),c?d(e):i}(l);if(f)return clearTimeout(u),u=setTimeout(v,t),d(l)}return void 0===u&&(u=setTimeout(v,t)),i}return t=xa(t)||0,ne(r)&&(c=!!r.leading,a=(f="maxWait"in r)?Oa(xa(r.maxWait)||0,t):a,p="trailing"in r?!!r.trailing:p),g.cancel=function(){void 0!==u&&clearTimeout(u),s=0,n=l=o=u=void 0},g.flush=function(){return void 0===u?i:b(va())},g},ka="Expected a function";var Ea=function(e,t,r){var n=!0,o=!0;if("function"!=typeof e)throw new TypeError(ka);return ne(r)&&(n="leading"in r?!!r.leading:n,o="trailing"in r?!!r.trailing:o),Ca(e,t,{leading:n,maxWait:t,trailing:o})},Aa=o(function(e,t){Object.defineProperty(t,"__esModule",{value:!0});t.calculateChange=function(e,t,r){var n=r.getBoundingClientRect(),o=n.width,a=n.height,i="number"==typeof e.pageX?e.pageX:e.touches[0].pageX,u="number"==typeof e.pageY?e.pageY:e.touches[0].pageY,l=i-(r.getBoundingClientRect().left+window.pageXOffset),s=u-(r.getBoundingClientRect().top+window.pageYOffset);l<0?l=0:l>o?l=o:s<0?s=0:s>a&&(s=a);var c=100*l/o,f=-100*s/a+100;return{h:t.h,s:c,v:f,a:t.a,source:"rgb"}}});n(Aa);Aa.calculateChange;var Ma=o(function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.Saturation=void 0;var n=function(){function e(e,t){for(var r=0;r1&&(r-=1),r<1/6?e+6*(t-e)*r:r<.5?t:r<2/3?e+(t-e)*(2/3-r)*6:e}if(e=M(e,360),t=M(t,100),r=M(r,100),0===t)n=o=a=r;else{var u=r<.5?r*(1+t):r+t-r*t,l=2*r-u;n=i(l,u,e+1/3),o=i(l,u,e),a=i(l,u,e-1/3)}return{r:255*n,g:255*o,b:255*a}}(e.h,l,c),f=!0,p="hsl"),e.hasOwnProperty("a")&&(a=e.a));var d,h,v;return a=A(a),{ok:f,format:e.format||p,r:i(255,u(o.r,0)),g:i(255,u(o.g,0)),b:i(255,u(o.b,0)),a:a}}(e);this._originalInput=e,this._r=c.r,this._g=c.g,this._b=c.b,this._a=c.a,this._roundA=a(100*this._a)/100,this._format=l.format||c.format,this._gradientType=l.gradientType,this._r<1&&(this._r=a(this._r)),this._g<1&&(this._g=a(this._g)),this._b<1&&(this._b=a(this._b)),this._ok=c.ok,this._tc_id=o++}function c(e,t,r){e=M(e,255),t=M(t,255),r=M(r,255);var n,o,a=u(e,t,r),l=i(e,t,r),s=(a+l)/2;if(a==l)n=o=0;else{var c=a-l;switch(o=s>.5?c/(2-a-l):c/(a+l),a){case e:n=(t-r)/c+(t>1)+720)%360;--t;)n.h=(n.h+o)%360,a.push(s(n));return a}function C(e,t){t=t||6;for(var r=s(e).toHsv(),n=r.h,o=r.s,a=r.v,i=[],u=1/t;t--;)i.push(s({h:n,s:o,v:a})),a=(a+u)%1;return i}s.prototype={isDark:function(){return this.getBrightness()<128},isLight:function(){return!this.isDark()},isValid:function(){return this._ok},getOriginalInput:function(){return this._originalInput},getFormat:function(){return this._format},getAlpha:function(){return this._a},getBrightness:function(){var e=this.toRgb();return(299*e.r+587*e.g+114*e.b)/1e3},getLuminance:function(){var e,r,n,o=this.toRgb();return e=o.r/255,r=o.g/255,n=o.b/255,.2126*(e<=.03928?e/12.92:t.pow((e+.055)/1.055,2.4))+.7152*(r<=.03928?r/12.92:t.pow((r+.055)/1.055,2.4))+.0722*(n<=.03928?n/12.92:t.pow((n+.055)/1.055,2.4))},setAlpha:function(e){return this._a=A(e),this._roundA=a(100*this._a)/100,this},toHsv:function(){var e=f(this._r,this._g,this._b);return{h:360*e.h,s:e.s,v:e.v,a:this._a}},toHsvString:function(){var e=f(this._r,this._g,this._b),t=a(360*e.h),r=a(100*e.s),n=a(100*e.v);return 1==this._a?"hsv("+t+", "+r+"%, "+n+"%)":"hsva("+t+", "+r+"%, "+n+"%, "+this._roundA+")"},toHsl:function(){var e=c(this._r,this._g,this._b);return{h:360*e.h,s:e.s,l:e.l,a:this._a}},toHslString:function(){var e=c(this._r,this._g,this._b),t=a(360*e.h),r=a(100*e.s),n=a(100*e.l);return 1==this._a?"hsl("+t+", "+r+"%, "+n+"%)":"hsla("+t+", "+r+"%, "+n+"%, "+this._roundA+")"},toHex:function(e){return p(this._r,this._g,this._b,e)},toHexString:function(e){return"#"+this.toHex(e)},toHex8:function(e){return function(e,t,r,n,o){var i=[T(a(e).toString(16)),T(a(t).toString(16)),T(a(r).toString(16)),T(H(n))];if(o&&i[0].charAt(0)==i[0].charAt(1)&&i[1].charAt(0)==i[1].charAt(1)&&i[2].charAt(0)==i[2].charAt(1)&&i[3].charAt(0)==i[3].charAt(1))return i[0].charAt(0)+i[1].charAt(0)+i[2].charAt(0)+i[3].charAt(0);return i.join("")}(this._r,this._g,this._b,this._a,e)},toHex8String:function(e){return"#"+this.toHex8(e)},toRgb:function(){return{r:a(this._r),g:a(this._g),b:a(this._b),a:this._a}},toRgbString:function(){return 1==this._a?"rgb("+a(this._r)+", "+a(this._g)+", "+a(this._b)+")":"rgba("+a(this._r)+", "+a(this._g)+", "+a(this._b)+", "+this._roundA+")"},toPercentageRgb:function(){return{r:a(100*M(this._r,255))+"%",g:a(100*M(this._g,255))+"%",b:a(100*M(this._b,255))+"%",a:this._a}},toPercentageRgbString:function(){return 1==this._a?"rgb("+a(100*M(this._r,255))+"%, "+a(100*M(this._g,255))+"%, "+a(100*M(this._b,255))+"%)":"rgba("+a(100*M(this._r,255))+"%, "+a(100*M(this._g,255))+"%, "+a(100*M(this._b,255))+"%, "+this._roundA+")"},toName:function(){return 0===this._a?"transparent":!(this._a<1)&&(E[p(this._r,this._g,this._b,!0)]||!1)},toFilter:function(e){var t="#"+d(this._r,this._g,this._b,this._a),r=t,n=this._gradientType?"GradientType = 1, ":"";if(e){var o=s(e);r="#"+d(o._r,o._g,o._b,o._a)}return"progid:DXImageTransform.Microsoft.gradient("+n+"startColorstr="+t+",endColorstr="+r+")"},toString:function(e){var t=!!e;e=e||this._format;var r=!1,n=this._a<1&&this._a>=0;return t||!n||"hex"!==e&&"hex6"!==e&&"hex3"!==e&&"hex4"!==e&&"hex8"!==e&&"name"!==e?("rgb"===e&&(r=this.toRgbString()),"prgb"===e&&(r=this.toPercentageRgbString()),"hex"!==e&&"hex6"!==e||(r=this.toHexString()),"hex3"===e&&(r=this.toHexString(!0)),"hex4"===e&&(r=this.toHex8String(!0)),"hex8"===e&&(r=this.toHex8String()),"name"===e&&(r=this.toName()),"hsl"===e&&(r=this.toHslString()),"hsv"===e&&(r=this.toHsvString()),r||this.toHexString()):"name"===e&&0===this._a?this.toName():this.toRgbString()},clone:function(){return s(this.toString())},_applyModification:function(e,t){var r=e.apply(null,[this].concat([].slice.call(t)));return this._r=r._r,this._g=r._g,this._b=r._b,this.setAlpha(r._a),this},lighten:function(){return this._applyModification(g,arguments)},brighten:function(){return this._applyModification(y,arguments)},darken:function(){return this._applyModification(_,arguments)},desaturate:function(){return this._applyModification(h,arguments)},saturate:function(){return this._applyModification(v,arguments)},greyscale:function(){return this._applyModification(b,arguments)},spin:function(){return this._applyModification(m,arguments)},_applyCombination:function(e,t){return e.apply(null,[this].concat([].slice.call(t)))},analogous:function(){return this._applyCombination(S,arguments)},complement:function(){return this._applyCombination(w,arguments)},monochromatic:function(){return this._applyCombination(C,arguments)},splitcomplement:function(){return this._applyCombination(O,arguments)},triad:function(){return this._applyCombination(x,arguments)},tetrad:function(){return this._applyCombination(j,arguments)}},s.fromRatio=function(e,t){if("object"==typeof e){var r={};for(var n in e)e.hasOwnProperty(n)&&(r[n]="a"===n?e[n]:F(e[n]));e=r}return s(e,t)},s.equals=function(e,t){return!(!e||!t)&&s(e).toRgbString()==s(t).toRgbString()},s.random=function(){return s.fromRatio({r:l(),g:l(),b:l()})},s.mix=function(e,t,r){r=0===r?0:r||50;var n=s(e).toRgb(),o=s(t).toRgb(),a=r/100;return s({r:(o.r-n.r)*a+n.r,g:(o.g-n.g)*a+n.g,b:(o.b-n.b)*a+n.b,a:(o.a-n.a)*a+n.a})},s.readability=function(e,r){var n=s(e),o=s(r);return(t.max(n.getLuminance(),o.getLuminance())+.05)/(t.min(n.getLuminance(),o.getLuminance())+.05)},s.isReadable=function(e,t,r){var n,o,a=s.readability(e,t);switch(o=!1,(n=function(e){var t,r;t=((e=e||{level:"AA",size:"small"}).level||"AA").toUpperCase(),r=(e.size||"small").toLowerCase(),"AA"!==t&&"AAA"!==t&&(t="AA");"small"!==r&&"large"!==r&&(r="small");return{level:t,size:r}}(r)).level+n.size){case"AAsmall":case"AAAlarge":o=a>=4.5;break;case"AAlarge":o=a>=3;break;case"AAAsmall":o=a>=7}return o},s.mostReadable=function(e,t,r){var n,o,a,i,u=null,l=0;o=(r=r||{}).includeFallbackColors,a=r.level,i=r.size;for(var c=0;cl&&(l=n,u=s(t[c]));return s.isReadable(e,u,{level:a,size:i})||!o?u:(r.includeFallbackColors=!1,s.mostReadable(e,["#fff","#000"],r))};var k=s.names={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"0ff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"00f",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",burntsienna:"ea7e5d",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"0ff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"f0f",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"},E=s.hexNames=function(e){var t={};for(var r in e)e.hasOwnProperty(r)&&(t[e[r]]=r);return t}(k);function A(e){return e=parseFloat(e),(isNaN(e)||e<0||e>1)&&(e=1),e}function M(e,r){(function(e){return"string"==typeof e&&-1!=e.indexOf(".")&&1===parseFloat(e)})(e)&&(e="100%");var n=function(e){return"string"==typeof e&&-1!=e.indexOf("%")}(e);return e=i(r,u(0,parseFloat(e))),n&&(e=parseInt(e*r,10)/100),t.abs(e-r)<1e-6?1:e%r/parseFloat(r)}function P(e){return i(1,u(0,e))}function R(e){return parseInt(e,16)}function T(e){return 1==e.length?"0"+e:""+e}function F(e){return e<=1&&(e=100*e+"%"),e}function H(e){return t.round(255*parseFloat(e)).toString(16)}function L(e){return R(e)/255}var z,D,U,B=(D="[\\s|\\(]+("+(z="(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)")+")[,|\\s]+("+z+")[,|\\s]+("+z+")\\s*\\)?",U="[\\s|\\(]+("+z+")[,|\\s]+("+z+")[,|\\s]+("+z+")[,|\\s]+("+z+")\\s*\\)?",{CSS_UNIT:new RegExp(z),rgb:new RegExp("rgb"+D),rgba:new RegExp("rgba"+U),hsl:new RegExp("hsl"+D),hsla:new RegExp("hsla"+U),hsv:new RegExp("hsv"+D),hsva:new RegExp("hsva"+U),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/});function W(e){return!!B.CSS_UNIT.exec(e)}e.exports?e.exports=s:window.tinycolor=s}(Math)}),Ta=o(function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.red=t.getContrastingColor=t.isValidHex=t.toState=t.simpleCheckForValidColor=void 0;var r=o(Pa),n=o(Ra);function o(e){return e&&e.__esModule?e:{default:e}}t.simpleCheckForValidColor=function(e){var t=0,n=0;return(0,r.default)(["r","g","b","a","h","s","l","v"],function(r){if(e[r]&&(t+=1,isNaN(e[r])||(n+=1),"s"===r||"l"===r)){/^\d+%$/.test(e[r])&&(n+=1)}}),t===n&&e};var a=t.toState=function(e,t){var r=e.hex?(0,n.default)(e.hex):(0,n.default)(e),o=r.toHsl(),a=r.toHsv(),i=r.toRgb(),u=r.toHex();return 0===o.s&&(o.h=t||0,a.h=t||0),{hsl:o,hex:"000000"===u&&0===i.a?"transparent":"#"+u,rgb:i,hsv:a,oldHue:e.h||t||o.h,source:e.source}};t.isValidHex=function(e){var t="#"===String(e).charAt(0)?1:0;return e.length!==4+t&&e.length<7+t&&(0,n.default)(e).isValid()},t.getContrastingColor=function(e){if(!e)return"#fff";var t=a(e);return"transparent"===t.hex?"rgba(0,0,0,0.4)":(299*t.rgb.r+587*t.rgb.g+114*t.rgb.b)/1e3>=128?"#000":"#fff"},t.red={hsl:{a:1,h:0,l:.5,s:1},hex:"#ff0000",rgb:{r:255,g:0,b:0,a:1},hsv:{h:0,s:1,v:1,a:1}};t.default=t});n(Ta);Ta.red,Ta.getContrastingColor,Ta.isValidHex,Ta.toState,Ta.simpleCheckForValidColor;var Fa=o(function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.ColorWrap=void 0;var n=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:"span";return function(r){function n(){var e,t,r;u(this,n);for(var o=arguments.length,a=Array(o),i=0;i100&&(e.a=100),e.a/=100,t({h:i.h,s:i.s,l:i.l,a:e.a,source:"rgb"},n))};return n.default.createElement("div",{style:s.fields,className:"flexbox-fix"},n.default.createElement("div",{style:s.double},n.default.createElement(za.EditableInput,{style:{input:s.input,label:s.label},label:"hex",value:u.replace("#",""),onChange:c})),n.default.createElement("div",{style:s.single},n.default.createElement(za.EditableInput,{style:{input:s.input,label:s.label},label:"r",value:r.r,onChange:c,dragLabel:"true",dragMax:"255"})),n.default.createElement("div",{style:s.single},n.default.createElement(za.EditableInput,{style:{input:s.input,label:s.label},label:"g",value:r.g,onChange:c,dragLabel:"true",dragMax:"255"})),n.default.createElement("div",{style:s.single},n.default.createElement(za.EditableInput,{style:{input:s.input,label:s.label},label:"b",value:r.b,onChange:c,dragLabel:"true",dragMax:"255"})),n.default.createElement("div",{style:s.alpha},n.default.createElement(za.EditableInput,{style:{input:s.input,label:s.label},label:"a",value:Math.round(100*r.a),onChange:c,dragLabel:"true",dragMax:"100"})))};r.default=u});n(Da);Da.SketchFields;var Ua=o(function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.SketchPresetColors=void 0;var n=Object.assign||function(e){for(var t=1;t-1&&e%1==0&&e-1&&e%1==0&&e<=W},N={};N["[object Float32Array]"]=N["[object Float64Array]"]=N["[object Int8Array]"]=N["[object Int16Array]"]=N["[object Int32Array]"]=N["[object Uint8Array]"]=N["[object Uint8ClampedArray]"]=N["[object Uint16Array]"]=N["[object Uint32Array]"]=!0,N["[object Arguments]"]=N["[object Array]"]=N["[object ArrayBuffer]"]=N["[object Boolean]"]=N["[object DataView]"]=N["[object Date]"]=N["[object Error]"]=N["[object Function]"]=N["[object Map]"]=N["[object Number]"]=N["[object Object]"]=N["[object RegExp]"]=N["[object Set]"]=N["[object String]"]=N["[object WeakMap]"]=!1;var V=function(e){return S(e)&&I(e.length)&&!!N[j(e)]};var $=function(e){return function(t){return e(t)}},X=o((function(e,t){var r=t&&!t.nodeType&&t,n=r&&e&&!e.nodeType&&e,o=n&&n.exports===r&&s.process,a=function(){try{var e=n&&n.require&&n.require("util").types;return e||o&&o.binding&&o.binding("util")}catch(e){}}();e.exports=a})),q=X&&X.isTypedArray,Y=q?$(q):V,K=Object.prototype.hasOwnProperty;var G=function(e,t){var r=O(e),n=!r&&H(e),o=!r&&!n&&z(e),a=!r&&!n&&!o&&Y(e),i=r||n||o||a,u=i?A(e.length,String):[],l=u.length;for(var s in e)!t&&!K.call(e,s)||i&&("length"==s||o&&("offset"==s||"parent"==s)||a&&("buffer"==s||"byteLength"==s||"byteOffset"==s)||B(s,l))||u.push(s);return u},J=Object.prototype;var Q=function(e){var t=e&&e.constructor;return e===("function"==typeof t&&t.prototype||J)};var Z=function(e,t){return function(r){return e(t(r))}},ee=Z(Object.keys,Object),te=Object.prototype.hasOwnProperty;var re=function(e){if(!Q(e))return ee(e);var t=[];for(var r in Object(e))te.call(e,r)&&"constructor"!=r&&t.push(r);return t};var ne=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)},oe="[object AsyncFunction]",ae="[object Function]",ie="[object GeneratorFunction]",ue="[object Proxy]";var le=function(e){if(!ne(e))return!1;var t=j(e);return t==ae||t==ie||t==oe||t==ue};var se=function(e){return null!=e&&I(e.length)&&!le(e)};var ce=function(e){return se(e)?G(e):re(e)};var fe=function(e,t){return e&&E(e,t,ce)};var pe=function(e){return e};var de=function(e){return"function"==typeof e?e:pe};var he=function(e,t){return e&&fe(e,de(t))},ve=Z(Object.getPrototypeOf,Object),be="[object Object]",ge=Function.prototype,ye=Object.prototype,_e=ge.toString,me=ye.hasOwnProperty,we=_e.call(Object);var xe=function(e){if(!S(e)||j(e)!=be)return!1;var t=ve(e);if(null===t)return!0;var r=me.call(t,"constructor")&&t.constructor;return"function"==typeof r&&r instanceof r&&_e.call(r)==we};var je=function(e,t){for(var r=-1,n=null==e?0:e.length,o=Array(n);++r-1};var Pe=function(e,t){var r=this.__data__,n=Ce(r,e);return n<0?(++this.size,r.push([e,t])):r[n][1]=t,this};function Re(e){var t=-1,r=null==e?0:e.length;for(this.clear();++tu))return!1;var s=a.get(e);if(s&&a.get(t))return s==t;var c=-1,f=!0,p=r&Tt?new At:void 0;for(a.set(e,t),a.set(t,e);++c0&&void 0!==arguments[0]?arguments[0]:[],i=[];return(0,a.default)(t,(function(t){Array.isArray(t)?e(t).map((function(e){return i.push(e)})):(0,o.default)(t)?(0,n.default)(t,(function(e,t){!0===e&&i.push(t),i.push(t+"-"+e)})):(0,r.default)(t)&&i.push(t)})),i};t.default=u}));n(yn);yn.flattenNames;var _n=function(e,t){for(var r=-1,n=null==e?0:e.length;++r1&&void 0!==arguments[1]?arguments[1]:[],a=e.default&&(0,n.default)(e.default)||{};return t.map((function(t){var n=e[t];return n&&(0,r.default)(n,(function(e,t){a[t]||(a[t]={}),a[t]=o({},a[t],n[t])})),t})),a};t.default=i}));n(zo);zo.mergeClasses;var Do=o((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.autoprefix=void 0;var r,n=(r=he)&&r.__esModule?r:{default:r},o=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:"span";return function(r){function n(){var r,l,s;i(this,n);for(var c=arguments.length,f=Array(c),p=0;p1&&void 0!==arguments[1]?arguments[1]:"span";return function(r){function n(){var r,l,s;i(this,n);for(var c=arguments.length,f=Array(c),p=0;p1&&void 0!==arguments[1])||arguments[1];r[e]=t};return 0===e&&n("first-child"),e===t-1&&n("last-child"),(0===e||e%2==0)&&n("even"),1===Math.abs(e%2)&&n("odd"),n("nth-child",e),r}}));n(Wo);var Io=o((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ReactCSS=t.loop=t.handleActive=t.handleHover=t.hover=void 0;var r=l(yn),n=l(zo),o=l(Do),a=l(Uo),i=l(Bo),u=l(Wo);function l(e){return e&&e.__esModule?e:{default:e}}t.hover=a.default,t.handleHover=a.default,t.handleActive=i.default,t.loop=u.default;var s=t.ReactCSS=function(e){for(var t=arguments.length,a=Array(t>1?t-1:0),i=1;i0){if(++t>=ea)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}(Zo);var oa=function(e,t){return na(Jo(e,t,pe),e+"")};var aa=function(e,t,r){if(!ne(r))return!1;var n=typeof t;return!!("number"==n?se(r)&&B(t,r.length):"string"==n&&t in r)&&Se(r[t],e)};var ia=function(e){return oa((function(t,r){var n=-1,o=r.length,a=o>1?r[o-1]:void 0,i=o>2?r[2]:void 0;for(a=e.length>3&&"function"==typeof a?(o--,a):void 0,i&&aa(r[0],r[1],i)&&(a=o<3?void 0:a,o=1),t=Object(t);++ni?1:Math.round(100*c/i)/100,t.a!==f)return{h:t.h,s:t.s,l:t.l,a:f,source:"rgb"}}else{var p=void 0;if(n!==(p=s<0?0:s>a?1:Math.round(100*s/a)/100))return{h:t.h,s:t.s,l:t.l,a:p,source:"rgb"}}return null}}));n(ua);ua.calculateChange;var la=o((function(e,t){Object.defineProperty(t,"__esModule",{value:!0});var r={},n=t.render=function(e,t,r,n){if("undefined"==typeof document&&!n)return null;var o=n?new n:document.createElement("canvas");o.width=2*r,o.height=2*r;var a=o.getContext("2d");return a?(a.fillStyle=e,a.fillRect(0,0,o.width,o.height),a.fillStyle=t,a.fillRect(0,0,r,r),a.translate(r,r),a.fillRect(0,0,r,r),o.toDataURL()):null};t.get=function(e,t,o,a){var i=e+"-"+t+"-"+o+(a?"-server":"");if(r[i])return r[i];var u=n(e,t,o,a);return r[i]=u,u}}));n(la);la.render,la.get;var sa=o((function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.Checkboard=void 0;var n=i(e),o=i(Io),a=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}(la);function i(e){return e&&e.__esModule?e:{default:e}}var u=r.Checkboard=function(e){var t=e.white,r=e.grey,i=e.size,u=e.renderers,l=e.borderRadius,s=e.boxShadow,c=(0,o.default)({default:{grid:{borderRadius:l,boxShadow:s,absolute:"0px 0px 0px 0px",background:"url("+a.get(t,r,i,u.canvas)+") center left"}}});return n.default.createElement("div",{style:c.grid})};u.defaultProps={size:8,white:"transparent",grey:"rgba(0,0,0,.08)",renderers:{}},r.default=u}));n(sa);sa.Checkboard;var ca=o((function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.Alpha=void 0;var n=Object.assign||function(e){for(var t=1;t-1},c=function(e){return Number(String(e).replace(/%/g,""))},f=r.EditableInput=function(e){function t(e){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t);var r=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));return r.handleBlur=function(){r.state.blurValue&&r.setState({value:r.state.blurValue,blurValue:null})},r.handleChange=function(e){r.setUpdatedValue(e.target.value,e)},r.handleKeyDown=function(e){var t=c(e.target.value);if(!isNaN(t)&&s(e.keyCode)){var n=r.getArrowOffset(),o=e.keyCode===u?t+n:t-n;r.setUpdatedValue(o,e)}},r.handleDrag=function(e){if(r.props.dragLabel){var t=Math.round(r.props.value+e.movementX);t>=0&&t<=r.props.dragMax&&r.props.onChange&&r.props.onChange(r.getValueObjectWithLabel(t),e)}},r.handleMouseDown=function(e){r.props.dragLabel&&(e.preventDefault(),r.handleDrag(e),window.addEventListener("mousemove",r.handleDrag),window.addEventListener("mouseup",r.handleMouseUp))},r.handleMouseUp=function(){r.unbindEventListeners()},r.unbindEventListeners=function(){window.removeEventListener("mousemove",r.handleDrag),window.removeEventListener("mouseup",r.handleMouseUp)},r.state={value:String(e.value).toUpperCase(),blurValue:String(e.value).toUpperCase()},r}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),n(t,[{key:"componentWillReceiveProps",value:function(e){var t=this.input;e.value!==this.state.value&&(t===document.activeElement?this.setState({blurValue:String(e.value).toUpperCase()}):this.setState({value:String(e.value).toUpperCase(),blurValue:!this.state.blurValue&&String(e.value).toUpperCase()}))}},{key:"componentWillUnmount",value:function(){this.unbindEventListeners()}},{key:"getValueObjectWithLabel",value:function(e){return function(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}({},this.props.label,e)}},{key:"getArrowOffset",value:function(){return this.props.arrowOffset||1}},{key:"setUpdatedValue",value:function(e,t){var r=null!==this.props.label?this.getValueObjectWithLabel(e):e;this.props.onChange&&this.props.onChange(r,t);var n,o=function(e){return String(e).indexOf("%")>-1}(t.target.value);this.setState({value:o?(n=e,n+"%"):e})}},{key:"render",value:function(){var e=this,t=(0,a.default)({default:{wrap:{position:"relative"}},"user-override":{wrap:this.props.style&&this.props.style.wrap?this.props.style.wrap:{},input:this.props.style&&this.props.style.input?this.props.style.input:{},label:this.props.style&&this.props.style.label?this.props.style.label:{}},"dragLabel-true":{label:{cursor:"ew-resize"}}},{"user-override":!0},this.props);return o.default.createElement("div",{style:t.wrap},o.default.createElement("input",{style:t.input,ref:function(t){return e.input=t},value:this.state.value,onKeyDown:this.handleKeyDown,onChange:this.handleChange,onBlur:this.handleBlur,placeholder:this.props.placeholder,spellCheck:"false"}),this.props.label&&!this.props.hideLabel?o.default.createElement("span",{style:t.label,onMouseDown:this.handleMouseDown},this.props.label):null)}}]),t}(e.PureComponent||e.Component);r.default=f}));n(fa);fa.EditableInput;var pa=o((function(e,t){Object.defineProperty(t,"__esModule",{value:!0});t.calculateChange=function(e,t,r,n){var o=n.clientWidth,a=n.clientHeight,i="number"==typeof e.pageX?e.pageX:e.touches[0].pageX,u="number"==typeof e.pageY?e.pageY:e.touches[0].pageY,l=i-(n.getBoundingClientRect().left+window.pageXOffset),s=u-(n.getBoundingClientRect().top+window.pageYOffset);if("vertical"===t){var c=void 0;if(s<0)c=359;else if(s>a)c=0;else{c=360*(-100*s/a+100)/100}if(r.h!==c)return{h:c,s:r.s,l:r.l,a:r.a,source:"rgb"}}else{var f=void 0;if(l<0)f=0;else if(l>o)f=359;else{f=360*(100*l/o)/100}if(r.h!==f)return{h:f,s:r.s,l:r.l,a:r.a,source:"rgb"}}return null}}));n(pa);pa.calculateChange;var da=o((function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.Hue=void 0;var n=function(){function e(e,t){for(var r=0;r=t||r<0||f&&e-s>=a}function v(){var e=va();if(h(e))return b(e);u=setTimeout(v,function(e){var r=t-(e-l);return f?Sa(r,a-(e-s)):r}(e))}function b(e){return u=void 0,p&&n?d(e):(n=o=void 0,i)}function g(){var e=va(),r=h(e);if(n=arguments,o=this,l=e,r){if(void 0===u)return function(e){return s=e,u=setTimeout(v,t),c?d(e):i}(l);if(f)return clearTimeout(u),u=setTimeout(v,t),d(l)}return void 0===u&&(u=setTimeout(v,t)),i}return t=xa(t)||0,ne(r)&&(c=!!r.leading,a=(f="maxWait"in r)?Oa(xa(r.maxWait)||0,t):a,p="trailing"in r?!!r.trailing:p),g.cancel=function(){void 0!==u&&clearTimeout(u),s=0,n=l=o=u=void 0},g.flush=function(){return void 0===u?i:b(va())},g},ka="Expected a function";var Ea=function(e,t,r){var n=!0,o=!0;if("function"!=typeof e)throw new TypeError(ka);return ne(r)&&(n="leading"in r?!!r.leading:n,o="trailing"in r?!!r.trailing:o),Ca(e,t,{leading:n,maxWait:t,trailing:o})},Aa=o((function(e,t){Object.defineProperty(t,"__esModule",{value:!0});t.calculateChange=function(e,t,r){var n=r.getBoundingClientRect(),o=n.width,a=n.height,i="number"==typeof e.pageX?e.pageX:e.touches[0].pageX,u="number"==typeof e.pageY?e.pageY:e.touches[0].pageY,l=i-(r.getBoundingClientRect().left+window.pageXOffset),s=u-(r.getBoundingClientRect().top+window.pageYOffset);l<0?l=0:l>o?l=o:s<0?s=0:s>a&&(s=a);var c=100*l/o,f=-100*s/a+100;return{h:t.h,s:c,v:f,a:t.a,source:"rgb"}}}));n(Aa);Aa.calculateChange;var Ma=o((function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.Saturation=void 0;var n=function(){function e(e,t){for(var r=0;r1&&(r-=1),r<1/6?e+6*(t-e)*r:r<.5?t:r<2/3?e+(t-e)*(2/3-r)*6:e}if(e=M(e,360),t=M(t,100),r=M(r,100),0===t)n=o=a=r;else{var u=r<.5?r*(1+t):r+t-r*t,l=2*r-u;n=i(l,u,e+1/3),o=i(l,u,e),a=i(l,u,e-1/3)}return{r:255*n,g:255*o,b:255*a}}(e.h,l,c),f=!0,p="hsl"),e.hasOwnProperty("a")&&(a=e.a));var d,h,v;return a=A(a),{ok:f,format:e.format||p,r:i(255,u(o.r,0)),g:i(255,u(o.g,0)),b:i(255,u(o.b,0)),a:a}}(e);this._originalInput=e,this._r=c.r,this._g=c.g,this._b=c.b,this._a=c.a,this._roundA=a(100*this._a)/100,this._format=l.format||c.format,this._gradientType=l.gradientType,this._r<1&&(this._r=a(this._r)),this._g<1&&(this._g=a(this._g)),this._b<1&&(this._b=a(this._b)),this._ok=c.ok,this._tc_id=o++}function c(e,t,r){e=M(e,255),t=M(t,255),r=M(r,255);var n,o,a=u(e,t,r),l=i(e,t,r),s=(a+l)/2;if(a==l)n=o=0;else{var c=a-l;switch(o=s>.5?c/(2-a-l):c/(a+l),a){case e:n=(t-r)/c+(t>1)+720)%360;--t;)n.h=(n.h+o)%360,a.push(s(n));return a}function C(e,t){t=t||6;for(var r=s(e).toHsv(),n=r.h,o=r.s,a=r.v,i=[],u=1/t;t--;)i.push(s({h:n,s:o,v:a})),a=(a+u)%1;return i}s.prototype={isDark:function(){return this.getBrightness()<128},isLight:function(){return!this.isDark()},isValid:function(){return this._ok},getOriginalInput:function(){return this._originalInput},getFormat:function(){return this._format},getAlpha:function(){return this._a},getBrightness:function(){var e=this.toRgb();return(299*e.r+587*e.g+114*e.b)/1e3},getLuminance:function(){var e,r,n,o=this.toRgb();return e=o.r/255,r=o.g/255,n=o.b/255,.2126*(e<=.03928?e/12.92:t.pow((e+.055)/1.055,2.4))+.7152*(r<=.03928?r/12.92:t.pow((r+.055)/1.055,2.4))+.0722*(n<=.03928?n/12.92:t.pow((n+.055)/1.055,2.4))},setAlpha:function(e){return this._a=A(e),this._roundA=a(100*this._a)/100,this},toHsv:function(){var e=f(this._r,this._g,this._b);return{h:360*e.h,s:e.s,v:e.v,a:this._a}},toHsvString:function(){var e=f(this._r,this._g,this._b),t=a(360*e.h),r=a(100*e.s),n=a(100*e.v);return 1==this._a?"hsv("+t+", "+r+"%, "+n+"%)":"hsva("+t+", "+r+"%, "+n+"%, "+this._roundA+")"},toHsl:function(){var e=c(this._r,this._g,this._b);return{h:360*e.h,s:e.s,l:e.l,a:this._a}},toHslString:function(){var e=c(this._r,this._g,this._b),t=a(360*e.h),r=a(100*e.s),n=a(100*e.l);return 1==this._a?"hsl("+t+", "+r+"%, "+n+"%)":"hsla("+t+", "+r+"%, "+n+"%, "+this._roundA+")"},toHex:function(e){return p(this._r,this._g,this._b,e)},toHexString:function(e){return"#"+this.toHex(e)},toHex8:function(e){return function(e,t,r,n,o){var i=[T(a(e).toString(16)),T(a(t).toString(16)),T(a(r).toString(16)),T(H(n))];if(o&&i[0].charAt(0)==i[0].charAt(1)&&i[1].charAt(0)==i[1].charAt(1)&&i[2].charAt(0)==i[2].charAt(1)&&i[3].charAt(0)==i[3].charAt(1))return i[0].charAt(0)+i[1].charAt(0)+i[2].charAt(0)+i[3].charAt(0);return i.join("")}(this._r,this._g,this._b,this._a,e)},toHex8String:function(e){return"#"+this.toHex8(e)},toRgb:function(){return{r:a(this._r),g:a(this._g),b:a(this._b),a:this._a}},toRgbString:function(){return 1==this._a?"rgb("+a(this._r)+", "+a(this._g)+", "+a(this._b)+")":"rgba("+a(this._r)+", "+a(this._g)+", "+a(this._b)+", "+this._roundA+")"},toPercentageRgb:function(){return{r:a(100*M(this._r,255))+"%",g:a(100*M(this._g,255))+"%",b:a(100*M(this._b,255))+"%",a:this._a}},toPercentageRgbString:function(){return 1==this._a?"rgb("+a(100*M(this._r,255))+"%, "+a(100*M(this._g,255))+"%, "+a(100*M(this._b,255))+"%)":"rgba("+a(100*M(this._r,255))+"%, "+a(100*M(this._g,255))+"%, "+a(100*M(this._b,255))+"%, "+this._roundA+")"},toName:function(){return 0===this._a?"transparent":!(this._a<1)&&(E[p(this._r,this._g,this._b,!0)]||!1)},toFilter:function(e){var t="#"+d(this._r,this._g,this._b,this._a),r=t,n=this._gradientType?"GradientType = 1, ":"";if(e){var o=s(e);r="#"+d(o._r,o._g,o._b,o._a)}return"progid:DXImageTransform.Microsoft.gradient("+n+"startColorstr="+t+",endColorstr="+r+")"},toString:function(e){var t=!!e;e=e||this._format;var r=!1,n=this._a<1&&this._a>=0;return t||!n||"hex"!==e&&"hex6"!==e&&"hex3"!==e&&"hex4"!==e&&"hex8"!==e&&"name"!==e?("rgb"===e&&(r=this.toRgbString()),"prgb"===e&&(r=this.toPercentageRgbString()),"hex"!==e&&"hex6"!==e||(r=this.toHexString()),"hex3"===e&&(r=this.toHexString(!0)),"hex4"===e&&(r=this.toHex8String(!0)),"hex8"===e&&(r=this.toHex8String()),"name"===e&&(r=this.toName()),"hsl"===e&&(r=this.toHslString()),"hsv"===e&&(r=this.toHsvString()),r||this.toHexString()):"name"===e&&0===this._a?this.toName():this.toRgbString()},clone:function(){return s(this.toString())},_applyModification:function(e,t){var r=e.apply(null,[this].concat([].slice.call(t)));return this._r=r._r,this._g=r._g,this._b=r._b,this.setAlpha(r._a),this},lighten:function(){return this._applyModification(g,arguments)},brighten:function(){return this._applyModification(y,arguments)},darken:function(){return this._applyModification(_,arguments)},desaturate:function(){return this._applyModification(h,arguments)},saturate:function(){return this._applyModification(v,arguments)},greyscale:function(){return this._applyModification(b,arguments)},spin:function(){return this._applyModification(m,arguments)},_applyCombination:function(e,t){return e.apply(null,[this].concat([].slice.call(t)))},analogous:function(){return this._applyCombination(S,arguments)},complement:function(){return this._applyCombination(w,arguments)},monochromatic:function(){return this._applyCombination(C,arguments)},splitcomplement:function(){return this._applyCombination(O,arguments)},triad:function(){return this._applyCombination(x,arguments)},tetrad:function(){return this._applyCombination(j,arguments)}},s.fromRatio=function(e,t){if("object"==typeof e){var r={};for(var n in e)e.hasOwnProperty(n)&&(r[n]="a"===n?e[n]:F(e[n]));e=r}return s(e,t)},s.equals=function(e,t){return!(!e||!t)&&s(e).toRgbString()==s(t).toRgbString()},s.random=function(){return s.fromRatio({r:l(),g:l(),b:l()})},s.mix=function(e,t,r){r=0===r?0:r||50;var n=s(e).toRgb(),o=s(t).toRgb(),a=r/100;return s({r:(o.r-n.r)*a+n.r,g:(o.g-n.g)*a+n.g,b:(o.b-n.b)*a+n.b,a:(o.a-n.a)*a+n.a})},s.readability=function(e,r){var n=s(e),o=s(r);return(t.max(n.getLuminance(),o.getLuminance())+.05)/(t.min(n.getLuminance(),o.getLuminance())+.05)},s.isReadable=function(e,t,r){var n,o,a=s.readability(e,t);switch(o=!1,(n=function(e){var t,r;t=((e=e||{level:"AA",size:"small"}).level||"AA").toUpperCase(),r=(e.size||"small").toLowerCase(),"AA"!==t&&"AAA"!==t&&(t="AA");"small"!==r&&"large"!==r&&(r="small");return{level:t,size:r}}(r)).level+n.size){case"AAsmall":case"AAAlarge":o=a>=4.5;break;case"AAlarge":o=a>=3;break;case"AAAsmall":o=a>=7}return o},s.mostReadable=function(e,t,r){var n,o,a,i,u=null,l=0;o=(r=r||{}).includeFallbackColors,a=r.level,i=r.size;for(var c=0;cl&&(l=n,u=s(t[c]));return s.isReadable(e,u,{level:a,size:i})||!o?u:(r.includeFallbackColors=!1,s.mostReadable(e,["#fff","#000"],r))};var k=s.names={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"0ff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"00f",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",burntsienna:"ea7e5d",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"0ff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"f0f",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"},E=s.hexNames=function(e){var t={};for(var r in e)e.hasOwnProperty(r)&&(t[e[r]]=r);return t}(k);function A(e){return e=parseFloat(e),(isNaN(e)||e<0||e>1)&&(e=1),e}function M(e,r){(function(e){return"string"==typeof e&&-1!=e.indexOf(".")&&1===parseFloat(e)})(e)&&(e="100%");var n=function(e){return"string"==typeof e&&-1!=e.indexOf("%")}(e);return e=i(r,u(0,parseFloat(e))),n&&(e=parseInt(e*r,10)/100),t.abs(e-r)<1e-6?1:e%r/parseFloat(r)}function P(e){return i(1,u(0,e))}function R(e){return parseInt(e,16)}function T(e){return 1==e.length?"0"+e:""+e}function F(e){return e<=1&&(e=100*e+"%"),e}function H(e){return t.round(255*parseFloat(e)).toString(16)}function L(e){return R(e)/255}var z,D,U,B=(D="[\\s|\\(]+("+(z="(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)")+")[,|\\s]+("+z+")[,|\\s]+("+z+")\\s*\\)?",U="[\\s|\\(]+("+z+")[,|\\s]+("+z+")[,|\\s]+("+z+")[,|\\s]+("+z+")\\s*\\)?",{CSS_UNIT:new RegExp(z),rgb:new RegExp("rgb"+D),rgba:new RegExp("rgba"+U),hsl:new RegExp("hsl"+D),hsla:new RegExp("hsla"+U),hsv:new RegExp("hsv"+D),hsva:new RegExp("hsva"+U),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/});function W(e){return!!B.CSS_UNIT.exec(e)}e.exports?e.exports=s:window.tinycolor=s}(Math)})),Ta=o((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.red=t.getContrastingColor=t.isValidHex=t.toState=t.simpleCheckForValidColor=void 0;var r=o(Pa),n=o(Ra);function o(e){return e&&e.__esModule?e:{default:e}}t.simpleCheckForValidColor=function(e){var t=0,n=0;return(0,r.default)(["r","g","b","a","h","s","l","v"],(function(r){if(e[r]&&(t+=1,isNaN(e[r])||(n+=1),"s"===r||"l"===r)){/^\d+%$/.test(e[r])&&(n+=1)}})),t===n&&e};var a=t.toState=function(e,t){var r=e.hex?(0,n.default)(e.hex):(0,n.default)(e),o=r.toHsl(),a=r.toHsv(),i=r.toRgb(),u=r.toHex();return 0===o.s&&(o.h=t||0,a.h=t||0),{hsl:o,hex:"000000"===u&&0===i.a?"transparent":"#"+u,rgb:i,hsv:a,oldHue:e.h||t||o.h,source:e.source}};t.isValidHex=function(e){var t="#"===String(e).charAt(0)?1:0;return e.length!==4+t&&e.length<7+t&&(0,n.default)(e).isValid()},t.getContrastingColor=function(e){if(!e)return"#fff";var t=a(e);return"transparent"===t.hex?"rgba(0,0,0,0.4)":(299*t.rgb.r+587*t.rgb.g+114*t.rgb.b)/1e3>=128?"#000":"#fff"},t.red={hsl:{a:1,h:0,l:.5,s:1},hex:"#ff0000",rgb:{r:255,g:0,b:0,a:1},hsv:{h:0,s:1,v:1,a:1}};t.default=t}));n(Ta);Ta.red,Ta.getContrastingColor,Ta.isValidHex,Ta.toState,Ta.simpleCheckForValidColor;var Fa=o((function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.ColorWrap=void 0;var n=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:"span";return function(r){function n(){var e,t,r;u(this,n);for(var o=arguments.length,a=Array(o),i=0;i100&&(e.a=100),e.a/=100,t({h:i.h,s:i.s,l:i.l,a:e.a,source:"rgb"},n))};return n.default.createElement("div",{style:s.fields,className:"flexbox-fix"},n.default.createElement("div",{style:s.double},n.default.createElement(za.EditableInput,{style:{input:s.input,label:s.label},label:"hex",value:u.replace("#",""),onChange:c})),n.default.createElement("div",{style:s.single},n.default.createElement(za.EditableInput,{style:{input:s.input,label:s.label},label:"r",value:r.r,onChange:c,dragLabel:"true",dragMax:"255"})),n.default.createElement("div",{style:s.single},n.default.createElement(za.EditableInput,{style:{input:s.input,label:s.label},label:"g",value:r.g,onChange:c,dragLabel:"true",dragMax:"255"})),n.default.createElement("div",{style:s.single},n.default.createElement(za.EditableInput,{style:{input:s.input,label:s.label},label:"b",value:r.b,onChange:c,dragLabel:"true",dragMax:"255"})),n.default.createElement("div",{style:s.alpha},n.default.createElement(za.EditableInput,{style:{input:s.input,label:s.label},label:"a",value:Math.round(100*r.a),onChange:c,dragLabel:"true",dragMax:"100"})))};r.default=u}));n(Da);Da.SketchFields;var Ua=o((function(t,r){Object.defineProperty(r,"__esModule",{value:!0}),r.SketchPresetColors=void 0;var n=Object.assign||function(e){for(var t=1;t 0 && arguments[0] !== undefined ? arguments[0] : 0; return state + 1; -}function ownKeys$3(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { keys.push.apply(keys, Object.getOwnPropertySymbols(object)); } if (enumerableOnly) keys = keys.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); return keys; } +}function ownKeys$3(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } -function _objectSpread$3(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys$3(source, true).forEach(function (key) { _defineProperty$3(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys$3(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } +function _objectSpread$3(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys$3(Object(source), true).forEach(function (key) { _defineProperty$3(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys$3(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty$3(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function reduce() { @@ -1705,7 +1705,7 @@ function _slicedToArray$2(arr, i) { return _arrayWithHoles$2(arr) || _iterableTo function _nonIterableRest$2() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } -function _iterableToArrayLimit$2(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } +function _iterableToArrayLimit$2(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles$2(arr) { if (Array.isArray(arr)) return arr; } @@ -1751,7 +1751,7 @@ function mapContainsValue(map, searchValue) { return true; } - isDone = done; + isDone = !!done; } while (!isDone); return false; @@ -1959,6 +1959,7 @@ function () { }, { key: "getActions", value: function getActions() { + /* eslint-disable-next-line @typescript-eslint/no-this-alias */ var manager = this; var dispatch = this.store.dispatch; @@ -2465,7 +2466,7 @@ function () { function _nonIterableRest$3() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } -function _iterableToArrayLimit$3(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } +function _iterableToArrayLimit$3(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles$3(arr) { if (Array.isArray(arr)) return arr; } @@ -2535,7 +2536,7 @@ function useDragHandler(spec, monitor, connector) { function _nonIterableRest$4() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } -function _iterableToArrayLimit$4(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } +function _iterableToArrayLimit$4(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles$4(arr) { if (Array.isArray(arr)) return arr; } /** @@ -2813,7 +2814,7 @@ function () { function _nonIterableRest$5() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } -function _iterableToArrayLimit$5(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } +function _iterableToArrayLimit$5(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles$5(arr) { if (Array.isArray(arr)) return arr; } function useDropTargetMonitor() { @@ -2864,7 +2865,7 @@ function useDropHandler(spec, monitor, connector) { function _nonIterableRest$6() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } -function _iterableToArrayLimit$6(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } +function _iterableToArrayLimit$6(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles$6(arr) { if (Array.isArray(arr)) return arr; } /** @@ -2896,9 +2897,18 @@ function useDrop(spec) { connector.reconnect(); }, [spec.options]); return [result, connectDropTarget]; -}function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } +}function _slicedToArray$7(arr, i) { return _arrayWithHoles$7(arr) || _iterableToArrayLimit$7(arr, i) || _nonIterableRest$7(); } + +function _nonIterableRest$7() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } + +function _iterableToArrayLimit$7(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } + +function _arrayWithHoles$7(arr) { if (Array.isArray(arr)) return arr; } + +function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } +var refCount$1 = 0; /** * A React component that provides the React-DnD context */ @@ -2907,13 +2917,53 @@ var DndProvider = React.memo(function (_ref) { var children = _ref.children, props = _objectWithoutProperties(_ref, ["children"]); - var context = 'manager' in props ? { - dragDropManager: props.manager - } : createSingletonDndContext(props.backend, props.context, props.options, props.debugMode); + var _getDndContextValue = getDndContextValue(props), + _getDndContextValue2 = _slicedToArray$7(_getDndContextValue, 2), + manager = _getDndContextValue2[0], + isGlobalInstance = _getDndContextValue2[1]; // memoized from props + + /** + * If the global context was used to store the DND context + * then where theres no more references to it we should + * clean it up to avoid memory leaks + */ + + + React.useEffect(function () { + if (isGlobalInstance) { + refCount$1++; + } + + return function () { + if (isGlobalInstance) { + refCount$1--; + + if (refCount$1 === 0) { + var context = getGlobalContext(); + context[instanceSymbol] = null; + } + } + }; + }, []); return React.createElement(DndContext.Provider, { - value: context + value: manager }, children); }); +DndProvider.displayName = 'DndProvider'; + +function getDndContextValue(props) { + if ('manager' in props) { + var _manager = { + dragDropManager: props.manager + }; + return [_manager, false]; + } + + var manager = createSingletonDndContext(props.backend, props.context, props.options, props.debugMode); + var isGlobalInstance = !props.context; + return [manager, isGlobalInstance]; +} + var instanceSymbol = Symbol.for('__REACT_DND_CONTEXT_INSTANCE__'); function createSingletonDndContext(backend) { @@ -2931,26 +2981,7 @@ function createSingletonDndContext(backend) { function getGlobalContext() { return typeof global$1 !== 'undefined' ? global$1 : window; -}/* - * A utility for rendering a drag preview image - */ - -var DragPreviewImage = React.memo(function (_ref) { - var connect = _ref.connect, - src = _ref.src; - - if (typeof Image !== 'undefined') { - var img = new Image(); - img.src = src; - - img.onload = function () { - return connect(img); - }; - } - - return null; -}); -DragPreviewImage.displayName = 'DragPreviewImage';// cheap lodash replacements +}// cheap lodash replacements function memoize(fn) { var result = null; @@ -3253,7 +3284,7 @@ function getDragPreviewOffset(sourceNode, dragPreview, clientOffset, anchorPoint }; }var FILE = '__NATIVE_FILE__'; var URL = '__NATIVE_URL__'; -var TEXT = '__NATIVE_TEXT__';var NativeTypes = /*#__PURE__*/Object.freeze({FILE: FILE,URL: URL,TEXT: TEXT});function getDataFromDataTransfer(dataTransfer, typesToTry, defaultValue) { +var TEXT = '__NATIVE_TEXT__';var NativeTypes=/*#__PURE__*/Object.freeze({__proto__:null,FILE: FILE,URL: URL,TEXT: TEXT});function getDataFromDataTransfer(dataTransfer, typesToTry, defaultValue) { var result = typesToTry.reduce(function (resultSoFar, typeToTry) { return resultSoFar || dataTransfer.getData(typeToTry); }, ''); @@ -3295,41 +3326,46 @@ var NativeDragSource = /*#__PURE__*/ function () { function NativeDragSource(config) { - var _this = this; - _classCallCheck$9(this, NativeDragSource); this.config = config; this.item = {}; - Object.keys(this.config.exposeProperties).forEach(function (property) { - Object.defineProperty(_this.item, property, { - configurable: true, - enumerable: true, - get: function get() { - // eslint-disable-next-line no-console - console.warn("Browser doesn't allow reading \"".concat(property, "\" until the drop event.")); - return null; - } - }); - }); + this.initializeExposedProperties(); } _createClass$9(NativeDragSource, [{ - key: "mutateItemByReadingDataTransfer", - value: function mutateItemByReadingDataTransfer(dataTransfer) { + key: "initializeExposedProperties", + value: function initializeExposedProperties() { + var _this = this; + + Object.keys(this.config.exposeProperties).forEach(function (property) { + Object.defineProperty(_this.item, property, { + configurable: true, + enumerable: true, + get: function get() { + // eslint-disable-next-line no-console + console.warn("Browser doesn't allow reading \"".concat(property, "\" until the drop event.")); + return null; + } + }); + }); + } + }, { + key: "loadDataTransfer", + value: function loadDataTransfer(dataTransfer) { var _this2 = this; - var newProperties = {}; - if (dataTransfer) { + var newProperties = {}; Object.keys(this.config.exposeProperties).forEach(function (property) { newProperties[property] = { - value: _this2.config.exposeProperties[property](dataTransfer, _this2.config.matchesTypes) + value: _this2.config.exposeProperties[property](dataTransfer, _this2.config.matchesTypes), + configurable: true, + enumerable: true }; }); + Object.defineProperties(this.item, newProperties); } - - Object.defineProperties(this.item, newProperties); } }, { key: "canDrag", @@ -3353,8 +3389,10 @@ function () { }]); return NativeDragSource; -}();function createNativeDragSource(type) { - return new NativeDragSource(nativeTypesConfig[type]); +}();function createNativeDragSource(type, dataTransfer) { + var result = new NativeDragSource(nativeTypesConfig[type]); + result.loadDataTransfer(dataTransfer); + return result; } function matchNativeItemType(dataTransfer) { if (!dataTransfer) { @@ -3406,9 +3444,9 @@ function () { }]); return OptionsReader; -}();function ownKeys$4(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { keys.push.apply(keys, Object.getOwnPropertySymbols(object)); } if (enumerableOnly) keys = keys.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); return keys; } +}();function ownKeys$4(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } -function _objectSpread$4(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys$4(source, true).forEach(function (key) { _defineProperty$5(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys$4(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } +function _objectSpread$4(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys$4(Object(source), true).forEach(function (key) { _defineProperty$5(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys$4(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty$5(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -3608,7 +3646,7 @@ function () { if (nativeType) { // A native item (such as file or URL) dragged from outside the document - _this.beginDragNativeItem(nativeType); + _this.beginDragNativeItem(nativeType, dataTransfer); } }; @@ -3718,7 +3756,7 @@ function () { e.preventDefault(); if (_this.isDraggingNativeItem()) { - _this.currentNativeSource.mutateItemByReadingDataTransfer(e.dataTransfer); + _this.currentNativeSource.loadDataTransfer(e.dataTransfer); } _this.enterLeaveCounter.reset(); @@ -3945,9 +3983,9 @@ function () { } }, { key: "beginDragNativeItem", - value: function beginDragNativeItem(type) { + value: function beginDragNativeItem(type, dataTransfer) { this.clearCurrentDragSourceNode(); - this.currentNativeSource = createNativeDragSource(type); + this.currentNativeSource = createNativeDragSource(type, dataTransfer); this.currentNativeHandle = this.registry.addSource(type, this.currentNativeSource); this.actions.beginDrag([this.currentNativeHandle]); } diff --git a/frontend/vendor/react-dnd/react-dnd.bundle.min.js b/frontend/vendor/react-dnd/react-dnd.bundle.min.js index 7a4faa605..8ffc75ea1 100644 --- a/frontend/vendor/react-dnd/react-dnd.bundle.min.js +++ b/frontend/vendor/react-dnd/react-dnd.bundle.min.js @@ -1 +1 @@ -!function(e){"use strict";var t,r="undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},n=function(e,t,r,n,i,o,a,u){if(!e){var c;if(void 0===t)c=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var s=[r,n,i,o,a,u],l=0;(c=new Error(t.replace(/%s/g,function(){return s[l++]}))).name="Invariant Violation"}throw c.framesToPop=1,c}},i="undefined"!=typeof window?e.useLayoutEffect:e.useEffect,o=function(e,t,r,n){var i=r?r.call(n,e,t):void 0;if(void 0!==i)return!!i;if(e===t)return!0;if("object"!=typeof e||!e||"object"!=typeof t||!t)return!1;var o=Object.keys(e),a=Object.keys(t);if(o.length!==a.length)return!1;for(var u=Object.prototype.hasOwnProperty.bind(t),c=0;c0&&void 0!==arguments[0]?arguments[0]:[],r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{publishSource:!0},n=r.publishSource,i=void 0===n||n,o=r.clientOffset,a=r.getSourceClientOffset,u=e.getMonitor(),c=e.getRegistry();e.dispatch(m(o)),T(t,u,c);var s=k(t,u);if(null!==s){var l=null;o&&(I(a),l=a(s)),e.dispatch(m(o,l));var f=c.getSource(s),d=f.beginDrag(u,s);E(d),c.pinSource(s);var g=c.getSourceType(s);return{type:h,payload:{itemType:g,item:d,sourceId:s,clientOffset:o||null,sourceClientOffset:l||null,isSourcePublic:!!i}}}e.dispatch(D)}}function T(e,t,r){n(!t.isDragging(),"Cannot call beginDrag while dragging."),e.forEach(function(e){n(r.getSource(e),"Expected sourceIds to be registered.")})}function I(e){n("function"==typeof e,"When clientOffset is provided, getSourceClientOffset must be a function.")}function E(e){n(O(e),"Item must be an object.")}function k(e,t){for(var r=null,n=e.length-1;n>=0;n--)if(t.canDragSource(e[n])){r=e[n];break}return r}function C(e){return function(){if(e.getMonitor().isDragging())return{type:p}}}function P(e,t){return null===t?null===e:Array.isArray(e)?e.some(function(e){return e===t}):e===t}function N(e){return function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=r.clientOffset;R(t);var i=t.slice(0),o=e.getMonitor(),a=e.getRegistry();x(i,o,a);var u=o.getItemType();return j(i,a,u),M(i,o,a),{type:v,payload:{targetIds:i,clientOffset:n||null}}}}function R(e){n(Array.isArray(e),"Expected targetIds to be an array.")}function x(e,t,r){n(t.isDragging(),"Cannot call hover while not dragging."),n(!t.didDrop(),"Cannot call hover after drop.");for(var i=0;i=0;n--){var i=e[n];P(t.getTargetType(i),r)||e.splice(n,1)}}function M(e,t,r){e.forEach(function(e){r.getTarget(e).hover(t,e)})}function A(e,t){var r=Object.keys(e);return Object.getOwnPropertySymbols&&r.push.apply(r,Object.getOwnPropertySymbols(e)),t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r}function L(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},r=e.getMonitor(),n=e.getRegistry();U(r);var i=B(r);i.forEach(function(i,o){var a=F(i,o,n,r),u={type:y,payload:{dropResult:L({},t,{},a)}};e.dispatch(u)})}}function U(e){n(e.isDragging(),"Cannot call drop while not dragging."),n(!e.didDrop(),"Cannot call drop twice during one drag operation.")}function F(e,t,r,i){var o=r.getTarget(e),a=o?o.drop(i,e):void 0;return function(e){n(void 0===e||O(e),"Drop result must either be an object or undefined.")}(a),void 0===a&&(a=0===t?{}:i.getDropResult()),a}function B(e){var t=e.getTargetIds().filter(e.canDropOnTarget,e);return t.reverse(),t}function Y(e){return function(){var t=e.getMonitor(),r=e.getRegistry();!function(e){n(e.isDragging(),"Cannot call endDrag while not dragging.")}(t);var i=t.getSourceId();return r.getSource(i,!0).endDrag(t,i),r.unpinSource(),{type:b}}}var G=function(e,t){return e===t};function X(e,t){var r=Object.keys(e);return Object.getOwnPropertySymbols&&r.push.apply(r,Object.getOwnPropertySymbols(e)),t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r}function W(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var K={initialSourceClientOffset:null,initialClientOffset:null,clientOffset:null};function V(){var e,t,r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:K,n=arguments.length>1?arguments[1]:void 0,i=n.payload;switch(n.type){case g:case h:return{initialSourceClientOffset:i.sourceClientOffset,initialClientOffset:i.clientOffset,clientOffset:i.clientOffset};case v:return e=r.clientOffset,t=i.clientOffset,!e&&!t||e&&t&&e.x===t.x&&e.y===t.y?r:function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:te,n=arguments.length>1?arguments[1]:void 0,i=n.payload;switch(n.type){case h:return $({},r,{itemType:i.itemType,item:i.item,sourceId:i.sourceId,isSourcePublic:i.isSourcePublic,dropResult:null,didDrop:!1});case p:return $({},r,{isSourcePublic:!0});case v:return $({},r,{targetIds:i.targetIds});case Q:return-1===r.targetIds.indexOf(i.targetId)?r:$({},r,{targetIds:(e=r.targetIds,t=i.targetId,e.filter(function(e){return e!==t}))});case y:return $({},r,{dropResult:i.dropResult,didDrop:!0,targetIds:[]});case b:return $({},r,{itemType:null,item:null,sourceId:null,dropResult:null,didDrop:!1,isSourcePublic:null,targetIds:[]});default:return r}}function ne(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;switch((arguments.length>1?arguments[1]:void 0).type){case q:case z:return e+1;case J:case Q:return e-1;default:return e}}var ie=[],oe=[];function ae(e,t){return e!==ie&&(e===oe||void 0===t||(r=e,t.filter(function(e){return r.indexOf(e)>-1})).length>0);var r}function ue(){var e=arguments.length>1?arguments[1]:void 0;switch(e.type){case v:break;case q:case z:case Q:case J:return ie;case h:case p:case b:case y:default:return oe}var t=e.payload,r=t.targetIds,n=void 0===r?[]:r,i=t.prevTargetIds,o=void 0===i?[]:i,a=function(e,t){var r=new Map,n=function(e){return r.set(e,r.has(e)?r.get(e)+1:1)};e.forEach(n),t.forEach(n);var i=[];return r.forEach(function(e,t){1===e&&i.push(t)}),i}(n,o);if(!(a.length>0||!function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:G;if(e.length!==t.length)return!1;for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:0)+1}function se(e,t){var r=Object.keys(e);return Object.getOwnPropertySymbols&&r.push.apply(r,Object.getOwnPropertySymbols(e)),t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r}function le(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},i=arguments.length>1?arguments[1]:void 0;return{dirtyHandlerIds:ue(n.dirtyHandlerIds,{type:i.type,payload:le({},i.payload,{prevTargetIds:(e=n,t="dragOperation.targetIds",r=[],t.split(".").reduce(function(e,t){return e&&e[t]?e[t]:r||null},e))})}),dragOffset:V(n.dragOffset,i),refCount:ne(n.refCount,i),dragOperation:re(n.dragOperation,i),stateId:ce(n.stateId)}}function ge(e,t){return{x:e.x-t.x,y:e.y-t.y}}function he(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{handlerIds:void 0},i=r.handlerIds;n("function"==typeof e,"listener must be a function."),n(void 0===i||Array.isArray(i),"handlerIds, when specified, must be an array of strings.");var o=this.store.getState().stateId,a=function(){var r=t.store.getState(),n=r.stateId;try{n===o||n===o+1&&!ae(r.dirtyHandlerIds,i)||e()}finally{o=n}};return this.store.subscribe(a)}},{key:"subscribeToOffsetChange",value:function(e){var t=this;n("function"==typeof e,"listener must be a function.");var r=this.store.getState().dragOffset;return this.store.subscribe(function(){var n=t.store.getState().dragOffset;n!==r&&(r=n,e())})}},{key:"canDragSource",value:function(e){if(!e)return!1;var t=this.registry.getSource(e);return n(t,"Expected to find a valid source."),!this.isDragging()&&t.canDrag(this,e)}},{key:"canDropOnTarget",value:function(e){if(!e)return!1;var t=this.registry.getTarget(e);return n(t,"Expected to find a valid target."),!(!this.isDragging()||this.didDrop())&&(P(this.registry.getTargetType(e),this.getItemType())&&t.canDrop(this,e))}},{key:"isDragging",value:function(){return Boolean(this.getItemType())}},{key:"isDraggingSource",value:function(e){if(!e)return!1;var t=this.registry.getSource(e,!0);return n(t,"Expected to find a valid source."),!(!this.isDragging()||!this.isSourcePublic())&&(this.registry.getSourceType(e)===this.getItemType()&&t.isDragging(this,e))}},{key:"isOverTarget",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{shallow:!1};if(!e)return!1;var r=t.shallow;if(!this.isDragging())return!1;var n=this.registry.getTargetType(e),i=this.getItemType();if(i&&!P(n,i))return!1;var o=this.getTargetIds();if(!o.length)return!1;var a=o.indexOf(e);return r?a===o.length-1:a>-1}},{key:"getItemType",value:function(){return this.store.getState().dragOperation.itemType}},{key:"getItem",value:function(){return this.store.getState().dragOperation.item}},{key:"getSourceId",value:function(){return this.store.getState().dragOperation.sourceId}},{key:"getTargetIds",value:function(){return this.store.getState().dragOperation.targetIds}},{key:"getDropResult",value:function(){return this.store.getState().dragOperation.dropResult}},{key:"didDrop",value:function(){return this.store.getState().dragOperation.didDrop}},{key:"isSourcePublic",value:function(){return this.store.getState().dragOperation.isSourcePublic}},{key:"getInitialClientOffset",value:function(){return this.store.getState().dragOffset.initialClientOffset}},{key:"getInitialSourceClientOffset",value:function(){return this.store.getState().dragOffset.initialSourceClientOffset}},{key:"getClientOffset",value:function(){return this.store.getState().dragOffset.clientOffset}},{key:"getSourceClientOffset",value:function(){return e=this.store.getState().dragOffset,n=e.clientOffset,i=e.initialClientOffset,o=e.initialSourceClientOffset,n&&i&&o?ge((r=o,{x:(t=n).x+r.x,y:t.y+r.y}),i):null;var e,t,r,n,i,o}},{key:"getDifferenceFromInitialOffset",value:function(){return e=this.store.getState().dragOffset,t=e.clientOffset,r=e.initialClientOffset,t&&r?ge(t,r):null;var e,t,r}}])&&he(t.prototype,r),i&&he(t,i),e}(),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},ye=be;function be(e){we.length||me(),we[we.length]=e}var me,we=[],Oe=0,De=1024;function Se(){for(;OeDe){for(var t=0,r=we.length-Oe;t1&&void 0!==arguments[1]&&arguments[1];n(this.isSourceId(e),"Expected a valid source ID.");var r=t&&e===this.pinnedSourceId,i=r?this.pinnedSource:this.dragSources.get(e);return i}},{key:"getTarget",value:function(e){return n(this.isTargetId(e),"Expected a valid target ID."),this.dropTargets.get(e)}},{key:"getSourceType",value:function(e){return n(this.isSourceId(e),"Expected a valid source ID."),this.types.get(e)}},{key:"getTargetType",value:function(e){return n(this.isTargetId(e),"Expected a valid target ID."),this.types.get(e)}},{key:"isSourceId",value:function(e){return Ye(e)===t.SOURCE}},{key:"isTargetId",value:function(e){return Ye(e)===t.TARGET}},{key:"removeSource",value:function(e){var t=this;n(this.getSource(e),"Expected an existing source."),this.store.dispatch(function(e){return{type:J,payload:{sourceId:e}}}(e)),je(function(){t.dragSources.delete(e),t.types.delete(e)})}},{key:"removeTarget",value:function(e){n(this.getTarget(e),"Expected an existing target."),this.store.dispatch(function(e){return{type:Q,payload:{targetId:e}}}(e)),this.dropTargets.delete(e),this.types.delete(e)}},{key:"pinSource",value:function(e){var t=this.getSource(e);n(t,"Expected an existing source."),this.pinnedSourceId=e,this.pinnedSource=t}},{key:"unpinSource",value:function(){n(this.pinnedSource,"No source is pinned at the time."),this.pinnedSourceId=null,this.pinnedSource=null}},{key:"addHandler",value:function(e,r,n){var i=Be(e);return this.types.set(i,r),e===t.SOURCE?this.dragSources.set(i,n):e===t.TARGET&&this.dropTargets.set(i,n),i}}])&&Ue(r.prototype,i),o&&Ue(r,o),e}();function We(e,t){for(var r=0;r0&&void 0!==arguments[0]&&arguments[0];!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.isSetUp=!1,this.handleRefCountChange=function(){var e=t.store.getState().refCount>0;t.backend&&(e&&!t.isSetUp?(t.backend.setup(),t.isSetUp=!0):!e&&t.isSetUp&&(t.backend.teardown(),t.isSetUp=!1))};var n=function(e){var t="undefined"!=typeof window&&window.__REDUX_DEVTOOLS_EXTENSION__;return d(de,e&&t&&t({name:"dnd-core",instanceId:"dnd-core"}))}(r);this.store=n,this.monitor=new pe(n,new Xe(n)),n.subscribe(this.handleRefCountChange)}var t,r,n;return t=e,(r=[{key:"receiveBackend",value:function(e){this.backend=e}},{key:"getMonitor",value:function(){return this.monitor}},{key:"getBackend",value:function(){return this.backend}},{key:"getRegistry",value:function(){return this.monitor.registry}},{key:"getActions",value:function(){var e=this,t=this.store.dispatch;var r=function(e){return{beginDrag:S(e),publishDragSource:C(e),hover:N(e),drop:H(e),endDrag:Y(e)}}(this);return Object.keys(r).reduce(function(n,i){var o,a=r[i];return n[i]=(o=a,function(){for(var r=arguments.length,n=new Array(r),i=0;i or
. Read more: https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute"),i?e.cloneElement(t,{ref:function(e){et(i,e),et(r,e)}}):e.cloneElement(t,{ref:r})}function rt(e){if("string"!=typeof e.type){var t=e.type.displayName||e.type.name||"the component";throw new Error("Only native element nodes can now be passed to React DnD connectors."+"You can either wrap ".concat(t," into a
, or turn it into a ")+"drag source or a drop target itself.")}}function nt(t){var r={};return Object.keys(t).forEach(function(n){var i=t[n];if(n.endsWith("Ref"))r[n]=t[n];else{var o=function(t){return function(){var r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;if(!e.isValidElement(r)){var i=r;return t(i,n),i}var o=r;rt(o);var a=n?function(e){return t(e,n)}:t;return tt(o,a)}}(i);r[n]=function(){return o}}}),r}function it(e){return(it="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function ot(e){return null!==e&&"object"===it(e)&&e.hasOwnProperty("current")}function at(e,t){for(var r=0;r=0||(i[r]=e[r]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(i[r]=e[r])}return i}var Ot=e.memo(function(t){var n=t.children,i=wt(t,["children"]),o="manager"in i?{dragDropManager:i.manager}:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0!==r?r:window,n=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,o=t;o[Dt]||(o[Dt]=function(e,t,r,n){return{dragDropManager:Ve(e,t,r,n)}}(e,t,n,i));return o[Dt]}(i.backend,i.context,i.options,i.debugMode);return e.createElement(qe.Provider,{value:o},n)}),Dt=Symbol.for("__REACT_DND_CONTEXT_INSTANCE__");function St(e){var t=null;return function(){return null==t&&(t=e()),t}}function Tt(e,t){for(var r=0;r0}},{key:"leave",value:function(e){var t,r,n=this.entered.length;return this.entered=(t=this.entered.filter(this.isNodeInDocument),r=e,t.filter(function(e){return e!==r})),n>0&&0===this.entered.length}},{key:"reset",value:function(){this.entered=[]}}])&&Tt(t.prototype,r),n&&Tt(t,n),e}(),Et=St(function(){return/firefox/i.test(navigator.userAgent)}),kt=St(function(){return Boolean(window.safari)});function Ct(e,t){for(var r=0;re))return r[u];s=u-1}}var f=e-t[a=Math.max(0,s)],d=f*f;return r[a]+n[a]*f+i[a]*d+o[a]*f*d}}])&&Ct(t.prototype,r),n&&Ct(t,n),e}(),Nt=1;function Rt(e){var t=e.nodeType===Nt?e:e.parentElement;if(!t)return null;var r=t.getBoundingClientRect(),n=r.top;return{x:r.left,y:n}}function xt(e){return{x:e.clientX,y:e.clientY}}function jt(e,t,r,n,i){var o,a=function(e){return"IMG"===e.nodeName&&(Et()||!document.documentElement.contains(e))}(t),u=Rt(a?e:t),c={x:r.x-u.x,y:r.y-u.y},s=e.offsetWidth,l=e.offsetHeight,f=n.anchorX,d=n.anchorY,g=function(e,t,r,n){var i=e?t.width:r,o=e?t.height:n;return kt()&&e&&(o/=window.devicePixelRatio,i/=window.devicePixelRatio),{dragPreviewWidth:i,dragPreviewHeight:o}}(a,t,s,l),h=g.dragPreviewWidth,p=g.dragPreviewHeight,v=i.offsetX,y=i.offsetY,b=0===y||y;return{x:0===v||v?v:new Pt([0,.5,1],[c.x,c.x/s*h,c.x+h-s]).interpolate(f),y:b?y:(o=new Pt([0,.5,1],[c.y,c.y/l*p,c.y+p-l]).interpolate(d),kt()&&a&&(o+=(window.devicePixelRatio-1)*p),o)}}var Mt,At="__NATIVE_FILE__",Lt="__NATIVE_URL__",_t="__NATIVE_TEXT__",Ht=Object.freeze({FILE:At,URL:Lt,TEXT:_t});function Ut(e,t,r){var n=t.reduce(function(t,r){return t||e.getData(r)},"");return null!=n?n:r}function Ft(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var Bt=(Ft(Mt={},At,{exposeProperties:{files:function(e){return Array.prototype.slice.call(e.files)},items:function(e){return e.items}},matchesTypes:["Files"]}),Ft(Mt,Lt,{exposeProperties:{urls:function(e,t){return Ut(e,t,"").split("\n")}},matchesTypes:["Url","text/uri-list"]}),Ft(Mt,_t,{exposeProperties:{text:function(e,t){return Ut(e,t,"")}},matchesTypes:["Text","text/plain"]}),Mt);function Yt(e,t){for(var r=0;r-1})})[0]||null}function Wt(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:[],r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{publishSource:!0},n=r.publishSource,i=void 0===n||n,o=r.clientOffset,a=r.getSourceClientOffset,u=e.getMonitor(),c=e.getRegistry();e.dispatch(m(o)),T(t,u,c);var s=k(t,u);if(null!==s){var l=null;o&&(I(a),l=a(s)),e.dispatch(m(o,l));var f=c.getSource(s),d=f.beginDrag(u,s);E(d),c.pinSource(s);var g=c.getSourceType(s);return{type:h,payload:{itemType:g,item:d,sourceId:s,clientOffset:o||null,sourceClientOffset:l||null,isSourcePublic:!!i}}}e.dispatch(D)}}function T(e,t,r){n(!t.isDragging(),"Cannot call beginDrag while dragging."),e.forEach((function(e){n(r.getSource(e),"Expected sourceIds to be registered.")}))}function I(e){n("function"==typeof e,"When clientOffset is provided, getSourceClientOffset must be a function.")}function E(e){n(w(e),"Item must be an object.")}function k(e,t){for(var r=null,n=e.length-1;n>=0;n--)if(t.canDragSource(e[n])){r=e[n];break}return r}function C(e){return function(){if(e.getMonitor().isDragging())return{type:p}}}function P(e,t){return null===t?null===e:Array.isArray(e)?e.some((function(e){return e===t})):e===t}function j(e){return function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=r.clientOffset;N(t);var i=t.slice(0),o=e.getMonitor(),a=e.getRegistry();x(i,o,a);var u=o.getItemType();return R(i,a,u),M(i,o,a),{type:v,payload:{targetIds:i,clientOffset:n||null}}}}function N(e){n(Array.isArray(e),"Expected targetIds to be an array.")}function x(e,t,r){n(t.isDragging(),"Cannot call hover while not dragging."),n(!t.didDrop(),"Cannot call hover after drop.");for(var i=0;i=0;n--){var i=e[n];P(t.getTargetType(i),r)||e.splice(n,1)}}function M(e,t,r){e.forEach((function(e){r.getTarget(e).hover(t,e)}))}function A(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function _(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},r=e.getMonitor(),n=e.getRegistry();U(r);var i=B(r);i.forEach((function(i,o){var a=F(i,o,n,r),u={type:y,payload:{dropResult:_({},t,{},a)}};e.dispatch(u)}))}}function U(e){n(e.isDragging(),"Cannot call drop while not dragging."),n(!e.didDrop(),"Cannot call drop twice during one drag operation.")}function F(e,t,r,i){var o=r.getTarget(e),a=o?o.drop(i,e):void 0;return function(e){n(void 0===e||w(e),"Drop result must either be an object or undefined.")}(a),void 0===a&&(a=0===t?{}:i.getDropResult()),a}function B(e){var t=e.getTargetIds().filter(e.canDropOnTarget,e);return t.reverse(),t}function Y(e){return function(){var t=e.getMonitor(),r=e.getRegistry();!function(e){n(e.isDragging(),"Cannot call endDrag while not dragging.")}(t);var i=t.getSourceId();return r.getSource(i,!0).endDrag(t,i),r.unpinSource(),{type:b}}}var G=function(e,t){return e===t};function X(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function W(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var K={initialSourceClientOffset:null,initialClientOffset:null,clientOffset:null};function V(){var e,t,r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:K,n=arguments.length>1?arguments[1]:void 0,i=n.payload;switch(n.type){case g:case h:return{initialSourceClientOffset:i.sourceClientOffset,initialClientOffset:i.clientOffset,clientOffset:i.clientOffset};case v:return e=r.clientOffset,t=i.clientOffset,!e&&!t||e&&t&&e.x===t.x&&e.y===t.y?r:function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:te,n=arguments.length>1?arguments[1]:void 0,i=n.payload;switch(n.type){case h:return $({},r,{itemType:i.itemType,item:i.item,sourceId:i.sourceId,isSourcePublic:i.isSourcePublic,dropResult:null,didDrop:!1});case p:return $({},r,{isSourcePublic:!0});case v:return $({},r,{targetIds:i.targetIds});case Q:return-1===r.targetIds.indexOf(i.targetId)?r:$({},r,{targetIds:(e=r.targetIds,t=i.targetId,e.filter((function(e){return e!==t})))});case y:return $({},r,{dropResult:i.dropResult,didDrop:!0,targetIds:[]});case b:return $({},r,{itemType:null,item:null,sourceId:null,dropResult:null,didDrop:!1,isSourcePublic:null,targetIds:[]});default:return r}}function ne(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;switch((arguments.length>1?arguments[1]:void 0).type){case q:case z:return e+1;case J:case Q:return e-1;default:return e}}var ie=[],oe=[];function ae(e,t){return e!==ie&&(e===oe||void 0===t||(r=e,t.filter((function(e){return r.indexOf(e)>-1}))).length>0);var r}function ue(){var e=arguments.length>1?arguments[1]:void 0;switch(e.type){case v:break;case q:case z:case Q:case J:return ie;case h:case p:case b:case y:default:return oe}var t=e.payload,r=t.targetIds,n=void 0===r?[]:r,i=t.prevTargetIds,o=void 0===i?[]:i,a=function(e,t){var r=new Map,n=function(e){return r.set(e,r.has(e)?r.get(e)+1:1)};e.forEach(n),t.forEach(n);var i=[];return r.forEach((function(e,t){1===e&&i.push(t)})),i}(n,o);if(!(a.length>0||!function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:G;if(e.length!==t.length)return!1;for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:0)+1}function se(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function le(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},i=arguments.length>1?arguments[1]:void 0;return{dirtyHandlerIds:ue(n.dirtyHandlerIds,{type:i.type,payload:le({},i.payload,{prevTargetIds:(e=n,t="dragOperation.targetIds",r=[],t.split(".").reduce((function(e,t){return e&&e[t]?e[t]:r||null}),e))})}),dragOffset:V(n.dragOffset,i),refCount:ne(n.refCount,i),dragOperation:re(n.dragOperation,i),stateId:ce(n.stateId)}}function ge(e,t){return{x:e.x-t.x,y:e.y-t.y}}function he(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{handlerIds:void 0},i=r.handlerIds;n("function"==typeof e,"listener must be a function."),n(void 0===i||Array.isArray(i),"handlerIds, when specified, must be an array of strings.");var o=this.store.getState().stateId,a=function(){var r=t.store.getState(),n=r.stateId;try{n===o||n===o+1&&!ae(r.dirtyHandlerIds,i)||e()}finally{o=n}};return this.store.subscribe(a)}},{key:"subscribeToOffsetChange",value:function(e){var t=this;n("function"==typeof e,"listener must be a function.");var r=this.store.getState().dragOffset;return this.store.subscribe((function(){var n=t.store.getState().dragOffset;n!==r&&(r=n,e())}))}},{key:"canDragSource",value:function(e){if(!e)return!1;var t=this.registry.getSource(e);return n(t,"Expected to find a valid source."),!this.isDragging()&&t.canDrag(this,e)}},{key:"canDropOnTarget",value:function(e){if(!e)return!1;var t=this.registry.getTarget(e);return n(t,"Expected to find a valid target."),!(!this.isDragging()||this.didDrop())&&P(this.registry.getTargetType(e),this.getItemType())&&t.canDrop(this,e)}},{key:"isDragging",value:function(){return Boolean(this.getItemType())}},{key:"isDraggingSource",value:function(e){if(!e)return!1;var t=this.registry.getSource(e,!0);return n(t,"Expected to find a valid source."),!(!this.isDragging()||!this.isSourcePublic())&&this.registry.getSourceType(e)===this.getItemType()&&t.isDragging(this,e)}},{key:"isOverTarget",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{shallow:!1};if(!e)return!1;var r=t.shallow;if(!this.isDragging())return!1;var n=this.registry.getTargetType(e),i=this.getItemType();if(i&&!P(n,i))return!1;var o=this.getTargetIds();if(!o.length)return!1;var a=o.indexOf(e);return r?a===o.length-1:a>-1}},{key:"getItemType",value:function(){return this.store.getState().dragOperation.itemType}},{key:"getItem",value:function(){return this.store.getState().dragOperation.item}},{key:"getSourceId",value:function(){return this.store.getState().dragOperation.sourceId}},{key:"getTargetIds",value:function(){return this.store.getState().dragOperation.targetIds}},{key:"getDropResult",value:function(){return this.store.getState().dragOperation.dropResult}},{key:"didDrop",value:function(){return this.store.getState().dragOperation.didDrop}},{key:"isSourcePublic",value:function(){return this.store.getState().dragOperation.isSourcePublic}},{key:"getInitialClientOffset",value:function(){return this.store.getState().dragOffset.initialClientOffset}},{key:"getInitialSourceClientOffset",value:function(){return this.store.getState().dragOffset.initialSourceClientOffset}},{key:"getClientOffset",value:function(){return this.store.getState().dragOffset.clientOffset}},{key:"getSourceClientOffset",value:function(){return e=this.store.getState().dragOffset,n=e.clientOffset,i=e.initialClientOffset,o=e.initialSourceClientOffset,n&&i&&o?ge((r=o,{x:(t=n).x+r.x,y:t.y+r.y}),i):null;var e,t,r,n,i,o}},{key:"getDifferenceFromInitialOffset",value:function(){return e=this.store.getState().dragOffset,t=e.clientOffset,r=e.initialClientOffset,t&&r?ge(t,r):null;var e,t,r}}])&&he(t.prototype,r),i&&he(t,i),e}(),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},ye=be;function be(e){Oe.length||me(),Oe[Oe.length]=e}var me,Oe=[],we=0,De=1024;function Se(){for(;weDe){for(var t=0,r=Oe.length-we;t1&&void 0!==arguments[1]&&arguments[1];n(this.isSourceId(e),"Expected a valid source ID.");var r=t&&e===this.pinnedSourceId,i=r?this.pinnedSource:this.dragSources.get(e);return i}},{key:"getTarget",value:function(e){return n(this.isTargetId(e),"Expected a valid target ID."),this.dropTargets.get(e)}},{key:"getSourceType",value:function(e){return n(this.isSourceId(e),"Expected a valid source ID."),this.types.get(e)}},{key:"getTargetType",value:function(e){return n(this.isTargetId(e),"Expected a valid target ID."),this.types.get(e)}},{key:"isSourceId",value:function(e){return Ye(e)===t.SOURCE}},{key:"isTargetId",value:function(e){return Ye(e)===t.TARGET}},{key:"removeSource",value:function(e){var t=this;n(this.getSource(e),"Expected an existing source."),this.store.dispatch(function(e){return{type:J,payload:{sourceId:e}}}(e)),Re((function(){t.dragSources.delete(e),t.types.delete(e)}))}},{key:"removeTarget",value:function(e){n(this.getTarget(e),"Expected an existing target."),this.store.dispatch(function(e){return{type:Q,payload:{targetId:e}}}(e)),this.dropTargets.delete(e),this.types.delete(e)}},{key:"pinSource",value:function(e){var t=this.getSource(e);n(t,"Expected an existing source."),this.pinnedSourceId=e,this.pinnedSource=t}},{key:"unpinSource",value:function(){n(this.pinnedSource,"No source is pinned at the time."),this.pinnedSourceId=null,this.pinnedSource=null}},{key:"addHandler",value:function(e,r,n){var i=Be(e);return this.types.set(i,r),e===t.SOURCE?this.dragSources.set(i,n):e===t.TARGET&&this.dropTargets.set(i,n),i}}])&&Ue(r.prototype,i),o&&Ue(r,o),e}();function We(e,t){for(var r=0;r0&&void 0!==arguments[0]&&arguments[0];!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.isSetUp=!1,this.handleRefCountChange=function(){var e=t.store.getState().refCount>0;t.backend&&(e&&!t.isSetUp?(t.backend.setup(),t.isSetUp=!0):!e&&t.isSetUp&&(t.backend.teardown(),t.isSetUp=!1))};var n=function(e){var t="undefined"!=typeof window&&window.__REDUX_DEVTOOLS_EXTENSION__;return d(de,e&&t&&t({name:"dnd-core",instanceId:"dnd-core"}))}(r);this.store=n,this.monitor=new pe(n,new Xe(n)),n.subscribe(this.handleRefCountChange)}var t,r,n;return t=e,(r=[{key:"receiveBackend",value:function(e){this.backend=e}},{key:"getMonitor",value:function(){return this.monitor}},{key:"getBackend",value:function(){return this.backend}},{key:"getRegistry",value:function(){return this.monitor.registry}},{key:"getActions",value:function(){var e=this,t=this.store.dispatch,r=function(e){return{beginDrag:S(e),publishDragSource:C(e),hover:j(e),drop:H(e),endDrag:Y(e)}}(this);return Object.keys(r).reduce((function(n,i){var o,a=r[i];return n[i]=(o=a,function(){for(var r=arguments.length,n=new Array(r),i=0;i or
. Read more: https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute"),i?e.cloneElement(t,{ref:function(e){et(i,e),et(r,e)}}):e.cloneElement(t,{ref:r})}function rt(e){if("string"!=typeof e.type){var t=e.type.displayName||e.type.name||"the component";throw new Error("Only native element nodes can now be passed to React DnD connectors."+"You can either wrap ".concat(t," into a
, or turn it into a ")+"drag source or a drop target itself.")}}function nt(t){var r={};return Object.keys(t).forEach((function(n){var i=t[n];if(n.endsWith("Ref"))r[n]=t[n];else{var o=function(t){return function(){var r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;if(!e.isValidElement(r)){var i=r;return t(i,n),i}var o=r;rt(o);var a=n?function(e){return t(e,n)}:t;return tt(o,a)}}(i);r[n]=function(){return o}}})),r}function it(e){return(it="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function ot(e){return null!==e&&"object"===it(e)&&e.hasOwnProperty("current")}function at(e,t){for(var r=0;r=0||(i[r]=e[r]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(i[r]=e[r])}return i}var Dt=0,St=e.memo((function(t){var r=t.children,n=Ot(function(e){if("manager"in e){return[{dragDropManager:e.manager},!1]}var t=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:It(),r=arguments.length>2?arguments[2]:void 0,n=arguments.length>3?arguments[3]:void 0,i=t;i[Tt]||(i[Tt]=function(e,t,r,n){return{dragDropManager:Ve(e,t,r,n)}}(e,t,r,n));return i[Tt]}(e.backend,e.context,e.options,e.debugMode),r=!e.context;return[t,r]}(wt(t,["children"])),2),i=n[0],o=n[1];return e.useEffect((function(){return o&&Dt++,function(){o&&(0===--Dt&&(It()[Tt]=null))}}),[]),e.createElement(qe.Provider,{value:i},r)}));St.displayName="DndProvider";var Tt=Symbol.for("__REACT_DND_CONTEXT_INSTANCE__");function It(){return void 0!==r?r:window}function Et(e){var t=null;return function(){return null==t&&(t=e()),t}}function kt(e,t){for(var r=0;r0}},{key:"leave",value:function(e){var t,r,n=this.entered.length;return this.entered=(t=this.entered.filter(this.isNodeInDocument),r=e,t.filter((function(e){return e!==r}))),n>0&&0===this.entered.length}},{key:"reset",value:function(){this.entered=[]}}])&&kt(t.prototype,r),n&&kt(t,n),e}(),Pt=Et((function(){return/firefox/i.test(navigator.userAgent)})),jt=Et((function(){return Boolean(window.safari)}));function Nt(e,t){for(var r=0;re))return r[u];s=u-1}}var f=e-t[a=Math.max(0,s)],d=f*f;return r[a]+n[a]*f+i[a]*d+o[a]*f*d}}])&&Nt(t.prototype,r),n&&Nt(t,n),e}(),Rt=1;function Mt(e){var t=e.nodeType===Rt?e:e.parentElement;if(!t)return null;var r=t.getBoundingClientRect(),n=r.top;return{x:r.left,y:n}}function At(e){return{x:e.clientX,y:e.clientY}}function _t(e,t,r,n,i){var o,a=function(e){return"IMG"===e.nodeName&&(Pt()||!document.documentElement.contains(e))}(t),u=Mt(a?e:t),c={x:r.x-u.x,y:r.y-u.y},s=e.offsetWidth,l=e.offsetHeight,f=n.anchorX,d=n.anchorY,g=function(e,t,r,n){var i=e?t.width:r,o=e?t.height:n;return jt()&&e&&(o/=window.devicePixelRatio,i/=window.devicePixelRatio),{dragPreviewWidth:i,dragPreviewHeight:o}}(a,t,s,l),h=g.dragPreviewWidth,p=g.dragPreviewHeight,v=i.offsetX,y=i.offsetY,b=0===y||y;return{x:0===v||v?v:new xt([0,.5,1],[c.x,c.x/s*h,c.x+h-s]).interpolate(f),y:b?y:(o=new xt([0,.5,1],[c.y,c.y/l*p,c.y+p-l]).interpolate(d),jt()&&a&&(o+=(window.devicePixelRatio-1)*p),o)}}var Lt,Ht="__NATIVE_FILE__",Ut="__NATIVE_URL__",Ft="__NATIVE_TEXT__",Bt=Object.freeze({__proto__:null,FILE:Ht,URL:Ut,TEXT:Ft});function Yt(e,t,r){var n=t.reduce((function(t,r){return t||e.getData(r)}),"");return null!=n?n:r}function Gt(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var Xt=(Gt(Lt={},Ht,{exposeProperties:{files:function(e){return Array.prototype.slice.call(e.files)},items:function(e){return e.items}},matchesTypes:["Files"]}),Gt(Lt,Ut,{exposeProperties:{urls:function(e,t){return Yt(e,t,"").split("\n")}},matchesTypes:["Url","text/uri-list"]}),Gt(Lt,Ft,{exposeProperties:{text:function(e,t){return Yt(e,t,"")}},matchesTypes:["Text","text/plain"]}),Lt);function Wt(e,t){for(var r=0;r-1}))}))[0]||null}function qt(e,t){for(var r=0;r