diff --git a/CHANGES.md b/CHANGES.md index cd54e2c70..055fc1ad2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ - Improve french translations [#731](https://github.com/penpot/penpot/pull/731) - Reimplement workspace presence (remove database state). - Replace Slate-Editor with DraftJS [Taiga #1346](https://tree.taiga.io/project/penpot/us/1346) +- Several enhancements in shape selection [Taiga #1195](https://tree.taiga.io/project/penpot/us/1195) ### :bug: Bugs fixed diff --git a/common/app/common/data.cljc b/common/app/common/data.cljc index 088a36f07..f65fd4161 100644 --- a/common/app/common/data.cljc +++ b/common/app/common/data.cljc @@ -407,3 +407,39 @@ (or default-value (str maybe-keyword))))) +(defn with-next + "Given a collectin will return a new collection where each element + is paried with the next item in the collection + (with-next (range 5)) => [[0 1] [1 2] [2 3] [3 4] [4 nil]" + [coll] + (map vector + coll + (concat [] (rest coll) [nil]))) + +(defn with-prev + "Given a collectin will return a new collection where each element + is paried with the previous item in the collection + (with-prev (range 5)) => [[0 nil] [1 0] [2 1] [3 2] [4 3]" + [coll] + (map vector + coll + (concat [nil] coll))) + +(defn with-prev-next + "Given a collection will return a new collection where every item is paired + with the previous and the next item of a collection + (with-prev-next (range 5)) => [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]" + [coll] + (map vector + coll + (concat [nil] coll) + (concat [] (rest coll) [nil]))) + +(defn prefix-keyword + "Given a keyword and a prefix will return a new keyword with the prefix attached + (prefix-keyword \"prefix\" :test) => :prefix-test" + [prefix kw] + (let [prefix (if (keyword? prefix) (name prefix) prefix) + kw (if (keyword? kw) (name kw) kw)] + (keyword (str prefix kw)))) + diff --git a/common/app/common/geom/shapes.cljc b/common/app/common/geom/shapes.cljc index b26f73996..a71e0792a 100644 --- a/common/app/common/geom/shapes.cljc +++ b/common/app/common/geom/shapes.cljc @@ -10,12 +10,14 @@ (ns app.common.geom.shapes (:require [app.common.data :as d] + [app.common.math :as mth] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.rect :as gpr] [app.common.geom.shapes.transforms :as gtr] + [app.common.geom.shapes.intersect :as gin] [app.common.spec :as us])) ;; --- Relative Movement @@ -156,29 +158,6 @@ ;; --- Helpers -(defn contained-in? - "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} (:selrect shape)] - (and (neg? (- sy1 ry1)) - (neg? (- sx1 rx1)) - (pos? (- sy2 ry2)) - (pos? (- sx2 rx2))))) - -;; TODO: This not will work for rotated shapes -(defn overlaps? - "Check if a shape overlaps with provided selection rect." - [shape rect] - (let [{sx1 :x1 sx2 :x2 sy1 :y1 sy2 :y2} (gpr/rect->selrect rect) - {rx1 :x1 rx2 :x2 ry1 :y1 ry2 :y2} (gpr/points->selrect (:points shape))] - - (and (< rx1 sx2) - (> rx2 sx1) - (< ry1 sy2) - (> ry2 sy1)))) - (defn fully-contained? "Checks if one rect is fully inside the other" [rect other] @@ -187,20 +166,6 @@ (<= (:y1 rect) (:y1 other)) (>= (:y2 rect) (:y2 other)))) -(defn has-point? - [shape position] - (let [{:keys [x y]} position - selrect {:x1 (- x 5) - :y1 (- y 5) - :x2 (+ x 5) - :y2 (+ y 5) - :x (- x 5) - :y (- y 5) - :width 10 - :height 10 - :type :rect}] - (overlaps? shape selrect))) - (defn pad-selrec ([selrect] (pad-selrec selrect 1)) ([selrect size] @@ -287,3 +252,7 @@ (d/export gsp/content->points) (d/export gsp/content->selrect) (d/export gsp/transform-content) + +;; Intersection +(d/export gin/overlaps?) +(d/export gin/has-point?) diff --git a/common/app/common/geom/shapes/intersect.cljc b/common/app/common/geom/shapes/intersect.cljc new file mode 100644 index 000000000..72ed180fb --- /dev/null +++ b/common/app/common/geom/shapes/intersect.cljc @@ -0,0 +1,296 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.common.geom.shapes.intersect + (:require + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.matrix :as gmt] + [app.common.geom.shapes.path :as gpp] + [app.common.geom.shapes.rect :as gpr] + [app.common.math :as mth])) + +(defn orientation + "Given three ordered points gives the orientation + (clockwise, counterclock or coplanar-line)" + [p1 p2 p3] + (let [{x1 :x y1 :y} p1 + {x2 :x y2 :y} p2 + {x3 :x y3 :y} p3 + v (- (* (- y2 y1) (- x3 x2)) + (* (- y3 y2) (- x2 x1)))] + (cond + (pos? v) ::clockwise + (neg? v) ::counter-clockwise + :else ::coplanar))) + +(defn on-segment? + "Given three colinear points p, q, r checks if q lies on segment pr" + [{qx :x qy :y} {px :x py :y} {rx :x ry :y}] + (and (<= qx (max px rx)) + (>= qx (min px rx)) + (<= qy (max py ry)) + (>= qy (min py ry)))) + +;; Based on solution described here +;; https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/ +(defn intersect-segments? + "Given two segments A and B defined by two points. + Checks if they intersects." + [[p1 q1] [p2 q2]] + (let [o1 (orientation p1 q1 p2) + o2 (orientation p1 q1 q2) + o3 (orientation p2 q2 p1) + o4 (orientation p2 q2 q1)] + + (or + ;; General case + (and (not= o1 o2) (not= o3 o4)) + + ;; p1, q1 and p2 colinear and p2 lies on p1q1 + (and (= o1 :coplanar) (on-segment? p2 p1 q1)) + + ;; p1, q1 and q2 colinear and q2 lies on p1q1 + (and (= o2 :coplanar) (on-segment? q2 p1 q1)) + + ;; p2, q2 and p1 colinear and p1 lies on p2q2 + (and (= o3 :coplanar) (on-segment? p1 p2 q2)) + + ;; p2, q2 and p1 colinear and q1 lies on p2q2 + (and (= o4 :coplanar) (on-segment? q1 p2 q2))))) + +(defn points->lines + "Given a set of points for a polygon will return + the lines that define it" + ([points] + (points->lines points true)) + + ([points closed?] + (map vector + points + (-> (rest points) + (vec) + (cond-> closed? + (conj (first points))))))) + +(defn intersects-lines? + "Checks if two sets of lines intersect in any point" + [lines-a lines-b] + + (loop [cur-line (first lines-a) + pending (rest lines-a)] + (if-not cur-line + ;; There is no line intersecting polygon b + false + + ;; Check if any of the segments intersect + (if (->> lines-b + (some #(intersect-segments? cur-line %))) + true + (recur (first pending) (rest pending)))))) + +(defn intersect-ray? + "Checks the intersection between segment qr and a ray + starting in point p with an angle of 0 degrees" + [{px :x py :y} [{x1 :x y1 :y} {x2 :x y2 :y}]] + + (if (or (and (<= y1 py) (> y2 py)) + (and (> y1 py) (<= y2 py))) + + ;; calculate the edge-ray intersect x-coord + (let [vt (/ (- py y1) (- y2 y1)) + ix (+ x1 (* vt (- x2 x1)))] + (< px ix)) + + false)) + +(defn is-point-inside-evenodd? + "Check if the point P is inside the polygon defined by `points`" + [p lines] + ;; Even-odd algorithm + ;; Cast a ray from the point in any direction and count the intersections + ;; if it's odd the point is inside the polygon + (let [] + (->> lines + (filter #(intersect-ray? p %)) + (count) + (odd?)))) + +(defn- next-windup + "Calculates the next windup number for the nonzero algorithm" + [wn {px :x py :y} [{x1 :x y1 :y} {x2 :x y2 :y}]] + + (let [line-side (- (* (- x2 x1) (- py y1)) + (* (- px x1) (- y2 y1)))] + (if (<= y1 py) + ;; Upward crossing + (if (and (> y2 py) (> line-side 0)) (inc wn) wn) + + ;; Downward crossing + (if (and (<= y2 py) (< line-side 0)) (dec wn) wn)))) + +(defn is-point-inside-nonzero? + "Check if the point P is inside the polygon defined by `points`" + [p lines] + ;; Non-zero winding number + ;; Calculates the winding number + + (loop [wn 0 + line (first lines) + lines (rest lines)] + + (if line + (let [wn (next-windup wn p line)] + (recur wn (first lines) (rest lines))) + (not= wn 0)))) + +;; A intersects with B +;; Three posible cases: +;; 1) A is inside of B +;; 2) B is inside of A +;; 3) A intersects B +;; 4) A is outside of B +;; +;; . check point in A is inside B => case 1 or 3 otherwise discard 1 +;; . check point in B is inside A => case 2 or 3 otherwise discard 2 +;; . check if intersection otherwise case 4 +;; +(defn overlaps-rect-points? + "Checks if the given rect intersects with the selrect" + [rect points] + + (let [rect-points (gpr/rect->points rect) + rect-lines (points->lines rect-points) + points-lines (points->lines points)] + + (or (is-point-inside-evenodd? (first rect-points) points-lines) + (is-point-inside-evenodd? (first points) rect-lines) + (intersects-lines? rect-lines points-lines)))) + +(defn overlaps-path? + "Checks if the given rect overlaps with the path in any point" + [shape rect] + + (let [rect-points (gpr/rect->points rect) + rect-lines (points->lines rect-points) + path-lines (gpp/path->lines shape) + start-point (-> shape :content (first) :params (gpt/point))] + + (or (is-point-inside-nonzero? (first rect-points) path-lines) + (is-point-inside-nonzero? start-point rect-lines) + (intersects-lines? rect-lines path-lines)))) + +(defn is-point-inside-ellipse? + "checks if a point is inside an ellipse" + [point {:keys [cx cy rx ry transform]}] + + (let [center (gpt/point cx cy) + transform (gmt/transform-in center transform) + {px :x py :y} (gpt/transform point transform)] + ;; Ellipse inequality formula + ;; https://en.wikipedia.org/wiki/Ellipse#Shifted_ellipse + (let [v (+ (/ (mth/sq (- px cx)) + (mth/sq rx)) + (/ (mth/sq (- py cy)) + (mth/sq ry)))] + (<= v 1)))) + +(defn intersects-line-ellipse? + "Checks wether a single line intersects with the given ellipse" + [[{x1 :x y1 :y} {x2 :x y2 :y}] {:keys [cx cy rx ry]}] + + ;; Given the ellipse inequality after inserting the line parametric equations + ;; we resolve t and gives us a cuadratic formula + ;; The result of this quadratic will give us a value of T that needs to be + ;; between 0-1 to be in the segment + + (let [a (+ (/ (mth/sq (- x2 x1)) + (mth/sq rx)) + (/ (mth/sq (- y2 y1)) + (mth/sq ry))) + + b (+ (/ (- (* 2 x1 (- x2 x1)) + (* 2 cx (- x2 x1))) + (mth/sq rx)) + (/ (- (* 2 y1 (- y2 y1)) + (* 2 cy (- y2 y1))) + (mth/sq ry))) + + c (+ (/ (+ (mth/sq x1) + (mth/sq cx) + (* -2 x1 cx)) + (mth/sq rx)) + (/ (+ (mth/sq y1) + (mth/sq cy) + (* -2 y1 cy)) + (mth/sq ry)) + -1) + + ;; B^2 - 4AC + determ (- (mth/sq b) (* 4 a c))] + + (if (mth/almost-zero? a) + ;; If a=0 we need to calculate the linear solution + (when-not (mth/almost-zero? b) + (let [t (/ (- c) b)] + (and (>= t 0) (<= t 1)))) + + (when (>= determ 0) + (let [t1 (/ (+ (- b) (mth/sqrt determ)) (* 2 a)) + t2 (/ (- (- b) (mth/sqrt determ)) (* 2 a))] + (or (and (>= t1 0) (<= t1 1)) + (and (>= t2 0) (<= t2 1)))))))) + +(defn intersects-lines-ellipse? + "Checks if a set of lines intersect with an ellipse in any point" + [rect-lines {:keys [cx cy transform] :as ellipse-data}] + (let [center (gpt/point cx cy) + transform (gmt/transform-in center transform)] + (some (fn [[p1 p2]] + (let [p1 (gpt/transform p1 transform) + p2 (gpt/transform p2 transform)] + (intersects-line-ellipse? [p1 p2] ellipse-data))) rect-lines))) + +(defn overlaps-ellipse? + "Checks if the given rect overlaps with an ellipse" + [shape rect] + + (let [rect-points (gpr/rect->points rect) + rect-lines (points->lines rect-points) + {:keys [x y width height]} shape + + center (gpt/point (+ x (/ width 2)) + (+ y (/ height 2))) + + ellipse-data {:cx (:x center) + :cy (:y center) + :rx (/ width 2) + :ry (/ height 2) + :transform (:transform-inverse shape)}] + + (or (is-point-inside-evenodd? center rect-lines) + (is-point-inside-ellipse? (first rect-points) ellipse-data) + (intersects-lines-ellipse? rect-lines ellipse-data)))) + +(defn overlaps? + "General case to check for overlaping between shapes and a rectangle" + [shape rect] + (or (not shape) + (let [path? (= :path (:type shape)) + circle? (= :circle (:type shape))] + (and (overlaps-rect-points? rect (:points shape)) + (or (not path?) (overlaps-path? shape rect)) + (or (not circle?) (overlaps-ellipse? shape rect)))))) + +(defn has-point? + "Check if the shape contains a point" + [shape point] + (let [lines (points->lines (:points shape))] + ;; TODO: Will only work for simple shapes + (is-point-inside-evenodd? point lines))) diff --git a/common/app/common/geom/shapes/path.cljc b/common/app/common/geom/shapes/path.cljc index 92553088a..f38467b33 100644 --- a/common/app/common/geom/shapes/path.cljc +++ b/common/app/common/geom/shapes/path.cljc @@ -161,3 +161,56 @@ (when closed? [{:command :close-path}]))))) + +(defonce num-segments 10) + +(defn curve->lines + "Transform the bezier curve given by the parameters into a series of straight lines + defined by the constant num-segments" + [start end h1 h2] + (let [offset (/ 1 num-segments) + tp (fn [t] (curve-values start end h1 h2 t))] + (loop [from 0 + result []] + + (let [to (min 1 (+ from offset)) + line [(tp from) (tp to)] + result (conj result line)] + + (if (>= to 1) + result + (recur to result)))))) + +(defn path->lines + "Given a path returns a list of lines that approximate the path" + [shape] + (loop [command (first (:content shape)) + pending (rest (:content shape)) + result [] + last-start nil + prev-point nil] + + (if-let [{:keys [command params]} command] + (let [point (if (= :close-path command) + last-start + (gpt/point params)) + + result (case command + :line-to (conj result [prev-point point]) + :curve-to (let [h1 (gpt/point (:c1x params) (:c1y params)) + h2 (gpt/point (:c2x params) (:c2y params))] + (into result (curve->lines prev-point point h1 h2))) + :move-to (cond-> result + last-start (conj [prev-point last-start])) + result) + last-start (if (= :move-to command) + point + last-start) + ] + (recur (first pending) + (rest pending) + result + last-start + point)) + + (conj result [prev-point last-start])))) diff --git a/common/app/common/math.cljc b/common/app/common/math.cljc index ecb1c2c6f..e676c3f34 100644 --- a/common/app/common/math.cljc +++ b/common/app/common/math.cljc @@ -70,6 +70,11 @@ [v] (- v)) +(defn sq + "Calculates the square of a number" + [v] + (* v v)) + (defn sqrt "Returns the square root of a number." [v] diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 2f33c9199..3ed7a4485 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -63,6 +63,8 @@ (d/export helpers/get-base-shape) (d/export helpers/is-parent?) (d/export helpers/get-index-in-parent) +(d/export helpers/calculate-z-index) +(d/export helpers/generate-child-all-parents-index) ;; Process changes (d/export changes/process-changes) diff --git a/common/app/common/pages/helpers.cljc b/common/app/common/pages/helpers.cljc index 3a4414d3b..d47a38383 100644 --- a/common/app/common/pages/helpers.cljc +++ b/common/app/common/pages/helpers.cljc @@ -169,6 +169,21 @@ (assoc index id (:parent-id obj))) {} objects)) +(defn generate-child-all-parents-index + "Creates an index where the key is the shape id and the value is a set + with all the parents" + ([objects] + (generate-child-all-parents-index objects (vals objects))) + + ([objects shapes] + (let [shape->parents + (fn [shape] + (->> (get-parents (:id shape) objects) + (into [])))] + (->> shapes + (map #(vector (:id %) (shape->parents %))) + (into {}))))) + (defn clean-loops "Clean a list of ids from circular references." [objects ids] @@ -333,6 +348,41 @@ (reduce red-fn cur-idx (reverse (:shapes object)))))] (into {} (rec-index '() uuid/zero)))) +(defn calculate-z-index + "Given a collection of shapes calculates their z-index. Greater index + means is displayed over other shapes with less index." + [objects] + + (let [is-frame? (fn [id] (= :frame (get-in objects [id :type]))) + root-children (get-in objects [uuid/zero :shapes]) + num-frames (->> root-children (filter is-frame?) count)] + (when (seq root-children) + (loop [current (peek root-children) + pending (pop root-children) + current-idx (+ (count objects) num-frames -1) + z-index {}] + + (let [children (->> (get-in objects [current :shapes])) + children (cond + (and (is-frame? current) (contains? z-index current)) + [] + + (and (is-frame? current) + (not (contains? z-index current))) + (into [current] children) + + :else + children) + pending (into (vec pending) children)] + (if (empty? pending) + (assoc z-index current current-idx) + + (let [] + (recur (peek pending) + (pop pending) + (dec current-idx) + (assoc z-index current current-idx))))))))) + (defn expand-region-selection "Given a selection selects all the shapes between the first and last in an indexed manner (shift selection)" diff --git a/frontend/resources/fonts/collection/bebas.eot b/frontend/resources/fonts/collection/bebas.eot deleted file mode 100644 index d36443689..000000000 Binary files a/frontend/resources/fonts/collection/bebas.eot and /dev/null differ diff --git a/frontend/resources/fonts/collection/bebas.svg b/frontend/resources/fonts/collection/bebas.svg deleted file mode 100644 index 16df322da..000000000 --- a/frontend/resources/fonts/collection/bebas.svg +++ /dev/null @@ -1,2808 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/collection/bebas.ttf b/frontend/resources/fonts/collection/bebas.ttf deleted file mode 100644 index 186d667a7..000000000 Binary files a/frontend/resources/fonts/collection/bebas.ttf and /dev/null differ diff --git a/frontend/resources/fonts/collection/bebas.woff b/frontend/resources/fonts/collection/bebas.woff deleted file mode 100644 index fe11c136e..000000000 Binary files a/frontend/resources/fonts/collection/bebas.woff and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams-bold.eot b/frontend/resources/fonts/collection/caviardreams-bold.eot deleted file mode 100644 index f7f94ba2a..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams-bold.eot and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams-bold.svg b/frontend/resources/fonts/collection/caviardreams-bold.svg deleted file mode 100644 index 202045176..000000000 --- a/frontend/resources/fonts/collection/caviardreams-bold.svg +++ /dev/null @@ -1,1363 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/collection/caviardreams-bold.ttf b/frontend/resources/fonts/collection/caviardreams-bold.ttf deleted file mode 100644 index 3b5fa43ec..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams-bold.ttf and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams-bold.woff b/frontend/resources/fonts/collection/caviardreams-bold.woff deleted file mode 100644 index 9a12dd0e7..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams-bold.woff and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams-bolditalic.eot b/frontend/resources/fonts/collection/caviardreams-bolditalic.eot deleted file mode 100644 index bacdf6cc1..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams-bolditalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams-bolditalic.svg b/frontend/resources/fonts/collection/caviardreams-bolditalic.svg deleted file mode 100644 index 984ce01c4..000000000 --- a/frontend/resources/fonts/collection/caviardreams-bolditalic.svg +++ /dev/null @@ -1,1043 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/collection/caviardreams-bolditalic.ttf b/frontend/resources/fonts/collection/caviardreams-bolditalic.ttf deleted file mode 100644 index a11179a6e..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams-bolditalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams-bolditalic.woff b/frontend/resources/fonts/collection/caviardreams-bolditalic.woff deleted file mode 100644 index 059e3676d..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams-bolditalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams-italic.eot b/frontend/resources/fonts/collection/caviardreams-italic.eot deleted file mode 100644 index 258dc22da..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams-italic.eot and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams-italic.svg b/frontend/resources/fonts/collection/caviardreams-italic.svg deleted file mode 100644 index dc5a7f179..000000000 --- a/frontend/resources/fonts/collection/caviardreams-italic.svg +++ /dev/null @@ -1,1360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/collection/caviardreams-italic.ttf b/frontend/resources/fonts/collection/caviardreams-italic.ttf deleted file mode 100644 index b968b3ec4..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams-italic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams-italic.woff b/frontend/resources/fonts/collection/caviardreams-italic.woff deleted file mode 100644 index 42171772c..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams-italic.woff and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams.eot b/frontend/resources/fonts/collection/caviardreams.eot deleted file mode 100644 index 1fed3953b..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams.eot and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams.svg b/frontend/resources/fonts/collection/caviardreams.svg deleted file mode 100644 index 1d9283783..000000000 --- a/frontend/resources/fonts/collection/caviardreams.svg +++ /dev/null @@ -1,1364 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/collection/caviardreams.ttf b/frontend/resources/fonts/collection/caviardreams.ttf deleted file mode 100644 index 3e7a3d9c1..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams.ttf and /dev/null differ diff --git a/frontend/resources/fonts/collection/caviardreams.woff b/frontend/resources/fonts/collection/caviardreams.woff deleted file mode 100644 index f4cdbe296..000000000 Binary files a/frontend/resources/fonts/collection/caviardreams.woff and /dev/null differ diff --git a/frontend/resources/fonts/collection/gooddog.eot b/frontend/resources/fonts/collection/gooddog.eot deleted file mode 100644 index b3955822f..000000000 Binary files a/frontend/resources/fonts/collection/gooddog.eot and /dev/null differ diff --git a/frontend/resources/fonts/collection/gooddog.svg b/frontend/resources/fonts/collection/gooddog.svg deleted file mode 100644 index 59aa85a41..000000000 --- a/frontend/resources/fonts/collection/gooddog.svg +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/collection/gooddog.ttf b/frontend/resources/fonts/collection/gooddog.ttf deleted file mode 100644 index 91f05260e..000000000 Binary files a/frontend/resources/fonts/collection/gooddog.ttf and /dev/null differ diff --git a/frontend/resources/fonts/collection/gooddog.woff b/frontend/resources/fonts/collection/gooddog.woff deleted file mode 100644 index 699564824..000000000 Binary files a/frontend/resources/fonts/collection/gooddog.woff and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans-bold.eot b/frontend/resources/fonts/collection/ptsans-bold.eot deleted file mode 100644 index 225d68198..000000000 Binary files a/frontend/resources/fonts/collection/ptsans-bold.eot and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans-bold.svg b/frontend/resources/fonts/collection/ptsans-bold.svg deleted file mode 100644 index 837b146b5..000000000 --- a/frontend/resources/fonts/collection/ptsans-bold.svg +++ /dev/null @@ -1,849 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/collection/ptsans-bold.ttf b/frontend/resources/fonts/collection/ptsans-bold.ttf deleted file mode 100644 index 2eb09ea01..000000000 Binary files a/frontend/resources/fonts/collection/ptsans-bold.ttf and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans-bold.woff b/frontend/resources/fonts/collection/ptsans-bold.woff deleted file mode 100644 index 700e7eb2f..000000000 Binary files a/frontend/resources/fonts/collection/ptsans-bold.woff and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans-bolditalic.eot b/frontend/resources/fonts/collection/ptsans-bolditalic.eot deleted file mode 100644 index 0cb05d7df..000000000 Binary files a/frontend/resources/fonts/collection/ptsans-bolditalic.eot and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans-bolditalic.svg b/frontend/resources/fonts/collection/ptsans-bolditalic.svg deleted file mode 100644 index 6b28d205a..000000000 --- a/frontend/resources/fonts/collection/ptsans-bolditalic.svg +++ /dev/null @@ -1,782 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/collection/ptsans-bolditalic.ttf b/frontend/resources/fonts/collection/ptsans-bolditalic.ttf deleted file mode 100644 index d08b41cb6..000000000 Binary files a/frontend/resources/fonts/collection/ptsans-bolditalic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans-bolditalic.woff b/frontend/resources/fonts/collection/ptsans-bolditalic.woff deleted file mode 100644 index bc13af496..000000000 Binary files a/frontend/resources/fonts/collection/ptsans-bolditalic.woff and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans-italic.eot b/frontend/resources/fonts/collection/ptsans-italic.eot deleted file mode 100644 index 12a0172e3..000000000 Binary files a/frontend/resources/fonts/collection/ptsans-italic.eot and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans-italic.svg b/frontend/resources/fonts/collection/ptsans-italic.svg deleted file mode 100644 index 5553d6930..000000000 --- a/frontend/resources/fonts/collection/ptsans-italic.svg +++ /dev/null @@ -1,619 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/collection/ptsans-italic.ttf b/frontend/resources/fonts/collection/ptsans-italic.ttf deleted file mode 100644 index b12d51339..000000000 Binary files a/frontend/resources/fonts/collection/ptsans-italic.ttf and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans-italic.woff b/frontend/resources/fonts/collection/ptsans-italic.woff deleted file mode 100644 index 395efa1ab..000000000 Binary files a/frontend/resources/fonts/collection/ptsans-italic.woff and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans.eot b/frontend/resources/fonts/collection/ptsans.eot deleted file mode 100644 index 87356d4ac..000000000 Binary files a/frontend/resources/fonts/collection/ptsans.eot and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans.svg b/frontend/resources/fonts/collection/ptsans.svg deleted file mode 100644 index ffe806695..000000000 --- a/frontend/resources/fonts/collection/ptsans.svg +++ /dev/null @@ -1,693 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/resources/fonts/collection/ptsans.ttf b/frontend/resources/fonts/collection/ptsans.ttf deleted file mode 100644 index 2a419fe4c..000000000 Binary files a/frontend/resources/fonts/collection/ptsans.ttf and /dev/null differ diff --git a/frontend/resources/fonts/collection/ptsans.woff b/frontend/resources/fonts/collection/ptsans.woff deleted file mode 100644 index c29e195ae..000000000 Binary files a/frontend/resources/fonts/collection/ptsans.woff and /dev/null differ diff --git a/frontend/resources/styles/collection/font-collection.scss b/frontend/resources/styles/collection/font-collection.scss deleted file mode 100644 index ed1d3701d..000000000 --- a/frontend/resources/styles/collection/font-collection.scss +++ /dev/null @@ -1,88 +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 Andrey Antukh -// Copyright (c) 2015-2016 Juan de la Cruz - -// source sans pro - -@include font-face-collection("sourcesanspro","sourcesanspro-extralight","100"); -@include font-face-collection("sourcesanspro","sourcesanspro-extralightitalic","100",italic); -@include font-face-collection("sourcesanspro","sourcesanspro-light","200"); -@include font-face-collection("sourcesanspro","sourcesanspro-lightitalic","200",italic); -@include font-face-collection("sourcesanspro","sourcesanspro-regular",normal); -@include font-face-collection("sourcesanspro","sourcesanspro-italic",normal,italic); -@include font-face-collection("sourcesanspro","sourcesanspro-semibold","500"); -@include font-face-collection("sourcesanspro","sourcesanspro-semibolditalic","500",italic); -@include font-face-collection("sourcesanspro","sourcesanspro-bold",bold); -@include font-face-collection("sourcesanspro","sourcesanspro-bolditalic",bold,italic); -@include font-face-collection("sourcesanspro","sourcesanspro-black","900"); -@include font-face-collection("sourcesanspro","sourcesanspro-blackitalic","900",italic); - -// Open Sans - -@include font-face-collection("opensans","opensans-extralight","100"); -@include font-face-collection("opensans","opensans-extralightitalic","100",italic); -@include font-face-collection("opensans","opensans-light","200"); -@include font-face-collection("opensans","opensans-lightitalic","200",italic); -@include font-face-collection("opensans","opensans-regular",normal); -@include font-face-collection("opensans","opensans-italic",normal,italic); -@include font-face-collection("opensans","opensans-semibold","500"); -@include font-face-collection("opensans","opensans-semibolditalic","500",italic); -@include font-face-collection("opensans","opensans-bold",bold); -@include font-face-collection("opensans","opensans-bolditalic",bold,italic); -@include font-face-collection("opensans","opensans-black","900"); -@include font-face-collection("opensans","opensans-blackitalic","900",italic); - -// Bebas - -@include font-face-collection("bebas","bebas",normal); - -// Caviar Dreams - -@include font-face-collection("caviardreams","caviardreams",normal); -@include font-face-collection("caviardreams","caviardreams-italic",normal,italic); -@include font-face-collection("caviardreams","caviardreams-bold",bold); -@include font-face-collection("caviardreams","caviardreams-bolditalic",bold,italic); - -// Good Dog - -@include font-face-collection("gooddog","gooddog",normal); - -// PT Sans - -@include font-face-collection("ptsans","ptsans",normal); -@include font-face-collection("ptsans","ptsans-italic",normal,italic); -@include font-face-collection("ptsans","ptsans-bold",bold); -@include font-face-collection("ptsans","ptsans-bolditalic",bold,italic); - -// Roboto - -@include font-face-collection("roboto","roboto-thin","100"); -@include font-face-collection("roboto","roboto-thinitalic","100",italic); -@include font-face-collection("roboto","roboto-light","200"); -@include font-face-collection("roboto","roboto-lightitalic","200",italic); -@include font-face-collection("roboto","roboto-regular",normal); -@include font-face-collection("roboto","roboto-italic",normal,italic); -@include font-face-collection("roboto","roboto-semibold","500"); -@include font-face-collection("roboto","roboto-semibolditalic","500",italic); -@include font-face-collection("roboto","roboto-bold",bold); -@include font-face-collection("roboto","roboto-bolditalic",bold,italic); -@include font-face-collection("roboto","roboto-black","900"); -@include font-face-collection("roboto","roboto-blackitalic","900",italic); - -// Roboto Condensed - -@include font-face-collection("robotocondensed","robotocondensed-thin","100"); -@include font-face-collection("robotocondensed","robotocondensed-thinitalic","100",italic); -@include font-face-collection("robotocondensed","robotocondensed-light","200"); -@include font-face-collection("robotocondensed","robotocondensed-lightitalic","200",italic); -@include font-face-collection("robotocondensed","robotocondensed-regular",normal); -@include font-face-collection("robotocondensed","robotocondensed-italic",normal,italic); -@include font-face-collection("robotocondensed","robotocondensed-semibold","500"); -@include font-face-collection("robotocondensed","robotocondensed-semibolditalic","500",italic); -@include font-face-collection("robotocondensed","robotocondensed-bold",bold); -@include font-face-collection("robotocondensed","robotocondensed-bolditalic",bold,italic); -@include font-face-collection("robotocondensed","robotocondensed-black","900"); -@include font-face-collection("robotocondensed","robotocondensed-blackitalic","900",italic); diff --git a/frontend/resources/styles/common/dependencies/fonts.scss b/frontend/resources/styles/common/dependencies/fonts.scss index d0dfc685a..dedb85bf4 100644 --- a/frontend/resources/styles/common/dependencies/fonts.scss +++ b/frontend/resources/styles/common/dependencies/fonts.scss @@ -2,8 +2,7 @@ // 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 +// Copyright (c) UXBOX Labs SL // Font sizes $fs8: 0.5rem; @@ -41,71 +40,31 @@ $title-lh: 1.25; $title-lh-sm: 1.15; // Work Sans -@include font-face("worksans","WorkSans-ExtraLight", "100"); -@include font-face("worksans","WorkSans-ExtraLightitalic","100",italic); -@include font-face("worksans","WorkSans-Light","200"); -@include font-face("worksans","WorkSans-LightItalic","200",italic); -@include font-face("worksans","WorkSans-Regular",normal); -@include font-face("worksans","WorkSans-Italic",normal,italic); -@include font-face("worksans","WorkSans-SemiBold","500"); -@include font-face("worksans","WorkSans-SemiBoldItalic","500",italic); -@include font-face("worksans","WorkSans-Bold",bold); -@include font-face("worksans","WorkSans-BoldItalic",bold,italic); -@include font-face("worksans","WorkSans-Black","900"); -@include font-face("worksans","WorkSans-BlackItalic","900",italic); +@include font-face("worksans","WorkSans-Thin", "100"); +@include font-face("worksans","WorkSans-ThinItalic", "100", italic); +@include font-face("worksans","WorkSans-ExtraLight", "200"); +@include font-face("worksans","WorkSans-ExtraLightitalic", "200", italic); +@include font-face("worksans","WorkSans-Light", "300"); +@include font-face("worksans","WorkSans-LightItalic", "300", italic); +@include font-face("worksans","WorkSans-Regular", normal); +@include font-face("worksans","WorkSans-Italic", normal, italic); +@include font-face("worksans","WorkSans-SemiBold", "600"); +@include font-face("worksans","WorkSans-SemiBoldItalic", "600", italic); +@include font-face("worksans","WorkSans-Bold", bold); +@include font-face("worksans","WorkSans-BoldItalic", bold, italic); +@include font-face("worksans","WorkSans-Black", "900"); +@include font-face("worksans","WorkSans-BlackItalic","900", italic); // Source Sans Pro -@include font-face("sourcesanspro","sourcesanspro-extralight", "100"); -@include font-face("sourcesanspro","sourcesanspro-extralightitalic","100",italic); -@include font-face("sourcesanspro","sourcesanspro-light","200"); -@include font-face("sourcesanspro","sourcesanspro-lightitalic","200",italic); -@include font-face("sourcesanspro","sourcesanspro-regular",normal); -@include font-face("sourcesanspro","sourcesanspro-italic",normal,italic); -@include font-face("sourcesanspro","sourcesanspro-semibold","500"); -@include font-face("sourcesanspro","sourcesanspro-semibolditalic","500",italic); -@include font-face("sourcesanspro","sourcesanspro-bold",bold); -@include font-face("sourcesanspro","sourcesanspro-bolditalic",bold,italic); -@include font-face("sourcesanspro","sourcesanspro-black","900"); -@include font-face("sourcesanspro","sourcesanspro-blackitalic","900",italic); - -// Open Sans -@include font-face("opensans","opensans-extralight","100"); -@include font-face("opensans","opensans-extralightitalic","100",italic); -@include font-face("opensans","opensans-light","200"); -@include font-face("opensans","opensans-lightitalic","200",italic); -@include font-face("opensans","opensans-regular",normal); -@include font-face("opensans","opensans-italic",normal,italic); -@include font-face("opensans","opensans-semibold","500"); -@include font-face("opensans","opensans-semibolditalic","500",italic); -@include font-face("opensans","opensans-bold",bold); -@include font-face("opensans","opensans-bolditalic",bold,italic); -@include font-face("opensans","opensans-black","900"); -@include font-face("opensans","opensans-blackitalic","900",italic); - -// Roboto -@include font-face("roboto","roboto-thin","100"); -@include font-face("roboto","roboto-thinitalic","100",italic); -@include font-face("roboto","roboto-light","200"); -@include font-face("roboto","roboto-lightitalic","200",italic); -@include font-face("roboto","roboto-regular",normal); -@include font-face("roboto","roboto-italic",normal,italic); -@include font-face("roboto","roboto-semibold","500"); -@include font-face("roboto","roboto-semibolditalic","500",italic); -@include font-face("roboto","roboto-bold",bold); -@include font-face("roboto","roboto-bolditalic",bold,italic); -@include font-face("roboto","roboto-black","900"); -@include font-face("roboto","roboto-blackitalic","900",italic); - -// Roboto Condensed -@include font-face("robotocondensed","robotocondensed-thin","100"); -@include font-face("robotocondensed","robotocondensed-thinitalic","100",italic); -@include font-face("robotocondensed","robotocondensed-light","200"); -@include font-face("robotocondensed","robotocondensed-lightitalic","200",italic); -@include font-face("robotocondensed","robotocondensed-regular",normal); -@include font-face("robotocondensed","robotocondensed-italic",normal,italic); -@include font-face("robotocondensed","robotocondensed-semibold","500"); -@include font-face("robotocondensed","robotocondensed-semibolditalic","500",italic); -@include font-face("robotocondensed","robotocondensed-bold",bold); -@include font-face("robotocondensed","robotocondensed-bolditalic",bold,italic); -@include font-face("robotocondensed","robotocondensed-black","900"); -@include font-face("robotocondensed","robotocondensed-blackitalic","900",italic); +@include font-face("sourcesanspro","sourcesanspro-extralight", "200"); +@include font-face("sourcesanspro","sourcesanspro-extralightitalic", "200", italic); +@include font-face("sourcesanspro","sourcesanspro-light", "300"); +@include font-face("sourcesanspro","sourcesanspro-lightitalic", "300", italic); +@include font-face("sourcesanspro","sourcesanspro-regular", normal); +@include font-face("sourcesanspro","sourcesanspro-italic", normal, italic); +@include font-face("sourcesanspro","sourcesanspro-semibold", "600"); +@include font-face("sourcesanspro","sourcesanspro-semibolditalic", "600", italic); +@include font-face("sourcesanspro","sourcesanspro-bold", bold); +@include font-face("sourcesanspro","sourcesanspro-bolditalic", bold, italic); +@include font-face("sourcesanspro","sourcesanspro-black", "900"); +@include font-face("sourcesanspro","sourcesanspro-blackitalic", "900", italic); diff --git a/frontend/resources/styles/main/partials/comments.scss b/frontend/resources/styles/main/partials/comments.scss index 05679f849..216de4c2a 100644 --- a/frontend/resources/styles/main/partials/comments.scss +++ b/frontend/resources/styles/main/partials/comments.scss @@ -349,6 +349,7 @@ z-index: 1000; pointer-events: none; overflow: hidden; + user-select: text; .threads { position: absolute; diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 778d4b313..80dddb37a 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -745,7 +745,7 @@ &:hover { background: $color-gray-60; - + .custom-select, .editable-select, input { @@ -1016,7 +1016,7 @@ .typography-name { font-size: 14px; } - + .row-flex { padding: 0.5rem 0; } diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index 520182046..65965da27 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -104,6 +104,7 @@ white-space: nowrap; padding-bottom: 2px; transition: bottom 0.5s; + z-index: 2; &.color-palette-open { bottom: 5rem; @@ -135,27 +136,52 @@ display: grid; grid-template-rows: 20px 1fr; grid-template-columns: 20px 1fr; + } - .viewport { - cursor: none; - grid-column: 1 / span 2; - grid-row: 1 / span 2; - overflow: hidden; + .viewport { + cursor: none; + grid-column: 1 / span 2; + grid-row: 1 / span 2; + overflow: hidden; + position: relative; - rect.selection-rect { - fill: rgba(235, 215, 92, 0.1); - stroke: #000000; - stroke-width: 0.1px; - } + .viewport-overlays { + position: absolute; + width: 100%; + height: 100%; + z-index: 10; + pointer-events: none; + cursor: initial; - g.controls { - rect.main { pointer-events: none; } + .pixel-overlay { + height: 100%; + left: 0; + pointer-events: initial; + position: absolute; + top: 0; + width: 100%; + z-index: 1; } } - .page-canvas, .page-layout { - overflow: visible; + .selection-rect { + fill: rgba(235, 215, 92, 0.1); + stroke: #000000; + stroke-width: 0.1px; } + + .render-shapes { + position: absolute; + } + + .viewport-controls { + position: absolute; + } + + } + + .page-canvas, .page-layout { + overflow: visible; } /* Rules */ @@ -231,14 +257,16 @@ } .viewport-actions { - position: absolute; - margin-left: auto; - width: 100%; - margin-top: 2rem; + align-items: center; display: flex; flex-direction: row; justify-content: center; - align-items: center; + margin-left: auto; + margin-top: 2rem; + position: absolute; + width: 100%; + z-index: 12; + pointer-events: initial; .path-actions { display: flex; @@ -315,3 +343,4 @@ margin-right: 0; } } + diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 4901d0a82..1334cbad8 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -439,13 +439,33 @@ (assoc :left-offset left-offset)))))))))))) -(defn start-pan [state] - (-> state - (assoc-in [:workspace-local :panning] true))) +(defn start-panning [] + (ptk/reify ::start-panning + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:workspace-local :panning] true))) -(defn finish-pan [state] - (-> state - (update :workspace-local dissoc :panning))) + ptk/WatchEvent + (watch [_ state stream] + (let [stopper (->> stream (rx/filter (ptk/type? ::finish-panning))) + zoom (-> (get-in state [:workspace-local :zoom]) gpt/point)] + (->> stream + (rx/filter ms/pointer-event?) + (rx/filter #(= :delta (:source %))) + (rx/map :pt) + (rx/take-until stopper) + (rx/map (fn [delta] + (let [delta (gpt/divide delta zoom)] + (update-viewport-position {:x #(- % (:x delta)) + :y #(- % (:y delta))}))))))))) + +(defn finish-panning [] + (ptk/reify ::finish-panning + ptk/UpdateEvent + (update [_ state] + (-> state + (update :workspace-local dissoc :panning))))) ;; --- Toggle layout flag @@ -981,15 +1001,12 @@ {:keys [id type shapes]} (get objects (first selected))] (case type - :text + (:text :path) (rx/of (dwc/start-edition-mode id)) :group (rx/of (dwc/select-shapes (into (d/ordered-set) [(last shapes)]))) - :path - (rx/of (dwc/start-edition-mode id) - (dwdp/start-path-edit id)) :else (rx/empty)))))))) @@ -1255,8 +1272,7 @@ (let [selected (get-in state [:workspace-local :selected])] (rx/concat (when-not (selected (:id shape)) - (rx/of (dws/deselect-all) - (dws/select-shape (:id shape)))) + (rx/of (dws/select-shape (:id shape)))) (rx/of (show-context-menu params))))))) (def hide-context-menu @@ -1734,6 +1750,7 @@ ;; Selection (d/export dws/select-shape) +(d/export dws/deselect-shape) (d/export dws/select-all) (d/export dws/deselect-all) (d/export dwc/select-shapes) @@ -1741,12 +1758,12 @@ (d/export dws/duplicate-selected) (d/export dws/handle-selection) (d/export dws/select-inside-group) -(d/export dws/select-last-layer) +;;(d/export dws/select-last-layer) (d/export dwd/select-for-drawing) (d/export dwc/clear-edition-mode) (d/export dwc/add-shape) (d/export dwc/start-edition-mode) -(d/export dwdp/start-path-edit) +#_(d/export dwc/start-path-edit) ;; Groups diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index c597bb5c9..808ed5259 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -518,6 +518,31 @@ (rx/of (expand-all-parents ids objects)))))) ;; --- Start shape "edition mode" +(defn stop-path-edit [] + (ptk/reify ::stop-path-edit + ptk/UpdateEvent + (update [_ state] + (let [id (get-in state [:workspace-local :edition])] + (update state :workspace-local dissoc :edit-path id))))) + +(defn start-path-edit + [id] + (ptk/reify ::start-path-edit + ptk/UpdateEvent + (update [_ state] + ;; Only edit if the object has been created + (if-let [id (get-in state [:workspace-local :edition])] + (assoc-in state [:workspace-local :edit-path id] {:edit-mode :move + :selected #{} + :snap-toggled true}) + state)) + + ptk/WatchEvent + (watch [_ state stream] + (->> stream + (rx/filter #(= % :interrupt)) + (rx/take 1) + (rx/map #(stop-path-edit)))))) (declare clear-edition-mode) @@ -527,8 +552,7 @@ (ptk/reify ::start-edition-mode ptk/UpdateEvent (update [_ state] - (let [page-id (:current-page-id state) - objects (get-in state [:workspace-data :pages-index page-id :objects])] + (let [objects (lookup-page-objects state)] ;; Can only edit objects that exist (if (contains? objects id) (-> state @@ -538,10 +562,15 @@ ptk/WatchEvent (watch [_ state stream] - (->> stream - (rx/filter interrupt?) - (rx/take 1) - (rx/map (constantly clear-edition-mode)))))) + (let [objects (lookup-page-objects state) + path? (= :path (get-in objects [id :type]))] + (rx/merge + (when path? + (rx/of (start-path-edit id))) + (->> stream + (rx/filter interrupt?) + (rx/take 1) + (rx/map (constantly clear-edition-mode)))))))) (def clear-edition-mode (ptk/reify ::clear-edition-mode diff --git a/frontend/src/app/main/data/workspace/drawing/path.cljs b/frontend/src/app/main/data/workspace/drawing/path.cljs index a1f2a4a29..4f23b1c3e 100644 --- a/frontend/src/app/main/data/workspace/drawing/path.cljs +++ b/frontend/src/app/main/data/workspace/drawing/path.cljs @@ -9,23 +9,22 @@ (ns app.main.data.workspace.drawing.path (:require - [clojure.spec.alpha :as s] - [app.common.spec :as us] - [beicon.core :as rx] - [potok.core :as ptk] - [app.common.math :as mth] + [app.common.data :as d] + [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.geom.matrix :as gmt] - [app.util.data :as ud] - [app.common.data :as cd] - [app.util.geom.path :as ugp] - [app.main.streams :as ms] - [app.main.store :as st] + [app.common.geom.shapes.path :as gsp] + [app.common.math :as mth] + [app.common.pages :as cp] + [app.common.spec :as us] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.drawing.common :as common] - [app.common.geom.shapes.path :as gsp] - [app.common.pages :as cp])) + [app.main.store :as st] + [app.main.streams :as ms] + [app.util.geom.path :as ugp] + [beicon.core :as rx] + [clojure.spec.alpha :as s] + [potok.core :as ptk])) ;; SCHEMAS @@ -83,7 +82,7 @@ [state & path] (let [edit-id (get-in state [:workspace-local :edition]) page-id (:current-page-id state)] - (cd/concat + (d/concat (if edit-id [:workspace-data :pages-index page-id :objects edit-id] [:workspace-drawing :object]) @@ -515,31 +514,7 @@ mousedown-events) (rx/of (finish-path "after-events"))))))) -(defn stop-path-edit [] - (ptk/reify ::stop-path-edit - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition])] - (update state :workspace-local dissoc :edit-path id))))) -(defn start-path-edit - [id] - (ptk/reify ::start-path-edit - ptk/UpdateEvent - (update [_ state] - ;; Only edit if the object has been created - (if-let [id (get-in state [:workspace-local :edition])] - (assoc-in state [:workspace-local :edit-path id] {:edit-mode :move - :selected #{} - :snap-toggled true}) - state)) - - ptk/WatchEvent - (watch [_ state stream] - (->> stream - (rx/filter #(= % :interrupt)) - (rx/take 1) - (rx/map #(stop-path-edit)))))) (defn modify-point [index prefix dx dy] (ptk/reify ::modify-point @@ -635,7 +610,7 @@ (let [point (ugp/command->point command)] (= point start-point))) - point-indices (->> (cd/enumerate content) + point-indices (->> (d/enumerate content) (filter command-for-point) (map first)) @@ -646,8 +621,8 @@ (assoc-in [index :y] dy))) handler-reducer (fn [modifiers [index prefix]] - (let [cx (ud/prefix-keyword prefix :x) - cy (ud/prefix-keyword prefix :y)] + (let [cx (d/prefix-keyword prefix :x) + cy (d/prefix-keyword prefix :y)] (-> modifiers (assoc-in [index cx] dx) (assoc-in [index cy] dy)))) @@ -680,8 +655,8 @@ ptk/WatchEvent (watch [_ state stream] (let [id (get-in state [:workspace-local :edition]) - cx (ud/prefix-keyword prefix :x) - cy (ud/prefix-keyword prefix :y) + cx (d/prefix-keyword prefix :x) + cy (d/prefix-keyword prefix :y) start-point @ms/mouse-position modifiers (get-in state [:workspace-local :edit-path id :content-modifiers]) start-delta-x (get-in modifiers [index cx] 0) @@ -838,7 +813,6 @@ (->> (rx/of (setup-frame-path) common/handle-finish-drawing (dwc/start-edition-mode shape-id) - (start-path-edit shape-id) (change-edit-mode :draw)))))) (defn handle-new-shape diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 11d982eb6..ff4dfbb44 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -85,7 +85,9 @@ ;; --- Toggle shape's selection status (selected or deselected) (defn select-shape - ([id] (select-shape id false)) + ([id] + (select-shape id false)) + ([id toggle?] (us/verify ::us/uuid id) (ptk/reify ::select-shape @@ -94,7 +96,7 @@ (update-in state [:workspace-local :selected] (fn [selected] (if-not toggle? - (conj selected id) + (conj (d/ordered-set) id) (if (contains? selected id) (disj selected id) (conj selected id)))))) @@ -137,8 +139,7 @@ ptk/WatchEvent (watch [_ state stream] - (let [page-id (:current-page-id state) - objects (dwc/lookup-page-objects state page-id)] + (let [objects (dwc/lookup-page-objects state)] (rx/of (dwc/expand-all-parents ids objects)))))) (defn select-all @@ -207,22 +208,21 @@ ptk/WatchEvent (watch [_ state stream] (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state) selected (get-in state [:workspace-local :selected]) initial-set (if preserve? selected lks/empty-linked-set) selrect (get-in state [:workspace-local :selrect]) - is-not-blocked (fn [shape-id] (not (get-in state [:workspace-data - :pages-index page-id - :objects shape-id - :blocked] false)))] + blocked? (fn [id] (get-in objects [id :blocked] false))] (rx/merge (rx/of (update-selrect nil)) (when selrect (->> (uw/ask! {:cmd :selection/query :page-id page-id :rect selrect}) - (rx/map #(into initial-set (filter is-not-blocked) %)) + (rx/map #(cp/clean-loops objects %)) + (rx/map #(into initial-set (filter (comp not blocked?)) %)) (rx/map select-shapes)))))))) (defn select-inside-group @@ -243,34 +243,8 @@ reverse (d/seek #(geom/has-point? % position)))] (when selected - (rx/of (deselect-all) (select-shape (:id selected))))))))) + (rx/of (select-shape (:id selected))))))))) -(defn select-last-layer - ([position] - (ptk/reify ::select-last-layer - ptk/WatchEvent - (watch [_ state stream] - (let [page-id (:current-page-id state) - objects (dwc/lookup-page-objects state page-id) - find-shape - (fn [selection] - (let [id (first selection) - shape (get objects id)] - (let [child-id (->> (cp/get-children id objects) - (map #(get objects %)) - (remove (comp empty :shapes)) - (filter #(geom/has-point? % position)) - (first) - :id)] - (or child-id id))))] - (->> (uw/ask! {:cmd :selection/query - :page-id page-id - :rect (geom/make-centered-rect position 1 1)}) - (rx/first) - (rx/filter (comp not empty?)) - (rx/map find-shape) - (rx/filter #(not (nil? %))) - (rx/map #(select-shape % false)))))))) ;; --- Duplicate Shapes (declare prepare-duplicate-change) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 636391406..339697a86 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -197,6 +197,8 @@ (declare start-move) (declare start-move-duplicate) +(declare start-local-displacement) +(declare clear-local-transform) (defn start-move-selected [] @@ -297,13 +299,11 @@ (->> snap-delta (rx/with-latest vector position) (rx/map (fn [[delta pos]] (-> (gpt/add pos delta) (gpt/round 0)))) - (rx/map gmt/translate-matrix) - (rx/map #(fn [state] (assoc-in state [:workspace-local :modifiers] {:displacement %})))) + (rx/map start-local-displacement)) (rx/of (set-modifiers ids) (apply-modifiers ids) (calculate-frame-for-move ids) - (fn [state] (update state :workspace-local dissoc :modifiers)) finish-transform)))))))) (defn- get-displacement-with-grid @@ -368,15 +368,11 @@ (->> move-events (rx/take-until stopper) (rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0)) - (rx/map gmt/translate-matrix) - (rx/map #(fn [state] (assoc-in state [:workspace-local :modifiers] {:displacement %})))) + (rx/map start-local-displacement)) (rx/of (move-selected direction shift?))) (rx/of (set-modifiers selected) (apply-modifiers selected) - (fn [state] (-> state - (update :workspace-local dissoc :modifiers) - (update :workspace-local dissoc :current-move-selected))) finish-transform))) (rx/empty)))))) @@ -486,6 +482,7 @@ (rx/of (dwc/start-undo-transaction) (dwc/commit-changes rchanges uchanges {:commit-local? true}) + (clear-local-transform) (dwc/commit-undo-transaction)))))) ;; --- Update Dimensions @@ -564,3 +561,18 @@ :displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))} false) (apply-modifiers selected)))))) + +(defn start-local-displacement [point] + (ptk/reify ::start-local-displacement + ptk/UpdateEvent + (update [_ state] + (let [mtx (gmt/translate-matrix point)] + (-> state + (assoc-in [:workspace-local :modifiers] {:displacement mtx})))))) + +(defn clear-local-transform [] + (ptk/reify ::clear-local-transform + ptk/UpdateEvent + (update [_ state] + (-> state + (update :workspace-local dissoc :modifiers :current-move-selected))))) diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index 209350233..be8622cfd 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -5,21 +5,21 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.fonts "Fonts management and loading logic." (:require-macros [app.main.fonts :refer [preload-gfonts]]) (:require - [beicon.core :as rx] - [promesa.core :as p] - [okulary.core :as l] - [cuerdas.core :as str] - [app.util.dom :as dom] - [app.util.timers :as ts] [app.common.data :as d] + [app.util.dom :as dom] + [app.util.object :as obj] + [app.util.timers :as ts] + [beicon.core :as rx] [clojure.set :as set] - [app.util.object :as obj])) + [cuerdas.core :as str] + [okulary.core :as l] + [promesa.core :as p])) (def google-fonts (preload-gfonts "fonts/gfonts.2020.04.23.json")) @@ -28,20 +28,17 @@ [{:id "sourcesanspro" :name "Source Sans Pro" :family "sourcesanspro" - :variants [{:id "100" :name "100" :weight "100" :style "normal"} - {:id "100italic" :name "100 (italic)" :weight "100" :style "italic"} - {:id "200" :name "200" :weight "200" :style "normal"} - {:id "200italic" :name "200 (italic)" :weight "200" :style "italic"} - {:id "300" :name "300" :weight "300" :style "normal"} - {:id "300italic" :name "300 (italic)" :weight "300" :style "italic"} - {:id "regular" :name "regular" :weight "400" :style "normal"} - {:id "italic" :name "italic" :weight "400" :style "italic"} - {:id "500" :name "500" :weight "500" :style "normal"} - {:id "500italic" :name "500 (italic)" :weight "500" :style "italic"} - {:id "bold" :name "bold" :weight "bold" :style "normal"} - {:id "bolditalic" :name "bold (italic)" :weight "bold" :style "italic"} - {:id "black" :name "black" :weight "900" :style "normal"} - {:id "blackitalic" :name "black (italic)" :weight "900" :style "italic"}]}]) + :variants + [{:id "200" :name "200" :weight "200" :style "normal" :suffix "extralight"} + {:id "200italic" :name "200 (italic)" :weight "200" :style "italic" :suffix "extralightitalic"} + {:id "300" :name "300" :weight "300" :style "normal" :suffix "light"} + {:id "300italic" :name "300 (italic)" :weight "300" :style "italic" :suffix "lightitalic"} + {:id "regular" :name "regular" :weight "400" :style "normal"} + {:id "italic" :name "italic" :weight "400" :style "italic"} + {:id "bold" :name "bold" :weight "bold" :style "normal"} + {:id "bolditalic" :name "bold (italic)" :weight "bold" :style "italic"} + {:id "black" :name "black" :weight "900" :style "normal"} + {:id "blackitalic" :name "black (italic)" :weight "900" :style "italic"}]}]) (defonce fontsdb (l/atom {})) (defonce fontsview (l/atom {})) @@ -75,7 +72,6 @@ [backend] (get @fontsview backend)) - ;; --- Fonts Loader (defonce loaded (l/atom #{})) @@ -93,12 +89,6 @@ variants (str/join "," (map :id variants))] (str base ":" variants "&display=block"))) -(defn font-url [font-id font-variant-id] - (let [{:keys [backend family] :as entry} (get @fontsdb font-id)] - (case backend - :google (gfont-url family {:id font-variant-id}) - (str "/fonts/" family "-" (or font-variant-id "regular") ".woff")))) - (defmulti ^:private load-font :backend) (defmethod load-font :builtin @@ -140,8 +130,3 @@ (or (d/seek #(or (= (:id %) "regular") (= (:name %) "regular")) variants) (first variants))) - -(defn fetch-font [font-id font-variant-id] - (let [font-url (font-url font-id font-variant-id)] - (-> (js/fetch font-url) - (p/then (fn [res] (.text res)))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index a515463b6..e7bc1e5d6 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -35,6 +35,9 @@ (def exception (l/derived :exception st/state)) +(def threads-ref + (l/derived :comment-threads st/state)) + ;; ---- Dashboard refs (def dashboard-local diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index d54cd86c0..5e78f1dd3 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -184,6 +184,7 @@ (->> (uw/ask! {:cmd :selection/query :page-id page-id :frame-id (->> shapes first :frame-id) + :include-frames? true :rect area-selrect}) (rx/map #(set/difference % (into #{} (map :id shapes)))) (rx/map (fn [ids] (map #(get objects %) ids))))) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 3cd342f1f..fcc077bc7 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -14,7 +14,6 @@ (def embed-ctx (mf/create-context false)) (def render-ctx (mf/create-context nil)) (def def-ctx (mf/create-context false)) -(def ghost-ctx (mf/create-context false)) (def current-route (mf/create-context nil)) (def current-team-id (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index a028c0bc8..81c7f6fa4 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -212,10 +212,14 @@ (defn use-stream "Wraps the subscription to a strem into a `use-effect` call" - [stream on-subscribe] - (mf/use-effect (fn [] - (let [sub (->> stream (rx/subs on-subscribe))] - #(rx/dispose! sub))))) + ([stream on-subscribe] + (use-stream stream (mf/deps) on-subscribe)) + ([stream deps on-subscribe] + (mf/use-effect + deps + (fn [] + (let [sub (->> stream (rx/subs on-subscribe))] + #(rx/dispose! sub)))))) ;; https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state (defn use-previous diff --git a/frontend/src/app/main/ui/shapes/text/embed.cljs b/frontend/src/app/main/ui/shapes/text/embed.cljs index 8e6a0b46f..a8f7d9ab8 100644 --- a/frontend/src/app/main/ui/shapes/text/embed.cljs +++ b/frontend/src/app/main/ui/shapes/text/embed.cljs @@ -5,7 +5,7 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020-2021 UXBOX Labs SL +;; Copyright (c) UXBOX Labs SL (ns app.main.ui.shapes.text.embed (:require @@ -26,7 +26,7 @@ font-style: %(style)s; font-weight: %(weight)s; font-display: block; - src: url(/fonts/%(family)s-%(style)s.woff) format('woff'); + src: url(/fonts/%(family)s-%(suffix)s.woff) format('woff'); } ") @@ -42,10 +42,19 @@ (defn get-local-font-css [font-id font-variant-id] (let [{:keys [family variants] :as font} (get @fonts/fontsdb font-id) - {:keys [name weight style] :as variant} (d/seek #(= (:id %) font-variant-id) variants)] - (-> (str/format font-face-template {:family family :style style :width weight}) + {:keys [name weight style suffix] :as variant} (d/seek #(= (:id %) font-variant-id) variants)] + (-> (str/format font-face-template {:family family :suffix (or suffix font-variant-id) :width weight}) (p/resolved)))) +(defn fetch-font-css + [font-id font-variant-id] + (let [{:keys [backend family] :as entry} (get @fonts/fontsdb font-id)] + (if (= :google backend) + (-> (fonts/gfont-url family [{:id font-variant-id}]) + (js/fetch) + (p/then (fn [res] (.text res))))) + (get-local-font-css font-id font-variant-id))) + (defn get-text-font-data [text] (->> text (re-seq #"url\(([^)]+)\)") @@ -54,11 +63,9 @@ (p/all))) (defn embed-font [{:keys [font-id font-variant-id] :or {font-variant-id "regular"}}] - (let [{:keys [backend]} (get @fonts/fontsdb font-id)] - (p/let [font-text (case backend - :google (fonts/fetch-font font-id font-variant-id) - (get-local-font-css font-id font-variant-id)) - url-to-data (get-text-font-data font-text) + (let [{:keys [backend family]} (get @fonts/fontsdb font-id)] + (p/let [font-text (fetch-font-css font-id font-variant-id) + url-to-data (get-text-font-data font-text) replace-text (fn [text [url data]] (str/replace text url data))] (reduce replace-text font-text url-to-data)))) diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 2fd32a5dd..cef42cf18 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -29,7 +29,8 @@ [app.main.ui.workspace.libraries] [app.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]] [app.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]] - [app.main.ui.workspace.viewport :refer [viewport viewport-actions coordinates]] + [app.main.ui.workspace.viewport :refer [viewport]] + [app.main.ui.workspace.coordinates :as coordinates] [app.util.dom :as dom] [app.util.keyboard :as kbd] [app.util.object :as obj] @@ -57,7 +58,7 @@ [:& vertical-rule {:zoom zoom :vbox vbox :vport vport}] - [:& coordinates {:colorpalette? colorpalette?}]])) + [:& coordinates/coordinates {:colorpalette? colorpalette?}]])) (mf/defc workspace-content {::mf/wrap-props false} @@ -80,7 +81,6 @@ :vport vport :colorpalette? (contains? layout :colorpalette)}]) - [:& viewport-actions] [:& viewport {:file file :local local :layout layout}]]] diff --git a/frontend/src/app/main/ui/workspace/comments.cljs b/frontend/src/app/main/ui/workspace/comments.cljs index 8475ab4d2..ff5012766 100644 --- a/frontend/src/app/main/ui/workspace/comments.cljs +++ b/frontend/src/app/main/ui/workspace/comments.cljs @@ -9,88 +9,20 @@ (ns app.main.ui.workspace.comments (:require - [app.config :as cfg] + [app.main.data.comments :as dcm] [app.main.data.workspace :as dw] [app.main.data.workspace.comments :as dwcm] - [app.main.data.comments :as dcm] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.context :as ctx] - [app.main.ui.components.dropdown :refer [dropdown]] - [app.main.ui.icons :as i] [app.main.ui.comments :as cmt] - [app.util.time :as dt] - [app.util.timers :as tm] + [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.context :as ctx] + [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [t tr]] - [cuerdas.core :as str] - [okulary.core :as l] + [app.util.timers :as tm] [rumext.alpha :as mf])) -(def threads-ref - (l/derived :comment-threads st/state)) - -(mf/defc comments-layer - [{:keys [vbox vport zoom file-id page-id drawing] :as props}] - (let [pos-x (* (- (:x vbox)) zoom) - pos-y (* (- (:y vbox)) zoom) - - profile (mf/deref refs/profile) - users (mf/deref refs/users) - local (mf/deref refs/comments-local) - threads-map (mf/deref threads-ref) - - threads (->> (vals threads-map) - (filter #(= (:page-id %) page-id)) - (dcm/apply-filters local profile)) - - on-bubble-click - (fn [{:keys [id] :as thread}] - (if (= (:open local) id) - (st/emit! (dcm/close-thread)) - (st/emit! (dcm/open-thread thread)))) - - on-draft-cancel - (mf/use-callback - (st/emitf :interrupt)) - - on-draft-submit - (mf/use-callback - (fn [draft] - (st/emit! (dcm/create-thread draft))))] - - (mf/use-effect - (mf/deps file-id) - (fn [] - (st/emit! (dwcm/initialize-comments file-id)) - (fn [] - (st/emit! ::dwcm/finalize)))) - - [:div.comments-section - [:div.workspace-comments-container - {:style {:width (str (:width vport) "px") - :height (str (:height vport) "px")}} - [:div.threads {:style {:transform (str/format "translate(%spx, %spx)" pos-x pos-y)}} - (for [item threads] - [:& cmt/thread-bubble {:thread item - :zoom zoom - :on-click on-bubble-click - :open? (= (:id item) (:open local)) - :key (:seqn item)}]) - - (when-let [id (:open local)] - (when-let [thread (get threads-map id)] - [:& cmt/thread-comments {:thread thread - :users users - :zoom zoom}])) - - (when-let [draft (:comment drawing)] - [:& cmt/draft-thread {:draft draft - :on-cancel on-draft-cancel - :on-submit on-draft-submit - :zoom zoom}])]]])) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Sidebar ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -130,7 +62,7 @@ (mf/defc comments-sidebar [] - (let [threads-map (mf/deref threads-ref) + (let [threads-map (mf/deref refs/threads-ref) profile (mf/deref refs/profile) users (mf/deref refs/users) local (mf/deref refs/comments-local) @@ -184,5 +116,3 @@ [:div.thread-groups-placeholder i/chat (tr "labels.no-comments-available")])])) - - diff --git a/frontend/src/app/main/ui/workspace/coordinates.cljs b/frontend/src/app/main/ui/workspace/coordinates.cljs new file mode 100644 index 000000000..296d652c0 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/coordinates.cljs @@ -0,0 +1,23 @@ +; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020-2021 UXBOX Labs SL + +(ns app.main.ui.workspace.coordinates + (:require + [app.main.ui.hooks :as hooks] + [app.main.streams :as ms] + [rumext.alpha :as mf])) + +(mf/defc coordinates + [{:keys [colorpalette?]}] + (let [coords (hooks/use-rxsub ms/mouse-position)] + [:ul.coordinates {:class (when colorpalette? "color-palette-open")} + [:span {:alt "x"} + (str "X: " (:x coords "-"))] + [:span {:alt "y"} + (str "Y: " (:y coords "-"))]])) diff --git a/frontend/src/app/main/ui/workspace/presence.cljs b/frontend/src/app/main/ui/workspace/presence.cljs index 722c2def4..1e73484e3 100644 --- a/frontend/src/app/main/ui/workspace/presence.cljs +++ b/frontend/src/app/main/ui/workspace/presence.cljs @@ -13,74 +13,8 @@ [app.main.refs :as refs] [app.main.store :as st] [app.util.router :as rt] - [app.util.time :as dt] - [app.util.timers :as ts] - [beicon.core :as rx] - [cuerdas.core :as str] [rumext.alpha :as mf])) -(def pointer-icon-path - (str "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")) - -(mf/defc session-cursor - [{:keys [session profile] :as props}] - (let [zoom (mf/deref refs/selected-zoom) - point (:point session) - color (:color session "#000000") - transform (str/fmt "translate(%s, %s) scale(%s)" (:x point) (:y point) (/ 4 zoom))] - [:g.multiuser-cursor {:transform transform} - [:path {:fill color - :d pointer-icon-path - }] - [:g {:transform "translate(0 -291.708)"} - [:rect {:width 25 - :height 5 - :x 7 - :y 291.5 - :fill color - :fill-opacity 0.8 - :paint-order "stroke fill markers" - :rx 1 - :ry 1}] - [:text {:x 8 - :y 295 - :width 25 - :height 5 - :overflow "hidden" - :fill "#fff" - :stroke-width 1 - :font-family "Works Sans" - :font-size 3 - :font-weight 400 - :letter-spacing 0 - :style { :line-height 1.25 } - :word-spacing 0} - (str (str/slice (:fullname profile) 0 14) - (when (> (count (:fullname profile)) 14) "..."))]]])) - -(mf/defc active-cursors - {::mf/wrap [mf/memo]} - [{:keys [page-id] :as props}] - (let [counter (mf/use-state 0) - users (mf/deref refs/users) - sessions (mf/deref refs/workspace-presence) - sessions (->> (vals sessions) - (filter #(= page-id (:page-id %))) - (filter #(>= 5000 (- (inst-ms (dt/now)) (inst-ms (:updated-at %))))))] - (mf/use-effect - nil - (fn [] - (let [sem (ts/schedule 1000 #(swap! counter inc))] - (fn [] (rx/dispose! sem))))) - - (for [session sessions] - (when (:point session) - [:& session-cursor {:session session - :profile (get users (:profile-id session)) - :key (:id session)}])))) - ;; --- SESSION WIDGET (mf/defc session-widget diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 51f9e51e6..0383ef143 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -16,12 +16,8 @@ common." (:require [app.common.geom.shapes :as geom] + [app.common.uuid :as uuid] [app.main.refs :as refs] - [app.main.store :as st] - [app.main.streams :as ms] - [app.main.ui.cursors :as cur] - [app.main.ui.hooks :as hooks] - [app.main.ui.context :as muc] [app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.image :as image] [app.main.ui.shapes.rect :as rect] @@ -29,15 +25,15 @@ [app.main.ui.workspace.shapes.common :as common] [app.main.ui.workspace.shapes.frame :as frame] [app.main.ui.workspace.shapes.group :as group] - [app.main.ui.workspace.shapes.svg-raw :as svg-raw] [app.main.ui.workspace.shapes.path :as path] + [app.main.ui.workspace.shapes.svg-raw :as svg-raw] [app.main.ui.workspace.shapes.text :as text] - [app.util.object :as obj] [app.util.debug :refer [debug?]] - [beicon.core :as rx] + [app.util.object :as obj] [okulary.core :as l] [rumext.alpha :as mf])) +(declare shape-wrapper) (declare group-wrapper) (declare svg-raw-wrapper) (declare frame-wrapper) @@ -54,28 +50,41 @@ (contains? (:selected local) id)))] (l/derived check-moving refs/workspace-local)))) +(mf/defc root-shape + "Draws the root shape of the viewport and recursively all the shapes" + {::mf/wrap-props false} + [props] + (let [objects (obj/get props "objects") + root-shapes (get-in objects [uuid/zero :shapes]) + shapes (->> root-shapes (mapv #(get objects %)))] + + (for [item shapes] + (if (= (:type item) :frame) + [:& frame-wrapper {:shape item + :key (:id item) + :objects objects}] + + [:& shape-wrapper {:shape item + :key (:id item)}])))) + (mf/defc shape-wrapper {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))] ::mf/wrap-props false} [props] (let [shape (obj/get props "shape") frame (obj/get props "frame") - ghost? (mf/use-ctx muc/ghost-ctx) shape (-> (geom/transform-shape shape) (geom/translate-to-frame frame)) opts #js {:shape shape :frame frame} - moving-iref (mf/use-memo (mf/deps (:id shape)) (make-is-moving-ref (:id shape))) - moving? (mf/deref moving-iref) svg-element? (and (= (:type shape) :svg-raw) - (not= :svg (get-in shape [:content :tag]))) - hide-moving? (and (not ghost?) moving?)] + (not= :svg (get-in shape [:content :tag])))] (when (and shape (not (:hidden shape))) [:* (if-not svg-element? - [:g.shape-wrapper {:style {:display (when hide-moving? "none")}} + [:g.shape-wrapper (case (:type shape) :path [:> path/path-wrapper opts] :text [:> text/text-wrapper opts] diff --git a/frontend/src/app/main/ui/workspace/shapes/common.cljs b/frontend/src/app/main/ui/workspace/shapes/common.cljs index 26db44abc..8646bc173 100644 --- a/frontend/src/app/main/ui/workspace/shapes/common.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/common.cljs @@ -10,7 +10,6 @@ (ns app.main.ui.workspace.shapes.common (:require [app.main.ui.shapes.shape :refer [shape-container]] - [app.main.ui.workspace.effects :as we] [rumext.alpha :as mf])) (defn generic-wrapper-factory @@ -19,10 +18,5 @@ {::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape")] - [:> shape-container {:shape shape - :on-mouse-down (we/use-mouse-down shape) - :on-double-click (we/use-double-click shape) - :on-context-menu (we/use-context-menu shape) - :on-pointer-over (we/use-pointer-enter shape) - :on-pointer-out (we/use-pointer-leave shape)} + [:> shape-container {:shape shape} [:& component {:shape shape}]]))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 679f9af15..51781461b 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -17,7 +17,6 @@ [app.main.ui.context :as muc] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.shape :refer [shape-container]] - [app.main.ui.workspace.effects :as we] [app.util.dom :as dom] [app.util.keyboard :as kbd] [app.util.timers :as ts] @@ -25,56 +24,6 @@ [okulary.core :as l] [rumext.alpha :as mf])) -(defn use-select-shape [{:keys [id]} edition] - (mf/use-callback - (mf/deps id edition) - (fn [event] - (when (not edition) - (let [selected @refs/selected-shapes - selected? (contains? selected id) - shift? (kbd/shift? event)] - (cond - (and selected? shift?) - (st/emit! (dw/select-shape id true)) - - (and (not (empty? selected)) (not shift?)) - (st/emit! (dw/deselect-all) (dw/select-shape id)) - - (not selected?) - (st/emit! (dw/select-shape id)))))))) - -;; Ensure that the label has always the same font -;; size, regardless of zoom -;; https://css-tricks.com/transforms-on-svg-elements/ -(defn text-transform - [{:keys [x y]} zoom] - (let [inv-zoom (/ 1 zoom)] - (str - "scale(" inv-zoom ", " inv-zoom ") " - "translate(" (* zoom x) ", " (* zoom y) ")"))) - -(mf/defc frame-title - [{:keys [frame]}] - (let [{:keys [width x y]} frame - zoom (mf/deref refs/selected-zoom) - edition (mf/deref refs/selected-edition) - label-pos (gpt/point x (- y (/ 10 zoom))) - handle-click (use-select-shape frame edition) - handle-mouse-down (we/use-mouse-down frame) - handle-pointer-enter (we/use-pointer-enter frame) - handle-pointer-leave (we/use-pointer-leave frame)] - [:text {:x 0 - :y 0 - :width width - :height 20 - :class "workspace-frame-label" - :transform (text-transform label-pos zoom) - :on-click handle-click - :on-mouse-down handle-mouse-down - :on-pointer-over handle-pointer-enter - :on-pointer-out handle-pointer-leave} - (:name frame)])) - (defn make-is-moving-ref [id] (let [check-moving (fn [local] @@ -89,17 +38,14 @@ (mf/fnc deferred {::mf/wrap-props false} [props] - (let [ghost? (mf/use-ctx muc/ghost-ctx) - tmp (mf/useState false) + (let [tmp (mf/useState false) ^boolean render? (aget tmp 0) ^js set-render (aget tmp 1)] (mf/use-layout-effect (fn [] (let [sem (ts/schedule-on-idle #(set-render true))] #(rx/dispose! sem)))) - (if ghost? - (mf/create-element component props) - (when render? (mf/create-element component props)))))) + (when render? (mf/create-element component props))))) (defn frame-wrapper-factory [shape-wrapper] @@ -108,38 +54,16 @@ {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "objects"])) custom-deferred] ::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - objects (unchecked-get props "objects") - ghost? (mf/use-ctx muc/ghost-ctx) - edition (mf/deref refs/selected-edition) + (let [shape (unchecked-get props "shape") + objects (unchecked-get props "objects") + edition (mf/deref refs/selected-edition) - moving-iref (mf/use-memo (mf/deps (:id shape)) - #(make-is-moving-ref (:id shape))) - moving? (mf/deref moving-iref) - - selected-iref (mf/use-memo (mf/deps (:id shape)) - #(refs/make-selected-ref (:id shape))) - selected? (mf/deref selected-iref) - - shape (gsh/transform-shape shape) - children (mapv #(get objects %) (:shapes shape)) - ds-modifier (get-in shape [:modifiers :displacement]) - - handle-context-menu (we/use-context-menu shape) - handle-double-click (use-select-shape shape edition) - handle-mouse-down (we/use-mouse-down shape) - - hide-moving? (and (not ghost?) moving?)] + shape (gsh/transform-shape shape) + children (mapv #(get objects %) (:shapes shape)) + ds-modifier (get-in shape [:modifiers :displacement])] (when (and shape (not (:hidden shape))) - [:g.frame-wrapper {:class (when selected? "selected") - :style {:display (when hide-moving? "none")} - :on-context-menu handle-context-menu - :on-double-click handle-double-click - :on-mouse-down handle-mouse-down} - - [:& frame-title {:frame shape}] - + [:g.frame-wrapper {:display (when (:hidden shape) "none")} [:> shape-container {:shape shape} [:& frame-shape {:shape shape diff --git a/frontend/src/app/main/ui/workspace/shapes/group.cljs b/frontend/src/app/main/ui/workspace/shapes/group.cljs index bb8ac5c04..e677c7d06 100644 --- a/frontend/src/app/main/ui/workspace/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/group.cljs @@ -17,7 +17,6 @@ [app.main.ui.hooks :as hooks] [app.main.ui.shapes.group :as group] [app.main.ui.shapes.shape :refer [shape-container]] - [app.main.ui.workspace.effects :as we] [app.util.debug :refer [debug?]] [app.util.dom :as dom] [rumext.alpha :as mf])) @@ -42,58 +41,13 @@ {:keys [id x y width height]} shape - transform (gsh/transform-matrix shape) - - ctrl? (mf/use-state false) childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape))) - childs (mf/deref childs-ref) - - is-child-selected-ref - (mf/use-memo (mf/deps (:id shape)) #(refs/is-child-selected? (:id shape))) - - is-child-selected? - (mf/deref is-child-selected-ref) - - mask-id (when (:masked-group? shape) (first (:shapes shape))) - - is-mask-selected-ref - (mf/use-memo (mf/deps mask-id) #(refs/make-selected-ref mask-id)) - - is-mask-selected? - (mf/deref is-mask-selected-ref) - - expand-mask? is-child-selected? - group-interactions? (not (or @ctrl? is-child-selected?)) - - handle-mouse-down (we/use-mouse-down shape) - handle-context-menu (we/use-context-menu shape) - handle-pointer-enter (we/use-pointer-enter shape) - handle-pointer-leave (we/use-pointer-leave shape) - handle-double-click (use-double-click shape)] - - (hooks/use-stream ms/keyboard-ctrl #(reset! ctrl? %)) + childs (mf/deref childs-ref)] [:> shape-container {:shape shape} [:g.group-shape [:& group-shape {:frame frame :shape shape - :childs childs - :expand-mask expand-mask? - :pointer-events (when group-interactions? "none")}] - - [:rect.group-actions - {:x x - :y y - :width width - :height height - :transform transform - :style {:pointer-events (when-not group-interactions? "none") - :fill (if (debug? :group) "red" "transparent") - :opacity 0.5} - :on-mouse-down handle-mouse-down - :on-context-menu handle-context-menu - :on-pointer-over handle-pointer-enter - :on-pointer-out handle-pointer-leave - :on-double-click handle-double-click}]]])))) + :childs childs}]]])))) diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs index b1935e84d..874c3d316 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs @@ -14,21 +14,11 @@ [app.main.store :as st] [app.main.ui.shapes.path :as path] [app.main.ui.shapes.shape :refer [shape-container]] - [app.main.ui.workspace.effects :as we] [app.main.ui.workspace.shapes.path.common :as pc] [app.util.dom :as dom] [app.util.geom.path :as ugp] [rumext.alpha :as mf])) -(defn use-double-click [{:keys [id]}] - (mf/use-callback - (mf/deps id) - (fn [event] - (dom/stop-propagation event) - (dom/prevent-default event) - (st/emit! (dw/start-edition-mode id) - (dw/start-path-edit id))))) - (mf/defc path-wrapper {::mf/wrap-props false} [props] @@ -37,19 +27,9 @@ content-modifiers (mf/deref content-modifiers-ref) editing-id (mf/deref refs/selected-edition) editing? (= editing-id (:id shape)) - shape (update shape :content ugp/apply-content-modifiers content-modifiers) - handle-mouse-down (we/use-mouse-down shape) - handle-context-menu (we/use-context-menu shape) - handle-pointer-enter (we/use-pointer-enter shape) - handle-pointer-leave (we/use-pointer-leave shape) - handle-double-click (use-double-click shape)] + shape (update shape :content ugp/apply-content-modifiers content-modifiers)] [:> shape-container {:shape shape - :pointer-events (when editing? "none") - :on-mouse-down handle-mouse-down - :on-context-menu handle-context-menu - :on-pointer-over handle-pointer-enter - :on-pointer-out handle-pointer-leave - :on-double-click handle-double-click} + :pointer-events (when editing? "none")} [:& path/path-shape {:shape shape :background? true}]])) diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs index 52ce86258..2e2dd189d 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -9,12 +9,12 @@ (ns app.main.ui.workspace.shapes.path.editor (:require + [app.common.data :as d] [app.common.geom.point :as gpt] [app.main.data.workspace.drawing.path :as drp] [app.main.store :as st] [app.main.ui.cursors :as cur] [app.main.ui.workspace.shapes.path.common :as pc] - [app.util.data :as d] [app.util.dom :as dom] [app.util.geom.path :as ugp] [goog.events :as events] @@ -35,32 +35,32 @@ on-click (fn [event] (when-not last-p? - (do (dom/stop-propagation event) - (dom/prevent-default event) + (dom/stop-propagation event) + (dom/prevent-default event) - (cond - (and (= edit-mode :move) (not selected?)) - (st/emit! (drp/select-node position)) + (cond + (and (= edit-mode :move) (not selected?)) + (st/emit! (drp/select-node position)) - (and (= edit-mode :move) selected?) - (st/emit! (drp/deselect-node position)))))) + (and (= edit-mode :move) selected?) + (st/emit! (drp/deselect-node position))))) on-mouse-down (fn [event] (when-not last-p? - (do (dom/stop-propagation event) - (dom/prevent-default event) + (dom/stop-propagation event) + (dom/prevent-default event) - (cond - (= edit-mode :move) - (st/emit! (drp/start-move-path-point position)) + (cond + (= edit-mode :move) + (st/emit! (drp/start-move-path-point position)) - (and (= edit-mode :draw) start-path?) - (st/emit! (drp/start-path-from-point position)) + (and (= edit-mode :draw) start-path?) + (st/emit! (drp/start-path-from-point position)) - (and (= edit-mode :draw) (not start-path?)) - (st/emit! (drp/close-path-drag-start position))))))] + (and (= edit-mode :draw) (not start-path?)) + (st/emit! (drp/close-path-drag-start position)))))] [:g.path-point [:circle.path-point @@ -170,7 +170,9 @@ selected-handlers selected-points hover-handlers - hover-points]} (mf/deref edit-path-ref) + hover-points] + :as edit-path} (mf/deref edit-path-ref) + {:keys [content]} shape content (ugp/apply-content-modifiers content content-modifiers) points (->> content ugp/content->points (into #{})) diff --git a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs index 637515d60..57eefbb86 100644 --- a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs @@ -12,7 +12,6 @@ [app.main.refs :as refs] [app.main.ui.shapes.svg-raw :as svg-raw] [app.main.ui.shapes.shape :refer [shape-container]] - [app.main.ui.workspace.effects :as we] [rumext.alpha :as mf] [app.common.geom.shapes :as gsh] [app.main.ui.context :as muc])) @@ -41,12 +40,6 @@ tag (get-in shape [:content :tag]) - handle-mouse-down (we/use-mouse-down shape) - handle-context-menu (we/use-context-menu shape) - handle-pointer-enter (we/use-pointer-enter shape) - handle-pointer-leave (we/use-pointer-leave shape) - handle-double-click (we/use-double-click shape) - def-ctx? (mf/use-ctx muc/def-ctx)] (cond @@ -64,12 +57,7 @@ :width width :height height :fill "transparent" - :stroke "none" - :on-mouse-down handle-mouse-down - :on-double-click handle-double-click - :on-context-menu handle-context-menu - :on-pointer-over handle-pointer-enter - :on-pointer-out handle-pointer-leave}]] + :stroke "none"}]] ;; We cannot wrap inside groups the shapes that go inside the defs tag ;; we use the context so we know when we should not render the container diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs index fcb1c88b6..e9b081441 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs @@ -19,9 +19,7 @@ [app.main.ui.context :as muc] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.text :as text] - [app.main.ui.workspace.effects :as we] [app.main.ui.workspace.shapes.common :as common] - [app.main.ui.workspace.shapes.text.editor :as editor] [app.util.dom :as dom] [app.util.logging :as log] [app.util.object :as obj] @@ -33,16 +31,6 @@ ;; Change this to :info :debug or :trace to debug this module (log/set-level! :warn) -;; --- Events - -(defn use-double-click [{:keys [id]}] - (mf/use-callback - (mf/deps id) - (fn [event] - (dom/stop-propagation event) - (dom/prevent-default event) - (st/emit! (dw/start-edition-mode id))))) - ;; --- Text Wrapper for workspace (mf/defc text-static-content @@ -107,15 +95,8 @@ {::mf/wrap-props false} [props] (let [{:keys [id x y width height] :as shape} (unchecked-get props "shape") - ghost? (mf/use-ctx muc/ghost-ctx) edition (mf/deref refs/selected-edition) - edition? (= edition id) - - handle-mouse-down (we/use-mouse-down shape) - handle-context-menu (we/use-context-menu shape) - handle-pointer-enter (we/use-pointer-enter shape) - handle-pointer-leave (we/use-pointer-leave shape) - handle-double-click (use-double-click shape)] + edition? (= edition id)] [:> shape-container {:shape shape} ;; We keep hidden the shape when we're editing so it keeps track of the size @@ -123,24 +104,4 @@ [:g.text-shape {:opacity (when edition? 0) :pointer-events "none"} - (if ghost? - [:& text-static-content {:shape shape}] - [:& text-resize-content {:shape shape}])] - - (when (and (not ghost?) edition?) - [:& editor/text-shape-edit {:key (str "editor" (:id shape)) - :shape shape}]) - - (when-not edition? - [:rect.text-actions - {:x x - :y y - :width width - :height height - :style {:fill "transparent"} - :on-mouse-down handle-mouse-down - :on-context-menu handle-context-menu - :on-pointer-over handle-pointer-enter - :on-pointer-out handle-pointer-leave - :on-double-click handle-double-click - :transform (gsh/transform-matrix shape)}])])) + [:& text-resize-content {:shape shape}]]])) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs index 8e98c981e..72e6eb92f 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -31,17 +31,6 @@ goog.events.EventType goog.events.KeyCodes)) -;; --- Data functions - -;; TODO: why we need this? -;; (defn- fix-gradients -;; "Fix for the gradient types that need to be keywords" -;; [content] -;; (let [fix-node -;; (fn [node] -;; (d/update-in-when node [:fill-color-gradient :type] keyword))] -;; (txt/map-node fix-node content))) - ;; --- Text Editor Rendering (mf/defc block-component @@ -95,22 +84,6 @@ blured (mf/use-var false) - on-click-outside - (fn [event] - (let [target (dom/get-target event) - options (dom/get-element-by-class "element-options") - assets (dom/get-element-by-class "assets-bar") - cpicker (dom/get-element-by-class "colorpicker-tooltip") - palette (dom/get-element-by-class "color-palette") - self (mf/ref-val self-ref)] - (when-not (or (and options (.contains options target)) - (and assets (.contains assets target)) - (and self (.contains self target)) - (and cpicker (.contains cpicker target)) - (and palette (.contains palette target)) - (= "foreignObject" (.-tagName ^js target))) - (st/emit! dw/clear-edition-mode)))) - on-key-up (fn [event] (dom/stop-propagation event) @@ -121,9 +94,7 @@ on-mount (fn [] - (let [keys [(events/listen js/document EventType.MOUSEDOWN on-click-outside) - (events/listen js/document EventType.CLICK on-click-outside) - (events/listen js/document EventType.KEYUP on-key-up)]] + (let [keys [(events/listen js/document EventType.KEYUP on-key-up)]] (st/emit! (dwt/initialize-editor-state shape default-decorator) (dwt/select-all shape)) #(do @@ -172,6 +143,7 @@ [:div.text-editor {:ref self-ref :style {:cursor cur/text} + :on-click (st/emitf (dwt/focus-editor)) :class (dom/classnames :align-top (= (:vertical-align content "top") "top") :align-center (= (:vertical-align content) "center") diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index 2a6c6d80b..1a4ff4354 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -50,3 +50,4 @@ (if (= drawing-tool :comments) [:& comments-sidebar] [:> options-toolbox props])]])) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index b683b10bf..8e0852aa9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -122,7 +122,7 @@ (if (:blocked item) (st/emit! (dw/update-shape-flags id {:blocked false})) (st/emit! (dw/update-shape-flags id {:blocked true}) - (dw/select-shape id true)))) + (dw/deselect-shape id)))) toggle-visibility (fn [event] @@ -147,11 +147,9 @@ (st/emit! (dw/select-shape id true)) (> (count selected) 1) - (st/emit! (dw/deselect-all) - (dw/select-shape id)) + (st/emit! (dw/select-shape id)) :else - (st/emit! (dw/deselect-all) - (dw/select-shape id))))) + (st/emit! (dw/select-shape id))))) on-context-menu (fn [event] @@ -164,8 +162,7 @@ on-drag (fn [{:keys [id]}] (when (not (contains? selected id)) - (st/emit! (dw/deselect-all) - (dw/select-shape id)))) + (st/emit! (dw/select-shape id)))) on-drop (fn [side {:keys [id] :as data}] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index bb235e5f0..eb9515f49 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -1,862 +1,285 @@ -; This Source Code Form is subject to the terms of the Mozilla Public +;; 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/. ;; ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) 2020-2021 UXBOX Labs SL (ns app.main.ui.workspace.viewport (:require - [app.common.data :as d] - [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.math :as mth] - [app.common.uuid :as uuid] - [app.config :as cfg] - [app.main.constants :as c] - [app.main.data.colors :as dwc] - [app.main.data.fetch :as mdf] - [app.main.data.modal :as modal] - [app.main.data.workspace :as dw] - [app.main.data.workspace.drawing :as dd] - [app.main.data.workspace.libraries :as dwl] [app.main.refs :as refs] - [app.main.store :as st] - [app.main.streams :as ms] [app.main.ui.context :as ctx] - [app.main.ui.cursors :as cur] - [app.main.ui.hooks :as hooks] - [app.main.ui.icons :as i] - [app.main.ui.workspace.colorpicker.pixel-overlay :refer [pixel-overlay]] - [app.main.ui.workspace.comments :refer [comments-layer]] - [app.main.ui.workspace.drawarea :refer [draw-area]] - [app.main.ui.workspace.frame-grid :refer [frame-grid]] - [app.main.ui.workspace.gradients :refer [gradient-handlers]] - [app.main.ui.workspace.presence :as presence] - [app.main.ui.workspace.selection :refer [selection-handlers]] - [app.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]] - [app.main.ui.workspace.shapes.interactions :refer [interactions]] - [app.main.ui.workspace.shapes.outline :refer [outline]] - [app.main.ui.workspace.shapes.path.actions :refer [path-actions]] - [app.main.ui.workspace.snap-distances :refer [snap-distances]] - [app.main.ui.workspace.snap-points :refer [snap-points]] - [app.util.dom :as dom] - [app.util.dom.dnd :as dnd] - [app.util.http :as http] - [app.util.keyboard :as kbd] - [app.util.object :as obj] - [app.util.perf :as perf] - [app.util.timers :as timers] + [app.main.ui.context :as muc] + [app.main.ui.workspace.shapes :as shapes] + [app.main.ui.workspace.shapes.text.editor :as editor] + [app.main.ui.workspace.viewport.actions :as actions] + [app.main.ui.workspace.viewport.comments :as comments] + [app.main.ui.workspace.viewport.drawarea :as drawarea] + [app.main.ui.workspace.viewport.frame-grid :as frame-grid] + [app.main.ui.workspace.viewport.gradients :as gradients] + [app.main.ui.workspace.viewport.hooks :as hooks] + [app.main.ui.workspace.viewport.interactions :as interactions] + [app.main.ui.workspace.viewport.pixel-overlay :as pixel-overlay] + [app.main.ui.workspace.viewport.presence :as presence] + [app.main.ui.workspace.viewport.selection :as selection] + [app.main.ui.workspace.viewport.snap-distances :as snap-distances] + [app.main.ui.workspace.viewport.snap-points :as snap-points] + [app.main.ui.workspace.viewport.utils :as utils] + [app.main.ui.workspace.viewport.widgets :as widgets] [beicon.core :as rx] - [clojure.set :as set] - [cuerdas.core :as str] - [goog.events :as events] - [potok.core :as ptk] - [promesa.core :as p] - [rumext.alpha :as mf]) - (:import goog.events.EventType - goog.events.WheelEvent - goog.events.KeyCodes)) - -(defonce css-mouse? - (cfg/check-browser? :firefox)) - -(defn get-cursor [cursor] - (if-not css-mouse? - (name cursor) - - (case cursor - :hand cur/hand - :comments cur/comments - :create-artboard cur/create-artboard - :create-rectangle cur/create-rectangle - :create-ellipse cur/create-ellipse - :pen cur/pen - :pencil cur/pencil - :create-shape cur/create-shape - :duplicate cur/duplicate - cur/pointer-inner))) - -;; --- Coordinates Widget - -(mf/defc coordinates - [{:keys [colorpalette?]}] - (let [coords (hooks/use-rxsub ms/mouse-position)] - [:ul.coordinates {:class (when colorpalette? "color-palette-open")} - [:span {:alt "x"} - (str "X: " (:x coords "-"))] - [:span {:alt "y"} - (str "Y: " (:y coords "-"))]])) - -(mf/defc cursor-tooltip - [{:keys [zoom tooltip] :as props}] - (let [coords (some-> (hooks/use-rxsub ms/mouse-position) - (gpt/divide (gpt/point zoom zoom))) - pos-x (- (:x coords) 100) - pos-y (+ (:y coords) 30)] - [:g {:transform (str "translate(" pos-x "," pos-y ")")} - [:foreignObject {:width 200 :height 100 :style {:text-align "center"}} - [:span tooltip]]])) - -;; --- Cursor tooltip - -(defn- get-shape-tooltip - "Return the shape tooltip text" - [shape] - (case (:type shape) - :icon "Click to place the Icon" - :image "Click to place the Image" - :rect "Drag to draw a Box" - :text "Drag to draw a Text Box" - :path "Click to draw a Path" - :circle "Drag to draw a Circle" - nil)) - -;; --- Selection Rect - -(mf/defc selection-rect - {:wrap [mf/memo]} - [{:keys [data] :as props}] - (when data - [:rect.selection-rect - {:x (:x data) - :y (:y data) - :width (:width data) - :height (:height data)}])) - -;; --- Viewport Positioning - -(defn- handle-viewport-positioning - [viewport-ref] - (let [node (mf/ref-val viewport-ref) - stoper (rx/filter #(= ::finish-positioning %) st/stream)] - - (st/emit! dw/start-pan) - - (->> st/stream - (rx/filter ms/pointer-event?) - (rx/filter #(= :delta (:source %))) - (rx/map :pt) - (rx/take-until stoper) - (rx/subs (fn [delta] - (let [zoom (gpt/point @refs/selected-zoom) - delta (gpt/divide delta zoom)] - (st/emit! (dw/update-viewport-position - {:x #(- % (:x delta)) - :y #(- % (:y delta))})))))))) + [rumext.alpha :as mf])) ;; --- Viewport -(declare remote-user-cursors) - -(mf/defc render-cursor - {::mf/wrap-props false} - [props] - (let [cursor (unchecked-get props "cursor") - viewport (unchecked-get props "viewport") - - visible? (mf/use-state true) - in-viewport? (mf/use-state true) - - cursor-ref (mf/use-ref nil) - - node (mf/ref-val cursor-ref) - - on-mouse-move - (mf/use-callback - (mf/deps node @visible?) - (fn [left top event] - - (let [target (dom/get-target event) - style (.getComputedStyle js/window target) - cursor (.getPropertyValue style "cursor") - - x (- (.-clientX event) left) - y (- (.-clientY event) top)] - - (cond - (and (= cursor "none") (not @visible?)) - (reset! visible? true) - - (and (not= cursor "none") @visible?) - (reset! visible? false)) - - (timers/raf - #(let [style (obj/get node "style")] - (obj/set! style "transform" (str "translate(" x "px, " y "px)")))))))] - - (mf/use-layout-effect - (mf/deps on-mouse-move) - (fn [] - (when viewport - (let [{:keys [left top]} (dom/get-bounding-rect viewport) - keys [(events/listen (dom/get-root) EventType.MOUSEMOVE (partial on-mouse-move left top)) - (events/listen viewport EventType.POINTERENTER #(reset! in-viewport? true)) - (events/listen viewport EventType.POINTERLEAVE #(reset! in-viewport? false))]] - - (fn [] - (doseq [key keys] - (events/unlistenByKey key))))))) - - [:svg {:ref cursor-ref - :width 20 - :height 20 - :viewBox "0 0 16 18" - :style {:position "absolute" - :pointer-events "none" - :will-change "transform" - :display (when-not (and @in-viewport? @visible?) "none")}} - [:use {:xlinkHref (str "#cursor-" cursor)}]])) - -;; TODO: revisit the refs usage (vs props) -(mf/defc shape-outlines - {::mf/wrap-props false} - [props] - (let [objects (unchecked-get props "objects") - selected (or (unchecked-get props "selected") #{}) - hover (or (unchecked-get props "hover") #{}) - edition (unchecked-get props "edition") - outline? (set/union selected hover) - show-outline? (fn [shape] (and (not (:hidden shape)) - (not (:blocked shape)) - (not= edition (:id shape)) - (outline? (:id shape)))) - - remove-groups? (mf/use-state false) - - shapes (cond->> (vals objects) - show-outline? (filter show-outline?) - @remove-groups? (remove #(= :group (:type %)))) - transform (mf/deref refs/current-transform) - color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes)))) - "#31EFB8" - "#00E0FF")] - (hooks/use-stream ms/keyboard-ctrl #(reset! remove-groups? %)) - (when (nil? transform) - [:g.outlines - (for [shape shapes] - [:& outline {:key (str "outline-" (:id shape)) - :shape (gsh/transform-shape shape) - :color color}])]))) - -(mf/defc pixel-grid - [{:keys [vbox zoom]}] - [:g.pixel-grid - [:defs - [:pattern {:id "pixel-grid" - :viewBox "0 0 1 1" - :width 1 - :height 1 - :pattern-units "userSpaceOnUse"} - [:path {:d "M 1 0 L 0 0 0 1" - :style {:fill "none" - :stroke "#59B9E2" - :stroke-opacity "0.2" - :stroke-width (str (/ 1 zoom))}}]]] - [:rect {:x (:x vbox) - :y (:y vbox) - :width (:width vbox) - :height (:height vbox) - :fill (str "url(#pixel-grid)") - :style {:pointer-events "none"}}]]) - -(mf/defc frames - {::mf/wrap [mf/memo] - ::mf/wrap-props false} - [props] - (let [hover (unchecked-get props "hover") - selected (unchecked-get props "selected") - ids (unchecked-get props "ids") - edition (unchecked-get props "edition") - data (mf/deref refs/workspace-page) - objects (:objects data) - root (get objects uuid/zero) - shapes (->> (:shapes root) - (map #(get objects %))) - - shapes (if ids - (->> ids (map #(get objects %))) - shapes)] - - [:* - [:g.shapes - (for [item shapes] - (if (= (:type item) :frame) - [:& frame-wrapper {:shape item - :key (:id item) - :objects objects}] - [:& shape-wrapper {:shape item - :key (:id item)}]))] - - [:& shape-outlines {:objects objects - :selected selected - :hover hover - :edition edition}]])) - -(mf/defc ghost-frames - {::mf/wrap [mf/memo] - ::mf/wrap-props false} - [props] - (let [modifiers (obj/get props "modifiers") - selected (obj/get props "selected") - - sobjects (mf/deref refs/selected-objects) - selrect-orig (gsh/selection-rect sobjects) - - xf (comp - (map #(assoc % :modifiers modifiers)) - (map gsh/transform-shape)) - - selrect (->> (into [] xf sobjects) - (gsh/selection-rect)) - - transform (when (and (mth/finite? (:x selrect-orig)) - (mth/finite? (:y selrect-orig))) - (str/fmt "translate(%s,%s)" (- (:x selrect-orig)) (- (:y selrect-orig))))] - [:& (mf/provider ctx/ghost-ctx) {:value true} - [:svg.ghost - {:x (mth/finite (:x selrect) 0) - :y (mth/finite (:y selrect) 0) - :width (mth/finite (:width selrect) 100) - :height (mth/finite (:height selrect) 100) - :style {:pointer-events "none"}} - - [:g {:transform transform} - [:& frames - {:ids selected}]]]])) - -(defn format-viewbox [vbox] - (str/join " " [(+ (:x vbox 0) (:left-offset vbox 0)) - (:y vbox 0) - (:width vbox 0) - (:height vbox 0)])) - (mf/defc viewport [{:keys [local layout file] :as props}] (let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check ;; that the new parameter is sent - {:keys [options-mode - zoom - vport - vbox + {:keys [edit-path edition - edit-path - tooltip - selected + modifiers + options-mode panning picking-color? - transform - hover - modifiers + selected selrect - show-distances?]} local + show-distances? + tooltip + transform + vbox + vport + zoom]} local - page-id (mf/use-ctx ctx/current-page-id) - selected-objects (mf/deref refs/selected-objects) + ;; CONTEXT + page-id (mf/use-ctx ctx/current-page-id) - alt? (mf/use-state false) - cursor (mf/use-state (get-cursor :pointer-inner)) + ;; DEREFS + drawing (mf/deref refs/workspace-drawing) + options (mf/deref refs/workspace-page-options) + objects (mf/deref refs/workspace-page-objects) - viewport-ref (mf/use-ref nil) - viewport-node (mf/use-state nil) + ;; STATE + alt? (mf/use-state false) + ctrl? (mf/use-state false) + cursor (mf/use-state (utils/get-cursor :pointer-inner)) + hover-ids (mf/use-state nil) + hover (mf/use-state nil) + frame-hover (mf/use-state nil) - zoom-view-ref (mf/use-ref nil) - last-position (mf/use-var nil) - disable-paste (mf/use-var false) - in-viewport? (mf/use-var false) + ;; REFS + viewport-ref (mf/use-ref nil) + zoom-view-ref (mf/use-ref nil) + render-ref (mf/use-ref nil) - drawing (mf/deref refs/workspace-drawing) - drawing-tool (:tool drawing) - drawing-obj (:object drawing) - drawing-path? (and edition (= :draw (get-in edit-path [edition :edit-mode]))) - zoom (or zoom 1) + ;; VARS + disable-paste (mf/use-var false) + in-viewport? (mf/use-var false) - show-grids? (contains? layout :display-grid) - show-snap-points? (and (contains? layout :dynamic-alignment) - (or drawing-obj transform)) - show-snap-distance? (and (contains? layout :dynamic-alignment) - (= transform :move) - (not (empty? selected))) + ;; STREAMS + move-stream (mf/use-memo #(rx/subject)) - on-mouse-down - (mf/use-callback - (mf/deps drawing-tool edition) - (fn [event] - (dom/stop-propagation event) - (let [event (.-nativeEvent event) - ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - alt? (kbd/alt? event)] - (when (= 1 (.-which event)) - (st/emit! (ms/->MouseEvent :down ctrl? shift? alt?))) + zoom (or zoom 1) + drawing-tool (:tool drawing) + drawing-obj (:object drawing) - (cond - (and (= 1 (.-which event)) (not edition)) - (if drawing-tool - (when (not (#{:comments :path} drawing-tool)) - (st/emit! (dd/start-drawing drawing-tool))) - (st/emit! (dw/handle-selection shift?))) + drawing-path? (and edition (= :draw (get-in edit-path [edition :edit-mode]))) + text-editing? (and edition (= :text (get-in objects [edition :type]))) - (and (= 2 (.-which event))) - (handle-viewport-positioning viewport-ref))))) + on-click (actions/on-click) + on-context-menu (actions/on-context-menu hover) + on-double-click (actions/on-double-click hover hover-ids objects) + on-drag-enter (actions/on-drag-enter) + on-drag-over (actions/on-drag-over) + on-drop (actions/on-drop file viewport-ref zoom) + on-mouse-down (actions/on-mouse-down @hover drawing-tool text-editing? edition edit-path selected) + on-mouse-up (actions/on-mouse-up disable-paste) + on-pointer-down (actions/on-pointer-down) + on-pointer-enter (actions/on-pointer-enter in-viewport?) + on-pointer-leave (actions/on-pointer-leave in-viewport?) + on-pointer-move (actions/on-pointer-move viewport-ref zoom move-stream) + on-pointer-up (actions/on-pointer-up) + on-move-selected (actions/on-move-selected hover selected) - on-context-menu - (mf/use-callback - (fn [event] - (dom/prevent-default event) - (let [position (dom/get-client-position event)] - (st/emit! (dw/show-context-menu {:position position}))))) + on-frame-enter (actions/on-frame-enter frame-hover) + on-frame-leave (actions/on-frame-leave frame-hover) + on-frame-select (actions/on-frame-select selected) - on-mouse-up - (mf/use-callback - (fn [event] - (dom/stop-propagation event) - (let [event (.-nativeEvent event) - ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - alt? (kbd/alt? event)] - (when (= 1 (.-which event)) - (st/emit! (ms/->MouseEvent :up ctrl? shift? alt?))) + disable-events? (contains? layout :comments) + show-comments? (= drawing-tool :comments) + show-cursor-tooltip? tooltip + show-draw-area? drawing-obj + show-gradient-handlers? (= (count selected) 1) + show-grids? (contains? layout :display-grid) + show-outlines? (and (nil? transform) (not edit-path)) + show-pixel-grid? (>= zoom 8) + show-presence? page-id + show-prototypes? (= options-mode :prototype) + show-selection-handlers? (seq selected) + show-snap-distance? (and (contains? layout :dynamic-alignment) (= transform :move) (not (empty? selected))) + show-snap-points? (and (contains? layout :dynamic-alignment) (or drawing-obj transform)) + show-selrect? (and selrect (empty? drawing)) + ] - (when (= 2 (.-which event)) - (do - (dom/prevent-default event) + (hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?) + (hooks/setup-viewport-size viewport-ref) + (hooks/setup-cursor cursor alt? panning drawing-tool drawing-path?) + (hooks/setup-resize layout viewport-ref) + (hooks/setup-keyboard alt? ctrl?) + (hooks/setup-hover-shapes page-id move-stream selected objects transform selected ctrl? hover hover-ids) + (hooks/setup-viewport-modifiers modifiers selected objects render-ref) - ;; We store this so in Firefox the middle button won't do a paste of the content - (reset! disable-paste true) - (timers/schedule #(reset! disable-paste false)) - (st/emit! dw/finish-pan - ::finish-positioning)))))) + [:div.viewport + [:div.viewport-overlays + (when show-comments? + [:& comments/comments-layer {:vbox vbox + :vport vport + :zoom zoom + :drawing drawing + :page-id page-id + :file-id (:id file)}]) - on-pointer-down - (mf/use-callback - (fn [event] - ;; We need to handle editor related stuff here because - ;; handling on editor dom node does not works properly. - (let [target (dom/get-target event) - editor (.closest ^js target ".public-DraftEditor-content")] - ;; Capture mouse pointer to detect the movements even if cursor - ;; leaves the viewport or the browser itself - ;; https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture - (if editor - (.setPointerCapture editor (.-pointerId event)) - (.setPointerCapture target (.-pointerId event)))))) + (when picking-color? + [:& pixel-overlay/pixel-overlay {:vport vport + :vbox vbox + :options options + :layout layout + :viewport-ref viewport-ref}]) - on-pointer-up - (mf/use-callback - (fn [event] - (let [target (dom/get-target event)] - ; Release pointer on mouse up - (.releasePointerCapture target (.-pointerId event))))) - - on-click - (mf/use-callback - (fn [event] - (let [ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - alt? (kbd/alt? event)] - (st/emit! (ms/->MouseEvent :click ctrl? shift? alt?))))) - - on-double-click - (mf/use-callback - (mf/deps drawing-path?) - (fn [event] - (dom/stop-propagation event) - (let [ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - alt? (kbd/alt? event)] - (st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt?))))) - - on-key-down - (mf/use-callback - (fn [event] - (let [bevent (.getBrowserEvent ^js event) - key (.-keyCode ^js event) - key (.normalizeKeyCode KeyCodes key) - ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - alt? (kbd/alt? event) - meta? (kbd/meta? event) - target (dom/get-target event)] - - (when-not (.-repeat bevent) - (st/emit! (ms/->KeyboardEvent :down key shift? ctrl? alt? meta?)) - (when (and (kbd/space? event) - (not= "rich-text" (obj/get target "className")) - (not= "INPUT" (obj/get target "tagName")) - (not= "TEXTAREA" (obj/get target "tagName"))) - (handle-viewport-positioning viewport-ref)))))) - - on-key-up - (mf/use-callback - (fn [event] - (let [key (.-keyCode event) - key (.normalizeKeyCode KeyCodes key) - ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - alt? (kbd/alt? event) - meta? (kbd/meta? event)] - (when (kbd/space? event) - (st/emit! dw/finish-pan ::finish-positioning)) - (st/emit! (ms/->KeyboardEvent :up key shift? ctrl? alt? meta?))))) - - translate-point-to-viewport - (mf/use-callback - (fn [pt] - (let [viewport (mf/ref-val viewport-ref) - vbox (.. ^js viewport -viewBox -baseVal) - brect (.getBoundingClientRect viewport) - brect (gpt/point (d/parse-integer (.-left brect)) - (d/parse-integer (.-top brect))) - box (gpt/point (.-x vbox) - (.-y vbox)) - ] - (-> (gpt/subtract pt brect) - (gpt/divide (gpt/point @refs/selected-zoom)) - (gpt/add box) - (gpt/round 0))))) - - on-mouse-move - (mf/use-callback - (fn [event] - (let [event (.getBrowserEvent ^js event) - raw-pt (dom/get-client-position event) - pt (translate-point-to-viewport raw-pt) - - ;; We calculate the delta because Safari's MouseEvent.movementX/Y drop - ;; events - delta (if @last-position - (gpt/subtract raw-pt @last-position) - (gpt/point 0 0))] - (reset! last-position raw-pt) - (st/emit! (ms/->PointerEvent :delta delta - (kbd/ctrl? event) - (kbd/shift? event) - (kbd/alt? event))) - (st/emit! (ms/->PointerEvent :viewport pt - (kbd/ctrl? event) - (kbd/shift? event) - (kbd/alt? event)))))) - - on-mouse-wheel - (mf/use-callback - (fn [event] - (let [node (mf/ref-val viewport-ref) - target (dom/get-target event)] - (cond - (or (kbd/ctrl? event) (kbd/meta? event)) - (let [event (.getBrowserEvent ^js event) - pos @ms/mouse-position] - (dom/prevent-default event) - (dom/stop-propagation event) - (let [delta (+ (.-deltaY ^js event) - (.-deltaX ^js event))] - (if (pos? delta) - (st/emit! (dw/decrease-zoom pos)) - (st/emit! (dw/increase-zoom pos))))) - - (.contains ^js node target) - (let [event (.getBrowserEvent ^js event) - delta-mode (.-deltaMode ^js event) - - unit (cond - (= delta-mode WheelEvent.DeltaMode.PIXEL) 1 - (= delta-mode WheelEvent.DeltaMode.LINE) 16 - (= delta-mode WheelEvent.DeltaMode.PAGE) 100) - - delta-y (-> (.-deltaY ^js event) - (* unit) - (/ @refs/selected-zoom)) - delta-x (-> (.-deltaX ^js event) - (* unit) - (/ @refs/selected-zoom))] - (dom/prevent-default event) - (dom/stop-propagation event) - (if (kbd/shift? event) - (st/emit! (dw/update-viewport-position {:x #(+ % delta-y)})) - (st/emit! (dw/update-viewport-position {:x #(+ % delta-x) - :y #(+ % delta-y)})))))))) - - on-drag-enter - (mf/use-callback - (fn [e] - (when (or (dnd/has-type? e "penpot/shape") - (dnd/has-type? e "penpot/component") - (dnd/has-type? e "Files") - (dnd/has-type? e "text/uri-list") - (dnd/has-type? e "text/asset-id")) - (dom/prevent-default e)))) - - on-drag-over - (mf/use-callback - (fn [e] - (when (or (dnd/has-type? e "penpot/shape") - (dnd/has-type? e "penpot/component") - (dnd/has-type? e "Files") - (dnd/has-type? e "text/uri-list") - (dnd/has-type? e "text/asset-id")) - (dom/prevent-default e)))) - - on-image-uploaded - (mf/use-callback - (fn [image {:keys [x y]}] - (st/emit! (dw/image-uploaded image x y)))) - - on-drop - (mf/use-callback - (fn [event] - (dom/prevent-default event) - (let [point (gpt/point (.-clientX event) (.-clientY event)) - viewport-coord (translate-point-to-viewport point) - asset-id (-> (dnd/get-data event "text/asset-id") uuid/uuid) - asset-name (dnd/get-data event "text/asset-name") - asset-type (dnd/get-data event "text/asset-type")] - (cond - (dnd/has-type? event "penpot/shape") - (let [shape (dnd/get-data event "penpot/shape") - final-x (- (:x viewport-coord) (/ (:width shape) 2)) - final-y (- (:y viewport-coord) (/ (:height shape) 2))] - (st/emit! (dw/add-shape (-> shape - (assoc :id (uuid/next)) - (assoc :x final-x) - (assoc :y final-y))))) - - (dnd/has-type? event "penpot/component") - (let [{:keys [component file-id]} (dnd/get-data event "penpot/component") - shape (get-in component [:objects (:id component)]) - final-x (- (:x viewport-coord) (/ (:width shape) 2)) - final-y (- (:y viewport-coord) (/ (:height shape) 2))] - (st/emit! (dwl/instantiate-component file-id - (:id component) - (gpt/point final-x final-y)))) - - ;; Will trigger when the user drags an image from a browser to the viewport - (dnd/has-type? event "text/uri-list") - (let [data (dnd/get-data event "text/uri-list") - lines (str/lines data) - urls (filter #(and (not (str/blank? %)) - (not (str/starts-with? % "#"))) - lines) - params {:file-id (:id file) - :uris urls}] - (st/emit! (dw/upload-media-workspace params viewport-coord))) - - ;; Will trigger when the user drags an SVG asset from the assets panel - (and (dnd/has-type? event "text/asset-id") (= asset-type "image/svg+xml")) - (let [path (cfg/resolve-file-media {:id asset-id}) - params {:file-id (:id file) - :uris [path] - :name asset-name - :mtype asset-type}] - (st/emit! (dw/upload-media-workspace params viewport-coord))) - - ;; Will trigger when the user drags an image from the assets SVG - (dnd/has-type? event "text/asset-id") - (let [params {:file-id (:id file) - :object-id asset-id - :name asset-name}] - (st/emit! (dw/clone-media-object - (with-meta params - {:on-success #(on-image-uploaded % viewport-coord)})))) - - ;; Will trigger when the user drags a file from their file explorer into the viewport - ;; Or the user pastes an image - ;; Or the user uploads an image using the image tool - :else - (let [files (dnd/get-files event) - params {:file-id (:id file) - :data (seq files)}] - (st/emit! (dw/upload-media-workspace params viewport-coord))))))) - - on-paste - (mf/use-callback - (fn [event] - ;; We disable the paste just after mouse-up of a middle button so when panning won't - ;; paste the content into the workspace - (let [tag-name (-> event dom/get-target dom/get-tag-name)] - (when (and (not (#{"INPUT" "TEXTAREA"} tag-name)) (not @disable-paste)) - (st/emit! (dw/paste-from-event event @in-viewport?)))))) - - on-resize - (mf/use-callback - (fn [event] - (let [node (mf/ref-val viewport-ref) - prnt (dom/get-parent node) - size (dom/get-client-size prnt)] - ;; We schedule the event so it fires after `initialize-page` event - (timers/schedule #(st/emit! (dw/update-viewport-size size)))))) - - options (mf/deref refs/workspace-page-options)] - - (mf/use-layout-effect - (fn [] - (let [node (mf/ref-val viewport-ref) - prnt (dom/get-parent node) - - keys [(events/listen js/document EventType.KEYDOWN on-key-down) - (events/listen js/document EventType.KEYUP on-key-up) - (events/listen node EventType.MOUSEMOVE on-mouse-move) - ;; bind with passive=false to allow the event to be cancelled - ;; https://stackoverflow.com/a/57582286/3219895 - (events/listen js/window EventType.WHEEL on-mouse-wheel #js {:passive false}) - (events/listen js/window EventType.RESIZE on-resize) - (events/listen js/window EventType.PASTE on-paste)]] - - (fn [] - (doseq [key keys] - (events/unlistenByKey key)))))) - - (mf/use-layout-effect - (fn [] - (mf/deps page-id) - (let [node (mf/ref-val viewport-ref) - prnt (dom/get-parent node) - size (dom/get-client-size prnt)] - ;; We schedule the event so it fires after `initialize-page` event - (timers/schedule #(st/emit! (dw/initialize-viewport size)))))) - - (mf/use-effect - (mf/deps @cursor @alt? panning drawing-tool drawing-path?) - (fn [] - (let [new-cursor - (cond - panning (get-cursor :hand) - (= drawing-tool :comments) (get-cursor :comments) - (= drawing-tool :frame) (get-cursor :create-artboard) - (= drawing-tool :rect) (get-cursor :create-rectangle) - (= drawing-tool :circle) (get-cursor :create-ellipse) - (or (= drawing-tool :path) - drawing-path?) (get-cursor :pen) - (= drawing-tool :curve) (get-cursor :pencil) - drawing-tool (get-cursor :create-shape) - @alt? (get-cursor :duplicate) - :else (get-cursor :pointer-inner))] - - (when (not= @cursor new-cursor) - (reset! cursor new-cursor))))) - - (mf/use-layout-effect (mf/deps layout) on-resize) - (hooks/use-stream ms/keyboard-alt #(reset! alt? %)) - - [:* - (when picking-color? - [:& pixel-overlay {:vport vport - :vbox vbox - :viewport @viewport-node - :options options - :layout layout}]) - - (when (= drawing-tool :comments) - [:& comments-layer {:vbox vbox - :vport vport - :zoom zoom - :drawing drawing - :page-id page-id - :file-id (:id file)}]) - - (when-not css-mouse? - [:& render-cursor {:viewport @viewport-node - :cursor @cursor}]) - - [:svg.viewport - {:xmlns "http://www.w3.org/2000/svg" + [:& widgets/viewport-actions]] + [:svg.render-shapes + {:id "render" + :ref render-ref + :xmlns "http://www.w3.org/2000/svg" :xmlnsXlink "http://www.w3.org/1999/xlink" :preserveAspectRatio "xMidYMid meet" - :key page-id + :key (str "render" page-id) :width (:width vport 0) :height (:height vport 0) - :view-box (format-viewbox vbox) - :ref #(do (mf/set-ref-val! viewport-ref %) - (reset! viewport-node %)) + :view-box (utils/format-viewbox vbox) + :style {:background-color (get options :background "#E8E9EA")}} + + [:& (mf/provider muc/embed-ctx) {:value true} + ;; Render root shape + [:& shapes/root-shape {:key page-id + :objects objects}]]] + + [:svg.viewport-controls + {:xmlns "http://www.w3.org/2000/svg" + :xmlnsXlink "http://www.w3.org/1999/xlink" + :preserveAspectRatio "xMidYMid meet" + :key (str "viewport" page-id) + :width (:width vport 0) + :height (:height vport 0) + :view-box (utils/format-viewbox vbox) + :ref viewport-ref :class (when drawing-tool "drawing") - :style {:cursor (when css-mouse? @cursor) - :background-color (get options :background "#E8E9EA")} - :on-context-menu on-context-menu - :on-click on-click - :on-double-click on-double-click - :on-mouse-down on-mouse-down - :on-mouse-up on-mouse-up - :on-pointer-down on-pointer-down - :on-pointer-up on-pointer-up - :on-pointer-enter #(reset! in-viewport? true) - :on-pointer-leave #(reset! in-viewport? false) - :on-drag-enter on-drag-enter - :on-drag-over on-drag-over - :on-drop on-drop} + :style {:cursor @cursor} - [:g {:style {:pointer-events (if (contains? layout :comments) - "none" - "auto")}} - [:& frames {:key page-id - :hover hover - :selected selected - :edition edition}] + :on-click on-click + :on-context-menu on-context-menu + :on-double-click on-double-click + :on-drag-enter on-drag-enter + :on-drag-over on-drag-over + :on-drop on-drop + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up + :on-pointer-down on-pointer-down + :on-pointer-enter on-pointer-enter + :on-pointer-leave on-pointer-leave + :on-pointer-move on-pointer-move + :on-pointer-up on-pointer-up + } - [:g {:style {:display (when (not= :move transform) "none")}} - [:& ghost-frames {:modifiers modifiers - :selected selected}]] + [:g {:style {:pointer-events (if disable-events? "none" "auto")}} - (when (seq selected) - [:& selection-handlers {:selected selected - :zoom zoom - :edition edition - :show-distances (and (not transform) show-distances?) - :disable-handlers (or drawing-tool edition)}]) + (when show-outlines? + [:& widgets/shape-outlines + {:objects objects + :selected selected + :hover (when (not= :frame (:type @hover)) + #{(or @frame-hover (:id @hover))}) + :edition edition}]) - (when (= (count selected) 1) - [:& gradient-handlers {:id (first selected) - :zoom zoom}]) + (when show-selection-handlers? + [:& selection/selection-handlers + {:selected selected + :zoom zoom + :edition edition + :show-distances (and (not transform) show-distances?) + :disable-handlers (or drawing-tool edition) + :on-move-selected on-move-selected}]) - (when drawing-obj - [:& draw-area {:shape drawing-obj - :zoom zoom - :tool drawing-tool - :modifiers modifiers}]) + (when text-editing? + [:& editor/text-shape-edit {:shape (get objects edition)}]) + + [:& widgets/frame-titles + {:objects objects + :selected selected + :zoom zoom + :modifiers modifiers + :on-frame-enter on-frame-enter + :on-frame-leave on-frame-leave + :on-frame-select on-frame-select}] + + (when show-gradient-handlers? + [:& gradients/gradient-handlers + {:id (first selected) + :zoom zoom}]) + + (when show-draw-area? + [:& drawarea/draw-area + {:shape drawing-obj + :zoom zoom + :tool drawing-tool + :modifiers modifiers}]) (when show-grids? - [:& frame-grid {:zoom zoom}]) + [:& frame-grid/frame-grid + {:zoom zoom}]) - (when (>= zoom 8) - [:& pixel-grid {:vbox vbox - :zoom zoom}]) + (when show-pixel-grid? + [:& widgets/pixel-grid + {:vbox vbox + :zoom zoom}]) (when show-snap-points? - [:& snap-points {:layout layout - :transform transform - :drawing drawing-obj - :zoom zoom - :page-id page-id - :selected selected - :modifiers modifiers}]) + [:& snap-points/snap-points + {:layout layout + :transform transform + :drawing drawing-obj + :zoom zoom + :page-id page-id + :selected selected + :modifiers modifiers}]) (when show-snap-distance? - [:& snap-distances {:layout layout - :zoom zoom - :transform transform - :selected selected - :page-id page-id}]) + [:& snap-distances/snap-distances + {:layout layout + :zoom zoom + :transform transform + :selected selected + :page-id page-id}]) - (when tooltip - [:& cursor-tooltip {:zoom zoom :tooltip tooltip}])] - - [:& presence/active-cursors {:page-id page-id}] - [:& selection-rect {:data selrect}] - - (when (= options-mode :prototype) - [:& interactions {:selected selected}])]])) + (when show-cursor-tooltip? + [:& widgets/cursor-tooltip + {:zoom zoom + :tooltip tooltip}]) -(mf/defc viewport-actions - {::mf/wrap [mf/memo]} - [] - (let [edition (mf/deref refs/selected-edition) - selected (mf/deref refs/selected-objects) - shape (-> selected first)] - (when (and (= (count selected) 1) - (= (:id shape) edition) - (= :path (:type shape))) - [:div.viewport-actions - [:& path-actions {:shape shape}]]))) + (when show-presence? + [:& presence/active-cursors + {:page-id page-id}]) + + [:& widgets/viewport-actions] + + (when show-prototypes? + [:& interactions/interactions + {:selected selected}]) + + (when show-selrect? + [:& widgets/selection-rect {:data selrect}])]]])) + diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs new file mode 100644 index 000000000..99eea2007 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -0,0 +1,453 @@ +; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020-2021 UXBOX Labs SL + +(ns app.main.ui.workspace.viewport.actions + (:require + [app.common.geom.point :as gpt] + [app.common.uuid :as uuid] + [app.config :as cfg] + [app.main.data.workspace :as dw] + [app.main.data.workspace.drawing :as dd] + [app.main.data.workspace.libraries :as dwl] + [app.main.store :as st] + [app.main.streams :as ms] + [app.main.ui.workspace.viewport.utils :as utils] + [app.util.dom :as dom] + [app.util.dom.dnd :as dnd] + [app.util.keyboard :as kbd] + [app.util.object :as obj] + [app.util.timers :as timers] + [beicon.core :as rx] + [cuerdas.core :as str] + [rumext.alpha :as mf]) + (:import goog.events.WheelEvent + goog.events.KeyCodes)) + +(defn on-mouse-down + [{:keys [id blocked hidden type]} drawing-tool text-editing? edition edit-path selected] + (mf/use-callback + (mf/deps id blocked hidden type drawing-tool text-editing? edition selected) + (fn [bevent] + (dom/stop-propagation bevent) + + (let [event (.-nativeEvent bevent) + ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + alt? (kbd/alt? event) + + left-click? (= 1 (.-which event)) + middle-click? (= 2 (.-which event)) + + frame? (= :frame type) + selected? (contains? selected id)] + + (when middle-click? + (dom/prevent-default bevent) + (st/emit! (dw/start-panning))) + + (when left-click? + (st/emit! (ms/->MouseEvent :down ctrl? shift? alt?)) + + (when (and (not= edition id) text-editing?) + (st/emit! dw/clear-edition-mode)) + + (when (and (or (not edition) (not= edition id)) (not blocked) (not hidden)) + (cond + (and drawing-tool (not (#{:comments :path} drawing-tool))) + (st/emit! (dd/start-drawing drawing-tool)) + + edit-path + ;; Handle node select-drawing. NOP at the moment + nil + + (or (not id) (and frame? (not selected?))) + (st/emit! (dw/handle-selection shift?)) + + :else + (st/emit! (when (or shift? (not selected?)) + (dw/select-shape id shift?)) + (when (not shift?) + (dw/start-move-selected)))))))))) + +(defn on-move-selected + [hover selected] + (mf/use-callback + (mf/deps @hover selected) + (fn [bevent] + (let [event (.-nativeEvent bevent) + shift? (kbd/shift? event) + left-click? (= 1 (.-which event))] + (when (and left-click? + (not shift?) + (or (not @hover) + (contains? selected (:id @hover)) + (contains? selected (:frame-id @hover)))) + (dom/prevent-default bevent) + (dom/stop-propagation bevent) + (st/emit! (dw/start-move-selected))))))) + +(defn on-frame-select + [selected] + (mf/use-callback + (mf/deps selected) + (fn [event id] + (let [shift? (kbd/shift? event) + selected? (contains? selected id)] + (st/emit! (when (or shift? (not selected?)) + (dw/select-shape id shift?)) + (when (not shift?) + (dw/start-move-selected))))))) + +(defn on-frame-enter + [frame-hover] + (mf/use-callback + (fn [id] + (reset! frame-hover id)))) + +(defn on-frame-leave + [frame-hover] + (mf/use-callback + (fn [] + (reset! frame-hover nil)))) + +(defn on-click + [] + (mf/use-callback + (fn [event] + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + alt? (kbd/alt? event)] + (st/emit! (ms/->MouseEvent :click ctrl? shift? alt?)))))) + +(defn on-double-click + [hover hover-ids objects] + (mf/use-callback + (mf/deps @hover @hover-ids) + (fn [event] + (dom/stop-propagation event) + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + alt? (kbd/alt? event) + + {:keys [id type] :as shape} @hover + + frame? (= :frame type) + group? (= :group type) + text? (= :text type) + path? (= :path type)] + + (st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt?)) + + (when shape + (cond frame? + (st/emit! (dw/select-shape id shift?)) + + (and group? (> (count @hover-ids) 1)) + (let [selected (get objects (second @hover-ids))] + (reset! hover selected) + (reset! hover-ids (into [] (rest @hover-ids))) + (st/emit! (dw/select-shape (:id selected)))) + + (or text? path?) + (st/emit! (dw/start-edition-mode id)) + + :else + ;; Do nothing + nil)))))) + +(defn on-context-menu + [hover] + (let [{:keys [id]} @hover] + (mf/use-callback + (mf/deps id) + (fn [event] + (dom/prevent-default event) + (let [position (dom/get-client-position event)] + (st/emit! (dw/show-context-menu {:position position + :shape @hover}))))))) + +(defn on-mouse-up + [disable-paste] + (mf/use-callback + (fn [event] + (dom/stop-propagation event) + + (let [event (.-nativeEvent event) + ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + alt? (kbd/alt? event) + + left-click? (= 1 (.-which event)) + middle-click? (= 2 (.-which event))] + + (when left-click? + (st/emit! (ms/->MouseEvent :up ctrl? shift? alt?))) + + (when middle-click? + (dom/prevent-default event) + + ;; We store this so in Firefox the middle button won't do a paste of the content + (reset! disable-paste true) + (timers/schedule #(reset! disable-paste false)) + (st/emit! (dw/finish-panning))))))) + +(defn on-pointer-enter [in-viewport?] + (mf/use-callback + (fn [] + (reset! in-viewport? true)))) + +(defn on-pointer-leave [in-viewport?] + (mf/use-callback + (fn [] + (reset! in-viewport? false)))) + +(defn on-pointer-down [] + (mf/use-callback + (fn [event] + ;; We need to handle editor related stuff here because + ;; handling on editor dom node does not works properly. + (let [target (dom/get-target event) + editor (.closest ^js target ".public-DraftEditor-content")] + ;; Capture mouse pointer to detect the movements even if cursor + ;; leaves the viewport or the browser itself + ;; https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture + (if editor + (.setPointerCapture editor (.-pointerId event)) + (.setPointerCapture target (.-pointerId event))))))) + +(defn on-pointer-up [] + (mf/use-callback + (fn [event] + (let [target (dom/get-target event)] + ; Release pointer on mouse up + (.releasePointerCapture target (.-pointerId event)))))) + +(defn on-key-down [] + (mf/use-callback + (fn [event] + (let [bevent (.getBrowserEvent ^js event) + key (.-keyCode ^js event) + key (.normalizeKeyCode KeyCodes key) + ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + alt? (kbd/alt? event) + meta? (kbd/meta? event) + target (dom/get-target event)] + + (when-not (.-repeat bevent) + (st/emit! (ms/->KeyboardEvent :down key shift? ctrl? alt? meta?)) + (when (and (kbd/space? event) + (not= "rich-text" (obj/get target "className")) + (not= "INPUT" (obj/get target "tagName")) + (not= "TEXTAREA" (obj/get target "tagName"))) + (st/emit! (dw/start-panning)))))))) + +(defn on-key-up [] + (mf/use-callback + (fn [event] + (let [key (.-keyCode event) + key (.normalizeKeyCode KeyCodes key) + ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + alt? (kbd/alt? event) + meta? (kbd/meta? event)] + (when (kbd/space? event) + (st/emit! (dw/finish-panning))) + (st/emit! (ms/->KeyboardEvent :up key shift? ctrl? alt? meta?)))))) + +(defn on-mouse-move [viewport-ref zoom] + (let [last-position (mf/use-var nil) + viewport (mf/ref-val viewport-ref)] + (mf/use-callback + (mf/deps zoom) + (fn [event] + (let [event (.getBrowserEvent ^js event) + raw-pt (dom/get-client-position event) + viewport (mf/ref-val viewport-ref) + pt (utils/translate-point-to-viewport viewport zoom raw-pt) + + ;; We calculate the delta because Safari's MouseEvent.movementX/Y drop + ;; events + delta (if @last-position + (gpt/subtract raw-pt @last-position) + (gpt/point 0 0))] + + (reset! last-position raw-pt) + (st/emit! (ms/->PointerEvent :delta delta + (kbd/ctrl? event) + (kbd/shift? event) + (kbd/alt? event))) + (st/emit! (ms/->PointerEvent :viewport pt + (kbd/ctrl? event) + (kbd/shift? event) + (kbd/alt? event)))))))) + +(defn on-pointer-move [viewport-ref zoom move-stream] + (mf/use-callback + (mf/deps zoom move-stream) + (fn [event] + (let [raw-pt (dom/get-client-position event) + viewport (mf/ref-val viewport-ref) + pt (utils/translate-point-to-viewport viewport zoom raw-pt)] + (rx/push! move-stream pt))))) + +(defn on-mouse-wheel [viewport-ref zoom] + (mf/use-callback + (mf/deps zoom) + (fn [event] + (let [event (.getBrowserEvent ^js event) + raw-pt (dom/get-client-position event) + viewport (mf/ref-val viewport-ref) + pt (utils/translate-point-to-viewport viewport zoom raw-pt) + + ctrl? (kbd/ctrl? event) + meta? (kbd/meta? event) + target (dom/get-target event)] + (cond + (or ctrl? meta?) + (do + (dom/prevent-default event) + (dom/stop-propagation event) + (let [delta (+ (.-deltaY ^js event) + (.-deltaX ^js event))] + (if (pos? delta) + (st/emit! (dw/decrease-zoom pt)) + (st/emit! (dw/increase-zoom pt))))) + + (.contains ^js viewport target) + (let [delta-mode (.-deltaMode ^js event) + + unit (cond + (= delta-mode WheelEvent.DeltaMode.PIXEL) 1 + (= delta-mode WheelEvent.DeltaMode.LINE) 16 + (= delta-mode WheelEvent.DeltaMode.PAGE) 100) + + delta-y (-> (.-deltaY ^js event) + (* unit) + (/ zoom)) + delta-x (-> (.-deltaX ^js event) + (* unit) + (/ zoom))] + (dom/prevent-default event) + (dom/stop-propagation event) + (if (kbd/shift? event) + (st/emit! (dw/update-viewport-position {:x #(+ % delta-y)})) + (st/emit! (dw/update-viewport-position {:x #(+ % delta-x) + :y #(+ % delta-y)}))))))))) + +(defn on-drag-enter [] + (mf/use-callback + (fn [e] + (when (or (dnd/has-type? e "penpot/shape") + (dnd/has-type? e "penpot/component") + (dnd/has-type? e "Files") + (dnd/has-type? e "text/uri-list") + (dnd/has-type? e "text/asset-id")) + (dom/prevent-default e))))) + +(defn on-drag-over [] + (mf/use-callback + (fn [e] + (when (or (dnd/has-type? e "penpot/shape") + (dnd/has-type? e "penpot/component") + (dnd/has-type? e "Files") + (dnd/has-type? e "text/uri-list") + (dnd/has-type? e "text/asset-id")) + (dom/prevent-default e))))) + +(defn on-image-uploaded [] + (mf/use-callback + (fn [image {:keys [x y]}] + (st/emit! (dw/image-uploaded image x y))))) + +(defn on-drop [file viewport-ref zoom] + (let [on-image-uploaded (on-image-uploaded)] + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (let [point (gpt/point (.-clientX event) (.-clientY event)) + viewport (mf/ref-val viewport-ref) + viewport-coord (utils/translate-point-to-viewport viewport zoom point) + asset-id (-> (dnd/get-data event "text/asset-id") uuid/uuid) + asset-name (dnd/get-data event "text/asset-name") + asset-type (dnd/get-data event "text/asset-type")] + (cond + (dnd/has-type? event "penpot/shape") + (let [shape (dnd/get-data event "penpot/shape") + final-x (- (:x viewport-coord) (/ (:width shape) 2)) + final-y (- (:y viewport-coord) (/ (:height shape) 2))] + (st/emit! (dw/add-shape (-> shape + (assoc :id (uuid/next)) + (assoc :x final-x) + (assoc :y final-y))))) + + (dnd/has-type? event "penpot/component") + (let [{:keys [component file-id]} (dnd/get-data event "penpot/component") + shape (get-in component [:objects (:id component)]) + final-x (- (:x viewport-coord) (/ (:width shape) 2)) + final-y (- (:y viewport-coord) (/ (:height shape) 2))] + (st/emit! (dwl/instantiate-component file-id + (:id component) + (gpt/point final-x final-y)))) + + ;; Will trigger when the user drags an image from a browser to the viewport + (dnd/has-type? event "text/uri-list") + (let [data (dnd/get-data event "text/uri-list") + lines (str/lines data) + urls (filter #(and (not (str/blank? %)) + (not (str/starts-with? % "#"))) + lines) + params {:file-id (:id file) + :uris urls}] + (st/emit! (dw/upload-media-workspace params viewport-coord))) + + ;; Will trigger when the user drags an SVG asset from the assets panel + (and (dnd/has-type? event "text/asset-id") (= asset-type "image/svg+xml")) + (let [path (cfg/resolve-file-media {:id asset-id}) + params {:file-id (:id file) + :uris [path] + :name asset-name + :mtype asset-type}] + (st/emit! (dw/upload-media-workspace params viewport-coord))) + + ;; Will trigger when the user drags an image from the assets SVG + (dnd/has-type? event "text/asset-id") + (let [params {:file-id (:id file) + :object-id asset-id + :name asset-name}] + (st/emit! (dw/clone-media-object + (with-meta params + {:on-success #(on-image-uploaded % viewport-coord)})))) + + ;; Will trigger when the user drags a file from their file explorer into the viewport + ;; Or the user pastes an image + ;; Or the user uploads an image using the image tool + :else + (let [files (dnd/get-files event) + params {:file-id (:id file) + :data (seq files)}] + (st/emit! (dw/upload-media-workspace params viewport-coord))))))))) + +(defn on-paste [disable-paste in-viewport?] + (mf/use-callback + (fn [event] + ;; We disable the paste just after mouse-up of a middle button so when panning won't + ;; paste the content into the workspace + (let [tag-name (-> event dom/get-target dom/get-tag-name)] + (when (and (not (#{"INPUT" "TEXTAREA"} tag-name)) (not @disable-paste)) + (st/emit! (dw/paste-from-event event @in-viewport?))))))) + +(defn on-resize [viewport-ref] + (mf/use-callback + (fn [event] + (let [node (mf/ref-val viewport-ref) + prnt (dom/get-parent node) + size (dom/get-client-size prnt)] + ;; We schedule the event so it fires after `initialize-page` event + (timers/schedule #(st/emit! (dw/update-viewport-size size))))))) diff --git a/frontend/src/app/main/ui/workspace/viewport/comments.cljs b/frontend/src/app/main/ui/workspace/viewport/comments.cljs new file mode 100644 index 000000000..06dc46bd3 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/comments.cljs @@ -0,0 +1,80 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.viewport.comments + (:require + [app.main.data.comments :as dcm] + [app.main.data.workspace.comments :as dwcm] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.comments :as cmt] + [cuerdas.core :as str] + [rumext.alpha :as mf])) + +(mf/defc comments-layer + [{:keys [vbox vport zoom file-id page-id drawing] :as props}] + (let [pos-x (* (- (:x vbox)) zoom) + pos-y (* (- (:y vbox)) zoom) + + profile (mf/deref refs/profile) + users (mf/deref refs/users) + local (mf/deref refs/comments-local) + threads-map (mf/deref refs/threads-ref) + + threads (->> (vals threads-map) + (filter #(= (:page-id %) page-id)) + (dcm/apply-filters local profile)) + + on-bubble-click + (fn [{:keys [id] :as thread}] + (if (= (:open local) id) + (st/emit! (dcm/close-thread)) + (st/emit! (dcm/open-thread thread)))) + + on-draft-cancel + (mf/use-callback + (st/emitf :interrupt)) + + on-draft-submit + (mf/use-callback + (fn [draft] + (st/emit! (dcm/create-thread draft))))] + + (mf/use-effect + (mf/deps file-id) + (fn [] + (st/emit! (dwcm/initialize-comments file-id)) + (fn [] + (st/emit! ::dwcm/finalize)))) + + [:div.comments-section + [:div.workspace-comments-container + {:style {:width (str (:width vport) "px") + :height (str (:height vport) "px")}} + [:div.threads {:style {:transform (str/format "translate(%spx, %spx)" pos-x pos-y)}} + (for [item threads] + [:& cmt/thread-bubble {:thread item + :zoom zoom + :on-click on-bubble-click + :open? (= (:id item) (:open local)) + :key (:seqn item)}]) + + (when-let [id (:open local)] + (when-let [thread (get threads-map id)] + [:& cmt/thread-comments {:thread thread + :users users + :zoom zoom}])) + + (when-let [draft (:comment drawing)] + [:& cmt/draft-thread {:draft draft + :on-cancel on-draft-cancel + :on-submit on-draft-submit + :zoom zoom}])]]])) + + diff --git a/frontend/src/app/main/ui/workspace/drawarea.cljs b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs similarity index 97% rename from frontend/src/app/main/ui/workspace/drawarea.cljs rename to frontend/src/app/main/ui/workspace/viewport/drawarea.cljs index 3c365dea0..fd1d179fd 100644 --- a/frontend/src/app/main/ui/workspace/drawarea.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs @@ -4,7 +4,7 @@ ;; ;; Copyright (c) 2015-2019 Andrey Antukh -(ns app.main.ui.workspace.drawarea +(ns app.main.ui.workspace.viewport.drawarea "Drawing components." (:require [rumext.alpha :as mf] diff --git a/frontend/src/app/main/ui/workspace/frame_grid.cljs b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs similarity index 98% rename from frontend/src/app/main/ui/workspace/frame_grid.cljs rename to frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs index 9f22bbd2d..f8978ad56 100644 --- a/frontend/src/app/main/ui/workspace/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs @@ -7,7 +7,7 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL -(ns app.main.ui.workspace.frame-grid +(ns app.main.ui.workspace.viewport.frame-grid (:require [rumext.alpha :as mf] [okulary.core :as l] diff --git a/frontend/src/app/main/ui/workspace/gradients.cljs b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs similarity index 99% rename from frontend/src/app/main/ui/workspace/gradients.cljs rename to frontend/src/app/main/ui/workspace/viewport/gradients.cljs index c88ba43b4..146e08d6e 100644 --- a/frontend/src/app/main/ui/workspace/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs @@ -7,7 +7,7 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL -(ns app.main.ui.workspace.gradients +(ns app.main.ui.workspace.viewport.gradients "Gradients handlers and renders" (:require [rumext.alpha :as mf] diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs new file mode 100644 index 000000000..c5b28f6a7 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -0,0 +1,144 @@ +; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020-2021 UXBOX Labs SL + +(ns app.main.ui.workspace.viewport.hooks + (:require + [app.common.data :as d] + [app.common.geom.shapes :as gsh] + [app.common.pages :as cp] + [app.main.data.workspace :as dw] + [app.main.store :as st] + [app.main.streams :as ms] + [app.main.ui.hooks :as hooks] + [app.main.ui.workspace.viewport.actions :as actions] + [app.main.ui.workspace.viewport.utils :as utils] + [app.main.worker :as uw] + [app.util.dom :as dom] + [app.util.timers :as timers] + [beicon.core :as rx] + [goog.events :as events] + [rumext.alpha :as mf]) + (:import goog.events.EventType)) + +(defn setup-dom-events [viewport-ref zoom disable-paste in-viewport?] + (let [on-key-down (actions/on-key-down) + on-key-up (actions/on-key-up) + on-mouse-move (actions/on-mouse-move viewport-ref zoom) + on-mouse-wheel (actions/on-mouse-wheel viewport-ref zoom) + on-resize (actions/on-resize viewport-ref) + on-paste (actions/on-paste disable-paste in-viewport?)] + (mf/use-layout-effect + (mf/deps on-key-down on-key-up on-mouse-move on-mouse-wheel on-resize on-paste) + (fn [] + (let [node (mf/ref-val viewport-ref) + prnt (dom/get-parent node) + + keys [(events/listen js/document EventType.KEYDOWN on-key-down) + (events/listen js/document EventType.KEYUP on-key-up) + (events/listen node EventType.MOUSEMOVE on-mouse-move) + ;; bind with passive=false to allow the event to be cancelled + ;; https://stackoverflow.com/a/57582286/3219895 + (events/listen js/window EventType.WHEEL on-mouse-wheel #js {:passive false}) + (events/listen js/window EventType.RESIZE on-resize) + (events/listen js/window EventType.PASTE on-paste)]] + + (fn [] + (doseq [key keys] + (events/unlistenByKey key)))))))) + +(defn setup-viewport-size [viewport-ref] + (mf/use-layout-effect + (fn [] + (let [node (mf/ref-val viewport-ref) + prnt (dom/get-parent node) + size (dom/get-client-size prnt)] + ;; We schedule the event so it fires after `initialize-page` event + (timers/schedule #(st/emit! (dw/initialize-viewport size))))))) + +(defn setup-cursor [cursor alt? panning drawing-tool drawing-path?] + (mf/use-effect + (mf/deps @cursor @alt? panning drawing-tool drawing-path?) + (fn [] + (let [new-cursor + (cond + panning (utils/get-cursor :hand) + (= drawing-tool :comments) (utils/get-cursor :comments) + (= drawing-tool :frame) (utils/get-cursor :create-artboard) + (= drawing-tool :rect) (utils/get-cursor :create-rectangle) + (= drawing-tool :circle) (utils/get-cursor :create-ellipse) + (or (= drawing-tool :path) + drawing-path?) (utils/get-cursor :pen) + (= drawing-tool :curve) (utils/get-cursor :pencil) + drawing-tool (utils/get-cursor :create-shape) + @alt? (utils/get-cursor :duplicate) + :else (utils/get-cursor :pointer-inner))] + + (when (not= @cursor new-cursor) + (reset! cursor new-cursor)))))) + +(defn setup-resize [layout viewport-ref] + (let [on-resize (actions/on-resize viewport-ref)] + (mf/use-layout-effect (mf/deps layout) on-resize))) + +(defn setup-keyboard [alt? ctrl?] + (hooks/use-stream ms/keyboard-alt #(reset! alt? %)) + (hooks/use-stream ms/keyboard-ctrl #(reset! ctrl? %))) + +(defn setup-hover-shapes [page-id move-stream selected objects transform selected ctrl? hover hover-ids] + (let [query-point + (mf/use-callback + (mf/deps page-id) + (fn [point] + (let [rect (gsh/center->rect point 8 8)] + (uw/ask! {:cmd :selection/query + :page-id page-id + :rect rect + :include-frames? true})))) + + over-shapes-stream + (->> move-stream + (rx/switch-map query-point)) + + roots (mf/use-memo + (mf/deps selected objects) + (fn [] + (let [roots-ids (cp/clean-loops objects selected)] + (->> roots-ids (mapv #(get objects %))))))] + + (hooks/use-stream + over-shapes-stream + (mf/deps page-id objects transform selected @ctrl?) + (fn [ids] + (let [remove-id? (into #{} (mapcat #(cp/get-parents % objects)) selected) + remove-id? (if @ctrl? + (d/concat remove-id? + (->> ids + (filterv #(= :group (get-in objects [% :type]))))) + remove-id?) + ids (->> ids (filterv (comp not remove-id?)))] + (when (not transform) + (reset! hover (get objects (first ids))) + (reset! hover-ids ids))))))) + +(defn setup-viewport-modifiers [modifiers selected objects render-ref] + (let [roots (mf/use-memo + (mf/deps objects selected) + (fn [] + (let [roots-ids (cp/clean-loops objects selected)] + (->> roots-ids (mapv #(get objects %))))))] + + ;; Layout effect is important so the code is executed before the modifiers + ;; are applied to the shape + (mf/use-layout-effect + (mf/deps modifiers roots) + + #(when-let [render-node (mf/ref-val render-ref)] + (if modifiers + (utils/update-transform render-node roots modifiers) + (utils/remove-transform render-node roots)))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs similarity index 98% rename from frontend/src/app/main/ui/workspace/shapes/interactions.cljs rename to frontend/src/app/main/ui/workspace/viewport/interactions.cljs index 469a636fc..50df4ea07 100644 --- a/frontend/src/app/main/ui/workspace/shapes/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs @@ -7,7 +7,7 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL -(ns app.main.ui.workspace.shapes.interactions +(ns app.main.ui.workspace.viewport.interactions "Visually show shape interactions in workspace" (:require [app.common.geom.point :as gpt] @@ -31,8 +31,6 @@ [event {:keys [id type] :as shape} selected] (do (dom/stop-propagation event) - (when-not (empty? selected) - (st/emit! (dw/deselect-all))) (st/emit! (dw/select-shape id)) (st/emit! (dw/start-create-interaction)))) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/pixel_overlay.cljs b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs similarity index 88% rename from frontend/src/app/main/ui/workspace/colorpicker/pixel_overlay.cljs rename to frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs index b62243465..c85f1382d 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/pixel_overlay.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs @@ -7,7 +7,7 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL -(ns app.main.ui.workspace.colorpicker.pixel-overlay +(ns app.main.ui.workspace.viewport.pixel-overlay (:require [app.common.uuid :as uuid] [app.main.data.colors :as dwc] @@ -59,7 +59,8 @@ [props] (let [vport (unchecked-get props "vport") vbox (unchecked-get props "vbox") - viewport-node (unchecked-get props "viewport") + viewport-ref (unchecked-get props "viewport-ref") + viewport-node (mf/ref-val viewport-ref) options (unchecked-get props "options") svg-ref (mf/use-ref nil) canvas-ref (mf/use-ref nil) @@ -133,7 +134,7 @@ (mf/deps img-ref) (fn [] (let [img-node (mf/ref-val img-ref) - svg-node (mf/ref-val svg-ref) + svg-node #_(mf/ref-val svg-ref) (dom/get-element "render") xml (-> (js/XMLSerializer.) (.serializeToString svg-node) js/encodeURIComponent @@ -160,30 +161,26 @@ #(rx/dispose! sub)))) (mf/use-effect - (mf/deps svg-ref) + #_(mf/deps svg-ref) (fn [] - (when svg-ref - (let [config #js {:attributes true - :childList true - :subtree true - :characterData true} - svg-node (mf/ref-val svg-ref) - observer (js/MutationObserver. handle-svg-change)] - (.observe observer svg-node config) - (handle-svg-change) + (let [config #js {:attributes true + :childList true + :subtree true + :characterData true} + svg-node #_(mf/ref-val svg-ref) (dom/get-element "render") + observer (js/MutationObserver. handle-svg-change) + ] + (.observe observer svg-node config) + (handle-svg-change) - ;; Disconnect on unmount - #(.disconnect observer))))) + ;; Disconnect on unmount + #(.disconnect observer) + ))) [:* - [:div.overlay + [:div.pixel-overlay {:tab-index 0 - :style {:position "absolute" - :top 0 - :left 0 - :width "100%" - :height "100%" - :cursor cur/picker} + :style {:cursor cur/picker} :on-mouse-down handle-mouse-down-picker :on-mouse-up handle-mouse-up-picker :on-mouse-move handle-mouse-move-picker} @@ -200,7 +197,7 @@ :width "100%" :height "100%"}}] - [:& (mf/provider muc/embed-ctx) {:value true} + #_[:& (mf/provider muc/embed-ctx) {:value true} [:svg.viewport {:ref svg-ref :preserveAspectRatio "xMidYMid meet" diff --git a/frontend/src/app/main/ui/workspace/viewport/presence.cljs b/frontend/src/app/main/ui/workspace/viewport/presence.cljs new file mode 100644 index 000000000..588ad81db --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/presence.cljs @@ -0,0 +1,84 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns app.main.ui.workspace.viewport.presence + (:require + [app.main.refs :as refs] + [app.util.time :as dt] + [app.util.timers :as ts] + [beicon.core :as rx] + [cuerdas.core :as str] + [rumext.alpha :as mf])) + +(def pointer-icon-path + (str "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")) + +(mf/defc session-cursor + [{:keys [session profile] :as props}] + (let [zoom (mf/deref refs/selected-zoom) + point (:point session) + color (:color session "#000000") + transform (str/fmt "translate(%s, %s) scale(%s)" (:x point) (:y point) (/ 4 zoom))] + [:g.multiuser-cursor {:transform transform} + [:path {:fill color + :d pointer-icon-path + }] + [:g {:transform "translate(0 -291.708)"} + [:rect {:width 25 + :height 5 + :x 7 + :y 291.5 + :fill color + :fill-opacity 0.8 + :paint-order "stroke fill markers" + :rx 1 + :ry 1}] + [:text {:x 8 + :y 295 + :width 25 + :height 5 + :overflow "hidden" + :fill "#fff" + :stroke-width 1 + :font-family "Works Sans" + :font-size 3 + :font-weight 400 + :letter-spacing 0 + :style { :line-height 1.25 } + :word-spacing 0} + (str (str/slice (:fullname profile) 0 14) + (when (> (count (:fullname profile)) 14) "..."))]]])) + +(mf/defc active-cursors + {::mf/wrap [mf/memo]} + [{:keys [page-id] :as props}] + (let [counter (mf/use-state 0) + users (mf/deref refs/users) + sessions (mf/deref refs/workspace-presence) + sessions (->> (vals sessions) + (filter #(= page-id (:page-id %))) + (filter #(>= 5000 (- (inst-ms (dt/now)) (inst-ms (:updated-at %))))))] + (mf/use-effect + nil + (fn [] + (let [sem (ts/schedule 1000 #(swap! counter inc))] + (fn [] (rx/dispose! sem))))) + + (for [session sessions] + (when (:point session) + [:& session-cursor {:session session + :profile (get users (:profile-id session)) + :key (:id session)}])))) + + + + + diff --git a/frontend/src/app/main/ui/workspace/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs similarity index 92% rename from frontend/src/app/main/ui/workspace/selection.cljs rename to frontend/src/app/main/ui/workspace/viewport/selection.cljs index 7ee4ece58..16b5edfbb 100644 --- a/frontend/src/app/main/ui/workspace/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -7,7 +7,7 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL -(ns app.main.ui.workspace.selection +(ns app.main.ui.workspace.viewport.selection "Selection handlers component." (:require [app.common.geom.matrix :as gmt] @@ -46,7 +46,7 @@ (def min-selrect-side 10) (def small-selrect-side 30) -(mf/defc selection-rect [{:keys [transform rect zoom color]}] +(mf/defc selection-rect [{:keys [transform rect zoom color on-move-selected]}] (when rect (let [{:keys [x y width height]} rect] [:rect.main @@ -55,6 +55,7 @@ :width width :height height :transform transform + :on-mouse-down on-move-selected :style {:stroke color :stroke-width (/ selection-rect-width zoom) :fill "transparent"}}]))) @@ -237,29 +238,27 @@ {::mf/wrap-props false} [props] (let [{:keys [overflow-text type] :as shape} (obj/get props "shape") - zoom (obj/get props "zoom") - color (obj/get props "color") - on-resize (obj/get props "on-resize") - on-rotate (obj/get props "on-rotate") - disable-handlers (obj/get props "disable-handlers") + zoom (obj/get props "zoom") + color (obj/get props "color") + on-move-selected (obj/get props "on-move-selected") + on-resize (obj/get props "on-resize") + on-rotate (obj/get props "on-rotate") + disable-handlers (obj/get props "disable-handlers") current-transform (mf/deref refs/current-transform) - hide? (mf/use-state false) selrect (-> (:selrect shape) minimum-selrect) transform (geom/transform-matrix shape {:no-flip true})] - (hooks/use-stream ms/keyboard-ctrl #(when (= type :group) (reset! hide? %))) - (when (not (#{:move :rotate} current-transform)) - [:g.controls {:style {:display (when @hide? "none")} - :pointer-events (when disable-handlers "none")} + [:g.controls {:pointer-events (when disable-handlers "none")} ;; Selection rect [:& selection-rect {:rect selrect :transform transform :zoom zoom - :color color}] + :color color + :on-move-selected on-move-selected}] [:& outline {:shape shape :color color}] ;; Handlers @@ -296,7 +295,7 @@ :fill "transparent"}}]])) (mf/defc multiple-selection-handlers - [{:keys [shapes selected zoom color show-distances disable-handlers] :as props}] + [{:keys [shapes selected zoom color show-distances disable-handlers on-move-selected] :as props}] (let [shape (geom/setup {:type :rect} (geom/selection-rect (->> shapes (map geom/transform-shape)))) shape-center (geom/center-shape shape) @@ -318,6 +317,7 @@ :zoom zoom :color color :disable-handlers disable-handlers + :on-move-selected on-move-selected :on-resize on-resize :on-rotate on-rotate}] @@ -331,7 +331,7 @@ [:circle {:cx (:x shape-center) :cy (:y shape-center) :r 5 :fill "yellow"}])])) (mf/defc single-selection-handlers - [{:keys [shape zoom color show-distances disable-handlers] :as props}] + [{:keys [shape zoom color show-distances disable-handlers on-move-selected] :as props}] (let [shape-id (:id shape) shape (geom/transform-shape shape) @@ -357,7 +357,8 @@ :color color :on-rotate on-rotate :on-resize on-resize - :disable-handlers disable-handlers}] + :disable-handlers disable-handlers + :on-move-selected on-move-selected}] (when show-distances [:& msr/measurement {:bounds vbox @@ -368,7 +369,7 @@ (mf/defc selection-handlers {::mf/wrap [mf/memo]} - [{:keys [selected edition zoom show-distances disable-handlers] :as props}] + [{:keys [selected edition zoom show-distances disable-handlers on-move-selected] :as props}] (let [;; We need remove posible nil values because on shape ;; deletion many shape will reamin selected and deleted ;; in the same time for small instant of time @@ -390,7 +391,8 @@ :zoom zoom :color color :show-distances show-distances - :disable-handlers disable-handlers}] + :disable-handlers disable-handlers + :on-move-selected on-move-selected}] (and (= type :text) (= edition (:id shape))) @@ -408,4 +410,5 @@ :zoom zoom :color color :show-distances show-distances - :disable-handlers disable-handlers}]))) + :disable-handlers disable-handlers + :on-move-selected on-move-selected}]))) diff --git a/frontend/src/app/main/ui/workspace/snap_distances.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs similarity index 99% rename from frontend/src/app/main/ui/workspace/snap_distances.cljs rename to frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs index 966c5f388..46051017e 100644 --- a/frontend/src/app/main/ui/workspace/snap_distances.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs @@ -7,7 +7,7 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL -(ns app.main.ui.workspace.snap-distances +(ns app.main.ui.workspace.viewport.snap-distances (:require [app.common.data :as d] [app.common.geom.point :as gpt] @@ -230,6 +230,7 @@ (->> (uw/ask! {:cmd :selection/query :page-id page-id :frame-id (:id frame) + :include-frames? true :rect rect}) (rx/map #(set/difference % selected)) (rx/map #(->> % (map (partial get @refs/workspace-page-objects))))) diff --git a/frontend/src/app/main/ui/workspace/snap_points.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs similarity index 99% rename from frontend/src/app/main/ui/workspace/snap_points.cljs rename to frontend/src/app/main/ui/workspace/viewport/snap_points.cljs index 6404b161a..bae922653 100644 --- a/frontend/src/app/main/ui/workspace/snap_points.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs @@ -7,7 +7,7 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL -(ns app.main.ui.workspace.snap-points +(ns app.main.ui.workspace.viewport.snap-points (:require [app.common.math :as mth] [app.common.data :as d] diff --git a/frontend/src/app/main/ui/workspace/viewport/streams.cljs b/frontend/src/app/main/ui/workspace/viewport/streams.cljs new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs new file mode 100644 index 000000000..7d5235483 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -0,0 +1,60 @@ +; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020-2021 UXBOX Labs SL + +(ns app.main.ui.workspace.viewport.utils + (:require + [app.util.dom :as dom] + [app.common.geom.point :as gpt] + [cuerdas.core :as str] + [app.common.data :as d] + [app.main.ui.cursors :as cur] + )) + +(defn update-transform [node shapes modifiers] + (doseq [{:keys [id type]} shapes] + (when-let [node (dom/get-element (str "shape-" id))] + (let [node (if (= :frame type) (.-parentNode node) node)] + (dom/set-attribute node "transform" (str (:displacement modifiers))))))) + +(defn remove-transform [node shapes] + (doseq [{:keys [id type]} shapes] + (when-let [node (dom/get-element (str "shape-" id))] + (let [node (if (= :frame type) (.-parentNode node) node)] + (dom/remove-attribute node "transform"))))) + +(defn format-viewbox [vbox] + (str/join " " [(+ (:x vbox 0) (:left-offset vbox 0)) + (:y vbox 0) + (:width vbox 0) + (:height vbox 0)])) + +(defn translate-point-to-viewport [viewport zoom pt] + (let [vbox (.. ^js viewport -viewBox -baseVal) + brect (dom/get-bounding-rect viewport) + brect (gpt/point (d/parse-integer (:left brect)) + (d/parse-integer (:top brect))) + box (gpt/point (.-x vbox) (.-y vbox)) + zoom (gpt/point zoom)] + (-> (gpt/subtract pt brect) + (gpt/divide zoom) + (gpt/add box) + (gpt/round 0)))) + +(defn get-cursor [cursor] + (case cursor + :hand cur/hand + :comments cur/comments + :create-artboard cur/create-artboard + :create-rectangle cur/create-rectangle + :create-ellipse cur/create-ellipse + :pen cur/pen + :pencil cur/pencil + :create-shape cur/create-shape + :duplicate cur/duplicate + cur/pointer-inner)) diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs new file mode 100644 index 000000000..073dadd56 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -0,0 +1,173 @@ +; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020-2021 UXBOX Labs SL + +(ns app.main.ui.workspace.viewport.widgets + (:require + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.pages :as cp] + [app.main.refs :as refs] + [app.main.streams :as ms] + [app.main.ui.hooks :as hooks] + [app.main.ui.workspace.shapes.outline :refer [outline]] + [app.main.ui.workspace.shapes.path.actions :refer [path-actions]] + [app.util.dom :as dom] + [clojure.set :as set] + [rumext.alpha :as mf])) + +(mf/defc shape-outlines + {::mf/wrap-props false} + [props] + (let [objects (unchecked-get props "objects") + selected (or (unchecked-get props "selected") #{}) + hover (or (unchecked-get props "hover") #{}) + edition (unchecked-get props "edition") + outline? (set/union selected hover) + show-outline? (fn [shape] (and (not (:hidden shape)) + (not (:blocked shape)) + (not= edition (:id shape)) + (outline? (:id shape)))) + + shapes (cond->> (vals objects) + show-outline? (filter show-outline?)) + + transform (mf/deref refs/current-transform) + color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes)))) + "#31EFB8" "#00E0FF")] + (when (nil? transform) + [:g.outlines + (for [shape shapes] + [:& outline {:key (str "outline-" (:id shape)) + :shape (gsh/transform-shape shape) + :color color}])]))) + + +(mf/defc pixel-grid + [{:keys [vbox zoom]}] + [:g.pixel-grid + [:defs + [:pattern {:id "pixel-grid" + :viewBox "0 0 1 1" + :width 1 + :height 1 + :pattern-units "userSpaceOnUse"} + [:path {:d "M 1 0 L 0 0 0 1" + :style {:fill "none" + :stroke "#59B9E2" + :stroke-opacity "0.2" + :stroke-width (str (/ 1 zoom))}}]]] + [:rect {:x (:x vbox) + :y (:y vbox) + :width (:width vbox) + :height (:height vbox) + :fill (str "url(#pixel-grid)") + :style {:pointer-events "none"}}]]) + +(mf/defc viewport-actions + {::mf/wrap [mf/memo]} + [] + (let [edition (mf/deref refs/selected-edition) + selected (mf/deref refs/selected-objects) + shape (-> selected first)] + (when (and (= (count selected) 1) + (= (:id shape) edition) + (= :path (:type shape))) + [:div.viewport-actions + [:& path-actions {:shape shape}]]))) + +(mf/defc cursor-tooltip + [{:keys [zoom tooltip] :as props}] + (let [coords (some-> (hooks/use-rxsub ms/mouse-position) + (gpt/divide (gpt/point zoom zoom))) + pos-x (- (:x coords) 100) + pos-y (+ (:y coords) 30)] + [:g {:transform (str "translate(" pos-x "," pos-y ")")} + [:foreignObject {:width 200 :height 100 :style {:text-align "center"}} + [:span tooltip]]])) + +(mf/defc selection-rect + {:wrap [mf/memo]} + [{:keys [data] :as props}] + (when data + [:rect.selection-rect + {:x (:x data) + :y (:y data) + :width (:width data) + :height (:height data)}])) + +;; Ensure that the label has always the same font +;; size, regardless of zoom +;; https://css-tricks.com/transforms-on-svg-elements/ +(defn text-transform + [{:keys [x y]} zoom] + (let [inv-zoom (/ 1 zoom)] + (str + "scale(" inv-zoom ", " inv-zoom ") " + "translate(" (* zoom x) ", " (* zoom y) ")"))) + +(mf/defc frame-title + [{:keys [frame modifiers selected? zoom on-frame-enter on-frame-leave on-frame-select]}] + (let [{:keys [width x y]} frame + label-pos (gpt/point x (- y (/ 10 zoom))) + + on-mouse-down + (mf/use-callback + (mf/deps (:id frame) on-frame-select) + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (on-frame-select event (:id frame)))) + + on-pointer-enter + (mf/use-callback + (mf/deps (:id frame) on-frame-enter) + (fn [event] + (on-frame-enter (:id frame)))) + + on-pointer-leave + (mf/use-callback + (mf/deps (:id frame) on-frame-leave) + (fn [event] + (on-frame-leave (:id frame))))] + + [:text {:x 0 + :y 0 + :width width + :height 20 + :class "workspace-frame-label" + :transform (str (when (and selected? modifiers) + (str (:displacement modifiers) " " )) + (text-transform label-pos zoom)) + :style {:fill (when selected? "#28c295")} + :on-mouse-down on-mouse-down + :on-pointer-enter on-pointer-enter + :on-pointer-leave on-pointer-leave} + (:name frame)])) + +(mf/defc frame-titles + {::mf/wrap-props false} + [props] + (let [objects (unchecked-get props "objects") + zoom (unchecked-get props "zoom") + modifiers (unchecked-get props "modifiers") + selected (or (unchecked-get props "selected") #{}) + on-frame-enter (unchecked-get props "on-frame-enter") + on-frame-leave (unchecked-get props "on-frame-leave") + on-frame-select (unchecked-get props "on-frame-select") + frames (cp/select-frames objects)] + + [:g.frame-titles + (for [frame frames] + [:& frame-title {:frame frame + :selected? (contains? selected (:id frame)) + :zoom zoom + :modifiers modifiers + :on-frame-enter on-frame-enter + :on-frame-leave on-frame-leave + :on-frame-select on-frame-select}])])) diff --git a/frontend/src/app/util/data.cljs b/frontend/src/app/util/data.cljs index 2350262e9..0a6c2889c 100644 --- a/frontend/src/app/util/data.cljs +++ b/frontend/src/app/util/data.cljs @@ -118,33 +118,6 @@ (into {})) m1)) -(defn with-next - "Given a collectin will return a new collection where each element - is paried with the next item in the collection - (with-next (range 5)) => [[0 1] [1 2] [2 3] [3 4] [4 nil]" - [coll] - (map vector - coll - (concat [] (rest coll) [nil]))) - -(defn with-prev - "Given a collectin will return a new collection where each element - is paried with the previous item in the collection - (with-prev (range 5)) => [[0 nil] [1 0] [2 1] [3 2] [4 3]" - [coll] - (map vector - coll - (concat [nil] coll))) - -(defn with-prev-next - "Given a collection will return a new collection where every item is paired - with the previous and the next item of a collection - (with-prev-next (range 5)) => [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]" - [coll] - (map vector - coll - (concat [nil] coll) - (concat [] (rest coll) [nil]))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Numbers Parsing @@ -248,7 +221,3 @@ ;; nil ;; (throw e#))))))) -(defn prefix-keyword [prefix kw] - (let [prefix (if (keyword? prefix) (name prefix) prefix) - kw (if (keyword? kw) (name kw) kw)] - (keyword (str prefix kw)))) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 99cdd7a93..2df4b6684 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -277,3 +277,9 @@ "image/svg+xml" "svg" "image/webp" "webp" nil)) + +(defn set-attribute [^js node ^string attr value] + (.setAttribute node attr value)) + +(defn remove-attribute [^js node ^string attr] + (.removeAttribute node attr)) diff --git a/frontend/src/app/util/geom/path.cljs b/frontend/src/app/util/geom/path.cljs index f753bb8ec..0cfcfad82 100644 --- a/frontend/src/app/util/geom/path.cljs +++ b/frontend/src/app/util/geom/path.cljs @@ -9,11 +9,9 @@ (ns app.util.geom.path (:require - [app.common.data :as cd] - [app.common.data :as cd] + [app.common.data :as d] [app.common.geom.point :as gpt] [app.util.a2c :refer [a2c]] - [app.util.data :as d] [app.util.geom.path-impl-simplify :as impl-simplify] [app.util.svg :as usvg] [cuerdas.core :as str])) @@ -262,24 +260,24 @@ (cond-> command (:relative command) (-> (assoc :relative false) - (cd/update-in-when [:params :c1x] + (:x pos)) - (cd/update-in-when [:params :c1y] + (:y pos)) + (d/update-in-when [:params :c1x] + (:x pos)) + (d/update-in-when [:params :c1y] + (:y pos)) - (cd/update-in-when [:params :c2x] + (:x pos)) - (cd/update-in-when [:params :c2y] + (:y pos)) + (d/update-in-when [:params :c2x] + (:x pos)) + (d/update-in-when [:params :c2y] + (:y pos)) - (cd/update-in-when [:params :cx] + (:x pos)) - (cd/update-in-when [:params :cy] + (:y pos)) + (d/update-in-when [:params :cx] + (:x pos)) + (d/update-in-when [:params :cy] + (:y pos)) - (cd/update-in-when [:params :x] + (:x pos)) - (cd/update-in-when [:params :y] + (:y pos)) + (d/update-in-when [:params :x] + (:x pos)) + (d/update-in-when [:params :y] + (:y pos)) (cond-> (= :line-to-horizontal (:command command)) - (cd/update-in-when [:params :value] + (:x pos)) + (d/update-in-when [:params :value] + (:x pos)) (= :line-to-vertical (:command command)) - (cd/update-in-when [:params :value] + (:y pos))))) + (d/update-in-when [:params :value] + (:y pos))))) params (:params command) orig-command command @@ -313,7 +311,7 @@ (update :params merge (quadratic->curve pos (gpt/point params) (calculate-opposite-handler pos prev-qc))))) result (if (= :elliptical-arc (:command command)) - (cd/concat result (arc->beziers pos command)) + (d/concat result (arc->beziers pos command)) (conj result command)) prev-cc (case (:command orig-command) @@ -453,7 +451,7 @@ []))) (group-by first) - (cd/mapm #(mapv second %2)))) + (d/mapm #(mapv second %2)))) (defn opposite-index "Calculate sthe opposite index given a prefix and an index" @@ -552,10 +550,10 @@ handler (gpt/add point handler-vector) handler-opposite (gpt/add point (gpt/negate handler-vector))] (-> content - (cd/update-when index make-curve prev) - (cd/update-when index update-handler :c2 handler) - (cd/update-when (inc index) make-curve command) - (cd/update-when (inc index) update-handler :c1 handler-opposite))) + (d/update-when index make-curve prev) + (d/update-when index update-handler :c2 handler) + (d/update-when (inc index) make-curve command) + (d/update-when (inc index) update-handler :c1 handler-opposite))) content))] (as-> content $ diff --git a/frontend/src/app/worker/selection.cljs b/frontend/src/app/worker/selection.cljs index 0372abf46..425ae2bd0 100644 --- a/frontend/src/app/worker/selection.cljs +++ b/frontend/src/app/worker/selection.cljs @@ -13,7 +13,7 @@ [okulary.core :as l] [app.common.data :as d] [app.common.exceptions :as ex] - [app.common.geom.shapes :as geom] + [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.common.spec :as us] [app.common.uuid :as uuid] @@ -44,39 +44,87 @@ nil)) (defmethod impl/handler :selection/query - [{:keys [page-id rect frame-id] :as message}] + [{:keys [page-id rect frame-id include-frames? include-groups? disabled-masks] :or {include-groups? true + disabled-masks #{}} :as message}] (when-let [index (get @state page-id)] (let [result (-> (qdt/search index (clj->js rect)) (es6-iterator-seq)) - matches? (fn [shape] - (and - ;; When not frame-id is passed, we filter the frames - (or (and (not frame-id) (not= :frame (:type shape))) - ;; If we pass a frame-id only get the area for shapes inside that frame - (= frame-id (:frame-id shape))) - (geom/overlaps? shape rect)))] + + ;; Check if the shape matches the filter criteria + match-criteria? + (fn [shape] + (and (not (:hidden shape)) + (or (not frame-id) (= frame-id (:frame-id shape))) + (case (:type shape) + :frame include-frames? + :group include-groups? + true))) + + overlaps? + (fn [shape] + (gsh/overlaps? shape rect)) + + overlaps-masks? + (fn [masks] + (->> masks + (some (comp not overlaps?)) + not)) + + ;; Shapes after filters of overlapping and criteria + matching-shapes + (into [] + (comp (map #(unchecked-get % "data")) + (filter match-criteria?) + (filter (comp overlaps? :frame)) + (filter (comp overlaps-masks? :masks)) + (filter overlaps?)) + result)] (into (d/ordered-set) - (comp (map #(unchecked-get % "data")) - (filter matches?) - (map :id)) - result)))) + (->> matching-shapes + (sort-by (comp - :z)) + (map :id)))))) + +(defn create-mask-index + "Retrieves the mask information for an object" + [objects parents-index] + (let [retrieve-masks + (fn [id parents] + (->> parents + (map #(get objects %)) + (filter #(:masked-group? %)) + ;; Retrieve the masking element + (mapv #(get objects (->> % :shapes first)))))] + (->> parents-index + (d/mapm retrieve-masks)))) (defn- create-index [objects] - (let [shapes (cp/select-toplevel-shapes objects {:include-frames? true}) - bounds (geom/selection-rect shapes) + (let [shapes (-> objects (dissoc uuid/zero) (vals)) + z-index (cp/calculate-z-index objects) + parents-index (cp/generate-child-all-parents-index objects) + masks-index (create-mask-index objects parents-index) + bounds (gsh/selection-rect shapes) bounds #js {:x (:x bounds) :y (:y bounds) :width (:width bounds) :height (:height bounds)}] - (reduce index-object + + (reduce (partial index-object objects z-index parents-index masks-index) (qdt/create bounds) shapes))) (defn- index-object - [index obj] - (let [{:keys [id x y width height]} (:selrect obj) - rect #js {:x x :y y :width width :height height}] - (qdt/insert index rect obj))) + [objects z-index parents-index masks-index index obj] + (let [{:keys [x y width height]} (:selrect obj) + shape-bound #js {:x x :y y :width width :height height} + parents (get parents-index (:id obj)) + masks (get masks-index (:id obj)) + z (get z-index (:id obj)) + frame (when (and (not= :frame (:type obj)) + (not= (:frame-id obj) uuid/zero)) + (get objects (:frame-id obj)))] + (qdt/insert index + shape-bound + (assoc obj :frame frame :masks masks :parents parents :z z))))