mirror of
https://github.com/penpot/penpot.git
synced 2025-12-12 06:24:17 +01:00
573 lines
16 KiB
Clojure
573 lines
16 KiB
Clojure
;; 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.parser
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.uuid :as uuid]
|
|
[app.util.object :as obj]
|
|
[cuerdas.core :as str]))
|
|
|
|
(defn parse-id
|
|
[id]
|
|
(when id (uuid/uuid id)))
|
|
|
|
(defn parse-keyword
|
|
[kw]
|
|
(when kw (keyword kw)))
|
|
|
|
(defn parse-hex
|
|
[color]
|
|
(if (string? color) (-> color str/lower) color))
|
|
|
|
(defn parse-point
|
|
[^js point]
|
|
(when point
|
|
{:x (obj/get point "x")
|
|
:y (obj/get point "y")}))
|
|
|
|
(defn parse-shape-type
|
|
[type]
|
|
(case type
|
|
"board" :frame
|
|
"boolean" :bool
|
|
"rectangle" :rect
|
|
"ellipse" :circle
|
|
(parse-keyword type)))
|
|
|
|
;; {
|
|
;; name?: string;
|
|
;; nameLike?: string;
|
|
;; type?:
|
|
;; | 'board'
|
|
;; | 'group'
|
|
;; | 'boolean'
|
|
;; | 'rectangle'
|
|
;; | 'path'
|
|
;; | 'text'
|
|
;; | 'ellipse'
|
|
;; | '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-shape-type)})))
|
|
|
|
;;export type ImageData = {
|
|
;; name?: string;
|
|
;; width: number;
|
|
;; height: number;
|
|
;; mtype?: string;
|
|
;; id: string;
|
|
;; keepApectRatio?: boolean;
|
|
;;}
|
|
(defn parse-image-data
|
|
[^js image-data]
|
|
(when (some? image-data)
|
|
(d/without-nils
|
|
{:id (-> (obj/get image-data "id") parse-id)
|
|
:name (obj/get image-data "name")
|
|
:width (obj/get image-data "width")
|
|
:height (obj/get image-data "height")
|
|
:mtype (obj/get image-data "mtype")
|
|
:keep-aspect-ratio (obj/get image-data "keepApectRatio")})))
|
|
|
|
;; export type Gradient = {
|
|
;; type: 'linear' | 'radial';
|
|
;; startX: number;
|
|
;; startY: number;
|
|
;; endX: number;
|
|
;; endY: number;
|
|
;; width: number;
|
|
;; stops: Array<{ color: string; opacity?: number; offset: number }>;
|
|
;; }
|
|
(defn parse-gradient-stop
|
|
[^js stop]
|
|
(when (some? stop)
|
|
(d/without-nils
|
|
{:color (-> (obj/get stop "color") parse-hex)
|
|
:opacity (obj/get stop "opacity")
|
|
:offset (obj/get stop "offset")})))
|
|
|
|
(defn parse-gradient
|
|
[^js gradient]
|
|
(when (some? gradient)
|
|
(d/without-nils
|
|
{:type (-> (obj/get gradient "type") parse-keyword)
|
|
:start-x (obj/get gradient "startX")
|
|
:start-y (obj/get gradient "startY")
|
|
:end-x (obj/get gradient "endX")
|
|
:end-y (obj/get gradient "endY")
|
|
:width (obj/get gradient "width")
|
|
:stops (->> (obj/get gradient "stops")
|
|
(mapv parse-gradient-stop))})))
|
|
|
|
;; export interface Color {
|
|
;; id?: string;
|
|
;; name?: string;
|
|
;; path?: string;
|
|
;; color?: string;
|
|
;; opacity?: number;
|
|
;; refId?: string;
|
|
;; refFile?: string;
|
|
;; gradient?: Gradient;
|
|
;; image?: ImageData;
|
|
;; }
|
|
(defn parse-color
|
|
[^js color]
|
|
(when (some? color)
|
|
(d/without-nils
|
|
{:id (-> (obj/get color "id") parse-id)
|
|
:name (obj/get color "name")
|
|
:path (obj/get color "path")
|
|
:color (-> (obj/get color "color") parse-hex)
|
|
:opacity (obj/get color "opacity")
|
|
:ref-id (-> (obj/get color "refId") parse-id)
|
|
: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 Shadow {
|
|
;; id?: string;
|
|
;; style?: 'drop-shadow' | 'inner-shadow';
|
|
;; offsetX?: number;
|
|
;; offsetY?: number;
|
|
;; blur?: number;
|
|
;; spread?: number;
|
|
;; hidden?: boolean;
|
|
;; color?: Color;
|
|
;; }
|
|
(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 Fill {
|
|
;; fillColor?: string;
|
|
;; fillOpacity?: number;
|
|
;; fillColorGradient?: Gradient;
|
|
;; fillColorRefFile?: string;
|
|
;; fillColorRefId?: string;
|
|
;; fillImage?: ImageData;
|
|
;;}
|
|
(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 Stroke {
|
|
;; strokeColor?: string;
|
|
;; strokeColorRefFile?: string;
|
|
;; strokeColorRefId?: string;
|
|
;; strokeOpacity?: number;
|
|
;; strokeStyle?: 'solid' | 'dotted' | 'dashed' | 'mixed' | 'none' | 'svg';
|
|
;; strokeWidth?: number;
|
|
;; strokeAlignment?: 'center' | 'inner' | 'outer';
|
|
;; strokeCapStart?: StrokeCap;
|
|
;; strokeCapEnd?: StrokeCap;
|
|
;; strokeColorGradient?: Gradient;
|
|
;; }
|
|
(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 Blur {
|
|
;; 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 Export {
|
|
;; 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" 1)
|
|
:suffix (obj/get export "suffix" "")})))
|
|
|
|
(defn parse-exports
|
|
[^js exports]
|
|
(when (some? exports)
|
|
(into [] (map parse-export) exports)))
|
|
|
|
;; export interface GuideColumnParams {
|
|
;; 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 GuideColumn {
|
|
;; type: 'column';
|
|
;; display: boolean;
|
|
;; params: GuideColumnParams;
|
|
;; }
|
|
(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 GuideRow {
|
|
;; type: 'row';
|
|
;; display: boolean;
|
|
;; params: GuideColumnParams;
|
|
;; }
|
|
|
|
(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 GuideSquareParams {
|
|
;; 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 GuideSquare {
|
|
;; type: 'square';
|
|
;; display: boolean;
|
|
;; params: GuideSquareParams;
|
|
;; }
|
|
(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 PathCommand {
|
|
;; 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 "params") parse-command-params)})))
|
|
|
|
(defn parse-path-content
|
|
[^js content]
|
|
(when (some? content)
|
|
(into [] (map parse-command) content)))
|
|
|
|
;; export interface Dissolve {
|
|
;; type: 'dissolve';
|
|
;; duration: number;
|
|
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
|
;; }
|
|
;;
|
|
;; export interface Slide {
|
|
;; type: 'slide';
|
|
;; way: 'in' | 'out';
|
|
;; direction?:
|
|
;; | 'right'
|
|
;; | 'left'
|
|
;; | 'up'
|
|
;; | 'down';
|
|
;; duration: number;
|
|
;; offsetEffect?: boolean;
|
|
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
|
;; }
|
|
;;
|
|
;; export interface Push {
|
|
;; type: 'push';
|
|
;; direction?:
|
|
;; | 'right'
|
|
;; | 'left'
|
|
;; | 'up'
|
|
;; | 'down';
|
|
;;
|
|
;; duration: number;
|
|
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
|
;; }
|
|
;;
|
|
;; export type Animation = Dissolve | Slide | Push;
|
|
|
|
(defn parse-animation
|
|
[^js animation]
|
|
(when animation
|
|
(let [animation-type (-> (obj/get animation "type") parse-keyword)]
|
|
(d/without-nils
|
|
(case animation-type
|
|
:dissolve
|
|
{:type animation-type
|
|
:duration (obj/get animation "duration")
|
|
:easing (-> (obj/get animation "easing") parse-keyword)}
|
|
|
|
:slide
|
|
{:type animation-type
|
|
:way (-> (obj/get animation "way") parse-keyword)
|
|
:direction (-> (obj/get animation "direction") parse-keyword)
|
|
:duration (obj/get animation "duration")
|
|
:easing (-> (obj/get animation "easing") parse-keyword)
|
|
:offset-effect (obj/get animation "offsetEffect")}
|
|
|
|
:push
|
|
{:type animation-type
|
|
:direction (-> (obj/get animation "direction") parse-keyword)
|
|
:duration (obj/get animation "duration")
|
|
:easing (-> (obj/get animation "easing") parse-keyword)}
|
|
|
|
nil)))))
|
|
|
|
;;export type Action =
|
|
;; | NavigateTo
|
|
;; | OpenOverlay
|
|
;; | ToggleOverlay
|
|
;; | CloseOverlay
|
|
;; | PreviousScreen
|
|
;; | OpenUrl;
|
|
;;
|
|
;;export interface NavigateTo {
|
|
;; type: 'navigate-to';
|
|
;; destination: Board;
|
|
;; preserveScrollPosition?: boolean;
|
|
;; animation: Animation;
|
|
;;}
|
|
;;
|
|
;;export interface OverlayAction {
|
|
;; destination: Board;
|
|
;; relativeTo?: Shape;
|
|
;; position?:
|
|
;; | 'manual'
|
|
;; | 'center'
|
|
;; | 'top-left'
|
|
;; | 'top-right'
|
|
;; | 'top-center'
|
|
;; | 'bottom-left'
|
|
;; | 'bottom-right'
|
|
;; | 'bottom-center';
|
|
;; manualPositionLocation?: Point;
|
|
;; closeWhenClickOutside?: boolean;
|
|
;; addBackgroundOverlay?: boolean;
|
|
;; animation: Animation;
|
|
;;}
|
|
;;
|
|
;;export interface OpenOverlay extends OverlayAction {
|
|
;; type: 'open-overlay';
|
|
;;}
|
|
;;
|
|
;;export interface ToggleOverlay extends OverlayAction {
|
|
;; type: 'toggle-overlay';
|
|
;;}
|
|
;;
|
|
;;export interface CloseOverlay {
|
|
;; type: 'close-overlay';
|
|
;; destination?: Board;
|
|
;; animation: Animation;
|
|
;;}
|
|
;;
|
|
;;export interface PreviousScreen {
|
|
;; type: 'previous-screen';
|
|
;;}
|
|
;;
|
|
;;export interface OpenUrl {
|
|
;; type: 'open-url';
|
|
;; url: string;
|
|
;;}
|
|
(defn parse-action
|
|
[action]
|
|
(when action
|
|
(let [action-type (-> (obj/get action "type") parse-keyword)]
|
|
(d/without-nils
|
|
(case action-type
|
|
:navigate-to
|
|
{:action-type :navigate
|
|
:destination (-> (obj/get action "destination") (obj/get "$id"))
|
|
:preserve-scroll (obj/get action "preserveScrollPosition")
|
|
:animation (-> (obj/get action "animation") parse-animation)}
|
|
|
|
(:open-overlay
|
|
:toggle-overlay)
|
|
{:action-type action-type
|
|
:destination (-> (obj/get action "destination") (obj/get "$id"))
|
|
:relative-to (-> (obj/get action "relativeTo") (obj/get "$id"))
|
|
:overlay-pos-type (-> (obj/get action "position") parse-keyword)
|
|
:overlay-position (-> (obj/get action "manualPositionLocation") parse-point)
|
|
:close-click-outside (obj/get action "closeWhenClickOutside")
|
|
:background-overlay (obj/get action "addBackgroundOverlay")
|
|
:animation (-> (obj/get action "animation") parse-animation)}
|
|
|
|
:close-overlay
|
|
{:action-type action-type
|
|
:destination (-> (obj/get action "destination") (obj/get "$id"))
|
|
:animation (-> (obj/get action "animation") parse-animation)}
|
|
|
|
:previous-screen
|
|
{:action-type :prev-screen}
|
|
|
|
:open-url
|
|
{:action-type action-type
|
|
:url (obj/get action "url")}
|
|
|
|
nil)))))
|
|
|
|
(defn parse-interaction
|
|
[^js interaction]
|
|
(when interaction
|
|
(let [trigger (-> (obj/get interaction "trigger") parse-keyword)
|
|
delay (obj/get interaction "trigger")
|
|
action (-> (obj/get interaction "action") parse-action)]
|
|
(d/without-nils
|
|
(d/patch-object {:event-type trigger :delay delay} action)))))
|