♻️ Make the namespacing independent of the branding.

This commit is contained in:
Andrey Antukh 2020-08-18 19:26:37 +02:00
parent aaf8b71837
commit 6c67c3c71b
305 changed files with 2399 additions and 2580 deletions

View file

@ -0,0 +1,34 @@
;; 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) 2016-2020 UXBOX Labs SL
(ns app.main.ui.shapes.attrs
(:require [app.util.object :as obj]))
(defn- stroke-type->dasharray
[style]
(case style
:mixed "5,5,1,5"
:dotted "5,5"
:dashed "10,10"
nil))
(defn extract-style-attrs
[shape]
(let [stroke-style (:stroke-style shape :none)
attrs #js {:fill (or (:fill-color shape) "transparent")
:fillOpacity (:fill-opacity shape nil)
:rx (:rx shape nil)
:ry (:ry shape nil)}]
(when (not= stroke-style :none)
(obj/merge! attrs
#js {:stroke (:stroke-color shape nil)
:strokeWidth (:stroke-width shape 1)
:strokeOpacity (:stroke-opacity shape nil)
:strokeDasharray (stroke-type->dasharray stroke-style)}))
attrs))

View file

@ -0,0 +1,41 @@
;; 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.shapes.circle
(:require
[rumext.alpha :as mf]
[app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]
[app.common.geom.shapes :as geom]
[app.util.object :as obj]))
(mf/defc circle-shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height]} shape
transform (geom/transform-matrix shape)
cx (+ x (/ width 2))
cy (+ y (/ height 2))
rx (/ width 2)
ry (/ height 2)
props (-> (attrs/extract-style-attrs shape)
(obj/merge!
#js {:cx cx
:cy cy
:rx rx
:ry ry
:transform transform
:id (str "shape-" id)}))]
[:& shape-custom-stroke {:shape shape
:base-props props
:elem-name "ellipse"}]))

View file

@ -0,0 +1,97 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
(ns app.main.ui.shapes.custom-stroke
(:require
[rumext.alpha :as mf]
[app.common.geom.shapes :as geom]
[app.util.object :as obj]))
; The SVG standard does not implement yet the 'stroke-alignment'
; attribute, to define the position of the stroke relative to the
; stroke axis (inner, center, outer). Here we implement a patch to be
; able to draw the stroke in the three cases. See discussion at:
; https://stackoverflow.com/questions/7241393/can-you-control-how-an-svgs-stroke-width-is-drawn
(mf/defc shape-custom-stroke
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
base-props (unchecked-get props "base-props")
elem-name (unchecked-get props "elem-name")
{:keys [id x y width height]} (geom/shape->rect-shape shape)
stroke-style (:stroke-style shape :none)
stroke-position (:stroke-alignment shape :center)]
(cond
;; Center alignment (or no stroke): the default in SVG
(or (= stroke-style :none) (= stroke-position :center))
[:> elem-name base-props]
;; Inner alignment: display the shape with double width stroke,
;; and clip the result with the original shape without stroke.
(= stroke-position :inner)
(let [clip-id (str "clip-" id)
clip-props (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke nil
:strokeWidth nil
:strokeOpacity nil
:strokeDasharray nil
:fill "white"
:fillOpacity 1
:transform nil}))
stroke-width (obj/get base-props "strokeWidth")
shape-props (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:strokeWidth (* stroke-width 2)
:clipPath (str "url('#" clip-id "')")}))]
[:*
[:> "clipPath" #js {:id clip-id}
[:> elem-name clip-props]]
[:> elem-name shape-props]])
;; Outer alingmnent: display the shape in two layers. One
;; without stroke (only fill), and another one only with stroke
;; at double width (transparent fill) and passed through a mask
;; that shows the whole shape, but hides the original shape
;; without stroke
(= stroke-position :outer)
(let [mask-id (str "mask-" id)
stroke-width (.-strokeWidth ^js base-props)
mask-props1 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke "white"
:strokeWidth (* stroke-width 2)
:strokeOpacity 1
:strokeDasharray nil
:fill "white"
:fillOpacity 1
:transform nil}))
mask-props2 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke nil
:strokeWidth nil
:strokeOpacity nil
:strokeDasharray nil
:fill "black"
:fillOpacity 1
:transform nil}))
shape-props1 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke nil
:strokeWidth nil
:strokeOpacity nil
:strokeDasharray nil}))
shape-props2 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:strokeWidth (* stroke-width 2)
:fill "none"
:fillOpacity 0
:mask (str "url('#" mask-id "')")}))]
[:*
[:mask {:id mask-id}
[:> elem-name mask-props1]
[:> elem-name mask-props2]]
[:> elem-name shape-props1]
[:> elem-name shape-props2]]))))

View file

@ -0,0 +1,45 @@
;; 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.shapes.frame
(:require
[rumext.alpha :as mf]
[app.common.data :as d]
[app.main.ui.shapes.attrs :as attrs]
[app.common.geom.shapes :as geom]
[app.util.object :as obj]))
(def frame-default-props {:fill-color "#ffffff"})
(defn frame-shape
[shape-wrapper]
(mf/fnc frame-shape
{::mf/wrap-props false}
[props]
(let [childs (unchecked-get props "childs")
shape (unchecked-get props "shape")
{:keys [id x y width height]} shape
props (-> (merge frame-default-props shape)
(attrs/extract-style-attrs)
(obj/merge!
#js {:x 0
:y 0
:id (str "shape-" id)
:width width
:height height}))]
[:svg {:x x :y y :width width :height height
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
[:> "rect" props]
(for [[i item] (d/enumerate childs)]
[:& shape-wrapper {:frame shape
:shape item
:key (:id item)}])])))

View file

@ -0,0 +1,43 @@
;; 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.shapes.group
(:require
[rumext.alpha :as mf]
[app.main.ui.shapes.attrs :as attrs]
[app.util.debug :refer [debug?]]
[app.common.geom.shapes :as geom]))
(defn group-shape
[shape-wrapper]
(mf/fnc group-shape
{::mf/wrap-props false}
[props]
(let [frame (unchecked-get props "frame")
shape (unchecked-get props "shape")
childs (unchecked-get props "childs")
is-child-selected? (unchecked-get props "is-child-selected?")
{:keys [id x y width height]} shape
transform (geom/transform-matrix shape)]
[:g
(for [item childs]
[:& shape-wrapper {:frame frame
:shape item
:key (:id item)}])
(when (not is-child-selected?)
[:rect {:transform transform
:x x
:y y
:fill (if (debug? :group) "red" "transparent")
:opacity 0.5
:id (str "group-" id)
:width width
:height height}])])))

View file

@ -0,0 +1,47 @@
;; 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.shapes.icon
(:require
[rumext.alpha :as mf]
[app.common.geom.shapes :as geom]
[app.main.ui.shapes.attrs :as attrs]
[app.util.object :as obj]))
(mf/defc icon-shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height metadata rotation content]} shape
transform (geom/transform-matrix shape)
vbox (apply str (interpose " " (:view-box metadata)))
props (-> (attrs/extract-style-attrs shape)
(obj/merge!
#js {:x x
:y y
:transform transform
:id (str "shape-" id)
:width width
:height height
:viewBox vbox
:preserveAspectRatio "none"
:dangerouslySetInnerHTML #js {:__html content}}))]
[:g {:transform transform}
[:> "svg" props]]))
(mf/defc icon-svg
[{:keys [shape] :as props}]
(let [{:keys [content id metadata]} shape
view-box (apply str (interpose " " (:view-box metadata)))
props {:viewBox view-box
:id (str "shape-" id)
:dangerouslySetInnerHTML #js {:__html content}}]
[:& "svg" props]))

View file

@ -0,0 +1,35 @@
;; 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.shapes.image
(:require
[rumext.alpha :as mf]
[app.config :as cfg]
[app.common.geom.shapes :as geom]
[app.main.ui.shapes.attrs :as attrs]
[app.util.object :as obj]))
(mf/defc image-shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height rotation metadata]} shape
transform (geom/transform-matrix shape)
uri (cfg/resolve-media-path (:path metadata))
props (-> (attrs/extract-style-attrs shape)
(obj/merge!
#js {:x x
:y y
:transform transform
:id (str "shape-" id)
:preserveAspectRatio "none"
:xlinkHref uri
:width width
:height height}))]
[:> "image" props]))

View file

@ -0,0 +1,67 @@
;; 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.shapes.path
(:require
[cuerdas.core :as str]
[rumext.alpha :as mf]
[app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]
[app.common.geom.shapes :as geom]
[app.util.object :as obj]))
;; --- Path Shape
(defn- render-path
[{:keys [segments close?] :as shape}]
(let [numsegs (count segments)]
(loop [buffer []
index 0]
(cond
(>= index numsegs)
(if close?
(str/join " " (conj buffer "Z"))
(str/join " " buffer))
(zero? index)
(let [{:keys [x y] :as segment} (nth segments index)
buffer (conj buffer (str/istr "M~{x},~{y}"))]
(recur buffer (inc index)))
:else
(let [{:keys [x y] :as segment} (nth segments index)
buffer (conj buffer (str/istr "L~{x},~{y}"))]
(recur buffer (inc index)))))))
(mf/defc path-shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
background? (unchecked-get props "background?")
{:keys [id x y width height]} (geom/shape->rect-shape shape)
transform (geom/transform-matrix shape)
pdata (render-path shape)
props (-> (attrs/extract-style-attrs shape)
(obj/merge!
#js {:transform transform
:id (str "shape-" id)
:d pdata}))]
(if background?
[:g
[:path {:stroke "transparent"
:fill "transparent"
:stroke-width "20px"
:d pdata}]
[:& shape-custom-stroke {:shape shape
:base-props props
:elem-name "path"}]]
[:& shape-custom-stroke {:shape shape
:base-props props
:elem-name "path"}])))

View file

@ -0,0 +1,36 @@
;; 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.shapes.rect
(:require
[rumext.alpha :as mf]
[app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]
[app.common.geom.shapes :as geom]
[app.util.object :as obj]))
(mf/defc rect-shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height]} shape
transform (geom/transform-matrix shape)
props (-> (attrs/extract-style-attrs shape)
(obj/merge!
#js {:x x
:y y
:transform transform
:id (str "shape-" id)
:width width
:height height}))]
[:& shape-custom-stroke {:shape shape
:base-props props
:elem-name "rect"}]))

View file

@ -0,0 +1,11 @@
;; 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.shapes.shape)

View file

@ -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/.
;;
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
(ns app.main.ui.shapes.text
(:require
[rumext.alpha :as mf]
[app.common.data :as d]
[app.common.geom.shapes :as geom]
[app.common.geom.matrix :as gmt]
[app.main.fonts :as fonts]
[app.util.object :as obj]))
;; --- Text Editor Rendering
(defn- generate-root-styles
[data]
(let [valign (obj/get data "vertical-align")
base #js {:height "100%"
:width "100%"
:display "flex"}]
(cond-> base
(= valign "top") (obj/set! "alignItems" "flex-start")
(= valign "center") (obj/set! "alignItems" "center")
(= valign "bottom") (obj/set! "alignItems" "flex-end"))))
(defn- generate-paragraph-styles
[data]
(let [base #js {:fontSize "14px"
:margin "inherit"
:lineHeight "1.2"}
lh (obj/get data "line-height")
ta (obj/get data "text-align")]
(cond-> base
ta (obj/set! "textAlign" ta)
lh (obj/set! "lineHeight" lh))))
(defn- generate-text-styles
[data]
(let [letter-spacing (obj/get data "letter-spacing")
text-decoration (obj/get data "text-decoration")
text-transform (obj/get data "text-transform")
font-id (obj/get data "font-id")
font-variant-id (obj/get data "font-variant-id")
font-family (obj/get data "font-family")
font-size (obj/get data "font-size")
fill (obj/get data "fill")
opacity (obj/get data "opacity")
fontsdb (deref fonts/fontsdb)
base #js {:textDecoration text-decoration
:color fill
:opacity opacity
:textTransform text-transform}]
(when (and (string? letter-spacing)
(pos? (alength letter-spacing)))
(obj/set! base "letterSpacing" (str letter-spacing "px")))
(when (and (string? font-size)
(pos? (alength font-size)))
(obj/set! base "fontSize" (str font-size "px")))
(when (and (string? font-id)
(pos? (alength font-id)))
(let [font (get fontsdb font-id)]
(fonts/ensure-loaded! font-id)
(let [font-family (or (:family font)
(obj/get data "fontFamily"))
font-variant (d/seek #(= font-variant-id (:id %))
(:variants font))
font-style (or (:style font-variant)
(obj/get data "fontStyle"))
font-weight (or (:weight font-variant)
(obj/get data "fontWeight"))]
(obj/set! base "fontFamily" font-family)
(obj/set! base "fontStyle" font-style)
(obj/set! base "fontWeight" font-weight))))
base))
(defn- render-text-node
([node] (render-text-node 0 node))
([index {:keys [type text children] :as node}]
(mf/html
(if (string? text)
(let [style (generate-text-styles (clj->js node))]
[:span {:style style :key index} text])
(let [children (map-indexed render-text-node children)]
(case type
"root"
(let [style (generate-root-styles (clj->js node))]
[:div.root.rich-text
{:key index
:style style
:xmlns "http://www.w3.org/1999/xhtml"}
children])
"paragraph-set"
(let [style #js {:display "inline-block"
:width "100%"}]
[:div.paragraphs {:key index :style style} children])
"paragraph"
(let [style (generate-paragraph-styles (clj->js node))]
[:p {:key index :style style} children])
nil))))))
(mf/defc text-content
{::mf/wrap-props false
::mf/wrap [mf/memo]}
[props]
(let [root (obj/get props "content")]
(render-text-node root)))
(defn- retrieve-colors
[shape]
(let [colors (into #{} (comp (map :fill)
(filter string?))
(tree-seq map? :children (:content shape)))]
(if (empty? colors)
"#000000"
(apply str (interpose "," colors)))))
(mf/defc text-shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
selected? (unchecked-get props "selected?")
{:keys [id x y width height rotation content]} shape]
[:foreignObject {:x x
:y y
:data-colors (retrieve-colors shape)
:transform (geom/transform-matrix shape)
:id (str id)
:width width
:height height}
[:& text-content {:content (:content shape)}]]))