0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-12 15:01:28 -05:00

Improved transformation from and to JS for plugins

This commit is contained in:
alonso.torres 2024-06-27 10:11:48 +02:00
parent 42230f2630
commit ac58a5b8fa
15 changed files with 1359 additions and 622 deletions

View file

@ -27,6 +27,7 @@
[app.plugins.events :as events]
[app.plugins.file :as file]
[app.plugins.fonts :as fonts]
[app.plugins.format :as format]
[app.plugins.library :as library]
[app.plugins.page :as page]
[app.plugins.parser :as parser]
@ -101,26 +102,12 @@
shapes (->> shapes
(map #(obj/get % "$id"))
(mapcat #(cfh/get-children-with-self objects %)))
file-id (:current-file-id @st/state)
shared-libs (:workspace-libraries @st/state)
shared-libs (:workspace-libraries @st/state)]
format-entry
(fn [{:keys [prop shape-id index]}]
#js {:property (d/name prop)
:index index
:shapeId (str shape-id)})
format-result
(fn [[color attrs]]
(let [shapes-info (apply array (map format-entry attrs))
color (u/to-js color)]
(obj/set! color "shapeInfo" shapes-info)
color))]
(apply
array
(->> (ctc/extract-all-colors shapes file-id shared-libs)
(group-by :attrs)
(map format-result))))))
(->> (ctc/extract-all-colors shapes file-id shared-libs)
(group-by :attrs)
(format/format-array format/format-color-result)))))
(replaceColor
[_ shapes old-color new-color]
@ -188,8 +175,8 @@
(p/create
(fn [resolve reject]
(->> (dwm/upload-media-url name file-id url)
(rx/map u/to-js)
(rx/take 1)
(rx/map format/format-image)
(rx/subs! resolve reject)))))))
(uploadMediaData
@ -205,7 +192,7 @@
:on-image identity
:on-svg identity})
(rx/take 1)
(rx/map u/to-js)
(rx/map format/format-image)
(rx/subs! resolve reject))))))
(group

View file

@ -12,6 +12,7 @@
[app.main.fonts :as fonts]
[app.main.store :as st]
[app.plugins.shape :as shape]
[app.plugins.text :as text]
[app.plugins.utils :as u]
[app.util.object :as obj]
[cuerdas.core :as str]))
@ -39,7 +40,7 @@
(applyToRange [_ range variant]
(cond
(not (shape/text-range? range))
(not (text/text-range? range))
(u/display-not-valid :applyToRange range)
;; TODO: Check variant inside font variants

View file

@ -4,5 +4,408 @@
;;
;; Copyright (c) KALEIDOS INC
(ns app.plugins.format)
(ns app.plugins.format
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.util.object :as obj]))
(defn format-id
[id]
(when id (dm/str id)))
(defn format-key
[kw]
(when kw (d/name kw)))
(defn format-array
[format-fn coll]
(when (some? coll)
(apply array (keep format-fn coll))))
;; export type PenpotPoint = { x: number; y: number };
(defn format-point
[{:keys [x y] :as point}]
(when (some? point)
(obj/clear-empty
#js {:x x :y y})))
;;export type PenpotBounds = {
;; x: number;
;; y: number;
;; width: number;
;; height: number;
;;};
(defn format-bounds
[{:keys [x y width height] :as bounds}]
(when (some? bounds)
(obj/clear-empty
#js {:x x :y y :width width :height height})))
;; export interface PenpotColorShapeInfoEntry {
;; readonly property: string;
;; readonly index?: number;
;; readonly shapeId: string;
;; }
(defn format-shape-info
[{:keys [prop shape-id index] :as info}]
(when (some? info)
(obj/clear-empty
#js {:property (d/name prop)
:index index
:shapeId (dm/str shape-id)})))
;; export type PenpotGradient = {
;; type: 'linear' | 'radial';
;; startX: number;
;; startY: number;
;; endX: number;
;; endY: number;
;; width: number;
;; stops: Array<{ color: string; opacity?: number; offset: number }>;
;; };
(defn format-stop
[{:keys [color opacity offset] :as stop}]
(when (some? stop)
(obj/clear-empty #js {:color color :opacity opacity :offset offset})))
(defn format-gradient
[{:keys [type start-x start-y end-x end-y width stops] :as gradient}]
(when (some? gradient)
(obj/clear-empty
#js {:type (format-key type)
:startX start-x
:startY start-y
:endX end-x
:endY end-y
:width width
:stops (format-array format-stop stops)})))
;; export type PenpotImageData = {
;; name?: string;
;; width: number;
;; height: number;
;; mtype?: string;
;; id: string;
;; keepApectRatio?: boolean;
;; };
(defn format-image
[{:keys [name width height mtype id keep-aspect-ratio] :as image}]
(when (some? image)
(obj/clear-empty
#js {:name name
:width width
:height height
:mtype mtype
:id (format-id id)
:keepAspectRatio keep-aspect-ratio})))
;; export interface PenpotColor {
;; id?: string;
;; name?: string;
;; path?: string;
;; color?: string;
;; opacity?: number;
;; refId?: string;
;; refFile?: string;
;; gradient?: PenpotGradient;
;; image?: PenpotImageData;
;; }
(defn format-color
[{:keys [id name path color opacity ref-id ref-file gradient image] :as color-data}]
(when (some? color-data)
(obj/clear-empty
#js {:id (format-id id)
:name name
:path path
:color color
:opacity opacity
:refId (format-id ref-id)
:refFile (format-id ref-file)
:gradient (format-gradient gradient)
:image (format-image image)})))
;; PenpotColor & PenpotColorShapeInfo
(defn format-color-result
[[color attrs]]
(let [shapes-info (apply array (map format-shape-info attrs))
color (format-color color)]
(obj/set! color "shapeInfo" shapes-info)
color))
;; export interface PenpotShadow {
;; id?: string;
;; style?: 'drop-shadow' | 'inner-shadow';
;; offsetX?: number;
;; offsetY?: number;
;; blur?: number;
;; spread?: number;
;; hidden?: boolean;
;; color?: PenpotColor;
;; }
(defn format-shadow
[{:keys [id style offset-x offset-y blur spread hidden color] :as shadow}]
(when (some? shadow)
(obj/clear-empty
#js {:id (-> id format-id)
:style (-> style format-key)
:offsetX offset-x
:offsetY offset-y
:blur blur
:spread spread
:hidden hidden
:color (format-color color)})))
(defn format-shadows
[shadows]
(when (some? shadows)
(format-array format-shadow shadows)))
;;export interface PenpotFill {
;; fillColor?: string;
;; fillOpacity?: number;
;; fillColorGradient?: PenpotGradient;
;; fillColorRefFile?: string;
;; fillColorRefId?: string;
;; fillImage?: PenpotImageData;
;;}
(defn format-fill
[{:keys [fill-color fill-opacity fill-color-gradient fill-color-ref-file fill-color-ref-id fill-image] :as fill}]
(when (some? fill)
(obj/clear-empty
#js {:fillColor fill-color
:fillOpacity fill-opacity
:fillColorGradient (format-gradient fill-color-gradient)
:fillColorRefFile (format-id fill-color-ref-file)
:fillColorRefId (format-id fill-color-ref-id)
:fillImage (format-image fill-image)})))
(defn format-fills
[fills]
(when (some? fills)
(format-array format-fill fills)))
;; export interface PenpotStroke {
;; strokeColor?: string;
;; strokeColorRefFile?: string;
;; strokeColorRefId?: string;
;; strokeOpacity?: number;
;; strokeStyle?: 'solid' | 'dotted' | 'dashed' | 'mixed' | 'none' | 'svg';
;; strokeWidth?: number;
;; strokeAlignment?: 'center' | 'inner' | 'outer';
;; strokeCapStart?: PenpotStrokeCap;
;; strokeCapEnd?: PenpotStrokeCap;
;; strokeColorGradient?: PenpotGradient;
;; }
(defn format-stroke
[{:keys [stroke-color stroke-color-ref-file stroke-color-ref-id
stroke-opacity stroke-style stroke-width stroke-alignment
stroke-cap-start stroke-cap-end stroke-color-gradient] :as stroke}]
(when (some? stroke)
(obj/clear-empty
#js {:strokeColor stroke-color
:strokeColorRefFile (format-id stroke-color-ref-file)
:strokeColorRefId (format-id stroke-color-ref-id)
:strokeOpacity stroke-opacity
:strokeStyle (format-key stroke-style)
:strokeWidth stroke-width
:strokeAlignment (format-key stroke-alignment)
:strokeCapStart (format-key stroke-cap-start)
:strokeCapEnd (format-key stroke-cap-end)
:strokeColorGradient (format-gradient stroke-color-gradient)})))
(defn format-strokes
[strokes]
(when (some? strokes)
(format-array format-stroke strokes)))
;; export interface PenpotBlur {
;; id?: string;
;; type?: 'layer-blur';
;; value?: number;
;; hidden?: boolean;
;; }
(defn format-blur
[{:keys [id type value hidden] :as blur}]
(when (some? blur)
(obj/clear-empty
#js {:id (format-id id)
:type (format-key type)
:value value
:hidden hidden})))
;; export interface PenpotExport {
;; type: 'png' | 'jpeg' | 'svg' | 'pdf';
;; scale: number;
;; suffix: string;
;; }
(defn format-export
[{:keys [type scale suffix] :as export}]
(when (some? export)
(obj/clear-empty
#js {:type (format-key type)
:scale scale
:suffix suffix})))
(defn format-exports
[exports]
(when (some? exports)
(format-array format-export exports)))
;; export interface PenpotFrameGuideColumnParams {
;; color: { color: string; opacity: number };
;; type?: 'stretch' | 'left' | 'center' | 'right';
;; size?: number;
;; margin?: number;
;; itemLength?: number;
;; gutter?: number;
;; }
(defn format-frame-guide-column-params
[{:keys [color type size margin item-length gutter] :as params}]
(when (some? params)
(obj/clear-empty
#js {:color (format-color color)
:type (format-key type)
:size size
:margin margin
:itemLength item-length
:gutter gutter})))
;; export interface PenpotFrameGuideColumn {
;; type: 'column';
;; display: boolean;
;; params: PenpotFrameGuideColumnParams;
;; }
(defn format-frame-guide-column
[{:keys [type display params] :as guide}]
(when (some? guide)
(obj/clear-empty
#js {:type (format-key type)
:display display
:params (format-frame-guide-column-params params)})))
;; export interface PenpotFrameGuideRow {
;; type: 'row';
;; display: boolean;
;; params: PenpotFrameGuideColumnParams;
;; }
(defn format-frame-guide-row
[{:keys [type display params] :as guide}]
(when (some? guide)
(obj/clear-empty
#js {:type (format-key type)
:display display
:params (format-frame-guide-column-params params)})))
;;export interface PenpotFrameGuideSquareParams {
;; color: { color: string; opacity: number };
;; size?: number;
;;}
(defn format-frame-guide-square-params
[{:keys [color size] :as params}]
(when (some? params)
(obj/clear-empty
#js {:color (format-color color)
:size size})))
;; export interface PenpotFrameGuideSquare {
;; type: 'square';
;; display: boolean;
;; params: PenpotFrameGuideSquareParams;
;; }
(defn format-frame-guide-square
[{:keys [type display params] :as guide}]
(when (some? guide)
(obj/clear-empty
#js {:type (format-key type)
:display display
:params (format-frame-guide-column-params params)})))
(defn format-frame-guide
[{:keys [type] :as guide}]
(when (some? guide)
(case type
:column (format-frame-guide-column guide)
:row (format-frame-guide-row guide)
:square (format-frame-guide-square guide))))
(defn format-frame-guides
[guides]
(when (some? guides)
(format-array format-frame-guide guides)))
;;interface PenpotPathCommand {
;; command:
;; | 'M' | 'move-to'
;; | 'Z' | 'close-path'
;; | 'L' | 'line-to'
;; | 'H' | 'line-to-horizontal'
;; | 'V' | 'line-to-vertical'
;; | 'C' | 'curve-to'
;; | 'S' | 'smooth-curve-to'
;; | 'Q' | 'quadratic-bezier-curve-to'
;; | 'T' | 'smooth-quadratic-bezier-curve-to'
;; | 'A' | 'elliptical-arc';
;;
;; params?: {
;; x?: number;
;; y?: number;
;; c1x: number;
;; c1y: number;
;; c2x: number;
;; c2y: number;
;; rx?: number;
;; ry?: number;
;; xAxisRotation?: number;
;; largeArcFlag?: boolean;
;; sweepFlag?: boolean;
;; };
;;}
(defn format-command-params
[{:keys [x y c1x c1y c2x c2y rx ry x-axis-rotation large-arc-flag sweep-flag] :as props}]
(when (some? props)
(obj/clear-empty
#js {:x x
:y y
:c1x c1x
:c1y c1y
:c2x c2x
:c2y c2y
:rx rx
:ry ry
:xAxisRotation x-axis-rotation
:largeArcFlag large-arc-flag
:sweepFlag sweep-flag})))
(defn format-command
[{:keys [command params] :as props}]
(when (some? props)
(obj/clear-empty
#js {:command (format-key command)
:params (format-command-params params)})))
(defn format-path-content
[content]
(when (some? content)
(format-array format-command content)))
;; export type PenpotTrackType = 'flex' | 'fixed' | 'percent' | 'auto';
;;
;; export interface PenpotTrack {
;; type: PenpotTrackType;
;; value: number | null;
;; }
(defn format-track
[{:keys [type value] :as track}]
(when (some? track)
(obj/clear-empty
#js {:type (-> type format-key)
:value value})))
(defn format-tracks
[tracks]
(when (some? tracks)
(format-array format-track tracks)))

View file

@ -13,6 +13,7 @@
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.transforms :as dwt]
[app.main.store :as st]
[app.plugins.format :as format]
[app.plugins.utils :as u]
[app.util.object :as obj]
[potok.v2.core :as ptk]))
@ -20,12 +21,6 @@
;; Define in `app.plugins.shape` we do this way to prevent circular dependency
(def shape-proxy? nil)
(defn- make-tracks
[tracks]
(.freeze
js/Object
(apply array (->> tracks (map u/to-js)))))
(deftype GridLayout [$plugin $file $page $id]
Object
@ -190,10 +185,10 @@
(st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value}))))))}
{:name "rows"
:get #(-> % u/proxy->shape :layout-grid-rows make-tracks)}
:get #(-> % u/proxy->shape :layout-grid-rows format/format-tracks)}
{:name "columns"
:get #(-> % u/proxy->shape :layout-grid-columns make-tracks)}
:get #(-> % u/proxy->shape :layout-grid-columns format/format-tracks)}
{:name "alignItems"
:get #(-> % u/proxy->shape :layout-align-items d/name)

View file

@ -22,7 +22,10 @@
[app.main.data.workspace.texts :as dwt]
[app.main.repo :as rp]
[app.main.store :as st]
[app.plugins.format :as format]
[app.plugins.parser :as parser]
[app.plugins.shape :as shape]
[app.plugins.text :as text]
[app.plugins.utils :as u]
[app.util.object :as obj]
[beicon.v2.core :as rx]
@ -49,27 +52,25 @@
(asFill [_]
(let [color (u/locate-library-color $file $id)]
(u/to-js
(d/without-nils
{:fill-color (:color color)
:fill-opacity (:opacity color)
:fill-color-gradient (:gradient color)
:fill-color-ref-file $file
:fill-color-ref-id $id
:fill-image (:image color)}))))
(format/format-fill
{:fill-color (:color color)
:fill-opacity (:opacity color)
:fill-color-gradient (:gradient color)
:fill-color-ref-file $file
:fill-color-ref-id $id
:fill-image (:image color)})))
(asStroke [_]
(let [color (u/locate-library-color $file $id)]
(u/to-js
(d/without-nils
{:stroke-color (:color color)
:stroke-opacity (:opacity color)
:stroke-color-gradient (:gradient color)
:stroke-color-ref-file $file
:stroke-color-ref-id $id
:stroke-image (:image color)
:stroke-style :solid
:stroke-alignment :inner}))))
(format/format-stroke
{:stroke-color (:color color)
:stroke-opacity (:opacity color)
:stroke-color-gradient (:gradient color)
:stroke-color-ref-file $file
:stroke-color-ref-id $id
:stroke-image (:image color)
:stroke-style :solid
:stroke-alignment :inner})))
(getPluginData
[self key]
@ -211,10 +212,10 @@
(st/emit! (dwl/update-color color file-id)))))}
{:name "gradient"
:get #(-> % u/proxy->library-color :gradient u/to-js)
:get #(-> % u/proxy->library-color :gradient format/format-gradient)
:set
(fn [self value]
(let [value (u/from-js value)]
(let [value (parser/parse-gradient value)]
(cond
(not (sm/validate ::ctc/gradient value))
(u/display-not-valid :library-color-gradient value)
@ -225,10 +226,10 @@
(st/emit! (dwl/update-color color file-id))))))}
{:name "image"
:get #(-> % u/proxy->library-color :image u/to-js)
:get #(-> % u/proxy->library-color :image format/format-image)
:set
(fn [self value]
(let [value (u/from-js value)]
(let [value (parser/parse-image-data value)]
(cond
(not (sm/validate ::ctc/image-color value))
(u/display-not-valid :library-color-image value)
@ -266,7 +267,7 @@
(applyToTextRange
[self range]
(cond
(not (shape/text-range? range))
(not (text/text-range? range))
(u/display-not-valid :applyToText range)
:else

View file

@ -13,9 +13,11 @@
[app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
[app.main.store :as st]
[app.plugins.parser :as parser]
[app.plugins.shape :as shape]
[app.plugins.utils :as u]
[app.util.object :as obj]))
[app.util.object :as obj]
[cuerdas.core :as str]))
(deftype PageProxy [$plugin $file $id]
Object
@ -34,11 +36,28 @@
(shape/shape-proxy $plugin $file $id uuid/zero))
(findShapes
[_]
[_ criteria]
;; Returns a lazy (iterable) of all available shapes
(when (and (some? $file) (some? $id))
(let [page (u/locate-page $file $id)]
(apply array (sequence (map (partial shape/shape-proxy $plugin)) (keys (:objects page)))))))
(let [criteria (parser/parse-criteria criteria)
match-criteria?
(if (some? criteria)
(fn [[_ shape]]
(and
(or (not (:name criteria))
(= (str/lower (:name criteria)) (str/lower (:name shape))))
(or (not (:name-like criteria))
(str/includes? (str/lower (:name shape)) (str/lower (:name-like criteria))))
(or (not (:type criteria))
(= (:type criteria) (:type shape)))))
identity)]
(when (and (some? $file) (some? $id))
(let [page (u/locate-page $file $id)
xf (comp
(filter match-criteria?)
(map #(shape/shape-proxy $plugin $file $id (first %))))]
(apply array (sequence xf (:objects page)))))))
;; Plugin data
(getPluginData

View file

@ -21,7 +21,29 @@
(defn parse-hex
[color]
(when color (-> color str/lower)))
(if (string? color) (-> color str/lower) color))
;; {
;; name?: string;
;; nameLike?: string;
;; type?:
;; | 'frame'
;; | 'group'
;; | 'bool'
;; | 'rect'
;; | 'path'
;; | 'text'
;; | 'circle'
;; | 'svg-raw'
;; | 'image';
;; }
(defn parse-criteria
[^js criteria]
(when (some? criteria)
(d/without-nils
{:name (obj/get criteria "name")
:name-like (obj/get criteria "nameLike")
:type (-> (obj/get criteria "type") parse-keyword)})))
;;export type PenpotImageData = {
;; name?: string;
@ -33,7 +55,7 @@
;;}
(defn parse-image-data
[^js image-data]
(when image-data
(when (some? image-data)
(d/without-nils
{:id (-> (obj/get image-data "id") parse-id)
:name (obj/get image-data "name")
@ -53,7 +75,7 @@
;; }
(defn parse-gradient-stop
[^js stop]
(when stop
(when (some? stop)
(d/without-nils
{:color (-> (obj/get stop "color") parse-hex)
:opacity (obj/get stop "opacity")
@ -61,7 +83,7 @@
(defn parse-gradient
[^js gradient]
(when gradient
(when (some? gradient)
(d/without-nils
{:type (-> (obj/get gradient "type") parse-keyword)
:start-x (obj/get gradient "startX")
@ -85,7 +107,7 @@
;; }
(defn parse-color
[^js color]
(when color
(when (some? color)
(d/without-nils
{:id (-> (obj/get color "id") parse-id)
:name (obj/get color "name")
@ -96,3 +118,279 @@
:ref-file (-> (obj/get color "refFile") parse-id)
:gradient (-> (obj/get color "gradient") parse-gradient)
:image (-> (obj/get color "image") parse-image-data)})))
;; export interface PenpotShadow {
;; id?: string;
;; style?: 'drop-shadow' | 'inner-shadow';
;; offsetX?: number;
;; offsetY?: number;
;; blur?: number;
;; spread?: number;
;; hidden?: boolean;
;; color?: PenpotColor;
;; }
(defn parse-shadow
[^js shadow]
(when (some? shadow)
(d/without-nils
{:id (-> (obj/get shadow "id") parse-id)
:style (-> (obj/get shadow "style") parse-keyword)
:offset-x (obj/get shadow "offsetX")
:offset-y (obj/get shadow "offsetY")
:blur (obj/get shadow "blur")
:spread (obj/get shadow "spread")
:hidden (obj/get shadow "hidden")
:color (-> (obj/get shadow "color") parse-color)})))
(defn parse-shadows
[^js shadows]
(when (some? shadows)
(into [] (map parse-shadow) shadows)))
;;export interface PenpotFill {
;; fillColor?: string;
;; fillOpacity?: number;
;; fillColorGradient?: PenpotGradient;
;; fillColorRefFile?: string;
;; fillColorRefId?: string;
;; fillImage?: PenpotImageData;
;;}
(defn parse-fill
[^js fill]
(when (some? fill)
(d/without-nils
{:fill-color (-> (obj/get fill "fillColor") parse-hex)
:fill-opacity (obj/get fill "fillOpacity")
:fill-color-gradient (-> (obj/get fill "fillColorGradient") parse-gradient)
:fill-color-ref-file (-> (obj/get fill "fillColorRefFile") parse-id)
:fill-color-ref-id (-> (obj/get fill "fillColorRefId") parse-id)
:fill-image (-> (obj/get fill "fillImage") parse-image-data)})))
(defn parse-fills
[^js fills]
(when (some? fills)
(into [] (map parse-fill) fills)))
;; export interface PenpotStroke {
;; strokeColor?: string;
;; strokeColorRefFile?: string;
;; strokeColorRefId?: string;
;; strokeOpacity?: number;
;; strokeStyle?: 'solid' | 'dotted' | 'dashed' | 'mixed' | 'none' | 'svg';
;; strokeWidth?: number;
;; strokeAlignment?: 'center' | 'inner' | 'outer';
;; strokeCapStart?: PenpotStrokeCap;
;; strokeCapEnd?: PenpotStrokeCap;
;; strokeColorGradient?: PenpotGradient;
;; }
(defn parse-stroke
[^js stroke]
(when (some? stroke)
(d/without-nils
{:stroke-color (-> (obj/get stroke "strokeColor") parse-hex)
:stroke-color-ref-file (-> (obj/get stroke "strokeColorRefFile") parse-id)
:stroke-color-ref-id (-> (obj/get stroke "strokeColorRefId") parse-id)
:stroke-opacity (obj/get stroke "strokeOpacity")
:stroke-style (-> (obj/get stroke "strokeStyle") parse-keyword)
:stroke-width (obj/get stroke "strokeWidth")
:stroke-alignment (-> (obj/get stroke "strokeAlignment") parse-keyword)
:stroke-cap-start (-> (obj/get stroke "strokeCapStart") parse-keyword)
:stroke-cap-end (-> (obj/get stroke "strokeCapEnd") parse-keyword)
:stroke-color-gradient (-> (obj/get stroke "strokeColorGradient") parse-gradient)})))
(defn parse-strokes
[^js strokes]
(when (some? strokes)
(into [] (map parse-stroke) strokes)))
;; export interface PenpotBlur {
;; id?: string;
;; type?: 'layer-blur';
;; value?: number;
;; hidden?: boolean;
;; }
(defn parse-blur
[^js blur]
(when (some? blur)
(d/without-nils
{:id (-> (obj/get blur "id") parse-id)
:type (-> (obj/get blur "type") parse-keyword)
:value (obj/get blur "value")
:hidden (obj/get blur "hidden")})))
;; export interface PenpotExport {
;; type: 'png' | 'jpeg' | 'svg' | 'pdf';
;; scale: number;
;; suffix: string;
;; }
(defn parse-export
[^js export]
(when (some? export)
(d/without-nils
{:type (-> (obj/get export "type") parse-keyword)
:scale (obj/get export "scale")
:suffix (obj/get export "suffix")})))
(defn parse-exports
[^js exports]
(when (some? exports)
(into [] (map parse-export) exports)))
;; export interface PenpotFrameGuideColumnParams {
;; color: { color: string; opacity: number };
;; type?: 'stretch' | 'left' | 'center' | 'right';
;; size?: number;
;; margin?: number;
;; itemLength?: number;
;; gutter?: number;
;; }
(defn parse-frame-guide-column-params
[^js params]
(when params
(d/without-nils
{:color (-> (obj/get params "color") parse-color)
:type (-> (obj/get params "type") parse-keyword)
:size (obj/get params "size")
:margin (obj/get params "margin")
:item-length (obj/get params "itemLength")
:gutter (obj/get params "gutter")})))
;; export interface PenpotFrameGuideColumn {
;; type: 'column';
;; display: boolean;
;; params: PenpotFrameGuideColumnParams;
;; }
(defn parse-frame-guide-column
[^js guide]
(when guide
(d/without-nils
{:type (-> (obj/get guide "type") parse-keyword)
:display (obj/get guide "display")
:params (-> (obj/get guide "params") parse-frame-guide-column-params)})))
;; export interface PenpotFrameGuideRow {
;; type: 'row';
;; display: boolean;
;; params: PenpotFrameGuideColumnParams;
;; }
(defn parse-frame-guide-row
[^js guide]
(when guide
(d/without-nils
{:type (-> (obj/get guide "type") parse-keyword)
:display (obj/get guide "display")
:params (-> (obj/get guide "params") parse-frame-guide-column-params)})))
;;export interface PenpotFrameGuideSquareParams {
;; color: { color: string; opacity: number };
;; size?: number;
;;}
(defn parse-frame-guide-square-params
[^js params]
(when (some? params)
(d/without-nils
{:color (-> (obj/get params "color") parse-color)
:size (obj/get params "size")})))
;; export interface PenpotFrameGuideSquare {
;; type: 'square';
;; display: boolean;
;; params: PenpotFrameGuideSquareParams;
;; }
(defn parse-frame-guide-square
[^js guide]
(when guide
(d/without-nils
{:type (-> (obj/get guide "type") parse-keyword)
:display (obj/get guide "display")
:params (-> (obj/get guide "params") parse-frame-guide-column-params)})))
(defn parse-frame-guide
[^js guide]
(when (some? guide)
(case (obj/get guide "type")
"column"
parse-frame-guide-column
"row"
parse-frame-guide-row
"square"
(parse-frame-guide-square guide))))
(defn parse-frame-guides
[^js guides]
(when (some? guides)
(into [] (map parse-frame-guide) guides)))
;;interface PenpotPathCommand {
;; command:
;; | 'M' | 'move-to'
;; | 'Z' | 'close-path'
;; | 'L' | 'line-to'
;; | 'H' | 'line-to-horizontal'
;; | 'V' | 'line-to-vertical'
;; | 'C' | 'curve-to'
;; | 'S' | 'smooth-curve-to'
;; | 'Q' | 'quadratic-bezier-curve-to'
;; | 'T' | 'smooth-quadratic-bezier-curve-to'
;; | 'A' | 'elliptical-arc';
;;
;; params?: {
;; x?: number;
;; y?: number;
;; c1x: number;
;; c1y: number;
;; c2x: number;
;; c2y: number;
;; rx?: number;
;; ry?: number;
;; xAxisRotation?: number;
;; largeArcFlag?: boolean;
;; sweepFlag?: boolean;
;; };
;;}
(defn parse-command-type
[^string command-type]
(case command-type
"M" :move-to
"Z" :close-path
"L" :line-to
"H" :line-to-horizontal
"V" :line-to-vertical
"C" :curve-to
"S" :smooth-curve-to
"Q" :quadratic-bezier-curve-to
"T" :smooth-quadratic-bezier-curve-to
"A" :elliptical-arc
(parse-keyword command-type)))
(defn parse-command-params
[^js params]
(when (some? params)
(d/without-nils
{:x (obj/get params "x")
:y (obj/get params "y")
:c1x (obj/get params "c1x")
:c1y (obj/get params "c1y")
:c2x (obj/get params "c2x")
:c2y (obj/get params "c2y")
:rx (obj/get params "rx")
:ry (obj/get params "ry")
:x-axis-rotation (obj/get params "xAxisRotation")
:large-arc-flag (obj/get params "largeArcFlag")
:sweep-flag (obj/get params "sweepFlag")})))
(defn parse-command
[^js command]
(when (some? command)
(d/without-nils
{:command (-> (obj/get command "command") parse-command-type)
:params (-> (obj/get command "paras") parse-command-params)})))
(defn parse-path-content
[^js content]
(when (some? content)
(into [] (map parse-command) content)))

View file

@ -9,6 +9,7 @@
(:require
[app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh]
[app.plugins.format :as format]
[app.plugins.shape :as shape]
[app.plugins.utils :as u]))
@ -22,4 +23,4 @@
(let [shapes (->> shapes (map u/proxy->shape))]
(-> (gsh/shapes->rect shapes)
(grc/rect->center)
(u/to-js)))))
(format/format-point)))))

View file

@ -38,243 +38,18 @@
[app.main.data.workspace.texts :as dwt]
[app.main.store :as st]
[app.plugins.flex :as flex]
[app.plugins.format :as format]
[app.plugins.grid :as grid]
[app.plugins.parser :as parser]
[app.plugins.text :as text]
[app.plugins.utils :as u]
[app.util.object :as obj]
[app.util.path.format :as upf]
[app.util.text-editor :as ted]
[cuerdas.core :as str]))
(def lib-typography-proxy? nil)
(def lib-component-proxy nil)
(deftype TextRange [$plugin $file $page $id start end]
Object
(applyTypography [_ typography]
(let [typography (u/proxy->library-typography typography)
attrs (-> typography
(assoc :typography-ref-file $file)
(assoc :typography-ref-id (:id typography))
(dissoc :id :name))]
(st/emit! (dwt/update-text-range $id start end attrs)))))
(defn mixed-value
[values]
(let [s (set values)]
(if (= (count s) 1) (first s) "mixed")))
(defn text-range?
[range]
(instance? TextRange range))
(defn text-range
[plugin-id file-id page-id id start end]
(-> (TextRange. plugin-id file-id page-id id start end)
(crc/add-properties!
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
{:name "shape"
:get #(-> % u/proxy->shape)}
{:name "characters"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text) (str/join "")))}
{:name "fontId"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-id) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontId value)
:else
(st/emit! (dwt/update-text-range id start end {:font-id value}))))}
{:name "fontFamily"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-family) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontFamily value)
:else
(st/emit! (dwt/update-text-range id start end {:font-family value}))))}
{:name "fontVariantId"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-variant-id) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontVariantId value)
:else
(st/emit! (dwt/update-text-range id start end {:font-variant-id value}))))}
{:name "fontSize"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-size) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontSize value)
:else
(st/emit! (dwt/update-text-range id start end {:font-size value}))))}
{:name "fontWeight"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-weight) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontWeight value)
:else
(st/emit! (dwt/update-text-range id start end {:font-weight value}))))}
{:name "fontStyle"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-style) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontStyle value)
:else
(st/emit! (dwt/update-text-range id start end {:font-style value}))))}
{:name "lineHeight"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :line-height) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :lineHeight value)
:else
(st/emit! (dwt/update-text-range id start end {:line-height value}))))}
{:name "letterSpacing"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :letter-spacing) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :letterSpacing value)
:else
(st/emit! (dwt/update-text-range id start end {:letter-spacing value}))))}
{:name "textTransform"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-transform) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :textTransform value)
:else
(st/emit! (dwt/update-text-range id start end {:text-transform value}))))}
{:name "textDecoration"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-decoration) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :textDecoration value)
:else
(st/emit! (dwt/update-text-range id start end {:text-decoration value}))))}
{:name "direction"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :direction) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :direction value)
:else
(st/emit! (dwt/update-text-range id start end {:direction value}))))}
{:name "align"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-align) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :text-align value)
:else
(st/emit! (dwt/update-text-range id start end {:text-align value}))))}
{:name "fills"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :fills) mixed-value u/array-to-js))
:set
(fn [_ value]
(let [value (mapv #(u/from-js %) value)]
(cond
(not (sm/validate [:vector ::cts/fill] value))
(u/display-not-valid :fills value)
:else
(st/emit! (dwt/update-text-range id start end {:fills value})))))})))
(declare shape-proxy)
(defn parse-command
[entry]
(update entry
:command
#(case %
"M" :move-to
"Z" :close-path
"L" :line-to
"H" :line-to-horizontal
"V" :line-to-vertical
"C" :curve-to
"S" :smooth-curve-to
"Q" :quadratic-bezier-curve-to
"T" :smooth-quadratic-bezier-curve-to
"A" :elliptical-arc
(keyword %))))
(defn text-props
[shape]
(d/merge
@ -282,6 +57,48 @@
(dwt/current-paragraph-values {:shape shape :attrs txt/paragraph-attrs})
(dwt/current-text-values {:shape shape :attrs txt/text-node-attrs})))
(declare shape-proxy)
(declare shape-proxy?)
#_(defn parse-command
[entry]
(update entry
:command
#(case %
"M" :move-to
"Z" :close-path
"L" :line-to
"H" :line-to-horizontal
"V" :line-to-vertical
"C" :curve-to
"S" :smooth-curve-to
"Q" :quadratic-bezier-curve-to
"T" :smooth-quadratic-bezier-curve-to
"A" :elliptical-arc
(keyword %))))
(defn- shadow-defaults
[shadow]
(d/patch-object
{:id (uuid/next)
:style :drop-shadow
:color {:color clr/black :opacity 0.2}
:offset-x 4
:offset-y 4
:blur 4
:spread 0
:hidden false}
shadow))
(defn- blur-defaults
[blur]
(d/patch-object
{:id (uuid/next)
:type :layer-blur
:value 4
:hidden false}
blur))
(deftype ShapeProxy [$plugin $file $page $id]
Object
(resize
@ -392,67 +209,107 @@
(getChildren
[_]
(let [shape (u/locate-shape $file $page $id)]
(if (or (cfh/frame-shape? shape) (cfh/group-shape? shape) (cfh/svg-raw-shape? shape) (cfh/bool-shape? shape))
(apply array (->> (u/locate-shape $file $page $id)
:shapes
(map #(shape-proxy $plugin $file $page %))))
(u/display-not-valid :getChildren (:type shape)))))
(cond
(and (not (cfh/frame-shape? shape))
(not (cfh/group-shape? shape))
(not (cfh/svg-raw-shape? shape))
(not (cfh/bool-shape? shape)))
(u/display-not-valid :getChildren (:type shape))
:else
(->> (u/locate-shape $file $page $id)
(:shapes)
(format/format-array #(shape-proxy $plugin $file $page %))))))
(appendChild
[_ child]
(let [shape (u/locate-shape $file $page $id)]
(if (or (cfh/frame-shape? shape) (cfh/group-shape? shape) (cfh/svg-raw-shape? shape) (cfh/bool-shape? shape))
(cond
(and (not (cfh/frame-shape? shape))
(not (cfh/group-shape? shape))
(not (cfh/svg-raw-shape? shape))
(not (cfh/bool-shape? shape)))
(u/display-not-valid :appendChild (:type shape))
(not (shape-proxy? child))
(u/display-not-valid :appendChild-child child)
:else
(let [child-id (obj/get child "$id")]
(st/emit! (dw/relocate-shapes #{child-id} $id 0)))
(u/display-not-valid :appendChild (:type shape)))))
(st/emit! (dw/relocate-shapes #{child-id} $id 0))))))
(insertChild
[_ index child]
(let [shape (u/locate-shape $file $page $id)]
(if (or (cfh/frame-shape? shape) (cfh/group-shape? shape) (cfh/svg-raw-shape? shape) (cfh/bool-shape? shape))
(cond
(and (not (cfh/frame-shape? shape))
(not (cfh/group-shape? shape))
(not (cfh/svg-raw-shape? shape))
(not (cfh/bool-shape? shape)))
(u/display-not-valid :insertChild (:type shape))
(not (shape-proxy? child))
(u/display-not-valid :insertChild-child child)
:else
(let [child-id (obj/get child "$id")]
(st/emit! (dw/relocate-shapes #{child-id} $id index)))
(u/display-not-valid :insertChild (:type shape)))))
(st/emit! (dw/relocate-shapes #{child-id} $id index))))))
;; Only for frames
(addFlexLayout
[_]
(let [shape (u/locate-shape $file $page $id)]
(if (cfh/frame-shape? shape)
(cond
(not (cfh/frame-shape? shape))
(u/display-not-valid :addFlexLayout (:type shape))
:else
(do (st/emit! (dwsl/create-layout-from-id $id :flex :from-frame? true :calculate-params? false))
(grid/grid-layout-proxy $plugin $file $page $id))
(u/display-not-valid :addFlexLayout (:type shape)))))
(grid/grid-layout-proxy $plugin $file $page $id)))))
(addGridLayout
[_]
(let [shape (u/locate-shape $file $page $id)]
(if (cfh/frame-shape? shape)
(cond
(not (cfh/frame-shape? shape))
(u/display-not-valid :addGridLayout (:type shape))
:else
(do (st/emit! (dwsl/create-layout-from-id $id :grid :from-frame? true :calculate-params? false))
(grid/grid-layout-proxy $plugin $file $page $id))
(u/display-not-valid :addGridLayout (:type shape)))))
(grid/grid-layout-proxy $plugin $file $page $id)))))
;; Make masks for groups
(makeMask
[_]
(let [shape (u/locate-shape $file $page $id)]
(if (cfh/group-shape? shape)
(st/emit! (dwg/mask-group #{$id}))
(u/display-not-valid :makeMask (:type shape)))))
(cond
(not (cfh/group-shape? shape))
(u/display-not-valid :makeMask (:type shape))
:else
(st/emit! (dwg/mask-group #{$id})))))
(removeMask
[_]
(let [shape (u/locate-shape $file $page $id)]
(if (cfh/mask-shape? shape)
(st/emit! (dwg/unmask-group #{$id}))
(u/display-not-valid :removeMask (:type shape)))))
(cond
(not (cfh/mask-shape? shape))
(u/display-not-valid :removeMask (:type shape))
:else
(st/emit! (dwg/unmask-group #{$id})))))
;; Only for path and bool shapes
(toD
[_]
(let [shape (u/locate-shape $file $page $id)]
(if (cfh/path-shape? shape)
(upf/format-path (:content shape))
(u/display-not-valid :makeMask (:type shape)))))
(cond
(not (cfh/path-shape? shape))
(u/display-not-valid :makeMask (:type shape))
:else
(upf/format-path (:content shape)))))
;; Text shapes
(getRange
@ -469,7 +326,7 @@
(u/display-not-valid :getRange-end end)
:else
(text-range $plugin $file $page $id start end))))
(text/text-range $plugin $file $page $id start end))))
(applyTypography
[_ typography]
@ -554,11 +411,6 @@
(let [[root component] (u/locate-component objects shape)]
(lib-component-proxy $plugin (:component-file root) (:id component)))))))
(crc/define-properties!
ShapeProxy
{:name js/Symbol.toStringTag
:get (fn [] (str "ShapeProxy"))})
(defn shape-proxy? [p]
(instance? ShapeProxy p))
@ -566,6 +418,11 @@
(do (set! flex/shape-proxy? shape-proxy?)
(set! grid/shape-proxy? shape-proxy?))
(crc/define-properties!
ShapeProxy
{:name js/Symbol.toStringTag
:get (fn [] (str "ShapeProxy"))})
(defn shape-proxy
([plugin-id id]
(shape-proxy plugin-id (:current-file-id @st/state) (:current-page-id @st/state) id))
@ -782,23 +639,11 @@
(st/emit! (dwsh/update-shapes [id] #(assoc % :blend-mode value))))))}
{:name "shadows"
:get #(-> % u/proxy->shape :shadow u/array-to-js)
:get #(-> % u/proxy->shape :shadow format/format-shadows)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (mapv (fn [val]
;; Merge default shadow properties
(d/patch-object
{:id (uuid/next)
:style :drop-shadow
:color {:color clr/black :opacity 0.2}
:offset-x 4
:offset-y 4
:blur 4
:spread 0
:hidden false}
(u/from-js val #{:style :type})))
value)]
value (mapv #(shadow-defaults (parser/parse-shadow %)) value)]
(cond
(not (sm/validate [:vector ::ctss/shadow] value))
(u/display-not-valid :shadows value)
@ -807,19 +652,13 @@
(st/emit! (dwsh/update-shapes [id] #(assoc % :shadow value))))))}
{:name "blur"
:get #(-> % u/proxy->shape :blur u/to-js)
:get #(-> % u/proxy->shape :blur format/format-blur)
:set
(fn [self value]
(if (nil? value)
(st/emit! (dwsh/update-shapes [id] #(dissoc % :blur)))
(let [id (obj/get self "$id")
value
(d/patch-object
{:id (uuid/next)
:type :layer-blur
:value 4
:hidden false}
(u/from-js value))]
value (blur-defaults (parser/parse-blur value))]
(cond
(not (sm/validate ::ctsb/blur value))
(u/display-not-valid :blur value)
@ -828,11 +667,11 @@
(st/emit! (dwsh/update-shapes [id] #(assoc % :blur value)))))))}
{:name "exports"
:get #(-> % u/proxy->shape :exports u/array-to-js)
:get #(-> % u/proxy->shape :exports format/format-exports)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (mapv #(u/from-js %) value)]
value (parser/parse-exports value)]
(cond
(not (sm/validate [:vector ::ctse/export] value))
(u/display-not-valid :exports value)
@ -989,13 +828,13 @@
;; Strokes and fills
{:name "fills"
:get #(if (cfh/text-shape? data)
(-> % u/proxy->shape text-props :fills u/array-to-js)
(-> % u/proxy->shape :fills u/array-to-js))
(-> % u/proxy->shape text-props :fills format/format-fills)
(-> % u/proxy->shape :fills format/format-fills))
:set
(fn [self value]
(let [shape (u/proxy->shape self)
id (:id shape)
value (mapv #(u/from-js %) value)]
value (parser/parse-fills value)]
(cond
(not (sm/validate [:vector ::cts/fill] value))
(u/display-not-valid :fills value)
@ -1007,11 +846,11 @@
(st/emit! (dwsh/update-shapes [id] #(assoc % :fills value))))))}
{:name "strokes"
:get #(-> % u/proxy->shape :strokes u/array-to-js)
:get #(-> % u/proxy->shape :strokes format/format-strokes)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (mapv #(u/from-js % #{:stroke-style :stroke-alignment}) value)]
value (parser/parse-strokes value)]
(cond
(not (sm/validate [:vector ::cts/stroke] value))
(u/display-not-valid :strokes value)
@ -1068,10 +907,10 @@
(flex/flex-layout-proxy plugin-id file-id page-id id))))}
{:name "guides"
:get #(-> % u/proxy->shape :grids u/array-to-js)
:get #(-> % u/proxy->shape :grids format/format-frame-guides)
:set (fn [self value]
(let [id (obj/get self "$id")
value (mapv #(u/from-js %) value)]
value (parser/parse-frame-guides value)]
(cond
(not (sm/validate [:vector ::ctg/grid] value))
(u/display-not-valid :guides value)
@ -1105,213 +944,21 @@
:else
(st/emit! (dwsl/update-layout #{id} {:layout-item-v-sizing value})))))})))
(cond-> (cfh/text-shape? data)
(crc/add-properties!
{:name "characters"
:get #(-> % u/proxy->shape :content txt/content->text)
:set
(fn [self value]
(let [id (obj/get self "$id")]
;; The user is currently editing the text. We need to update the
;; editor as well
(cond
(or (not (string? value)) (empty? value))
(u/display-not-valid :characters value)
(contains? (:workspace-editor-state @st/state) id)
(let [shape (u/proxy->shape self)
editor
(-> shape
(txt/change-text value)
:content
ted/import-content
ted/create-editor-state)]
(st/emit! (dwt/update-editor-state shape editor)))
:else
(st/emit! (dwsh/update-shapes [id] #(txt/change-text % value))))))}
{:name "growType"
:get #(-> % u/proxy->shape :grow-type d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(cond
(not (contains? #{:auto-width :auto-height :fixed} value))
(u/display-not-valid :growType value)
:else
(st/emit! (dwsh/update-shapes [id] #(assoc % :grow-type value))))))}
{:name "fontId"
:get #(-> % u/proxy->shape text-props :font-id)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontId value)
:else
(st/emit! (dwt/update-attrs id {:font-id value})))))}
{:name "fontFamily"
:get #(-> % u/proxy->shape text-props :font-family)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontFamily value)
:else
(st/emit! (dwt/update-attrs id {:font-family value})))))}
{:name "fontVariantId"
:get #(-> % u/proxy->shape text-props :font-variant-id)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontVariantId value)
:else
(st/emit! (dwt/update-attrs id {:font-variant-id value})))))}
{:name "fontSize"
:get #(-> % u/proxy->shape text-props :font-size)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontSize value)
:else
(st/emit! (dwt/update-attrs id {:font-size value})))))}
{:name "fontWeight"
:get #(-> % u/proxy->shape text-props :font-weight)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontWeight value)
:else
(st/emit! (dwt/update-attrs id {:font-weight value})))))}
{:name "fontStyle"
:get #(-> % u/proxy->shape text-props :font-style)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontStyle value)
:else
(st/emit! (dwt/update-attrs id {:font-style value})))))}
{:name "lineHeight"
:get #(-> % u/proxy->shape text-props :line-height)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :lineHeight value)
:else
(st/emit! (dwt/update-attrs id {:line-height value})))))}
{:name "letterSpacing"
:get #(-> % u/proxy->shape text-props :letter-spacing)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :letterSpacing value)
:else
(st/emit! (dwt/update-attrs id {:letter-spacing value})))))}
{:name "textTransform"
:get #(-> % u/proxy->shape text-props :text-transform)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :textTransform value)
:else
(st/emit! (dwt/update-attrs id {:text-transform value})))))}
{:name "textDecoration"
:get #(-> % u/proxy->shape text-props :text-decoration)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :textDecoration value)
:else
(st/emit! (dwt/update-attrs id {:text-decoration value})))))}
{:name "direction"
:get #(-> % u/proxy->shape text-props :text-direction)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :textDecoration value)
:else
(st/emit! (dwt/update-attrs id {:text-decoration value})))))}
{:name "align"
:get #(-> % u/proxy->shape text-props :text-align)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :align value)
:else
(st/emit! (dwt/update-attrs id {:text-align value})))))}
{:name "verticalAlign"
:get #(-> % u/proxy->shape text-props :vertical-align)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :verticalAlign value)
:else
(st/emit! (dwt/update-attrs id {:vertical-align value})))))}))
(cond-> (cfh/text-shape? data) (text/add-text-props))
(cond-> (or (cfh/path-shape? data) (cfh/bool-shape? data))
(crc/add-properties!
{:name "content"
:get #(-> % u/proxy->shape :content u/array-to-js)
:get #(-> % u/proxy->shape :content format/format-path-content)
:set
(fn [_ value]
(let [content
(->> value
(map u/from-js)
(mapv parse-command)
(->> (parser/parse-path-content value)
(spp/simplify-commands))]
(cond
(not (cfh/path-shape? data))
(u/display-not-valid :content-type type)
(not (sm/validate ::ctsp/content content))
(u/display-not-valid :content value)

View file

@ -0,0 +1,434 @@
;; 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) KALEIDOS INC
(ns app.plugins.text
(:require
[app.common.data :as d]
[app.common.record :as crc]
[app.common.schema :as sm]
[app.common.text :as txt]
[app.common.types.shape :as cts]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.texts :as dwt]
[app.main.store :as st]
[app.plugins.format :as format]
[app.plugins.parser :as parser]
[app.plugins.utils :as u]
[app.util.object :as obj]
[app.util.text-editor :as ted]
[cuerdas.core :as str]))
(defn mixed-value
[values]
(let [s (set values)]
(if (= (count s) 1) (first s) "mixed")))
(deftype TextRange [$plugin $file $page $id start end]
Object
(applyTypography [_ typography]
(let [typography (u/proxy->library-typography typography)
attrs (-> typography
(assoc :typography-ref-file $file)
(assoc :typography-ref-id (:id typography))
(dissoc :id :name))]
(st/emit! (dwt/update-text-range $id start end attrs)))))
(defn text-range?
[range]
(instance? TextRange range))
(defn text-props
[shape]
(d/merge
(dwt/current-root-values {:shape shape :attrs txt/root-attrs})
(dwt/current-paragraph-values {:shape shape :attrs txt/paragraph-attrs})
(dwt/current-text-values {:shape shape :attrs txt/text-node-attrs})))
(defn text-range
[plugin-id file-id page-id id start end]
(-> (TextRange. plugin-id file-id page-id id start end)
(crc/add-properties!
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
{:name "shape"
:get #(-> % u/proxy->shape)}
{:name "characters"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text) (str/join "")))}
{:name "fontId"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-id) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontId value)
:else
(st/emit! (dwt/update-text-range id start end {:font-id value}))))}
{:name "fontFamily"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-family) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontFamily value)
:else
(st/emit! (dwt/update-text-range id start end {:font-family value}))))}
{:name "fontVariantId"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-variant-id) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontVariantId value)
:else
(st/emit! (dwt/update-text-range id start end {:font-variant-id value}))))}
{:name "fontSize"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-size) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontSize value)
:else
(st/emit! (dwt/update-text-range id start end {:font-size value}))))}
{:name "fontWeight"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-weight) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontWeight value)
:else
(st/emit! (dwt/update-text-range id start end {:font-weight value}))))}
{:name "fontStyle"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-style) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :fontStyle value)
:else
(st/emit! (dwt/update-text-range id start end {:font-style value}))))}
{:name "lineHeight"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :line-height) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :lineHeight value)
:else
(st/emit! (dwt/update-text-range id start end {:line-height value}))))}
{:name "letterSpacing"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :letter-spacing) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :letterSpacing value)
:else
(st/emit! (dwt/update-text-range id start end {:letter-spacing value}))))}
{:name "textTransform"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-transform) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :textTransform value)
:else
(st/emit! (dwt/update-text-range id start end {:text-transform value}))))}
{:name "textDecoration"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-decoration) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :textDecoration value)
:else
(st/emit! (dwt/update-text-range id start end {:text-decoration value}))))}
{:name "direction"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :direction) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :direction value)
:else
(st/emit! (dwt/update-text-range id start end {:direction value}))))}
{:name "align"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-align) mixed-value))
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :text-align value)
:else
(st/emit! (dwt/update-text-range id start end {:text-align value}))))}
{:name "fills"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :fills) mixed-value format/format-fills))
:set
(fn [_ value]
(let [value (parser/parse-fills value)]
(cond
(not (sm/validate [:vector ::cts/fill] value))
(u/display-not-valid :fills value)
:else
(st/emit! (dwt/update-text-range id start end {:fills value})))))})))
(defn add-text-props
[shape-proxy]
(crc/add-properties!
shape-proxy
{:name "characters"
:get #(-> % u/proxy->shape :content txt/content->text)
:set
(fn [self value]
(let [id (obj/get self "$id")]
;; The user is currently editing the text. We need to update the
;; editor as well
(cond
(or (not (string? value)) (empty? value))
(u/display-not-valid :characters value)
(contains? (:workspace-editor-state @st/state) id)
(let [shape (u/proxy->shape self)
editor
(-> shape
(txt/change-text value)
:content
ted/import-content
ted/create-editor-state)]
(st/emit! (dwt/update-editor-state shape editor)))
:else
(st/emit! (dwsh/update-shapes [id] #(txt/change-text % value))))))}
{:name "growType"
:get #(-> % u/proxy->shape :grow-type d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(cond
(not (contains? #{:auto-width :auto-height :fixed} value))
(u/display-not-valid :growType value)
:else
(st/emit! (dwsh/update-shapes [id] #(assoc % :grow-type value))))))}
{:name "fontId"
:get #(-> % u/proxy->shape text-props :font-id)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontId value)
:else
(st/emit! (dwt/update-attrs id {:font-id value})))))}
{:name "fontFamily"
:get #(-> % u/proxy->shape text-props :font-family)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontFamily value)
:else
(st/emit! (dwt/update-attrs id {:font-family value})))))}
{:name "fontVariantId"
:get #(-> % u/proxy->shape text-props :font-variant-id)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontVariantId value)
:else
(st/emit! (dwt/update-attrs id {:font-variant-id value})))))}
{:name "fontSize"
:get #(-> % u/proxy->shape text-props :font-size)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontSize value)
:else
(st/emit! (dwt/update-attrs id {:font-size value})))))}
{:name "fontWeight"
:get #(-> % u/proxy->shape text-props :font-weight)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontWeight value)
:else
(st/emit! (dwt/update-attrs id {:font-weight value})))))}
{:name "fontStyle"
:get #(-> % u/proxy->shape text-props :font-style)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :fontStyle value)
:else
(st/emit! (dwt/update-attrs id {:font-style value})))))}
{:name "lineHeight"
:get #(-> % u/proxy->shape text-props :line-height)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :lineHeight value)
:else
(st/emit! (dwt/update-attrs id {:line-height value})))))}
{:name "letterSpacing"
:get #(-> % u/proxy->shape text-props :letter-spacing)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :letterSpacing value)
:else
(st/emit! (dwt/update-attrs id {:letter-spacing value})))))}
{:name "textTransform"
:get #(-> % u/proxy->shape text-props :text-transform)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :textTransform value)
:else
(st/emit! (dwt/update-attrs id {:text-transform value})))))}
{:name "textDecoration"
:get #(-> % u/proxy->shape text-props :text-decoration)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :textDecoration value)
:else
(st/emit! (dwt/update-attrs id {:text-decoration value})))))}
{:name "direction"
:get #(-> % u/proxy->shape text-props :text-direction)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :textDecoration value)
:else
(st/emit! (dwt/update-attrs id {:text-decoration value})))))}
{:name "align"
:get #(-> % u/proxy->shape text-props :text-align)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :align value)
:else
(st/emit! (dwt/update-attrs id {:text-align value})))))}
{:name "verticalAlign"
:get #(-> % u/proxy->shape text-props :vertical-align)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (string? value))
(u/display-not-valid :verticalAlign value)
:else
(st/emit! (dwt/update-attrs id {:vertical-align value})))))}))

View file

@ -8,6 +8,7 @@
(:require
[app.common.record :as crc]
[app.config :as cfg]
[app.plugins.format :as format]
[app.plugins.utils :as u]
[app.util.object :as obj]))
@ -54,5 +55,5 @@
(-> (ActiveUserProxy. plugin-id session-id)
(add-user-properties)
(crc/add-properties!
{:name "position" :get (fn [_] (-> (u/locate-presence session-id) :point u/to-js))}
{:name "position" :get (fn [_] (-> (u/locate-presence session-id) :point format/format-point))}
{:name "zoom" :get (fn [_] (-> (u/locate-presence session-id) :zoom))})))

View file

@ -9,13 +9,10 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.spec :as us]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.main.store :as st]
[app.util.object :as obj]
[cuerdas.core :as str]
[promesa.core :as p]))
(defn locate-file
@ -143,58 +140,6 @@
(-> (get-state self attr)
(mapfn))))
(defn from-js
"Converts the object back to js"
([obj]
(from-js obj #{:type}))
([obj keyword-keys]
(when (some? obj)
(let [process-node
(fn process-node [node]
(reduce-kv
(fn [m k v]
(let [k (keyword (str/kebab k))
v (cond (map? v)
(process-node v)
(vector? v)
(mapv process-node v)
(and (string? v) (re-matches us/uuid-rx v))
(uuid/uuid v)
(contains? keyword-keys k)
(keyword v)
:else v)]
(assoc m k v)))
{}
node))]
(process-node (js->clj obj))))))
(defn to-js
"Converts to javascript an camelize the keys"
[obj]
(when (some? obj)
(let [result
(reduce-kv
(fn [m k v]
(let [v (cond (object? v) (to-js v)
(uuid? v) (dm/str v)
:else v)]
(assoc m (str/camel (name k)) v)))
{}
obj)]
(clj->js result))))
(defn array-to-js
[value]
(if (coll? value)
(.freeze
js/Object
(apply array (->> value (map to-js))))
value))
(defn result-p
"Creates a pair of atom+promise. The promise will be resolved when the atom gets a value.
We use this to return the promise to the library clients and resolve its value when a value is passed

View file

@ -14,6 +14,7 @@
[app.main.data.workspace.viewport :as dwv]
[app.main.data.workspace.zoom :as dwz]
[app.main.store :as st]
[app.plugins.format :as format]
[app.plugins.utils :as u]
[app.util.object :as obj]))
@ -88,6 +89,6 @@
:get
(fn [_]
(let [vport (dm/get-in @st/state [:workspace-local :vport])]
(.freeze js/Object (clj->js vport))))}))
(.freeze js/Object (format/format-bounds vport))))}))

View file

@ -156,3 +156,8 @@
x)
:else x)))
(defn clear-empty
[^js obj]
(when (some? obj)
(js* "Object.entries(~{}).reduce((a, [k,v]) => (v == null ? a : (a[k]=v, a)), {}) " obj)))

View file

@ -162,9 +162,8 @@
:offset-y 4
:blur 4
:spread 0
:color {:color "#FABADA" :opacity 1}
:color {:color "#fabada" :opacity 1}
:hidden false}]))))
(let [shadow #js {:style "fail"}]
(set! (.-shadows shape) #js [shadow])
(t/is (= (-> (. shape -shadows) (aget 0) (aget "style")) "drop-shadow"))))
@ -208,20 +207,20 @@
(t/testing " - fills"
(set! (.-fills shape) #js [#js {:fillColor 100}])
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#B1B2B5"))
(t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#B1B2B5" :fill-opacity 1}]))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#B1B2B5"))
(set! (.-fills shape) #js [#js {:fillColor "#FABADA" :fillOpacity 1}])
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#FABADA"))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillOpacity")) 1))
(t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#FABADA" :fill-opacity 1}])))
(set! (.-fills shape) #js [#js {:fillColor "#fabada" :fillOpacity 1}])
(t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#fabada" :fill-opacity 1}]))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#fabada"))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillOpacity")) 1)))
(t/testing " - strokes"
(set! (.-fills shape) #js [#js {:strokeColor "#FABADA" :strokeOpacity 1 :stroke-width 5}])
(t/is (= (-> (. shape -fills) (aget 0) (aget "strokeColor")) "#FABADA"))
(t/is (= (-> (. shape -fills) (aget 0) (aget "strokeOpacity")) 1))
(t/is (= (-> (. shape -fills) (aget 0) (aget "strokeWidth")) 5))
(t/is (= (get-in @store (get-shape-path :fills)) [{:stroke-color "#FABADA" :stroke-opacity 1 :stroke-width 5}]))))
(set! (.-strokes shape) #js [#js {:strokeColor "#fabada" :strokeOpacity 1 :strokeWidth 5}])
(t/is (= (get-in @store (get-shape-path :strokes)) [{:stroke-color "#fabada" :stroke-opacity 1 :stroke-width 5}]))
(t/is (= (-> (. shape -strokes) (aget 0) (aget "strokeColor")) "#fabada"))
(t/is (= (-> (. shape -strokes) (aget 0) (aget "strokeOpacity")) 1))
(t/is (= (-> (. shape -strokes) (aget 0) (aget "strokeWidth")) 5))))
(t/testing "Relative properties"
(let [frame (.createFrame context)]