mirror of
https://github.com/penpot/penpot.git
synced 2026-09-09 12:19:58 -04:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6586631293 | ||
|
|
66fb4a69ba | ||
|
|
3d4a5ca2aa |
No files matched your search
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.arrow
|
||||
"Bulk Ladybug ingest through in-memory Arrow.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.debug
|
||||
"In-memory Ladybug sessions for the debug graph console."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.ingest
|
||||
"Penpot file -> Ladybug graph projection."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.ladybug
|
||||
"Ladybug access layer for graph-backed Penpot.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.meta
|
||||
"`GraphMeta`: the graph's own account of who built it and from what.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.projection.document
|
||||
"Project a Penpot file-data map into Ladybug nodes and structural edges.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.projection.transforms
|
||||
"Derived graph links: edges a reader could compute from the projected
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.report
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.schema
|
||||
"Ladybug DDL facade for the graph-backed Penpot vertical slice.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.schema.contract
|
||||
"Deliberate choices in Penpot's graph schema, recorded as data.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.schema.nodes
|
||||
"Single source of truth for graph node tables.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.schema.projection
|
||||
"Derive Ladybug node column schemas from Penpot Malli sources.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.schema.types
|
||||
"Map Malli schemas to Ladybug column types.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.schema.values
|
||||
"Shape a Penpot value into the plain data its Ladybug column type wants.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.stats
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.graph.sync
|
||||
"Incremental Ladybug graph updates from Penpot file-change events."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.rpc.commands.plugins
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.storage.pending-gc
|
||||
"A maintenance task that reclaims storage objects created in 'pending'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.tasks.demo-purge
|
||||
"Task handler for delayed demo profile deletion. Submitted at demo
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns backend-tests.demo-test
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns backend-tests.graph-binder-gate-test
|
||||
"Binder gate for the incremental-sync statement templates.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns backend-tests.graph-sync-parity-test
|
||||
"Cold projection and incremental sync are two implementations of one mapping,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns backend-tests.passwords-test
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns backend-tests.rpc-demo-test
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns backend-tests.rpc-plugins-test
|
||||
(:require
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
[app.common.types.shape.text :as ctst]
|
||||
[app.common.types.text :as types.text]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]))
|
||||
@@ -1999,108 +1998,6 @@
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "0027-normalize-constrained-values"
|
||||
;; Existing files can contain values outside the limits now shared by the UI
|
||||
;; and file schemas. Normalize them before checking the migrated file.
|
||||
[data _]
|
||||
(letfn [(clamp-minimum [value minimum]
|
||||
(if (number? value)
|
||||
(max value minimum)
|
||||
value))
|
||||
|
||||
(positive-or-default [value default]
|
||||
(if (and (number? value) (not (pos? value)))
|
||||
default
|
||||
value))
|
||||
|
||||
(clamp-attrs [value attrs]
|
||||
(reduce #(d/update-when %1 %2 clamp-minimum 0) value attrs))
|
||||
|
||||
(repair-vector [value repair-item]
|
||||
(if (vector? value)
|
||||
(mapv repair-item value)
|
||||
value))
|
||||
|
||||
(repair-grid-params [params type]
|
||||
(cond
|
||||
(= type :square)
|
||||
(d/update-when params :size clamp-minimum 0.01)
|
||||
|
||||
(#{:row :column} type)
|
||||
(d/update-when params :size clamp-minimum 1)
|
||||
|
||||
:else
|
||||
params))
|
||||
|
||||
(repair-grid [grid]
|
||||
(d/update-when grid :params repair-grid-params (:type grid)))
|
||||
|
||||
(repair-default-grids [grids]
|
||||
(-> grids
|
||||
(d/update-when :square repair-grid-params :square)
|
||||
(d/update-when :row repair-grid-params :row)
|
||||
(d/update-when :column repair-grid-params :column)))
|
||||
|
||||
(repair-grid-track [track]
|
||||
(d/update-when track :value clamp-minimum 0))
|
||||
|
||||
(repair-export [export]
|
||||
(d/update-when export :scale positive-or-default 1))
|
||||
|
||||
(repair-stroke [stroke]
|
||||
(clamp-attrs stroke [:stroke-width
|
||||
:stroke-width-top
|
||||
:stroke-width-right
|
||||
:stroke-width-bottom
|
||||
:stroke-width-left]))
|
||||
|
||||
(repair-shadow [shadow]
|
||||
(d/update-when shadow :blur clamp-minimum 0))
|
||||
|
||||
(repair-blur [blur]
|
||||
(d/update-when blur :value clamp-minimum 0))
|
||||
|
||||
(repair-shape [shape]
|
||||
(-> shape
|
||||
(clamp-attrs [:r1 :r2 :r3 :r4
|
||||
:layout-item-min-w :layout-item-max-w
|
||||
:layout-item-min-h :layout-item-max-h])
|
||||
(d/update-when :layout-gap clamp-attrs [:row-gap :column-gap])
|
||||
(d/update-when :layout-padding clamp-attrs [:p1 :p2 :p3 :p4])
|
||||
(d/update-when :layout-grid-rows repair-vector repair-grid-track)
|
||||
(d/update-when :layout-grid-columns repair-vector repair-grid-track)
|
||||
(d/update-when :strokes repair-vector repair-stroke)
|
||||
(d/update-when :shadow repair-vector repair-shadow)
|
||||
(d/update-when :blur repair-blur)
|
||||
(d/update-when :background-blur repair-blur)
|
||||
(d/update-when :exports repair-vector repair-export)
|
||||
(d/update-when :grids repair-vector repair-grid)))
|
||||
|
||||
(truncate-property-text [value]
|
||||
(if (and (string? value)
|
||||
(> (count value) ctv/property-max-length))
|
||||
(subs value 0 ctv/property-max-length)
|
||||
value))
|
||||
|
||||
(repair-variant-property [property]
|
||||
(-> property
|
||||
(d/update-when :name truncate-property-text)
|
||||
(d/update-when :value truncate-property-text)))
|
||||
|
||||
(repair-container [container]
|
||||
(-> container
|
||||
(d/update-when :objects d/update-vals repair-shape)
|
||||
(d/update-when :variant-properties repair-vector repair-variant-property)))
|
||||
|
||||
(repair-page [page]
|
||||
(-> page
|
||||
(repair-container)
|
||||
(d/update-when :default-grids repair-default-grids)))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index d/update-vals repair-page)
|
||||
(d/update-when :components d/update-vals repair-container))))
|
||||
|
||||
(def available-migrations
|
||||
(into (d/ordered-set)
|
||||
["legacy-2"
|
||||
@@ -2184,5 +2081,4 @@
|
||||
"0023-repair-token-themes-with-inexistent-sets"
|
||||
"0024b-fix-stroke-cap-placement"
|
||||
"0025-repair-empty-text-content"
|
||||
"0026-fix-svg-raw-shapes-uuids"
|
||||
"0027-normalize-constrained-values"]))
|
||||
"0026-fix-svg-raw-shapes-uuids"]))
|
||||
@@ -76,20 +76,15 @@
|
||||
::sm/text]) ;; Leave references or formulas to be checked by the resolver
|
||||
|
||||
(def schema:token-value-typography-map
|
||||
[:and
|
||||
[:map
|
||||
[:font-family {:optional true} schema:token-value-font-family]
|
||||
[:font-size {:optional true} schema:token-value-numeric]
|
||||
[:font-weight {:optional true} schema:token-value-font-weight]
|
||||
[:line-height {:optional true} schema:token-value-percent]
|
||||
[:letter-spacing {:optional true} schema:token-value-generic]
|
||||
[:paragraph-spacing {:optional true} schema:token-value-generic]
|
||||
[:text-decoration {:optional true} schema:token-value-generic]
|
||||
[:text-case {:optional true} schema:token-value-generic]]
|
||||
[:fn (fn [value]
|
||||
(and (seq value)
|
||||
(or (not (contains? value :line-height))
|
||||
(contains? value :font-size))))]])
|
||||
[:map
|
||||
[:font-family {:optional true} schema:token-value-font-family]
|
||||
[:font-size {:optional true} schema:token-value-numeric]
|
||||
[:font-weight {:optional true} schema:token-value-font-weight]
|
||||
[:line-height {:optional true} schema:token-value-percent]
|
||||
[:letter-spacing {:optional true} schema:token-value-generic]
|
||||
[:paragraph-spacing {:optional true} schema:token-value-generic]
|
||||
[:text-decoration {:optional true} schema:token-value-generic]
|
||||
[:text-case {:optional true} schema:token-value-generic]])
|
||||
|
||||
(def schema:token-value-typography
|
||||
[:or
|
||||
@@ -97,7 +92,7 @@
|
||||
schema:token-value-composite-ref])
|
||||
|
||||
(def schema:token-value-shadow-vector
|
||||
[:vector {:min 1}
|
||||
[:vector
|
||||
[:map
|
||||
[:offset-x :string]
|
||||
[:offset-y :string]
|
||||
@@ -281,7 +276,6 @@
|
||||
[tokens-lib set-id]
|
||||
[:and
|
||||
[:string {:min 1 :max 255 :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
|
||||
[:fn #(not (str/blank? (ctob/normalize-set-name %)))]
|
||||
[:fn {:error/fn #(tr "errors.token-set-already-exists")}
|
||||
(fn [name]
|
||||
(or (nil? tokens-lib)
|
||||
@@ -322,7 +316,6 @@
|
||||
[tokens-lib group theme-id]
|
||||
[:and
|
||||
[:string {:min 1 :max 255 :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
|
||||
[:fn #(not (str/blank? %))]
|
||||
[:fn {:error/fn #(tr "errors.token-theme-already-exists" (str group "/" (:value %)))}
|
||||
(fn [name]
|
||||
(or (nil? tokens-lib)
|
||||
|
||||
@@ -868,14 +868,6 @@
|
||||
(register! ::safe-number [::number {:gen/gen (sg/small-double)
|
||||
:max max-safe-int
|
||||
:min min-safe-int}])
|
||||
(register! ::non-negative-safe-number
|
||||
[:and {:gen/gen (sg/small-double :min 0)}
|
||||
::safe-number
|
||||
[:fn #(not (neg? %))]])
|
||||
(register! ::positive-safe-number
|
||||
[:and {:gen/gen (sg/small-double :min 0.01)}
|
||||
::safe-number
|
||||
[:fn pos?]])
|
||||
|
||||
(defn parse-boolean
|
||||
[v]
|
||||
@@ -1101,12 +1093,6 @@
|
||||
(def valid-safe-number?
|
||||
(lazy-validator ::safe-number))
|
||||
|
||||
(def valid-non-negative-safe-number?
|
||||
(lazy-validator ::non-negative-safe-number))
|
||||
|
||||
(def valid-positive-safe-number?
|
||||
(lazy-validator ::positive-safe-number))
|
||||
|
||||
(def valid-safe-int?
|
||||
(lazy-validator ::safe-int))
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
(ns app.common.types.grid
|
||||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.generators :as sg]
|
||||
[app.common.types.color :as clr]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -19,28 +18,18 @@
|
||||
[:color clr/schema:hex-color]
|
||||
[:opacity ::sm/safe-number]])
|
||||
|
||||
(def schema:grid-count
|
||||
[:and {:gen/gen (sg/small-double :min 1)}
|
||||
::sm/safe-number
|
||||
[:fn #(<= 1 %)]])
|
||||
|
||||
(def schema:square-size
|
||||
[:and {:gen/gen (sg/small-double :min 0.01)}
|
||||
::sm/safe-number
|
||||
[:fn #(<= 0.01 %)]])
|
||||
|
||||
(def schema:column-params
|
||||
[:map {:title "ColumnGridParams"}
|
||||
[:color schema:grid-color]
|
||||
[:type {:optional true} [::sm/one-of #{:stretch :left :center :right}]]
|
||||
[:size {:optional true} [:maybe schema:grid-count]]
|
||||
[:size {:optional true} [:maybe ::sm/safe-number]]
|
||||
[:margin {:optional true} [:maybe ::sm/safe-number]]
|
||||
[:item-length {:optional true} [:maybe ::sm/safe-number]]
|
||||
[:gutter {:optional true} [:maybe ::sm/safe-number]]])
|
||||
|
||||
(def schema:square-params
|
||||
[:map {:title "SquareGridParams"}
|
||||
[:size {:optional true} [:maybe schema:square-size]]
|
||||
[:size {:optional true} [:maybe ::sm/safe-number]]
|
||||
[:color schema:grid-color]])
|
||||
|
||||
(def schema:grid
|
||||
@@ -89,3 +78,4 @@
|
||||
{:square default-square-params
|
||||
:column default-layout-params
|
||||
:row default-layout-params})
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
[app.common.types.grid :as ctg]
|
||||
[app.common.types.plugins :as ctpg]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SCHEMAS
|
||||
@@ -74,24 +73,6 @@
|
||||
(def check-page
|
||||
(sm/check-fn schema:page))
|
||||
|
||||
(defn normalize-page-name
|
||||
[name]
|
||||
(some-> name str/trim))
|
||||
|
||||
(defn valid-page-name?
|
||||
[name]
|
||||
(let [name (normalize-page-name name)]
|
||||
(and (string? name) (not (str/blank? name)))))
|
||||
|
||||
(defn valid-flow-starting-frame?
|
||||
[page frame-id flow-id]
|
||||
(let [frame (get-in page [:objects frame-id])]
|
||||
(and (= :frame (:type frame))
|
||||
(not-any? (fn [[id flow]]
|
||||
(and (not= id flow-id)
|
||||
(= frame-id (:starting-frame flow))))
|
||||
(:flows page)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; INIT & HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.common.types.path.fit
|
||||
"Curve fitting helpers."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.common.types.path.selection
|
||||
"Transforms selected path nodes and handlers."
|
||||
|
||||
@@ -138,13 +138,13 @@
|
||||
[:stroke-opacity {:optional true} ::sm/safe-number]
|
||||
[:stroke-style {:optional true}
|
||||
[::sm/one-of #{:solid :dotted :dashed :mixed}]]
|
||||
[:stroke-width {:optional true} ::sm/non-negative-safe-number]
|
||||
[:stroke-width {:optional true} ::sm/safe-number]
|
||||
;; wasm-render only, backwards compatible
|
||||
[:stroke-per-side {:optional true} :boolean]
|
||||
[:stroke-width-top {:optional true} ::sm/non-negative-safe-number]
|
||||
[:stroke-width-right {:optional true} ::sm/non-negative-safe-number]
|
||||
[:stroke-width-bottom {:optional true} ::sm/non-negative-safe-number]
|
||||
[:stroke-width-left {:optional true} ::sm/non-negative-safe-number]
|
||||
[:stroke-width-top {:optional true} ::sm/safe-number]
|
||||
[:stroke-width-right {:optional true} ::sm/safe-number]
|
||||
[:stroke-width-bottom {:optional true} ::sm/safe-number]
|
||||
[:stroke-width-left {:optional true} ::sm/safe-number]
|
||||
[:stroke-dash {:optional true} ::sm/safe-number]
|
||||
[:stroke-gap {:optional true} ::sm/safe-number]
|
||||
[:stroke-alignment {:optional true}
|
||||
@@ -211,10 +211,10 @@
|
||||
[:constraints-v {:optional true}
|
||||
[::sm/one-of vertical-constraint-types]]
|
||||
[:fixed-scroll {:optional true} :boolean]
|
||||
[:r1 {:optional true} ::sm/non-negative-safe-number]
|
||||
[:r2 {:optional true} ::sm/non-negative-safe-number]
|
||||
[:r3 {:optional true} ::sm/non-negative-safe-number]
|
||||
[:r4 {:optional true} ::sm/non-negative-safe-number]
|
||||
[:r1 {:optional true} ::sm/safe-number]
|
||||
[:r2 {:optional true} ::sm/safe-number]
|
||||
[:r3 {:optional true} ::sm/safe-number]
|
||||
[:r4 {:optional true} ::sm/safe-number]
|
||||
[:opacity {:optional true} ::sm/safe-number]
|
||||
[:grids {:optional true}
|
||||
[:vector {:gen/max 2} ctg/schema:grid]]
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
[:map {:title "BackgroundBlur"}
|
||||
[:id ::sm/uuid]
|
||||
[:type [:enum :background-blur]]
|
||||
[:value ::sm/non-negative-safe-number]
|
||||
[:hidden :boolean]])
|
||||
[:value ::sm/safe-number]
|
||||
[:hidden :boolean]])
|
||||
@@ -12,5 +12,5 @@
|
||||
[:map {:title "Blur"}
|
||||
[:id ::sm/uuid]
|
||||
[:type [:enum :layer-blur]]
|
||||
[:value ::sm/non-negative-safe-number]
|
||||
[:value ::sm/safe-number]
|
||||
[:hidden :boolean]])
|
||||
@@ -13,5 +13,5 @@
|
||||
(def schema:export
|
||||
[:map {:title "ShapeExport"}
|
||||
[:type [::sm/one-of types]]
|
||||
[:scale ::sm/positive-safe-number]
|
||||
[:scale ::sm/safe-number]
|
||||
[:suffix :string]])
|
||||
@@ -10,9 +10,7 @@
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.generators :as sg]
|
||||
[app.common.uri :as uri]
|
||||
[cuerdas.core :as str]))
|
||||
[app.common.schema.generators :as sg]))
|
||||
|
||||
;; WARNING: options are not deleted when changing event or action
|
||||
;; type, so it can be restored if the user changes it back later.
|
||||
@@ -218,17 +216,15 @@
|
||||
(declare calc-overlay-pos-initial)
|
||||
(declare allowed-animation?)
|
||||
|
||||
(defn valid-event-type-for-shape?
|
||||
[shape event-type]
|
||||
(and (contains? event-types event-type)
|
||||
(or (not= event-type :after-delay)
|
||||
(cfh/frame-shape? shape))))
|
||||
|
||||
(defn set-event-type
|
||||
[interaction event-type shape]
|
||||
(assert (check-interaction interaction))
|
||||
(assert (valid-event-type-for-shape? shape event-type)
|
||||
"event type incompatible with shape")
|
||||
(assert (contains? event-types event-type)
|
||||
"should be a valid event type")
|
||||
|
||||
(assert (or (not= event-type :after-delay)
|
||||
(cfh/frame-shape? shape))
|
||||
"the `:after-delay` event type incompatible with not frame shapes")
|
||||
|
||||
(if (= (:event-type interaction) event-type)
|
||||
interaction
|
||||
@@ -294,19 +290,12 @@
|
||||
(defn set-delay
|
||||
[interaction delay]
|
||||
(assert (check-interaction interaction))
|
||||
(assert (and (sm/check-safe-int delay) (not (neg? delay))))
|
||||
(assert (sm/check-safe-int delay))
|
||||
(assert (has-delay interaction)
|
||||
"expected compatible interaction event type")
|
||||
|
||||
(assoc interaction :delay delay))
|
||||
|
||||
(defn valid-delay?
|
||||
[interaction]
|
||||
(or (not (has-delay interaction))
|
||||
(let [delay (:delay interaction)]
|
||||
(and (sm/valid-safe-int? delay)
|
||||
(not (neg? delay))))))
|
||||
|
||||
;; FIXME: rename to proper name, very confusing one because it does
|
||||
;; not checks if interaction has distination, it checks if it can have
|
||||
;; one.
|
||||
@@ -336,31 +325,6 @@
|
||||
(assoc :overlay-pos-type :center
|
||||
:overlay-position (gpt/point 0 0))))
|
||||
|
||||
(defn valid-destination?
|
||||
[objects shape destination]
|
||||
(or (nil? destination)
|
||||
(let [target (get objects destination)]
|
||||
(and (cfh/frame-shape? target)
|
||||
(not= destination (:id shape))
|
||||
(not= destination (:frame-id shape))))))
|
||||
|
||||
(defn normalize-url
|
||||
[value]
|
||||
(when (string? value)
|
||||
(let [value (str/trim value)
|
||||
explicit-scheme? (re-find #"(?i)^[a-z][a-z0-9+.-]*:" value)]
|
||||
(when (or (not explicit-scheme?)
|
||||
(re-find #"(?i)^https?://" value))
|
||||
(let [value (if explicit-scheme? value (str "http://" value))]
|
||||
(try
|
||||
(let [parsed (uri/uri value)]
|
||||
(when (and (not (re-find #"\s" value))
|
||||
(contains? #{"http" "https"} (:scheme parsed))
|
||||
(seq (:host parsed)))
|
||||
value))
|
||||
(catch #?(:clj Exception :cljs :default) _
|
||||
nil)))))))
|
||||
|
||||
(defn has-preserve-scroll
|
||||
[interaction]
|
||||
(= (:action-type interaction) :navigate))
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
;; :layout-justify-content ;; :start :center :end :space-between :space-around :space-evenly
|
||||
;; :layout-wrap-type ;; :wrap, :nowrap
|
||||
;; :layout-padding-type ;; :simple, :multiple
|
||||
;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num}
|
||||
;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
|
||||
|
||||
;; layout-grid-rows ;; vector of grid-track
|
||||
;; layout-grid-columns ;; vector of grid-track
|
||||
@@ -103,7 +103,7 @@
|
||||
(def ^:private schema:grid-track
|
||||
[:map {:title "GridTrack"}
|
||||
[:type [::sm/one-of grid-track-types]]
|
||||
[:value {:optional true} [:maybe ::sm/non-negative-safe-number]]])
|
||||
[:value {:optional true} [:maybe ::sm/safe-number]]])
|
||||
|
||||
(def schema:layout-attrs
|
||||
[:map {:title "LayoutAttrs"}
|
||||
@@ -111,17 +111,17 @@
|
||||
[:layout-flex-dir {:optional true} [::sm/one-of flex-direction-types]]
|
||||
[:layout-gap {:optional true}
|
||||
[:map
|
||||
[:row-gap {:optional true} ::sm/non-negative-safe-number]
|
||||
[:column-gap {:optional true} ::sm/non-negative-safe-number]]]
|
||||
[:row-gap {:optional true} ::sm/safe-number]
|
||||
[:column-gap {:optional true} ::sm/safe-number]]]
|
||||
[:layout-gap-type {:optional true} [::sm/one-of gap-types]]
|
||||
[:layout-wrap-type {:optional true} [::sm/one-of wrap-types]]
|
||||
[:layout-padding-type {:optional true} [::sm/one-of padding-type]]
|
||||
[:layout-padding {:optional true}
|
||||
[:map
|
||||
[:p1 ::sm/non-negative-safe-number]
|
||||
[:p2 ::sm/non-negative-safe-number]
|
||||
[:p3 ::sm/non-negative-safe-number]
|
||||
[:p4 ::sm/non-negative-safe-number]]]
|
||||
[:p1 ::sm/safe-number]
|
||||
[:p2 ::sm/safe-number]
|
||||
[:p3 ::sm/safe-number]
|
||||
[:p4 ::sm/safe-number]]]
|
||||
[:layout-justify-content {:optional true} [::sm/one-of justify-content-types]]
|
||||
[:layout-justify-items {:optional true} [::sm/one-of justify-items-types]]
|
||||
[:layout-align-content {:optional true} [::sm/one-of align-content-types]]
|
||||
@@ -163,10 +163,10 @@
|
||||
[:m2 {:optional true} ::sm/safe-number]
|
||||
[:m3 {:optional true} ::sm/safe-number]
|
||||
[:m4 {:optional true} ::sm/safe-number]]]
|
||||
[:layout-item-max-h {:optional true} ::sm/non-negative-safe-number]
|
||||
[:layout-item-min-h {:optional true} ::sm/non-negative-safe-number]
|
||||
[:layout-item-max-w {:optional true} ::sm/non-negative-safe-number]
|
||||
[:layout-item-min-w {:optional true} ::sm/non-negative-safe-number]
|
||||
[:layout-item-max-h {:optional true} ::sm/safe-number]
|
||||
[:layout-item-min-h {:optional true} ::sm/safe-number]
|
||||
[:layout-item-max-w {:optional true} ::sm/safe-number]
|
||||
[:layout-item-min-w {:optional true} ::sm/safe-number]
|
||||
[:layout-item-h-sizing {:optional true} [::sm/one-of item-h-sizing-types]]
|
||||
[:layout-item-v-sizing {:optional true} [::sm/one-of item-v-sizing-types]]
|
||||
[:layout-item-align-self {:optional true} [::sm/one-of item-align-self-types]]
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
[:style [::sm/one-of styles]]
|
||||
[:offset-x ::sm/safe-number]
|
||||
[:offset-y ::sm/safe-number]
|
||||
[:blur ::sm/non-negative-safe-number]
|
||||
[:blur ::sm/safe-number]
|
||||
[:spread ::sm/safe-number]
|
||||
[:hidden :boolean]
|
||||
[:color schema:color]])
|
||||
@@ -35,3 +35,4 @@
|
||||
|
||||
(def valid-shadow?
|
||||
(sm/validator schema:shadow))
|
||||
|
||||
@@ -56,40 +56,6 @@
|
||||
(def text-transform-attrs
|
||||
[:text-transform])
|
||||
|
||||
(def font-size-min 3)
|
||||
(def font-size-max 1000)
|
||||
(def spacing-min -200)
|
||||
(def spacing-max 200)
|
||||
(def text-transform-values
|
||||
#{"uppercase" "capitalize" "lowercase" "none" "unset"})
|
||||
|
||||
(def ^:private numeric-text-re
|
||||
#"^-?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)$")
|
||||
|
||||
(defn- valid-numeric-text-in-range?
|
||||
[value min-value max-value]
|
||||
(and (string? value)
|
||||
(re-matches numeric-text-re value)
|
||||
(let [value (d/parse-double value)]
|
||||
(and (some? value)
|
||||
(<= min-value value max-value)))))
|
||||
|
||||
(defn valid-font-size?
|
||||
[value]
|
||||
(valid-numeric-text-in-range? value font-size-min font-size-max))
|
||||
|
||||
(defn valid-line-height?
|
||||
[value]
|
||||
(valid-numeric-text-in-range? value spacing-min spacing-max))
|
||||
|
||||
(defn valid-letter-spacing?
|
||||
[value]
|
||||
(valid-numeric-text-in-range? value spacing-min spacing-max))
|
||||
|
||||
(defn valid-text-transform?
|
||||
[value]
|
||||
(contains? text-transform-values value))
|
||||
|
||||
(def text-fills
|
||||
[:fills])
|
||||
|
||||
|
||||
@@ -122,13 +122,9 @@
|
||||
|
||||
(def composite-dtcg-token-type->token-type
|
||||
"Same as above, in the opposite direction."
|
||||
(let [mapping (assoc dtcg-token-type->token-type
|
||||
"lineHeights" :line-height
|
||||
"lineHeight" :line-height)]
|
||||
(into mapping
|
||||
(map (fn [[key value]]
|
||||
[(keyword (str/kebab key)) value]))
|
||||
mapping)))
|
||||
(assoc dtcg-token-type->token-type
|
||||
"lineHeights" :line-height
|
||||
"lineHeight" :line-height))
|
||||
|
||||
(def token-types
|
||||
(into #{} (keys token-type->dtcg-token-type)))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.common.types.tokens-status
|
||||
(:require
|
||||
|
||||
@@ -16,12 +16,10 @@
|
||||
;; SCHEMA
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def property-max-length 60)
|
||||
|
||||
(def schema:variant-property
|
||||
[:map
|
||||
[:name [:string {:max property-max-length}]]
|
||||
[:value [:string {:max property-max-length}]]])
|
||||
[:name :string]
|
||||
[:value :string]])
|
||||
|
||||
(def schema:variant-component
|
||||
"A component that is part of a variant set"
|
||||
@@ -49,29 +47,9 @@
|
||||
|
||||
(def property-prefix "Property ")
|
||||
(def property-regex (re-pattern (str property-prefix "(\\d+)")))
|
||||
(def property-max-length 60)
|
||||
(def value-prefix "Value ")
|
||||
|
||||
(defn normalize-property-text
|
||||
[value]
|
||||
(some-> value str/trim))
|
||||
|
||||
(defn valid-property-name?
|
||||
[value]
|
||||
(let [value (normalize-property-text value)]
|
||||
(and (string? value)
|
||||
(not (str/blank? value))
|
||||
(<= (count value) property-max-length))))
|
||||
|
||||
(defn valid-property-value?
|
||||
[value]
|
||||
(let [value (normalize-property-text value)]
|
||||
(and (string? value)
|
||||
(<= (count value) property-max-length))))
|
||||
|
||||
(defn can-remove-property?
|
||||
[properties]
|
||||
(> (count properties) 1))
|
||||
|
||||
(defn properties-to-name
|
||||
"Transform the properties into a name, with the values separated by comma"
|
||||
[properties]
|
||||
@@ -146,8 +124,8 @@
|
||||
(mapv #(str/split % "=" 2))
|
||||
(every? #(and (= 2 (count %))
|
||||
(not (str/blank? (first %)))
|
||||
(<= (count (first %)) property-max-length)
|
||||
(<= (count (second %)) property-max-length)))))
|
||||
(< (count (first %)) property-max-length)
|
||||
(< (count (second %)) property-max-length)))))
|
||||
|
||||
(defn find-properties-to-remove
|
||||
"Compares two property maps to find which properties should be removed"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns common-tests.files-migrations-0026-test
|
||||
(:require
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.files.migrations :as cfm]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
@@ -74,135 +73,3 @@
|
||||
(let [shape (get-in data' [:pages-index page-id :objects shape-id])]
|
||||
(t/is (nil? (:stroke-cap-start shape)) "top-level cap removed even with no strokes")
|
||||
(t/is (nil? (:stroke-cap-end shape)) "top-level cap removed even with no strokes"))))
|
||||
|
||||
(t/deftest migration-0027-normalizes-constrained-shape-values
|
||||
(let [file-id (uuid/next)
|
||||
page-id (uuid/next)
|
||||
shape-id (uuid/next)
|
||||
shape (-> (cts/setup-shape {:id shape-id :type :frame})
|
||||
(assoc :r1 -1
|
||||
:r2 -2
|
||||
:r3 -3
|
||||
:r4 -4
|
||||
:layout-gap {:row-gap -5 :column-gap -6}
|
||||
:layout-padding {:p1 -7 :p2 -8 :p3 -9 :p4 -10}
|
||||
:layout-grid-rows [{:type :fixed :value -11}]
|
||||
:layout-grid-columns [{:type :percent :value -12}]
|
||||
:layout-item-min-w -13
|
||||
:layout-item-max-w -14
|
||||
:layout-item-min-h -15
|
||||
:layout-item-max-h -16
|
||||
:strokes [{:stroke-color "#000000"
|
||||
:stroke-width -17
|
||||
:stroke-width-top -18
|
||||
:stroke-width-right -19
|
||||
:stroke-width-bottom -20
|
||||
:stroke-width-left -21}]
|
||||
:shadow [{:id nil
|
||||
:style :drop-shadow
|
||||
:offset-x 0
|
||||
:offset-y 0
|
||||
:blur -22
|
||||
:spread 0
|
||||
:hidden false
|
||||
:color {:color "#000000" :opacity 1}}]
|
||||
:blur {:id (uuid/next)
|
||||
:type :layer-blur
|
||||
:value -23
|
||||
:hidden false}
|
||||
:background-blur {:id (uuid/next)
|
||||
:type :background-blur
|
||||
:value -24
|
||||
:hidden false}
|
||||
:exports [{:type :png :scale 0 :suffix ""}]
|
||||
:grids [{:type :square
|
||||
:display true
|
||||
:params {:size 0
|
||||
:color {:color "#000000" :opacity 1}}}
|
||||
{:type :column
|
||||
:display true
|
||||
:params {:size -25
|
||||
:color {:color "#000000" :opacity 1}}}]))
|
||||
data (-> (ctf/make-file-data file-id page-id)
|
||||
(assoc-in [:pages-index page-id :objects shape-id] shape))]
|
||||
|
||||
(t/is (thrown? #?(:clj Exception :cljs js/Error)
|
||||
(ctf/check-file-data data))
|
||||
"new schemas reject legacy negative values")
|
||||
|
||||
(let [data' (cfm/migrate-data data "0027-normalize-constrained-values")
|
||||
shape' (get-in data' [:pages-index page-id :objects shape-id])]
|
||||
(t/is (= data' (ctf/check-file-data data')) "migrated file data passes the schema")
|
||||
(t/is (every? zero? (map #(get shape' %) [:r1 :r2 :r3 :r4])) "corner radii clamped")
|
||||
(t/is (= {:row-gap 0 :column-gap 0} (:layout-gap shape')) "layout gaps clamped")
|
||||
(t/is (= {:p1 0 :p2 0 :p3 0 :p4 0} (:layout-padding shape')) "layout padding clamped")
|
||||
(t/is (= [0] (mapv :value (:layout-grid-rows shape'))) "row tracks clamped")
|
||||
(t/is (= [0] (mapv :value (:layout-grid-columns shape'))) "column tracks clamped")
|
||||
(t/is (every? zero?
|
||||
(map #(get shape' %)
|
||||
[:layout-item-min-w :layout-item-max-w
|
||||
:layout-item-min-h :layout-item-max-h]))
|
||||
"layout item bounds clamped")
|
||||
(t/is (every? zero?
|
||||
(map (first (:strokes shape'))
|
||||
[:stroke-width :stroke-width-top :stroke-width-right
|
||||
:stroke-width-bottom :stroke-width-left]))
|
||||
"stroke widths clamped")
|
||||
(t/is (zero? (get-in shape' [:shadow 0 :blur])) "shadow blur clamped")
|
||||
(t/is (zero? (get-in shape' [:blur :value])) "layer blur clamped")
|
||||
(t/is (zero? (get-in shape' [:background-blur :value])) "background blur clamped")
|
||||
(t/is (= 1 (get-in shape' [:exports 0 :scale])) "export scale reset to default")
|
||||
(t/is (= 0.01 (get-in shape' [:grids 0 :params :size])) "square grid size clamped")
|
||||
(t/is (= 1 (get-in shape' [:grids 1 :params :size])) "column grid count clamped"))))
|
||||
|
||||
(t/deftest migration-0027-normalizes-page-grids-and-variant-properties
|
||||
(let [file-id (uuid/next)
|
||||
page-id (uuid/next)
|
||||
component-id (uuid/next)
|
||||
long-name (apply str (repeat 61 "n"))
|
||||
long-value (apply str (repeat 61 "v"))
|
||||
data (-> (ctf/make-file-data file-id page-id)
|
||||
(assoc-in [:pages-index page-id :default-grids]
|
||||
{:square {:size -1
|
||||
:color {:color "#000000" :opacity 1}}
|
||||
:row {:size 0.5
|
||||
:color {:color "#000000" :opacity 1}}
|
||||
:column {:size -2
|
||||
:color {:color "#000000" :opacity 1}}})
|
||||
(assoc-in [:components component-id]
|
||||
{:id component-id
|
||||
:name "Variant"
|
||||
:variant-properties [{:name long-name
|
||||
:value long-value}]}))
|
||||
data' (cfm/migrate-data data "0027-normalize-constrained-values")]
|
||||
|
||||
(t/is (= 0.01 (get-in data' [:pages-index page-id :default-grids :square :size]))
|
||||
"default square grid size clamped")
|
||||
(t/is (= 1 (get-in data' [:pages-index page-id :default-grids :row :size]))
|
||||
"default row grid count reset")
|
||||
(t/is (= 1 (get-in data' [:pages-index page-id :default-grids :column :size]))
|
||||
"default column grid count reset")
|
||||
(t/is (= 60 (count (get-in data' [:components component-id :variant-properties 0 :name])))
|
||||
"variant property name truncated")
|
||||
(t/is (= 60 (count (get-in data' [:components component-id :variant-properties 0 :value])))
|
||||
"variant property value truncated")
|
||||
(t/is (= data' (cfm/migrate-data data' "0027-normalize-constrained-values"))
|
||||
"migration is idempotent")))
|
||||
|
||||
(t/deftest migration-0027-runs-through-file-migration
|
||||
(let [migration-id "0027-normalize-constrained-values"
|
||||
shape-id (uuid/next)
|
||||
file (ctf/make-file {:name "Legacy constrained values"})
|
||||
page-id (first (get-in file [:data :pages]))
|
||||
shape (-> (cts/setup-shape {:id shape-id :type :rect})
|
||||
(assoc :r1 -1))
|
||||
file (-> file
|
||||
(assoc :migrations (disj cfm/available-migrations migration-id))
|
||||
(assoc-in [:data :pages-index page-id :objects shape-id] shape))
|
||||
file' (cfm/migrate-file file {})]
|
||||
|
||||
(t/is (cfm/need-migration? file) "new migration detected")
|
||||
(t/is (not (cfm/need-migration? file')) "new migration recorded")
|
||||
(t/is (contains? (:migrations file') migration-id) "migration id persisted")
|
||||
(t/is (zero? (get-in file' [:data :pages-index page-id :objects shape-id :r1]))
|
||||
"migration repaired file data before schema validation")))
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns common-tests.types.tokens-status-test
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.auth
|
||||
"Resolves the caller's session cookie to a real profile id.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.handlers.export
|
||||
"Handle export jobs"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.handlers.jobs
|
||||
"REST surface for export jobs, under `/api/export/jobs`.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.jobs
|
||||
"Export job model and lifecycle.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.jobs.scheduler
|
||||
"Admission control for export jobs.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.jobs.store
|
||||
"Redis persistence for export jobs.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.jobs.utils
|
||||
"Temp file ownership for export jobs.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.router
|
||||
"Method + path dispatch.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.wasm.pool
|
||||
"Pool of headless render workers.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.wasm.render
|
||||
"Headless render pipeline: renders exports with the render-wasm Skia pipeline,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.wasm.worker
|
||||
"Render worker entry point.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns exporter-tests.export-shapes-test
|
||||
"Chunking of the browser backend."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns exporter-tests.jobs-test
|
||||
"Job state machine. Runs without redis: a store write with no connection is
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns exporter-tests.scheduler-test
|
||||
"Admission control. A headless job leases one render worker for its whole run,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns exporter-tests.wasm-pool-test
|
||||
"Worker leasing, against a stub pool: `with-worker` must give the worker back
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
[app.main.repo :as rp]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(def ^:private schema:comment-thread
|
||||
@@ -67,13 +66,6 @@
|
||||
|
||||
(def r-mentions #"@\[([^\]]*)\]\(([^\)]*)\)")
|
||||
|
||||
(defn valid-comment-content?
|
||||
[content]
|
||||
(when (string? content)
|
||||
(let [content (str/trim content)]
|
||||
(and (not (str/blank? content))
|
||||
(not= content "\u200b")))))
|
||||
|
||||
(defn extract-mentions
|
||||
"Retrieves the mentions in the content as an array of uuids"
|
||||
[content]
|
||||
@@ -712,3 +704,4 @@
|
||||
(rx/map (fn [profiles]
|
||||
#(update % :profiles merge (d/index-by :id profiles)))))))))
|
||||
|
||||
|
||||
@@ -1042,17 +1042,6 @@
|
||||
second)
|
||||
0)))))
|
||||
|
||||
(defn component-swap-nesting-loop?
|
||||
[objects shape library-data component-id]
|
||||
(let [component (ctkl/get-component library-data component-id true)
|
||||
page (ctf/get-component-page library-data component)
|
||||
root (ctf/get-component-root library-data component)]
|
||||
(and page
|
||||
root
|
||||
(cfh/components-nesting-loop?
|
||||
(cfh/get-children-with-self (:objects page) (:id root))
|
||||
(cfh/get-parents-with-self objects (:parent-id shape))))))
|
||||
|
||||
(defn component-swap
|
||||
"Swaps a component with another one"
|
||||
[shape file-id id-new-component keep-touched?]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.main.data.workspace.path.clipboard
|
||||
(:require
|
||||
|
||||
@@ -733,45 +733,6 @@
|
||||
(redirect-to-page page-id)
|
||||
(combine current-page))))))
|
||||
|
||||
(defn valid-components-for-variants?
|
||||
[state page-id ids]
|
||||
(let [ids (distinct ids)
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
data (dsh/lookup-file-data state)]
|
||||
(and (= page-id (:current-page-id state))
|
||||
(> (count ids) 1)
|
||||
(every?
|
||||
(fn [id]
|
||||
(let [shape (get objects id)
|
||||
component (ctkl/get-component data (:component-id shape) false)]
|
||||
(and (ctc/main-instance? shape)
|
||||
component
|
||||
(not (ctc/is-variant? component)))))
|
||||
ids))))
|
||||
|
||||
(defn valid-variant-switch?
|
||||
[state shape pos val]
|
||||
(let [libraries (dsh/lookup-libraries state)
|
||||
component (ctf/get-component libraries
|
||||
(:component-file shape)
|
||||
(:component-id shape)
|
||||
:include-deleted? false)
|
||||
component-file-data (dm/get-in libraries [(:component-file shape) :data])
|
||||
component-page (dsh/get-page component-file-data (:main-instance-page component))
|
||||
component-page-objects (:objects component-page)
|
||||
variant-components (when component
|
||||
(cfv/find-variant-components component-file-data
|
||||
component-page-objects
|
||||
(:variant-id component)))]
|
||||
(and (ctc/instance-head? shape)
|
||||
(ctc/in-component-copy? shape)
|
||||
(ctc/is-variant? component)
|
||||
(nat-int? pos)
|
||||
(< pos (count (:variant-properties component)))
|
||||
(string? val)
|
||||
(some #(= val (dm/get-in % [:variant-properties pos :value]))
|
||||
variant-components))))
|
||||
|
||||
(defn combine-selected-as-variants
|
||||
[options]
|
||||
(ptk/reify ::combine-selected-as-variants
|
||||
@@ -838,3 +799,4 @@
|
||||
(with-meta (meta it))))))
|
||||
(rx/of (dwu/commit-undo-transaction undo-id)
|
||||
(dws/select-shapes ids)))))))
|
||||
|
||||
@@ -146,7 +146,11 @@
|
||||
|
||||
(defn- blank-content?
|
||||
[content]
|
||||
(not (dcm/valid-comment-content? content)))
|
||||
(let [content (str/trim content)]
|
||||
(or (str/blank? content)
|
||||
(str/empty? content)
|
||||
(and (= (count content) 1)
|
||||
(= (first content) zero-width-space)))))
|
||||
|
||||
;; Component that renders the component content
|
||||
(mf/defc comment-content*
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.files.variant :as cfv]
|
||||
[app.common.path-names :as cpn]
|
||||
[app.common.types.component :as ctk]
|
||||
@@ -389,7 +390,7 @@
|
||||
(mf/use-fn
|
||||
(mf/deps component-ids)
|
||||
(fn [pos value]
|
||||
(let [value (ctv/normalize-property-text (d/nilv value ""))]
|
||||
(let [value (d/nilv (str/trim value) "")]
|
||||
(doseq [id component-ids]
|
||||
(st/emit!
|
||||
(ev/event {::ev/name "variant-edit-property-value" ::ev/origin "workspace:combo-design-tab"})
|
||||
@@ -400,11 +401,11 @@
|
||||
(mf/use-fn
|
||||
(mf/deps variant-id)
|
||||
(fn [event]
|
||||
(let [value (ctv/normalize-property-text (dom/get-target-val event))
|
||||
(let [value (str/trim (dom/get-target-val event))
|
||||
pos (-> (dom/get-current-target event)
|
||||
(dom/get-data "position")
|
||||
int)]
|
||||
(when (ctv/valid-property-name? value)
|
||||
(when (seq value)
|
||||
(st/emit!
|
||||
(dwv/update-property-name variant-id pos value {:trigger "workspace:design-tab-variant"}))))))
|
||||
|
||||
@@ -742,6 +743,17 @@
|
||||
(->> (concat groups components)
|
||||
(sort-by :name)))
|
||||
|
||||
find-parent-components
|
||||
(mf/use-fn
|
||||
(mf/deps objects)
|
||||
(fn [shape]
|
||||
(->> (cfh/get-parents objects (:id shape))
|
||||
(map :component-id)
|
||||
(remove nil?))))
|
||||
|
||||
;; Get the ids of the components that are parents of the shapes, to avoid loops
|
||||
parent-components (mapcat find-parent-components shapes)
|
||||
|
||||
libraries-options (map (fn [library] {:value (:id library)
|
||||
:label (:name library)})
|
||||
(vals libraries))
|
||||
@@ -831,9 +843,10 @@
|
||||
(let [data (dm/get-in libraries [current-library-id :data])
|
||||
container (ctf/get-component-page data item)
|
||||
root-shape (ctf/get-component-root data item)
|
||||
loop? (some #(dwl/component-swap-nesting-loop?
|
||||
objects % data (:id item))
|
||||
shapes)]
|
||||
components (->> (cfh/get-children-with-self (:objects container) (:id root-shape))
|
||||
(keep :component-id)
|
||||
set)
|
||||
loop? (some #(contains? components %) parent-components)]
|
||||
[:> component-swap-item* {:key (dm/str (:id item))
|
||||
:item item
|
||||
:loop loop?
|
||||
@@ -1230,11 +1243,11 @@
|
||||
(mf/use-fn
|
||||
(mf/deps variant-id)
|
||||
(fn [event]
|
||||
(let [value (ctv/normalize-property-text (dom/get-target-val event))
|
||||
(let [value (dom/get-target-val event)
|
||||
pos (-> (dom/get-current-target event)
|
||||
(dom/get-data "position")
|
||||
int)]
|
||||
(when (ctv/valid-property-name? value)
|
||||
(when (seq value)
|
||||
(st/emit!
|
||||
(dwv/update-property-name variant-id pos value {:trigger "workspace:design-tab-component"}))))))
|
||||
|
||||
@@ -1245,7 +1258,7 @@
|
||||
(let [pos (-> (dom/get-current-target event)
|
||||
(dom/get-data "position")
|
||||
int)]
|
||||
(when (ctv/can-remove-property? properties)
|
||||
(when (> (count properties) 1)
|
||||
(st/emit!
|
||||
(ev/event {::ev/name "variant-remove-property" ::ev/origin "workspace:button-design-tab"})
|
||||
(dwv/remove-property variant-id pos))))))
|
||||
|
||||
@@ -245,13 +245,17 @@
|
||||
(fn [event]
|
||||
(let [target (dom/get-target event)
|
||||
value (dom/get-value target)
|
||||
normalized (ctsi/normalize-url value)]
|
||||
(when (and normalized (not= normalized value))
|
||||
(dom/set-value! target normalized))
|
||||
(if normalized
|
||||
has-prefix? (or (str/starts-with? value "http://")
|
||||
(str/starts-with? value "https://"))
|
||||
value (if has-prefix?
|
||||
value
|
||||
(str "http://" value))]
|
||||
(when-not has-prefix?
|
||||
(dom/set-value! target value))
|
||||
(if (dom/valid? target)
|
||||
(do
|
||||
(dom/remove-class! target "error")
|
||||
(update-interaction index #(ctsi/set-url % normalized)))
|
||||
(update-interaction index #(ctsi/set-url % value)))
|
||||
(dom/add-class! target "error")))))
|
||||
|
||||
change-overlay-pos-type
|
||||
|
||||
@@ -620,8 +620,8 @@
|
||||
:options size-options
|
||||
:type "number"
|
||||
:placeholder (tr "settings.multiple")
|
||||
:min txt/font-size-min
|
||||
:max txt/font-size-max
|
||||
:min 3
|
||||
:max 1000
|
||||
:on-change on-font-size-change
|
||||
:on-blur on-blur}])]
|
||||
|
||||
@@ -669,8 +669,8 @@
|
||||
:alt (tr "workspace.options.text-options.line-height")}
|
||||
deprecated-icon/text-lineheight]
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:min txt/spacing-min
|
||||
:max txt/spacing-max
|
||||
{:min -200
|
||||
:max 200
|
||||
:step 0.1
|
||||
:default-value "1.2"
|
||||
:class (stl/css :line-height-input)
|
||||
@@ -688,8 +688,8 @@
|
||||
:alt (tr "workspace.options.text-options.letter-spacing")}
|
||||
deprecated-icon/text-letterspacing]
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:min txt/spacing-min
|
||||
:max txt/spacing-max
|
||||
{:min -200
|
||||
:max 200
|
||||
:step 0.1
|
||||
:default-value "0"
|
||||
:class (stl/css :letter-spacing-input)
|
||||
|
||||
@@ -138,8 +138,8 @@
|
||||
(mf/use-fn
|
||||
(mf/deps id is-separator?)
|
||||
(fn [event]
|
||||
(let [new-name (ctp/normalize-page-name (dom/get-target-val event))]
|
||||
(if (not (ctp/valid-page-name? new-name))
|
||||
(let [new-name (str/trim (dom/get-target-val event))]
|
||||
(if (str/empty? new-name)
|
||||
(when is-separator?
|
||||
(st/emit! (dw/delete-page id)))
|
||||
(st/emit! (dw/rename-page id new-name))))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.main.ui.workspace.viewport.path-state
|
||||
(:require
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.uuid :as uuid]
|
||||
@@ -704,15 +705,13 @@
|
||||
:createVariantFromComponents
|
||||
(fn [shapes]
|
||||
(cond
|
||||
(or (not (array? shapes))
|
||||
(not (seq shapes))
|
||||
(or (not (seq shapes))
|
||||
(not (every? u/is-main-component-proxy? shapes)))
|
||||
(u/not-valid plugin-id :shapes shapes)
|
||||
|
||||
:else
|
||||
(let [state @st/state
|
||||
file-id (:current-file-id state)
|
||||
page-id (:current-page-id state)
|
||||
(let [file-id (obj/get (first shapes) "$file")
|
||||
page-id (obj/get (first shapes) "$page")
|
||||
;; Keep the input order: it determines the order of the
|
||||
;; resulting variant components (see combine-as-variants)
|
||||
ids (->> shapes
|
||||
@@ -720,17 +719,23 @@
|
||||
(distinct)
|
||||
(vec))
|
||||
|
||||
valid? (and (every? #(and (= file-id (obj/get % "$file"))
|
||||
(= page-id (obj/get % "$page")))
|
||||
shapes)
|
||||
(dwv/valid-components-for-variants? state page-id ids))]
|
||||
;; Check that every component is:
|
||||
;; - in the same page
|
||||
;; - not already a variant
|
||||
valid?
|
||||
(every?
|
||||
(fn [id]
|
||||
(let [shape (u/locate-shape file-id page-id id)
|
||||
component (u/locate-library-component file-id (:component-id shape))]
|
||||
(not (ctk/is-variant? component))))
|
||||
ids)]
|
||||
(if valid?
|
||||
(let [variant-id (uuid/next)]
|
||||
(st/emit! (-> (dwv/combine-as-variants
|
||||
ids
|
||||
{:trigger "plugin:combine-as-variants" :variant-id variant-id})
|
||||
(se/add-event plugin-id)))
|
||||
(shape/shape-proxy plugin-id file-id page-id variant-id))
|
||||
(shape/shape-proxy plugin-id variant-id))
|
||||
|
||||
(u/not-valid plugin-id :shapes "One of the components is not on the same page or is already a variant")))))
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
(fn [content]
|
||||
(let [profile (:profile @st/state)]
|
||||
(cond
|
||||
(not (dc/valid-comment-content? content))
|
||||
(or (not (string? content)) (empty? content))
|
||||
(u/not-valid plugin-id :content "Not valid")
|
||||
|
||||
(not= (:id profile) (:owner-id data))
|
||||
@@ -188,7 +188,7 @@
|
||||
(not (r/check-permission plugin-id "comment:write"))
|
||||
(u/not-valid plugin-id :reply "Plugin doesn't have 'comment:write' permission")
|
||||
|
||||
(not (dc/valid-comment-content? content))
|
||||
(or (not (string? content)) (empty? content))
|
||||
(u/not-valid plugin-id :reply "Not valid")
|
||||
|
||||
:else
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :rowGap value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -183,7 +183,7 @@
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :columnGap value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -201,7 +201,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :verticalPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -219,7 +219,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :horizontalPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -237,7 +237,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :topPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -255,7 +255,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :rightPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -273,7 +273,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :bottomPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -291,7 +291,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :leftPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -641,7 +641,7 @@
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :maxWidth value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -659,7 +659,7 @@
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :minWidth value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -677,7 +677,7 @@
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :maxHeight value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -695,7 +695,7 @@
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :minHeight value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
|
||||
@@ -21,9 +21,8 @@
|
||||
(defn font-variant-proxy? [p]
|
||||
(obj/type-of? p "FontVariantProxy"))
|
||||
|
||||
(defn font-variant-proxy [font-id name id weight style]
|
||||
(defn font-variant-proxy [name id weight style]
|
||||
(obj/reify {:name "FontVariantProxy"}
|
||||
:$font-id {:enumerable false :get (constantly font-id)}
|
||||
:name {:get (fn [] name)}
|
||||
:fontVariantId {:get (fn [] id)}
|
||||
:fontWeight {:get (fn [] weight)}
|
||||
@@ -48,8 +47,8 @@
|
||||
{:get
|
||||
(fn []
|
||||
(format/format-array
|
||||
(fn [{variant-id :id :keys [name style weight]}]
|
||||
(font-variant-proxy id name variant-id weight style))
|
||||
(fn [{:keys [id name style weight]}]
|
||||
(font-variant-proxy name id weight style))
|
||||
variants))}
|
||||
|
||||
:applyToText
|
||||
@@ -64,11 +63,6 @@
|
||||
(not (u/page-active? (obj/get text "$page")))
|
||||
(u/not-valid plugin-id :applyToText "Cannot modify a page that is not currently active")
|
||||
|
||||
(and (some? variant)
|
||||
(or (not (font-variant-proxy? variant))
|
||||
(not= id (obj/get variant "$font-id"))))
|
||||
(u/not-valid plugin-id :applyToText variant)
|
||||
|
||||
:else
|
||||
(let [text-id (obj/get text "$id")
|
||||
values {:font-id id
|
||||
@@ -90,11 +84,6 @@
|
||||
(not (u/page-active? (obj/get range "$page")))
|
||||
(u/not-valid plugin-id :applyToRange "Cannot modify a page that is not currently active")
|
||||
|
||||
(and (some? variant)
|
||||
(or (not (font-variant-proxy? variant))
|
||||
(not= id (obj/get variant "$font-id"))))
|
||||
(u/not-valid plugin-id :applyToRange variant)
|
||||
|
||||
:else
|
||||
(let [range-id (obj/get range "$id")
|
||||
start (obj/get range "$start")
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :rowGap value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -208,7 +208,7 @@
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :columnGap value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -226,7 +226,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :verticalPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -244,7 +244,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :horizontalPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -262,7 +262,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :topPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -280,7 +280,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :rightPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -298,7 +298,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :bottomPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -316,7 +316,7 @@
|
||||
:set
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :leftPadding value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -356,7 +356,7 @@
|
||||
(u/not-valid plugin-id :addRow-type type)
|
||||
|
||||
(and (or (= :percent type) (= :flex type) (= :fixed type))
|
||||
(not (sm/valid-non-negative-safe-number? value)))
|
||||
(not (sm/valid-safe-number? value)))
|
||||
(u/not-valid plugin-id :addRow-value value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -383,7 +383,7 @@
|
||||
(u/not-valid plugin-id :addRowAtIndex-type type)
|
||||
|
||||
(and (or (= :percent type) (= :flex type) (= :fixed type))
|
||||
(not (sm/valid-non-negative-safe-number? value)))
|
||||
(not (sm/valid-safe-number? value)))
|
||||
(u/not-valid plugin-id :addRowAtIndex-value value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -402,8 +402,8 @@
|
||||
(not (contains? ctl/grid-track-types type))
|
||||
(u/not-valid plugin-id :addColumn-type type)
|
||||
|
||||
(and (or (= :percent type) (= :flex type) (= :fixed type))
|
||||
(not (sm/valid-non-negative-safe-number? value)))
|
||||
(and (or (= :percent type) (= :flex type) (= :lex type))
|
||||
(not (sm/valid-safe-number? value)))
|
||||
(u/not-valid plugin-id :addColumn-value value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -430,7 +430,7 @@
|
||||
(u/not-valid plugin-id :addColumnAtIndex-type type)
|
||||
|
||||
(and (or (= :percent type) (= :flex type) (= :fixed type))
|
||||
(not (sm/valid-non-negative-safe-number? value)))
|
||||
(not (sm/valid-safe-number? value)))
|
||||
(u/not-valid plugin-id :addColumnAtIndex-value value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -495,7 +495,7 @@
|
||||
(u/not-valid plugin-id :setColumn-type type)
|
||||
|
||||
(and (or (= :percent type) (= :flex type) (= :fixed type))
|
||||
(not (sm/valid-non-negative-safe-number? value)))
|
||||
(not (sm/valid-safe-number? value)))
|
||||
(u/not-valid plugin-id :setColumn-value value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -522,7 +522,7 @@
|
||||
(u/not-valid plugin-id :setRow-type type)
|
||||
|
||||
(and (or (= :percent type) (= :flex type) (= :fixed type))
|
||||
(not (sm/valid-non-negative-safe-number? value)))
|
||||
(not (sm/valid-safe-number? value)))
|
||||
(u/not-valid plugin-id :setRow-value value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
|
||||
@@ -14,15 +14,12 @@
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.types.typography :as ctt]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.plugins :as dp]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.data.workspace.variants :as dwv]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.plugins.format :as format]
|
||||
@@ -35,7 +32,6 @@
|
||||
[app.plugins.utils :as u]
|
||||
[app.util.object :as obj]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(declare lib-color-proxy)
|
||||
@@ -293,20 +289,6 @@
|
||||
(defn lib-typography-proxy? [p]
|
||||
(obj/type-of? p "LibraryTypographyProxy"))
|
||||
|
||||
(defn- font-data
|
||||
[font variant]
|
||||
{:font-id (:id font)
|
||||
:font-family (:family font)
|
||||
:font-variant-id (:id variant)
|
||||
:font-style (:style variant)
|
||||
:font-weight (:weight variant)})
|
||||
|
||||
(defn- variant-data
|
||||
[variant]
|
||||
{:font-variant-id (:id variant)
|
||||
:font-style (:style variant)
|
||||
:font-weight (:weight variant)})
|
||||
|
||||
(defn lib-typography-proxy
|
||||
[plugin-id file-id id]
|
||||
(assert (uuid? file-id))
|
||||
@@ -359,150 +341,136 @@
|
||||
:get #(-> % u/proxy->library-typography :font-id)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [font (when (string? value) (fonts/get-font-data value))
|
||||
variant (fonts/get-default-variant font)]
|
||||
(cond
|
||||
(nil? font)
|
||||
(u/not-valid plugin-id :fontId value)
|
||||
(cond
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :fontId value)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontId "Plugin doesn't have 'library:write' permission")
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontId "Plugin doesn't have 'library:write' permission")
|
||||
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(merge (font-data font variant)))]
|
||||
(st/emit! (dwl/update-typography typo file-id))))))}
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(assoc :font-id value))]
|
||||
(st/emit! (dwl/update-typography typo file-id)))))}
|
||||
|
||||
:fontFamily
|
||||
{:this true
|
||||
:get #(-> % u/proxy->library-typography :font-family)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [font (when (string? value) (fonts/find-font-data {:family value}))
|
||||
variant (fonts/get-default-variant font)]
|
||||
(cond
|
||||
(nil? font)
|
||||
(u/not-valid plugin-id :fontFamily value)
|
||||
(cond
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :fontFamily value)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontFamily "Plugin doesn't have 'library:write' permission")
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontFamily "Plugin doesn't have 'library:write' permission")
|
||||
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(merge (font-data font variant)))]
|
||||
(st/emit! (dwl/update-typography typo file-id))))))}
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(assoc :font-family value))]
|
||||
(st/emit! (dwl/update-typography typo file-id)))))}
|
||||
|
||||
:fontVariantId
|
||||
{:this true
|
||||
:get #(-> % u/proxy->library-typography :font-variant-id)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [typo (u/proxy->library-typography self)
|
||||
font (fonts/get-font-data (:font-id typo))
|
||||
variant (when (string? value) (fonts/find-variant font {:id value}))]
|
||||
(cond
|
||||
(nil? variant)
|
||||
(u/not-valid plugin-id :fontVariantId value)
|
||||
(cond
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :fontVariantId value)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontVariantId "Plugin doesn't have 'library:write' permission")
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontVariantId "Plugin doesn't have 'library:write' permission")
|
||||
|
||||
:else
|
||||
(st/emit! (dwl/update-typography (merge typo (variant-data variant)) file-id)))))}
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(assoc :font-variant-id value))]
|
||||
(st/emit! (dwl/update-typography typo file-id)))))}
|
||||
|
||||
:fontSize
|
||||
{:this true
|
||||
:get #(-> % u/proxy->library-typography :font-size)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [value (some-> value str/trim)]
|
||||
(cond
|
||||
(not (txt/valid-font-size? value))
|
||||
(u/not-valid plugin-id :fontSize value)
|
||||
(cond
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :fontSize value)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontSize "Plugin doesn't have 'library:write' permission")
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontSize "Plugin doesn't have 'library:write' permission")
|
||||
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(assoc :font-size value))]
|
||||
(st/emit! (dwl/update-typography typo file-id))))))}
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(assoc :font-size value))]
|
||||
(st/emit! (dwl/update-typography typo file-id)))))}
|
||||
|
||||
:fontWeight
|
||||
{:this true
|
||||
:get #(-> % u/proxy->library-typography :font-weight)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [typo (u/proxy->library-typography self)
|
||||
font (fonts/get-font-data (:font-id typo))
|
||||
variant (when (string? value)
|
||||
(or (fonts/find-variant font {:style (:font-style typo) :weight value})
|
||||
(fonts/find-variant font {:weight value})))]
|
||||
(cond
|
||||
(nil? variant)
|
||||
(u/not-valid plugin-id :fontWeight value)
|
||||
(cond
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :fontWeight value)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontWeight "Plugin doesn't have 'library:write' permission")
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontWeight "Plugin doesn't have 'library:write' permission")
|
||||
|
||||
:else
|
||||
(st/emit! (dwl/update-typography (merge typo (variant-data variant)) file-id)))))}
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(assoc :font-weight value))]
|
||||
(st/emit! (dwl/update-typography typo file-id)))))}
|
||||
|
||||
:fontStyle
|
||||
{:this true
|
||||
:get #(-> % u/proxy->library-typography :font-style)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [typo (u/proxy->library-typography self)
|
||||
font (fonts/get-font-data (:font-id typo))
|
||||
variant (when (string? value)
|
||||
(or (fonts/find-variant font {:weight (:font-weight typo) :style value})
|
||||
(fonts/find-variant font {:style value})))]
|
||||
(cond
|
||||
(nil? variant)
|
||||
(u/not-valid plugin-id :fontStyle value)
|
||||
(cond
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :fontStyle value)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontStyle "Plugin doesn't have 'library:write' permission")
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :fontStyle "Plugin doesn't have 'library:write' permission")
|
||||
|
||||
:else
|
||||
(st/emit! (dwl/update-typography (merge typo (variant-data variant)) file-id)))))}
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(assoc :font-style value))]
|
||||
(st/emit! (dwl/update-typography typo file-id)))))}
|
||||
|
||||
:lineHeight
|
||||
{:this true
|
||||
:get #(-> % u/proxy->library-typography :line-height)
|
||||
:get #(-> % u/proxy->library-typography :font-height)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [value (some-> value str/trim)]
|
||||
(cond
|
||||
(not (txt/valid-line-height? value))
|
||||
(u/not-valid plugin-id :lineHeight value)
|
||||
(cond
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :lineHeight value)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :lineHeight "Plugin doesn't have 'library:write' permission")
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :lineHeight "Plugin doesn't have 'library:write' permission")
|
||||
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(assoc :line-height value))]
|
||||
(st/emit! (dwl/update-typography typo file-id))))))}
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(assoc :font-height value))]
|
||||
(st/emit! (dwl/update-typography typo file-id)))))}
|
||||
|
||||
:letterSpacing
|
||||
{:this true
|
||||
:get #(-> % u/proxy->library-typography :letter-spacing)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [value (some-> value str/trim)]
|
||||
(cond
|
||||
(not (txt/valid-letter-spacing? value))
|
||||
(u/not-valid plugin-id :letterSpacing value)
|
||||
(cond
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :letterSpacing value)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :letterSpacing "Plugin doesn't have 'library:write' permission")
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :letterSpacing "Plugin doesn't have 'library:write' permission")
|
||||
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(assoc :letter-spacing value))]
|
||||
(st/emit! (dwl/update-typography typo file-id))))))}
|
||||
:else
|
||||
(let [typo (-> (u/proxy->library-typography self)
|
||||
(assoc :letter-spacing value))]
|
||||
(st/emit! (dwl/update-typography typo file-id)))))}
|
||||
|
||||
:textTransform
|
||||
{:this true
|
||||
@@ -510,7 +478,7 @@
|
||||
:set
|
||||
(fn [self value]
|
||||
(cond
|
||||
(not (txt/valid-text-transform? value))
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :textTransform value)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
@@ -527,11 +495,6 @@
|
||||
(not (obj/type-of? font "FontProxy"))
|
||||
(u/not-valid plugin-id :setFont font)
|
||||
|
||||
(and (some? variant)
|
||||
(or (not (obj/type-of? variant "FontVariantProxy"))
|
||||
(not= (obj/get font "fontId") (obj/get variant "$font-id"))))
|
||||
(u/not-valid plugin-id :setFont variant)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :setFont "Plugin doesn't have 'library:write' permission")
|
||||
|
||||
@@ -757,8 +720,7 @@
|
||||
|
||||
:removeProperty
|
||||
(fn [pos]
|
||||
(let [properties (->> (get-variant-components file-id id) first :variant-properties)
|
||||
nprops (count properties)]
|
||||
(let [nprops (->> (get-variant-components file-id id) first :variant-properties count)]
|
||||
(cond
|
||||
(or (not (nat-int? pos)) (>= pos nprops))
|
||||
(u/not-valid plugin-id :pos pos)
|
||||
@@ -766,9 +728,6 @@
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :removeProperty "Plugin doesn't have 'library:write' permission")
|
||||
|
||||
(not (ctv/can-remove-property? properties))
|
||||
(u/not-valid plugin-id :removeProperty "A variant must keep at least one property")
|
||||
|
||||
:else
|
||||
(st/emit!
|
||||
(se/event plugin-id "remove-property")
|
||||
@@ -781,7 +740,7 @@
|
||||
(or (not (nat-int? pos)) (>= pos nprops))
|
||||
(u/not-valid plugin-id :pos pos)
|
||||
|
||||
(not (ctv/valid-property-name? name))
|
||||
(not (string? name))
|
||||
(u/not-valid plugin-id :name name)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
@@ -789,8 +748,7 @@
|
||||
|
||||
:else
|
||||
(st/emit!
|
||||
(dwv/update-property-name id pos (ctv/normalize-property-text name)
|
||||
{:trigger "plugin:rename-property"})))))))
|
||||
(dwv/update-property-name id pos name {:trigger "plugin:rename-property"})))))))
|
||||
|
||||
(set! shape/variant-proxy variant-proxy)
|
||||
|
||||
@@ -1020,8 +978,8 @@
|
||||
(or (not (nat-int? pos)) (>= pos nprops))
|
||||
(u/not-valid plugin-id :pos (str pos))
|
||||
|
||||
(not (ctv/valid-property-value? value))
|
||||
(u/not-valid plugin-id :value value)
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :name value)
|
||||
|
||||
(not (r/check-permission plugin-id "library:write"))
|
||||
(u/not-valid plugin-id :setVariantProperty "Plugin doesn't have 'library:write' permission")
|
||||
@@ -1029,7 +987,7 @@
|
||||
:else
|
||||
(st/emit!
|
||||
(se/event plugin-id "variant-edit-property-value")
|
||||
(dwv/update-property-value id pos (ctv/normalize-property-text value))))))))
|
||||
(dwv/update-property-value id pos value)))))))
|
||||
|
||||
(defn library-proxy? [p]
|
||||
(obj/type-of? p "LibraryProxy"))
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.color :as cc]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.comments :as dc]
|
||||
[app.main.data.common :as dcm]
|
||||
@@ -79,19 +78,15 @@
|
||||
(shape/shape-proxy plugin-id file-id page-id frame)))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [page (u/locate-page file-id page-id)]
|
||||
(cond
|
||||
(or (not (shape/shape-proxy? value))
|
||||
(not= file-id (obj/get value "$file"))
|
||||
(not= page-id (obj/get value "$page"))
|
||||
(not (ctp/valid-flow-starting-frame? page (obj/get value "$id") id)))
|
||||
(u/not-valid plugin-id :startingBoard value)
|
||||
(cond
|
||||
(not (shape/shape-proxy? value))
|
||||
(u/not-valid plugin-id :startingBoard value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :startingBoard "Plugin doesn't have 'content:write' permission")
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :startingBoard "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(st/emit! (dwi/update-flow page-id id #(assoc % :starting-frame (obj/get value "$id")))))))}
|
||||
:else
|
||||
(st/emit! (dwi/update-flow page-id id #(assoc % :starting-frame (obj/get value "$id"))))))}
|
||||
|
||||
:remove
|
||||
(fn []
|
||||
@@ -120,16 +115,15 @@
|
||||
:get #(-> % u/proxy->page :name)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [value (ctp/normalize-page-name value)]
|
||||
(cond
|
||||
(not (ctp/valid-page-name? value))
|
||||
(u/not-valid plugin-id :name value)
|
||||
(cond
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :name value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission")
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(st/emit! (dw/rename-page id value)))))}
|
||||
:else
|
||||
(st/emit! (dw/rename-page id value))))}
|
||||
|
||||
:getRoot
|
||||
(fn []
|
||||
@@ -325,26 +319,22 @@
|
||||
|
||||
:createFlow
|
||||
(fn [name frame]
|
||||
(let [page (u/locate-page file-id id)]
|
||||
(cond
|
||||
(or (not (string? name)) (empty? name))
|
||||
(u/not-valid plugin-id :createFlow-name name)
|
||||
(cond
|
||||
(or (not (string? name)) (empty? name))
|
||||
(u/not-valid plugin-id :createFlow-name name)
|
||||
|
||||
(or (not (shape/shape-proxy? frame))
|
||||
(not= file-id (obj/get frame "$file"))
|
||||
(not= id (obj/get frame "$page"))
|
||||
(not (ctp/valid-flow-starting-frame? page (obj/get frame "$id") nil)))
|
||||
(u/not-valid plugin-id :createFlow-frame frame)
|
||||
(not (shape/shape-proxy? frame))
|
||||
(u/not-valid plugin-id :createFlow-frame frame)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :createFlow "Plugin doesn't have 'content:write' permission")
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :createFlow "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(let [flow-id (uuid/next)]
|
||||
(st/emit!
|
||||
(dwi/add-flow flow-id id name (obj/get frame "$id"))
|
||||
(se/event plugin-id "add-flow"))
|
||||
(flow-proxy plugin-id file-id id flow-id)))))
|
||||
:else
|
||||
(let [flow-id (uuid/next)]
|
||||
(st/emit!
|
||||
(dwi/add-flow flow-id id name (obj/get frame "$id"))
|
||||
(se/event plugin-id "add-flow"))
|
||||
(flow-proxy plugin-id file-id id flow-id))))
|
||||
|
||||
:removeFlow
|
||||
(fn [flow]
|
||||
@@ -362,8 +352,7 @@
|
||||
|
||||
:addRulerGuide
|
||||
(fn [orientation value board]
|
||||
(let [shape (when (shape/shape-proxy? board)
|
||||
(u/locate-shape file-id id (obj/get board "$id")))]
|
||||
(let [shape (u/proxy->shape board)]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :addRulerGuide "Value not a safe number")
|
||||
@@ -371,10 +360,8 @@
|
||||
(not (contains? #{"vertical" "horizontal"} orientation))
|
||||
(u/not-valid plugin-id :addRulerGuide "Orientation should be either 'vertical' or 'horizontal'")
|
||||
|
||||
(and (some? board)
|
||||
(and (some? shape)
|
||||
(or (not (shape/shape-proxy? board))
|
||||
(not= file-id (obj/get board "$file"))
|
||||
(not= id (obj/get board "$page"))
|
||||
(not (cfh/frame-shape? shape))))
|
||||
(u/not-valid plugin-id :addRulerGuide "The shape is not a board")
|
||||
|
||||
@@ -418,7 +405,7 @@
|
||||
(let [shape (when board (u/proxy->shape board))
|
||||
position (parser/parse-point position)]
|
||||
(cond
|
||||
(not (dc/valid-comment-content? content))
|
||||
(or (not (string? content)) (empty? content))
|
||||
(u/not-valid plugin-id :addCommentThread "Content not valid")
|
||||
|
||||
(or (not (sm/valid-safe-number? (:x position)))
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.json :as json]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]))
|
||||
@@ -507,7 +506,7 @@
|
||||
|
||||
:open-url
|
||||
{:action-type action-type
|
||||
:url (ctsi/normalize-url (obj/get action "url"))}
|
||||
:url (obj/get action "url")}
|
||||
|
||||
nil)))))
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
[app.plugins.strokes :as strokes]
|
||||
[app.plugins.system-events :as se]
|
||||
[app.plugins.text :as text]
|
||||
[app.plugins.tokens :refer [applied-tokens-plugin->applied-tokens token-attr-plugin->token-attr token-attr? valid-token-resolution?]]
|
||||
[app.plugins.tokens :refer [applied-tokens-plugin->applied-tokens token-attr-plugin->token-attr token-attr?]]
|
||||
[app.plugins.utils :as u]
|
||||
[app.util.http :as http]
|
||||
[app.util.object :as obj]
|
||||
@@ -79,21 +79,6 @@
|
||||
(defn interaction-proxy? [p]
|
||||
(obj/type-of? p "InteractionProxy"))
|
||||
|
||||
(defn- valid-interaction-action?
|
||||
[file-id page-id source raw-action interaction]
|
||||
(let [page (u/locate-page file-id page-id)
|
||||
destination-proxy (obj/get raw-action "destination")
|
||||
destination-id (:destination interaction)
|
||||
animation-type (get-in interaction [:animation :animation-type])]
|
||||
(and (sm/validate ctsi/schema:interaction interaction)
|
||||
(ctsi/valid-delay? interaction)
|
||||
(or (nil? destination-proxy)
|
||||
(and (shape-proxy? destination-proxy)
|
||||
(= file-id (obj/get destination-proxy "$file"))
|
||||
(= page-id (obj/get destination-proxy "$page"))))
|
||||
(ctsi/valid-destination? (:objects page) source destination-id)
|
||||
(ctsi/allowed-animation? (:action-type interaction) animation-type))))
|
||||
|
||||
(defn interaction-proxy
|
||||
[plugin-id file-id page-id shape-id index]
|
||||
(obj/reify {:name "InteractionProxy"}
|
||||
@@ -113,10 +98,9 @@
|
||||
:get #(-> % u/proxy->interaction :event-type format/format-key)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [value (parser/parse-keyword value)
|
||||
shape (u/locate-shape file-id page-id shape-id)]
|
||||
(let [value (parser/parse-keyword value)]
|
||||
(cond
|
||||
(not (ctsi/valid-event-type-for-shape? shape value))
|
||||
(not (contains? ctsi/event-types value))
|
||||
(u/not-valid plugin-id :trigger value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -124,9 +108,9 @@
|
||||
|
||||
:else
|
||||
(st/emit! (dwi/update-interaction
|
||||
shape
|
||||
(u/locate-shape file-id page-id shape-id)
|
||||
index
|
||||
#(ctsi/set-event-type % value shape)
|
||||
#(assoc % :event-type value)
|
||||
{:page-id page-id})))))}
|
||||
|
||||
:delay
|
||||
@@ -153,13 +137,12 @@
|
||||
:get #(-> % u/proxy->interaction (format/format-action plugin-id file-id page-id))
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [shape (u/locate-shape file-id page-id shape-id)
|
||||
params (parser/parse-action value)
|
||||
(let [params (parser/parse-action value)
|
||||
interaction
|
||||
(-> (u/proxy->interaction self)
|
||||
(d/patch-object params))]
|
||||
(cond
|
||||
(not (valid-interaction-action? file-id page-id shape value interaction))
|
||||
(not (sm/validate ctsi/schema:interaction interaction))
|
||||
(u/not-valid plugin-id :action interaction)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -524,7 +507,7 @@
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :borderRadiusTopLeft value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -543,7 +526,7 @@
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :borderRadiusTopRight value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -562,7 +545,7 @@
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :borderRadiusBottomRight value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -581,7 +564,7 @@
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :borderRadiusBottomLeft value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -1517,12 +1500,7 @@
|
||||
|
||||
:swapComponent
|
||||
(fn [component]
|
||||
(let [shape (u/locate-shape file-id page-id id)
|
||||
objects (u/locate-objects file-id page-id)
|
||||
valid-component? (obj/type-of? component "LibraryComponentProxy")
|
||||
target-file (when valid-component? (obj/get component "$file"))
|
||||
target-id (when valid-component? (obj/get component "$id"))
|
||||
target-data (some-> (u/locate-file target-file) :data)]
|
||||
(let [shape (u/locate-shape file-id page-id id)]
|
||||
(cond
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :swapComponent "Plugin doesn't have 'content:write' permission")
|
||||
@@ -1530,19 +1508,16 @@
|
||||
(not (u/page-active? page-id))
|
||||
(u/not-valid plugin-id :swapComponent "Cannot modify a page that is not currently active")
|
||||
|
||||
(not valid-component?)
|
||||
(not (obj/type-of? component "LibraryComponentProxy"))
|
||||
(u/not-valid plugin-id :swapComponent "Component not valid")
|
||||
|
||||
(not (ctk/in-component-copy? shape))
|
||||
(u/not-valid plugin-id :swapComponent "The shape is not a component copy instance")
|
||||
|
||||
(dwl/component-swap-nesting-loop? objects shape target-data target-id)
|
||||
(u/not-valid plugin-id :swapComponent "The swap would create a component nesting loop")
|
||||
|
||||
:else
|
||||
(st/emit! (dwl/component-swap shape
|
||||
target-file
|
||||
target-id
|
||||
(obj/get component "$file")
|
||||
(obj/get component "$id")
|
||||
true)))))
|
||||
|
||||
:resetOverrides
|
||||
@@ -1636,15 +1611,11 @@
|
||||
;; Interactions
|
||||
:addInteraction
|
||||
(fn [trigger action delay]
|
||||
(let [shape (u/locate-shape file-id page-id id)
|
||||
event-type (parser/parse-keyword trigger)
|
||||
interaction (when (ctsi/valid-event-type-for-shape? shape event-type)
|
||||
(-> ctsi/default-interaction
|
||||
(ctsi/set-event-type event-type shape)
|
||||
(d/patch-object (parser/parse-action action))
|
||||
(cond-> (some? delay) (assoc :delay delay))))]
|
||||
(let [interaction
|
||||
(-> ctsi/default-interaction
|
||||
(d/patch-object (parser/parse-interaction trigger action delay)))]
|
||||
(cond
|
||||
(not (valid-interaction-action? file-id page-id shape action interaction))
|
||||
(not (sm/validate ctsi/schema:interaction interaction))
|
||||
(u/not-valid plugin-id :addInteraction interaction)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -1744,23 +1715,15 @@
|
||||
[:fn token-proxy?]
|
||||
[:maybe [::sm/set [:and ::sm/keyword [:fn token-attr?]]]]]
|
||||
:fn (fn [token attrs]
|
||||
(let [set-id (obj/get token "$set-id")
|
||||
token-id (obj/get token "$id")
|
||||
token (u/locate-token file-id set-id token-id)
|
||||
(let [token (u/locate-token file-id (obj/get token "$set-id") (obj/get token "$id"))
|
||||
kw-attrs (into #{} (map token-attr-plugin->token-attr attrs))]
|
||||
(cond
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :applyToken "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(nil? token)
|
||||
(u/not-valid plugin-id :applyToken token-id)
|
||||
|
||||
(not (valid-token-resolution? file-id set-id token-id))
|
||||
(u/not-valid plugin-id :applyToken (:value token))
|
||||
|
||||
(some #(not (token-attr? %)) kw-attrs)
|
||||
(u/not-valid plugin-id :applyToken attrs)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :applyToken "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(st/emit!
|
||||
(-> (dwta/toggle-token {:token token
|
||||
@@ -1783,12 +1746,6 @@
|
||||
:switchVariant
|
||||
(fn [pos value]
|
||||
(cond
|
||||
(not (u/page-active? page-id))
|
||||
(u/not-valid plugin-id :switchVariant "Cannot modify a page that is not currently active")
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :switchVariant "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(not (nat-int? pos))
|
||||
(u/not-valid plugin-id :pos pos)
|
||||
|
||||
@@ -1799,11 +1756,11 @@
|
||||
(u/not-valid plugin-id :switchVariant "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(let [shape (u/locate-shape file-id page-id id)]
|
||||
(if (dwv/valid-variant-switch? @st/state shape pos value)
|
||||
(let [shape (u/locate-shape file-id page-id id)
|
||||
component (u/locate-library-component file-id (:component-id shape))]
|
||||
(when (and component (ctk/is-variant? component))
|
||||
(st/emit! (-> (dwv/variants-switch {:shapes [shape] :pos pos :val value})
|
||||
(se/add-event plugin-id)))
|
||||
(u/not-valid plugin-id :switchVariant "Shape, property, or value is not valid")))))
|
||||
(se/add-event plugin-id)))))))
|
||||
|
||||
:combineAsVariants
|
||||
(fn [ids]
|
||||
@@ -1825,7 +1782,13 @@
|
||||
(distinct))
|
||||
ids)
|
||||
|
||||
valid? (dwv/valid-components-for-variants? @st/state page-id ids)]
|
||||
valid?
|
||||
(every?
|
||||
(fn [id]
|
||||
(let [shape (u/locate-shape file-id page-id id)
|
||||
component (u/locate-library-component file-id (:component-id shape))]
|
||||
(not (ctk/is-variant? component))))
|
||||
ids)]
|
||||
|
||||
(if valid?
|
||||
(let [variant-id (uuid/next)]
|
||||
@@ -1833,7 +1796,7 @@
|
||||
ids
|
||||
{:trigger "plugin:combine-as-variants" :variant-id variant-id})
|
||||
(se/add-event plugin-id)))
|
||||
(shape-proxy plugin-id file-id page-id variant-id))
|
||||
(shape-proxy plugin-id variant-id))
|
||||
|
||||
(u/not-valid plugin-id :ids "One of the components is not on the same page or is already a variant"))))))
|
||||
|
||||
|
||||
@@ -28,6 +28,13 @@
|
||||
[app.util.text-editor :as ted]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;; This regex seems duplicated but probably in the future when we support diferent units
|
||||
;; this will need to reflect changes for each property
|
||||
|
||||
(def ^:private font-size-re #"^\d*\.?\d*$")
|
||||
(def ^:private line-height-re #"^\d*\.?\d*$")
|
||||
(def ^:private letter-spacing-re #"^-?\d*\.?\d*$")
|
||||
(def ^:private text-transform-re #"uppercase|capitalize|lowercase|none")
|
||||
(def ^:private text-decoration-re #"underline|line-through|none")
|
||||
(def ^:private text-direction-re #"ltr|rtl")
|
||||
(def ^:private text-align-re #"left|center|right|justify")
|
||||
@@ -153,7 +160,7 @@
|
||||
(let [font (fonts/find-font-data {:family value})
|
||||
variant (fonts/get-default-variant font)]
|
||||
(cond
|
||||
(nil? font)
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :fontFamily value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -175,10 +182,9 @@
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [font (fonts/get-font-data (obj/get self "fontId"))
|
||||
variant (when (string? value)
|
||||
(fonts/find-variant font {:id value}))]
|
||||
variant (fonts/get-variant font value)]
|
||||
(cond
|
||||
(nil? variant)
|
||||
(not (string? value))
|
||||
(u/not-valid plugin-id :fontVariantId value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -201,7 +207,7 @@
|
||||
(fn [_ value]
|
||||
(let [value (str/trim (dm/str value))]
|
||||
(cond
|
||||
(not (txt/valid-font-size? value))
|
||||
(or (empty? value) (not (re-matches font-size-re value)))
|
||||
(u/not-valid plugin-id :fontSize value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -283,7 +289,7 @@
|
||||
(fn [_ value]
|
||||
(let [value (str/trim (dm/str value))]
|
||||
(cond
|
||||
(not (txt/valid-line-height? value))
|
||||
(or (empty? value) (not (re-matches line-height-re value)))
|
||||
(u/not-valid plugin-id :lineHeight value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -306,7 +312,7 @@
|
||||
(fn [_ value]
|
||||
(let [value (str/trim (dm/str value))]
|
||||
(cond
|
||||
(not (txt/valid-letter-spacing? value))
|
||||
(or (not (string? value)) (not (re-matches letter-spacing-re value)))
|
||||
(u/not-valid plugin-id :letterSpacing value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -328,7 +334,7 @@
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(not (txt/valid-text-transform? value))
|
||||
(and (string? value) (not (re-matches text-transform-re value)))
|
||||
(u/not-valid plugin-id :textTransform value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -544,8 +550,7 @@
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")
|
||||
font (fonts/get-font-data (obj/get self "fontId"))
|
||||
variant (when (string? value)
|
||||
(fonts/find-variant font {:id value}))]
|
||||
variant (fonts/get-variant font value)]
|
||||
(cond
|
||||
(not variant)
|
||||
(u/not-valid plugin-id :fontVariantId value)
|
||||
@@ -566,7 +571,7 @@
|
||||
(let [id (obj/get self "$id")
|
||||
value (str/trim (dm/str value))]
|
||||
(cond
|
||||
(not (txt/valid-font-size? value))
|
||||
(or (empty? value) (not (re-matches font-size-re value)))
|
||||
(u/not-valid plugin-id :fontSize value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -635,7 +640,7 @@
|
||||
(let [id (obj/get self "$id")
|
||||
value (str/trim (dm/str value))]
|
||||
(cond
|
||||
(not (txt/valid-line-height? value))
|
||||
(or (empty? value) (not (re-matches line-height-re value)))
|
||||
(u/not-valid plugin-id :lineHeight value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -654,7 +659,7 @@
|
||||
(let [id (obj/get self "$id")
|
||||
value (str/trim (dm/str value))]
|
||||
(cond
|
||||
(not (txt/valid-letter-spacing? value))
|
||||
(or (not (string? value)) (not (re-matches letter-spacing-re value)))
|
||||
(u/not-valid plugin-id :letterSpacing value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
@@ -672,7 +677,7 @@
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(cond
|
||||
(not (txt/valid-text-transform? value))
|
||||
(or (not (string? value)) (not (re-matches text-transform-re value)))
|
||||
(u/not-valid plugin-id :textTransform value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
(ns app.plugins.tokens
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.files.tokens :as cfo]
|
||||
[app.common.json :as json]
|
||||
[app.common.schema :as sm]
|
||||
@@ -85,50 +84,6 @@
|
||||
[attr]
|
||||
(cto/token-attr? (token-attr-plugin->token-attr attr)))
|
||||
|
||||
(defn- token-name-schema
|
||||
[file-id set-id token]
|
||||
(let [tokens-lib (u/locate-tokens-lib file-id)
|
||||
tokens (-> (ctob/get-tokens tokens-lib set-id)
|
||||
(dissoc (:name token))
|
||||
(ctob/tokens-tree))]
|
||||
(cfo/make-token-name-schema tokens)))
|
||||
|
||||
(defn- token-value-schema
|
||||
[token]
|
||||
(let [base (cfo/make-token-value-schema (:type token))]
|
||||
(if (= :font-family (:type token))
|
||||
[:or :string base]
|
||||
base)))
|
||||
|
||||
(defn- normalize-token-value
|
||||
[token value]
|
||||
(case (:type token)
|
||||
:font-family (ctob/convert-dtcg-font-family value)
|
||||
:typography (ctob/convert-dtcg-typography-composite value)
|
||||
:shadow (ctob/convert-dtcg-shadow-composite value)
|
||||
value))
|
||||
|
||||
(defn- valid-token-candidate?
|
||||
[file-id set-id token attrs]
|
||||
(let [tokens-lib (u/locate-tokens-lib file-id)
|
||||
candidate (merge (datafy token) attrs)
|
||||
tokens (-> (merge (ctob/get-all-tokens-map tokens-lib)
|
||||
(ctob/get-tokens tokens-lib set-id))
|
||||
(dissoc (:name token))
|
||||
(assoc (:name candidate) candidate))
|
||||
resolved (get (ts/resolve-tokens tokens) (:name candidate))]
|
||||
(and (sm/validate (token-name-schema file-id set-id token) (:name candidate))
|
||||
(sm/validate (cfo/make-token-value-schema (:type candidate)) (:value candidate))
|
||||
(or (nil? (:description candidate))
|
||||
(sm/validate cfo/schema:token-description (:description candidate)))
|
||||
(contains? resolved :resolved-value)
|
||||
(empty? (:errors resolved)))))
|
||||
|
||||
(defn valid-token-resolution?
|
||||
[file-id set-id id]
|
||||
(when-let [token (u/locate-token file-id set-id id)]
|
||||
(valid-token-candidate? file-id set-id token {})))
|
||||
|
||||
(defn- apply-token-to-shapes
|
||||
[plugin-id file-id set-id id shape-ids attrs]
|
||||
(cond
|
||||
@@ -137,17 +92,8 @@
|
||||
|
||||
:else
|
||||
(let [token (u/locate-token file-id set-id id)]
|
||||
(cond
|
||||
(nil? token)
|
||||
(u/not-valid plugin-id :applyToSelected id)
|
||||
|
||||
(not (valid-token-candidate? file-id set-id token {}))
|
||||
(u/not-valid plugin-id :applyToSelected (:value token))
|
||||
|
||||
(some #(not (token-attr? %)) attrs)
|
||||
(if (some #(not (token-attr? %)) attrs)
|
||||
(u/not-valid plugin-id :applyToSelected attrs)
|
||||
|
||||
:else
|
||||
(st/emit!
|
||||
(-> (dwta/toggle-token {:token token
|
||||
:attrs (into #{} (map token-attr-plugin->token-attr) attrs)
|
||||
@@ -258,8 +204,9 @@
|
||||
(fn [_]
|
||||
(let [token (u/locate-token file-id set-id id)]
|
||||
(ctob/get-name token)))
|
||||
:schema (fn [_]
|
||||
(token-name-schema file-id set-id (u/locate-token file-id set-id id)))
|
||||
:schema (cfo/make-token-name-schema
|
||||
(some-> (u/locate-tokens-lib file-id)
|
||||
(ctob/get-tokens set-id)))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
@@ -267,11 +214,8 @@
|
||||
(u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(let [token (u/locate-token file-id set-id id)]
|
||||
(if (valid-token-candidate? file-id set-id token {:name value})
|
||||
(st/emit! (-> (dwtl/update-token set-id id {:name value})
|
||||
(se/add-event plugin-id)))
|
||||
(u/not-valid plugin-id :name value)))))}
|
||||
(st/emit! (-> (dwtl/update-token set-id id {:name value})
|
||||
(se/add-event plugin-id)))))}
|
||||
|
||||
:type
|
||||
{:this true
|
||||
@@ -286,11 +230,14 @@
|
||||
(fn [_]
|
||||
(let [token (u/locate-token file-id set-id id)]
|
||||
(json/->js (:value token))))
|
||||
:decode/fn (fn [value]
|
||||
(let [token (u/locate-token file-id set-id id)]
|
||||
(normalize-token-value token (json/->clj value))))
|
||||
:schema (fn [_]
|
||||
(token-value-schema (u/locate-token file-id set-id id)))
|
||||
:schema (let [token (u/locate-token file-id set-id id)
|
||||
base (cfo/make-token-value-schema (:type token))]
|
||||
;; plugin-types declares the fontFamilies value as
|
||||
;; `string | string[]`, but the core schema only accepts a
|
||||
;; vector/ref; also accept a plain string (normalized in :set).
|
||||
(if (= :font-family (:type token))
|
||||
[:or :string base]
|
||||
base))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
@@ -299,10 +246,10 @@
|
||||
|
||||
:else
|
||||
(let [token (u/locate-token file-id set-id id)
|
||||
value (normalize-token-value token value)]
|
||||
(if (valid-token-candidate? file-id set-id token {:value value})
|
||||
(st/emit! (dwtl/update-token set-id id {:value value}))
|
||||
(u/not-valid plugin-id :value value)))))}
|
||||
value (cond-> value
|
||||
(= :font-family (:type token))
|
||||
(ctob/convert-dtcg-font-family))]
|
||||
(st/emit! (dwtl/update-token set-id id {:value value})))))}
|
||||
|
||||
:resolvedValue
|
||||
{:this true
|
||||
@@ -354,11 +301,9 @@
|
||||
;; - return the new token proxy using the locally forced id
|
||||
;; - do the same with sets and themes
|
||||
(let [token (u/locate-token file-id set-id id)
|
||||
names (map :name (vals (ctob/get-tokens (u/locate-tokens-lib file-id) set-id)))
|
||||
name (cfh/generate-unique-name (:name token) names :suffix "copy")
|
||||
token' (ctob/make-token (-> (datafy token)
|
||||
(assoc :name name)
|
||||
(dissoc :id :modified-at)))]
|
||||
(dissoc :id
|
||||
:modified-at)))]
|
||||
(st/emit! (-> (dwtl/create-token set-id token')
|
||||
(se/add-event plugin-id)))
|
||||
(token-proxy plugin-id file-id set-id (:id token')))))
|
||||
@@ -417,10 +362,9 @@
|
||||
(if (some? set)
|
||||
(ctob/get-name set)
|
||||
initial-name)))
|
||||
:schema (fn [_]
|
||||
(cfo/make-token-set-name-schema
|
||||
(u/locate-tokens-lib file-id)
|
||||
id))
|
||||
:schema (cfo/make-token-set-name-schema
|
||||
(u/locate-tokens-lib file-id)
|
||||
id)
|
||||
:set
|
||||
(fn [_ name]
|
||||
(cond
|
||||
@@ -610,12 +554,11 @@
|
||||
(fn [_]
|
||||
(let [theme (u/locate-token-theme file-id id)]
|
||||
(:group theme)))
|
||||
:schema (fn [_]
|
||||
(let [theme (u/locate-token-theme file-id id)]
|
||||
(cfo/make-token-theme-group-schema
|
||||
(u/locate-tokens-lib file-id)
|
||||
(:name theme)
|
||||
(:id theme))))
|
||||
:schema (let [theme (u/locate-token-theme file-id id)]
|
||||
(cfo/make-token-theme-group-schema
|
||||
(u/locate-tokens-lib file-id)
|
||||
(:name theme)
|
||||
(:id theme)))
|
||||
:set
|
||||
(fn [_ group]
|
||||
(cond
|
||||
@@ -632,12 +575,11 @@
|
||||
(fn [_]
|
||||
(let [theme (u/locate-token-theme file-id id)]
|
||||
(:name theme)))
|
||||
:schema (fn [_]
|
||||
(let [theme (u/locate-token-theme file-id id)]
|
||||
(cfo/make-token-theme-name-schema
|
||||
(u/locate-tokens-lib file-id)
|
||||
(:group theme)
|
||||
(:id theme))))
|
||||
:schema (let [theme (u/locate-token-theme file-id id)]
|
||||
(cfo/make-token-theme-name-schema
|
||||
(u/locate-tokens-lib file-id)
|
||||
(:id theme)
|
||||
(:group theme)))
|
||||
:set
|
||||
(fn [_ name]
|
||||
(cond
|
||||
@@ -723,14 +665,8 @@
|
||||
(u/not-valid plugin-id :duplicate "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(let [tokens-lib (u/locate-tokens-lib file-id)
|
||||
theme (u/locate-token-theme file-id id)
|
||||
names (->> (ctob/get-themes tokens-lib)
|
||||
(filter #(= (:group theme) (:group %)))
|
||||
(map :name))
|
||||
name (cfh/generate-unique-name (:name theme) names :suffix "copy")
|
||||
(let [theme (u/locate-token-theme file-id id)
|
||||
theme' (ctob/make-token-theme (-> (datafy theme)
|
||||
(assoc :name name)
|
||||
(dissoc :id
|
||||
:modified-at)))]
|
||||
(st/emit! (dwtl/create-token-theme theme'))
|
||||
@@ -794,12 +730,17 @@
|
||||
|
||||
:addSet
|
||||
{:enumerable false
|
||||
:schema (fn [_]
|
||||
[:tuple (-> (sm/schema (cfo/make-token-set-schema
|
||||
(u/locate-tokens-lib file-id)
|
||||
nil))
|
||||
(sm/dissoc-key :id)
|
||||
(sm/merge [:map [:active {:optional true} ::sm/boolean]]))])
|
||||
:schema [:tuple (-> (sm/schema (cfo/make-token-set-schema
|
||||
(u/locate-tokens-lib file-id)
|
||||
nil))
|
||||
(sm/dissoc-key :id) ;; We don't allow plugins to set the id
|
||||
;; Allow an optional `active` flag so a plugin can create
|
||||
;; an already-active set in a single call. Newly created
|
||||
;; sets are inactive by default (only active sets affect
|
||||
;; shapes and reference resolution). `active` is not part
|
||||
;; of the token-set data model, so the :fn strips it and
|
||||
;; applies it through the set-activation logic.
|
||||
(sm/merge [:map [:active {:optional true} ::sm/boolean]]))]
|
||||
|
||||
:fn (fn [attrs]
|
||||
(cond
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
:set
|
||||
(fn [value]
|
||||
(cond
|
||||
(not (sm/valid-non-negative-safe-number? value))
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :value value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns frontend-tests.logic.path-actions-test
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
|
||||
(ns frontend-tests.logic.path-clipboard-test
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
|
||||
(ns frontend-tests.logic.path-helpers-test
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
|
||||
(ns frontend-tests.logic.path-lifecycle-test
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
|
||||
(ns frontend-tests.logic.path-test-helpers
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
|
||||
(ns frontend-tests.logic.path-tools-test
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns frontend-tests.logic.wasm-modifiers-nil-id-test
|
||||
"Reproduces the production crash \"Cannot read properties of null
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns frontend-tests.plugins.flex-test
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns frontend-tests.plugins.user-test
|
||||
(:require
|
||||
|
||||
@@ -156,23 +156,4 @@ describe.skipIfMocked('Comments', () => {
|
||||
cleanup(thread);
|
||||
}
|
||||
});
|
||||
|
||||
for (const content of [' ', '\n\t', '\u200b']) {
|
||||
test(`blank comment content ${JSON.stringify(content)} rejects everywhere`, async (ctx) => {
|
||||
const p = page(ctx);
|
||||
await expectReject(() => p.addCommentThread(content, { x: 0, y: 0 }));
|
||||
|
||||
const thread = await p.addCommentThread('parent', { x: 12, y: 12 });
|
||||
try {
|
||||
await expectReject(() => thread.reply(content));
|
||||
const comments = await thread.findComments();
|
||||
expect(comments.length).toBeGreaterThan(0);
|
||||
expect(() => {
|
||||
comments[0].content = content;
|
||||
}).toThrow();
|
||||
} finally {
|
||||
cleanup(thread);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -222,21 +222,13 @@ describe('Component instances', () => {
|
||||
const inst = comp.instance();
|
||||
ctx.board.appendChild(inst);
|
||||
|
||||
const mainFill = main.fills?.[0];
|
||||
const mainColor =
|
||||
typeof mainFill === 'string' ? mainFill : mainFill?.fillColor;
|
||||
const mainColor = main.fills?.[0]?.fillColor;
|
||||
inst.fills = [{ fillColor: '#FF0000', fillOpacity: 1 }];
|
||||
// The override applied (fill getter normalizes to lowercase).
|
||||
const overrideFill = inst.fills?.[0];
|
||||
const overrideColor =
|
||||
typeof overrideFill === 'string' ? overrideFill : overrideFill?.fillColor;
|
||||
expect(overrideColor?.toLowerCase()).toBe('#ff0000');
|
||||
expect(inst.fills?.[0]?.fillColor?.toLowerCase()).toBe('#ff0000');
|
||||
|
||||
inst.resetOverrides();
|
||||
const resetFill = inst.fills?.[0];
|
||||
expect(
|
||||
typeof resetFill === 'string' ? resetFill : resetFill?.fillColor,
|
||||
).toBe(mainColor);
|
||||
expect(inst.fills?.[0]?.fillColor).toBe(mainColor);
|
||||
});
|
||||
|
||||
test('resetOverrides on a plain shape throws', (ctx) => {
|
||||
@@ -274,23 +266,6 @@ describe('Component instances', () => {
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test('swapComponent rejects a component nesting loop', (ctx) => {
|
||||
const leaf = ctx.penpot.createRectangle();
|
||||
ctx.board.appendChild(leaf);
|
||||
const inner = ctx.penpot.library.local.createComponent([leaf]);
|
||||
|
||||
const wrapper = ctx.penpot.createBoard();
|
||||
ctx.board.appendChild(wrapper);
|
||||
wrapper.appendChild(inner.instance());
|
||||
const outer = ctx.penpot.library.local.createComponent([wrapper]);
|
||||
const nested = (outer.mainInstance() as Board).children.find((child) =>
|
||||
child.isComponentInstance(),
|
||||
);
|
||||
|
||||
expect(nested).toBeDefined();
|
||||
if (nested) expect(() => nested.swapComponent(outer)).toThrow();
|
||||
});
|
||||
|
||||
test('two instances of one component are independent but share the source', (ctx) => {
|
||||
const comp = makeComponent(ctx);
|
||||
const first = comp.instance();
|
||||
|
||||
@@ -51,9 +51,7 @@ describe('File', () => {
|
||||
// The exporter service may be unavailable in the headless runner, so a
|
||||
// rejection here is treated as an environment limitation; when it does
|
||||
// run, the result must be a non-empty byte array.
|
||||
const data = await file
|
||||
.export('penpot', 'detach-libraries')
|
||||
.catch(() => null);
|
||||
const data = await file.export('penpot', 'detach').catch(() => null);
|
||||
if (data) {
|
||||
expect(data.length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
@@ -267,16 +267,14 @@ describe('Fills & strokes', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('negative strokeWidth throws', (ctx) => {
|
||||
test('negative strokeWidth is accepted (currently unvalidated)', (ctx) => {
|
||||
// The plugin API does not constrain strokeWidth to be non-negative, so a
|
||||
// negative value is stored as-is rather than rejected. This pins the current
|
||||
// (lenient) behaviour.
|
||||
const r = rect(ctx);
|
||||
expect(() => {
|
||||
r.strokes = [{ strokeColor: '#000000', strokeWidth: -3 }];
|
||||
}).toThrow();
|
||||
|
||||
r.strokes = [{ strokeColor: '#000000', strokeWidth: 1 }];
|
||||
expect(() => {
|
||||
r.strokes[0].strokeWidth = -1;
|
||||
}).toThrow();
|
||||
r.strokes = [{ strokeColor: '#000000', strokeWidth: -3 }];
|
||||
expect(r.strokes).toHaveLength(1);
|
||||
expect(typeof r.strokes[0].strokeWidth).toBe('number');
|
||||
});
|
||||
|
||||
test('invalid strokeStyle throws', (ctx) => {
|
||||
|
||||
@@ -119,17 +119,4 @@ describe('Fonts', () => {
|
||||
expect(t.fontVariantId).toBe(variant.fontVariantId);
|
||||
expect(t.fontWeight).toBe(variant.fontWeight);
|
||||
});
|
||||
|
||||
test('a font rejects a variant owned by another font', (ctx) => {
|
||||
const fonts = ctx.penpot.fonts.all;
|
||||
const first = fonts[0];
|
||||
const second = fonts.find((font) => font.fontId !== first.fontId);
|
||||
if (second) {
|
||||
const t = text(ctx);
|
||||
expect(() => first.applyToText(t, second.variants[0])).toThrow();
|
||||
expect(() =>
|
||||
first.applyToRange(t.getRange(0, 5), second.variants[0]),
|
||||
).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -156,7 +156,7 @@ describe('Interactions', () => {
|
||||
|
||||
test('after-delay trigger carries a delay', (ctx) => {
|
||||
const dest = board(ctx);
|
||||
const r = board(ctx);
|
||||
const r = rect(ctx);
|
||||
const interaction = r.addInteraction(
|
||||
'after-delay',
|
||||
{ type: 'navigate-to', destination: dest },
|
||||
@@ -169,7 +169,7 @@ describe('Interactions', () => {
|
||||
// A zero delay is a valid value (fires immediately), not an error.
|
||||
test('after-delay accepts a zero delay', (ctx) => {
|
||||
const dest = board(ctx);
|
||||
const r = board(ctx);
|
||||
const r = rect(ctx);
|
||||
const interaction = r.addInteraction(
|
||||
'after-delay',
|
||||
{ type: 'navigate-to', destination: dest },
|
||||
@@ -198,7 +198,7 @@ describe('Interactions', () => {
|
||||
// "don't persist" — that is stale: CI confirms they do.)
|
||||
test('interaction delay and action setters persist', (ctx) => {
|
||||
const dest = board(ctx);
|
||||
const r = board(ctx);
|
||||
const r = rect(ctx);
|
||||
const interaction = r.addInteraction(
|
||||
'after-delay',
|
||||
{ type: 'navigate-to', destination: dest },
|
||||
@@ -218,7 +218,7 @@ describe('Interactions', () => {
|
||||
// The delay setter accepts zero (fires immediately) as a valid value.
|
||||
test('delay setter accepts a zero value', (ctx) => {
|
||||
const dest = board(ctx);
|
||||
const r = board(ctx);
|
||||
const r = rect(ctx);
|
||||
const interaction = r.addInteraction(
|
||||
'after-delay',
|
||||
{ type: 'navigate-to', destination: dest },
|
||||
@@ -361,205 +361,35 @@ describe('Interactions', () => {
|
||||
expect(interaction.trigger).toBe('mouse-enter');
|
||||
});
|
||||
|
||||
test('unknown interaction triggers are rejected', (ctx) => {
|
||||
const dest = board(ctx);
|
||||
const r = rect(ctx);
|
||||
expect(() =>
|
||||
r.addInteraction('unknown-trigger' as unknown as 'click', {
|
||||
type: 'navigate-to',
|
||||
destination: dest,
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
const interaction = r.addInteraction('click', {
|
||||
type: 'navigate-to',
|
||||
destination: dest,
|
||||
});
|
||||
expect(() => {
|
||||
interaction.trigger = 'unknown-trigger' as unknown as 'click';
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Edge cases. "fail" tests assert invalid interaction input is
|
||||
// rejected; the "success" test checks several triggers coexisting.
|
||||
// ---------------------------------------------------------------------------
|
||||
test('navigate-to a removed board throws', (ctx) => {
|
||||
// addInteraction validates the interaction's structure (schema) but not the
|
||||
// liveness of a navigate destination nor the format of an open-url string,
|
||||
// so both of these are accepted rather than rejected. These pin the current
|
||||
// (lenient) behaviour.
|
||||
test('navigate-to a removed board is accepted (dangling destination)', (ctx) => {
|
||||
const dest = board(ctx);
|
||||
const r = rect(ctx);
|
||||
dest.remove();
|
||||
expect(() =>
|
||||
r.addInteraction('click', { type: 'navigate-to', destination: dest }),
|
||||
).toThrow();
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
test('open-url rejects invalid input and normalizes a bare hostname', (ctx) => {
|
||||
test('open-url accepts an arbitrary url string', (ctx) => {
|
||||
const r = rect(ctx);
|
||||
expect(() =>
|
||||
r.addInteraction('click', {
|
||||
type: 'open-url',
|
||||
url: 'not a valid url',
|
||||
}),
|
||||
).toThrow();
|
||||
expect(() =>
|
||||
r.addInteraction('click', {
|
||||
type: 'open-url',
|
||||
url: 'ftp://example.com/file',
|
||||
}),
|
||||
).toThrow();
|
||||
const interaction = r.addInteraction('click', {
|
||||
type: 'open-url',
|
||||
url: 'example.com/path',
|
||||
url: 'not a valid url',
|
||||
});
|
||||
expect(interaction.action.type).toBe('open-url');
|
||||
if (interaction.action.type === 'open-url') {
|
||||
expect(interaction.action.url).toBe('http://example.com/path');
|
||||
expect(interaction.action.url).toBe('not a valid url');
|
||||
}
|
||||
});
|
||||
|
||||
test('after-delay is board-only and initializes its default delay', (ctx) => {
|
||||
const dest = board(ctx);
|
||||
const r = rect(ctx);
|
||||
expect(() =>
|
||||
r.addInteraction('after-delay', {
|
||||
type: 'navigate-to',
|
||||
destination: dest,
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
const source = board(ctx);
|
||||
const interaction = source.addInteraction('click', {
|
||||
type: 'navigate-to',
|
||||
destination: dest,
|
||||
});
|
||||
interaction.trigger = 'after-delay';
|
||||
expect(interaction.delay).toBeCloseTo(600, 0);
|
||||
expect(() => {
|
||||
const invalid = r.addInteraction('click', {
|
||||
type: 'navigate-to',
|
||||
destination: dest,
|
||||
});
|
||||
invalid.trigger = 'after-delay';
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('after-delay creation rejects invalid delays', (ctx) => {
|
||||
const dest = board(ctx);
|
||||
const source = board(ctx);
|
||||
for (const delay of ['bad', 1.5, -1]) {
|
||||
expect(() =>
|
||||
source.addInteraction(
|
||||
'after-delay',
|
||||
{ type: 'navigate-to', destination: dest },
|
||||
delay as unknown as number,
|
||||
),
|
||||
).toThrow();
|
||||
}
|
||||
|
||||
const interaction = source.addInteraction(
|
||||
'after-delay',
|
||||
{ type: 'navigate-to', destination: dest },
|
||||
10,
|
||||
);
|
||||
for (const delay of ['bad', 1.5, -1]) {
|
||||
expect(() => {
|
||||
interaction.delay = delay as unknown as number;
|
||||
}).toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
test('navigation destinations must be eligible boards', (ctx) => {
|
||||
const source = board(ctx);
|
||||
const child = rect(ctx);
|
||||
const rectangle = rect(ctx);
|
||||
expect(() =>
|
||||
source.addInteraction('click', {
|
||||
type: 'navigate-to',
|
||||
destination: source,
|
||||
}),
|
||||
).toThrow();
|
||||
expect(() =>
|
||||
child.addInteraction('click', {
|
||||
type: 'navigate-to',
|
||||
destination: ctx.board,
|
||||
}),
|
||||
).toThrow();
|
||||
expect(() =>
|
||||
source.addInteraction('click', {
|
||||
type: 'navigate-to',
|
||||
destination: rectangle as unknown as Board,
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test('interaction destinations must belong to the current page', async (ctx) => {
|
||||
const original = ctx.penpot.currentPage;
|
||||
expect(original).not.toBeNull();
|
||||
if (!original) return;
|
||||
|
||||
const source = rect(ctx);
|
||||
const localDestination = board(ctx);
|
||||
const interaction = source.addInteraction('click', {
|
||||
type: 'navigate-to',
|
||||
destination: localDestination,
|
||||
});
|
||||
const otherPage = ctx.penpot.createPage();
|
||||
try {
|
||||
await ctx.penpot.openPage(otherPage);
|
||||
const otherBoard = ctx.penpot.createBoard();
|
||||
(otherPage.root as Board).appendChild(otherBoard);
|
||||
await ctx.penpot.openPage(original);
|
||||
|
||||
expect(() =>
|
||||
source.addInteraction('click', {
|
||||
type: 'navigate-to',
|
||||
destination: otherBoard,
|
||||
}),
|
||||
).toThrow();
|
||||
expect(() => {
|
||||
interaction.action = {
|
||||
type: 'navigate-to',
|
||||
destination: otherBoard,
|
||||
};
|
||||
}).toThrow();
|
||||
} finally {
|
||||
if (ctx.penpot.currentPage?.id !== original.id) {
|
||||
await ctx.penpot.openPage(original);
|
||||
}
|
||||
otherPage.remove();
|
||||
}
|
||||
});
|
||||
|
||||
test('push animation is rejected for overlay actions and replacements', (ctx) => {
|
||||
const overlay = board(ctx);
|
||||
const r = rect(ctx);
|
||||
const push = {
|
||||
type: 'push' as const,
|
||||
direction: 'left' as const,
|
||||
duration: 300,
|
||||
easing: 'linear' as const,
|
||||
};
|
||||
expect(() =>
|
||||
r.addInteraction('click', {
|
||||
type: 'open-overlay',
|
||||
destination: overlay,
|
||||
animation: push,
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
const interaction = r.addInteraction('click', {
|
||||
type: 'navigate-to',
|
||||
destination: overlay,
|
||||
});
|
||||
expect(() => {
|
||||
interaction.action = {
|
||||
type: 'open-overlay',
|
||||
destination: overlay,
|
||||
animation: push,
|
||||
};
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('several triggers on one shape coexist', (ctx) => {
|
||||
const dest = board(ctx);
|
||||
const r = rect(ctx);
|
||||
|
||||
@@ -82,34 +82,6 @@ describe('Layout', () => {
|
||||
expect(flex.leftPadding).toBeCloseTo(4.5, 2);
|
||||
});
|
||||
|
||||
test('every flex gap and padding setter rejects negative values', (ctx) => {
|
||||
const flex = board(ctx).addFlexLayout();
|
||||
expect(() => {
|
||||
flex.rowGap = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
flex.columnGap = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
flex.verticalPadding = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
flex.horizontalPadding = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
flex.topPadding = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
flex.rightPadding = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
flex.bottomPadding = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
flex.leftPadding = -1;
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
// paddingType is "simple" (sides mirrored) or "multiple" (each side independent).
|
||||
test('paddingType round-trips', (ctx) => {
|
||||
const flex = board(ctx).addFlexLayout();
|
||||
@@ -213,37 +185,6 @@ describe('Layout', () => {
|
||||
expect(grid.columns[0].type).toBe('percent');
|
||||
});
|
||||
|
||||
test('grid track creation and replacement reject negative values', (ctx) => {
|
||||
const grid = board(ctx).addGridLayout();
|
||||
for (const type of ['fixed', 'percent', 'flex'] as const) {
|
||||
expect(() => grid.addRow(type, -1)).toThrow();
|
||||
expect(() => grid.addColumn(type, -1)).toThrow();
|
||||
}
|
||||
expect(() =>
|
||||
grid.addColumn('fixed', 'bad' as unknown as number),
|
||||
).toThrow();
|
||||
grid.addRow('flex', 1);
|
||||
grid.addColumn('flex', 1);
|
||||
expect(() => grid.addRowAtIndex(0, 'fixed', -1)).toThrow();
|
||||
expect(() => grid.addColumnAtIndex(0, 'fixed', -1)).toThrow();
|
||||
expect(() => grid.setRow(0, 'flex', -1)).toThrow();
|
||||
expect(() => grid.setColumn(0, 'flex', -1)).toThrow();
|
||||
});
|
||||
|
||||
test('retained grid track proxies reject negative values', (ctx) => {
|
||||
const grid = board(ctx).addGridLayout();
|
||||
grid.addRow('fixed', 10);
|
||||
grid.addColumn('fixed', 10);
|
||||
const row = grid.rows[0];
|
||||
const column = grid.columns[0];
|
||||
expect(() => {
|
||||
row.value = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
column.value = -1;
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('removeRow and removeColumn drop tracks', (ctx) => {
|
||||
const grid = board(ctx).addGridLayout();
|
||||
grid.addRow('flex', 1);
|
||||
@@ -297,34 +238,6 @@ describe('Layout', () => {
|
||||
expect(grid.leftPadding).toBeCloseTo(4.5, 2);
|
||||
});
|
||||
|
||||
test('every grid gap and padding setter rejects negative values', (ctx) => {
|
||||
const grid = board(ctx).addGridLayout();
|
||||
expect(() => {
|
||||
grid.rowGap = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
grid.columnGap = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
grid.verticalPadding = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
grid.horizontalPadding = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
grid.topPadding = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
grid.rightPadding = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
grid.bottomPadding = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
grid.leftPadding = -1;
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
// paddingType behaves the same as on flex layouts (see issue #10278).
|
||||
test('paddingType round-trips', (ctx) => {
|
||||
const grid = board(ctx).addGridLayout();
|
||||
@@ -520,29 +433,6 @@ describe('Layout', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('every layout child min and max bound rejects negatives', (ctx) => {
|
||||
const b = board(ctx);
|
||||
const flex = b.addFlexLayout();
|
||||
const rect = ctx.penpot.createRectangle();
|
||||
flex.appendChild(rect);
|
||||
const child = rect.layoutChild;
|
||||
expect(child).toBeDefined();
|
||||
if (child) {
|
||||
expect(() => {
|
||||
child.minWidth = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
child.maxWidth = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
child.minHeight = -1;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
child.maxHeight = -1;
|
||||
}).toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
// marginType is the child-margin counterpart of a layout's paddingType.
|
||||
test('marginType round-trips', (ctx) => {
|
||||
const b = board(ctx);
|
||||
@@ -601,7 +491,7 @@ describe('Layout', () => {
|
||||
b.resize(300, 200);
|
||||
b.addFlexLayout();
|
||||
|
||||
const found = ctx.penpot.currentPage!.getShapeById(b.id) as Board;
|
||||
const found = ctx.penpot.currentPage.getShapeById(b.id) as Board;
|
||||
expect(found).not.toBeNull();
|
||||
const child = ctx.penpot.createRectangle();
|
||||
found.appendChild(child);
|
||||
|
||||
@@ -206,85 +206,28 @@ describe('Library', () => {
|
||||
const typo = ctx.penpot.library.local.createTypography();
|
||||
expect(typeof typo.fontFamily).toBe('string');
|
||||
|
||||
const font = ctx.penpot.fonts.all[0];
|
||||
typo.fontFamily = font.fontFamily;
|
||||
typo.fontId = font.fontId;
|
||||
expect(typo.fontFamily).toBe(font.fontFamily);
|
||||
expect(typo.fontId).toBe(font.fontId);
|
||||
typo.fontFamily = 'Arial';
|
||||
typo.fontId = 'gfont-arial';
|
||||
expect(typo.fontFamily).toBe('Arial');
|
||||
expect(typo.fontId).toBe('gfont-arial');
|
||||
});
|
||||
|
||||
test('typography style members round-trip', (ctx) => {
|
||||
const typo = ctx.penpot.library.local.createTypography();
|
||||
const font =
|
||||
ctx.penpot.fonts.all.find((item) =>
|
||||
item.variants.some((variant) => variant.fontStyle === 'italic'),
|
||||
) ?? ctx.penpot.fonts.all[0];
|
||||
typo.setFont(font);
|
||||
const italic = font.variants.find(
|
||||
(variant) => variant.fontStyle === 'italic',
|
||||
);
|
||||
if (italic) {
|
||||
typo.fontStyle = italic.fontStyle;
|
||||
expect(typo.fontStyle).toBe(italic.fontStyle);
|
||||
expect(typo.fontVariantId).toBe(italic.fontVariantId);
|
||||
expect(typo.fontWeight).toBe(italic.fontWeight);
|
||||
}
|
||||
typo.fontStyle = 'italic';
|
||||
typo.textTransform = 'uppercase';
|
||||
const variant = font.variants[font.variants.length - 1];
|
||||
typo.fontWeight = variant.fontWeight;
|
||||
typo.fontVariantId = variant.fontVariantId;
|
||||
typo.fontWeight = '700';
|
||||
typo.fontVariantId = 'regular';
|
||||
typo.lineHeight = '1.5';
|
||||
typo.letterSpacing = '1';
|
||||
expect(typo.fontStyle).toBe('italic');
|
||||
expect(typo.textTransform).toBe('uppercase');
|
||||
expect(typo.fontWeight).toBe(variant.fontWeight);
|
||||
expect(typo.fontStyle).toBe(variant.fontStyle);
|
||||
expect(typo.fontVariantId).toBe(variant.fontVariantId);
|
||||
expect(typo.lineHeight).toBe('1.5');
|
||||
expect(typo.fontWeight).toBe('700');
|
||||
expect(typo.fontVariantId).toBe('regular');
|
||||
expect(typeof typo.lineHeight).toBe('string');
|
||||
expect(typeof typo.letterSpacing).toBe('string');
|
||||
});
|
||||
|
||||
test('typography text values use text validation', (ctx) => {
|
||||
const typo = ctx.penpot.library.local.createTypography();
|
||||
expect(() => {
|
||||
typo.fontSize = '2';
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typo.fontSize = '12px';
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typo.lineHeight = '201';
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typo.lineHeight = '12px';
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typo.letterSpacing = '12px';
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typo.textTransform = 'not-a-transform' as unknown as 'uppercase';
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('typography font fields require installed fonts and variants', (ctx) => {
|
||||
const typo = ctx.penpot.library.local.createTypography();
|
||||
expect(() => {
|
||||
typo.fontId = 'missing-font';
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typo.fontFamily = 'Missing Font Family';
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typo.fontVariantId = 'missing-variant';
|
||||
}).toThrow();
|
||||
|
||||
const fonts = ctx.penpot.fonts.all;
|
||||
const first = fonts[0];
|
||||
const second = fonts.find((font) => font.fontId !== first.fontId);
|
||||
if (second) {
|
||||
expect(() => typo.setFont(first, second.variants[0])).toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
test('typography setFont updates the font', (ctx) => {
|
||||
const typo = ctx.penpot.library.local.createTypography();
|
||||
const font = ctx.penpot.fonts.all[0];
|
||||
|
||||
Loaded 100 of 111 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user