Compare commits

...

26 Commits

Author SHA1 Message Date
Andrey Antukh
bc8b72f6f1 WIP 2026-01-27 19:23:03 +01:00
Andrey Antukh
3dbd931e96 💄 Move flag check to the entry point instead of initialize event 2026-01-27 18:39:45 +01:00
Andrey Antukh
014e3b0e4b Move devtools perf logging helpers to util.perf ns 2026-01-27 18:37:04 +01:00
Andrey Antukh
b6f5000d1c ⬆️ Update pnpm 2026-01-27 17:57:07 +01:00
Andrey Antukh
0527124f2f Merge remote-tracking branch 'origin/staging-render' into develop 2026-01-27 17:56:03 +01:00
Andrey Antukh
faf91ac70d Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-27 17:53:16 +01:00
Eva Marco
9ca76c745f 🐛 Fix app freeze on token name change (#8214) 2026-01-27 17:31:50 +01:00
Andrey Antukh
e8fd4698c9 🔧 Update caddy configuration 2026-01-27 15:10:53 +01:00
Andrey Antukh
0ab126748f 💄 Add format rule for code comments (#8211)
* 💄 Add format rule for code comments

* ⬆️ Update linter and formatter on devenv
2026-01-27 15:07:18 +01:00
Yamila Moreno
71a5ab9913 🔧 Delete unused workflow 2026-01-27 13:47:05 +01:00
Elena Torró
9808b6ca57 Merge pull request #8205 from penpot/superalex-improve-huge-shapes-render
🎉 Improving huge shapes render
2026-01-27 13:08:25 +01:00
Aitor Moreno
de41cb5488 🐛 Fix add/remove fills to text nodes 2026-01-27 12:17:10 +01:00
Alejandro Alonso
b40ccaf030 🎉 Improve zoom actions for huge shapes 2026-01-27 11:11:38 +01:00
Alejandro Alonso
7d3ac38749 🎉 Improve huge shapes rendering 2026-01-27 11:11:38 +01:00
Elena Torro
8d1bc6c50c 🐛 Fix flex layout sorting on reverse order with no z-index 2026-01-27 09:34:36 +01:00
Andrey Antukh
3112b240a0 📎 Add missing entry on changelog 2026-01-27 09:28:41 +01:00
Andrey Antukh
56fd66b91a 🐛 Fix several issues related to path edition (#8187)
*  Improve save-path-content event consistency

Mainly removing possible race conditions from the event
implementation.

*  Ensure path content snapshot on start-path-edit event

*  Reuse already available shape-id on split-segments
2026-01-27 09:27:42 +01:00
Elena Torro
2a7c24f6fd 🐛 Fix shape operations on sidebar when using interaction observer 2026-01-27 09:03:41 +01:00
Alejandro Alonso
947aa22dee Merge pull request #8173 from penpot/elenatorro-improve-surface-performance
🔧 Improve surface rendering performance
2026-01-27 07:21:23 +01:00
David Barragán Merino
1ce0b60e3d 🔧 Run all the jobs if the workflow is launched manually 2026-01-26 17:13:54 +01:00
Elena Torro
5209a8b423 🔧 Improve surface rendering performance 2026-01-26 16:10:22 +01:00
Aitor Moreno
f4f4f5bbb5 🐛 Fix multiple issues and tests 2026-01-26 14:14:06 +01:00
David Barragán Merino
ef80901400 🔧 Enable secret inheritance 2026-01-26 14:00:55 +01:00
David Barragán Merino
5306bed548 🔧 Define deploy plugin packages workflows 2026-01-26 13:47:57 +01:00
David Barragán Merino
92a319ddd1 🔧 Rename wrangle to wrangler 2026-01-26 13:47:57 +01:00
David Barragán Merino
68a6d4c9a8 🔧 Add deploy plugin packages workflow placeholder and wrangle config files 2026-01-26 13:47:57 +01:00
129 changed files with 1882 additions and 1054 deletions

View File

@@ -45,6 +45,15 @@
:potok/reify-type
{:level :error}
:redundant-primitive-coercion
{:level :off}
:unused-excluded-var
{:level :off}
:unresolved-excluded-var
{:level :off}
:missing-protocol-method
{:level :off}

View File

@@ -2,6 +2,11 @@
:remove-multiple-non-indenting-spaces? false
:remove-surrounding-whitespace? true
:remove-consecutive-blank-lines? false
:indent-line-comments? true
:parallel? true
:align-form-columns? false
;; :align-map-columns? false
;; :align-single-column-lines? false
:extra-indents {rumext.v2/fnc [[:inner 0]]
cljs.test/async [[:inner 0]]
promesa.exec/thread [[:inner 0]]

View File

@@ -1,21 +0,0 @@
name: _NITRATE MODULE
on:
schedule:
- cron: '36 5-20 * * 1-5'
jobs:
build-bundle:
uses: ./.github/workflows/build-bundle.yml
secrets: inherit
with:
gh_ref: "nitrate-module"
build_wasm: "yes"
build_storybook: "yes"
build-docker:
needs: build-bundle
uses: ./.github/workflows/build-docker.yml
secrets: inherit
with:
gh_ref: "nitrate-module"

View File

@@ -69,7 +69,8 @@
- Fix exception on uploading large fonts [Github #8135](https://github.com/penpot/penpot/pull/8135)
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
- Fix incorrect handling of input values on layout gap and padding inputs [Github #8113](https://github.com/penpot/penpot/issues/8113)
- Fix several race conditions on path editor [Github #8187](https://github.com/penpot/penpot/pull/8187)
- Fix app freeze when introducing an error on a very long token name [Taiga #13214](https://tree.taiga.io/project/penpot/issue/13214)
## 2.12.1

View File

@@ -873,11 +873,8 @@
(import-storage-objects cfg)
(let [files (get manifest :files)
result (reduce (fn [result {:keys [id] :as file}]
result (reduce (fn [result file]
(let [name' (get file :name)
name' (if (map? name)
(get name id)
name')
file (assoc file :name name')]
(conj result (import-file cfg file))))
[]

View File

@@ -6,7 +6,6 @@
(ns app.http.sse
"SSE (server sent events) helpers"
(:refer-clojure :exclude [tap])
(:require
[app.common.data :as d]
[app.common.logging :as l]

View File

@@ -248,11 +248,11 @@
invitations (into #{}
(comp
;; We don't re-send invitations to
;; already existing members
;; We don't re-send invitations to
;; already existing members
(remove #(contains? team-members (:email %)))
;; We don't send invitations to
;; join-requested members
;; We don't send invitations to
;; join-requested members
(remove #(contains? join-requests (:email %)))
(map (fn [{:keys [email role]}]
(create-invitation cfg

View File

@@ -8,7 +8,7 @@
"A generic asynchronous events notifications subsystem; used mainly
for mark event points in functions and be able to attach listeners
to them. Mainly used in http.sse for progress reporting."
(:refer-clojure :exclude [tap run!])
(:refer-clojure :exclude [run!])
(:require
[app.common.exceptions :as ex]
[app.common.logging :as l]

View File

@@ -841,7 +841,7 @@
out (th/command! data)
error (:error out)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (th/ex-info? error))
(t/is (th/ex-of-type? error :not-found))))
@@ -863,7 +863,7 @@
out (th/command! data)
error (:error out)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (th/ex-info? error))
(t/is (th/ex-of-type? error :not-found))))
@@ -1261,7 +1261,7 @@
(t/is (= 1 (count rows)))
(t/is (every? #(some? (:data %)) rows)))
;; Mark the file ellegible again for GC
;; Mark the file ellegible again for GC
(th/db-update! :file
{:has-media-trimmed false}
{:id (:id file)})
@@ -1318,7 +1318,7 @@
{:file-id (:id file)
:type "fragment"}
{:order-by [:created-at]})]
;; (pp/pprint rows)
;; (pp/pprint rows)
(t/is (= 2 (count rows)))
(t/is (nil? (:data row1)))
(t/is (= "storage" (:backend row1)))

View File

@@ -536,7 +536,7 @@
:token rtoken}
{:keys [result error] :as out} (th/command! data)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (nil? error))
(t/is (map? result))
(t/is (string? (:invitation-token result))))))

View File

@@ -30,7 +30,7 @@
:team-id (:id team)
:name "test project"}
out (th/command! data)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (nil? (:error out)))
(let [result (:result out)]
@@ -93,7 +93,7 @@
:id project-id}
out (th/command! data)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (nil? (:error out)))
(t/is (nil? (:result out))))

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
"packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264",
"type": "module",
"repository": {
"type": "git",

View File

@@ -1092,9 +1092,9 @@
(if (number? num)
(try
(let [num-str (mth/to-fixed num precision)
;; Remove all trailing zeros after the comma 100.00000
;; Remove all trailing zeros after the comma 100.00000
num-str (str/replace num-str trail-zeros-regex-1 "")]
;; Remove trailing zeros after a decimal number: 0.001|00|
;; Remove trailing zeros after a decimal number: 0.001|00|
(if-let [m (re-find trail-zeros-regex-2 num-str)]
(str/replace num-str (first m) (second m))
num-str))

View File

@@ -44,7 +44,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Set parent to root frame.
;; Set parent to root frame.
(log/debug :hint " -> set to " :parent-id uuid/zero)
(assoc shape :parent-id uuid/zero))]
@@ -57,7 +57,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [parent-shape]
; Add shape to parent's children list
;; Add shape to parent's children list
(log/debug :hint " -> add children to" :parent-id (:id parent-shape))
(update parent-shape :shapes conj (:id shape)))]
@@ -70,7 +70,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Remove duplicated
;; Remove duplicated
(log/debug :hint " -> remove duplicated children")
(update shape :shapes distinct))]
@@ -102,7 +102,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Locate the first frame in parents and set frame-id to it.
;; Locate the first frame in parents and set frame-id to it.
(let [page (ctpl/get-page file-data page-id)
frame (cfh/get-frame (:objects page) (:parent-id shape))
frame-id (or (:id frame) uuid/zero)]
@@ -118,7 +118,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Locate the first frame in parents and set frame-id to it.
;; Locate the first frame in parents and set frame-id to it.
(let [page (ctpl/get-page file-data page-id)
frame (cfh/get-frame (:objects page) (:parent-id shape))
frame-id (or (:id frame) uuid/zero)]
@@ -134,7 +134,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Set the :shape as main instance root
;; Set the :shape as main instance root
(log/debug :hint " -> set :main-instance")
(assoc shape :main-instance true))]
@@ -147,12 +147,13 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Set :component-file to local file
;; Set :component-file to local file
(log/debug :hint " -> set :component-file to local file")
(assoc shape :component-file (:id file-data)))]
; There is no solution that may recover it with confidence
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
;; shape)]
;; There is no solution that may recover it with confidence
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
;; shape)]
(log/dbg :hint "repairing shape :component-main-external" :id (:id shape) :name (:name shape) :page-id page-id)
(-> (pcb/empty-changes nil page-id)
@@ -166,12 +167,12 @@
repair-shape
(fn [shape]
; Detach the shape and convert it to non instance.
;; Detach the shape and convert it to non instance.
(log/debug :hint " -> detach shape" :shape-id (:id shape))
(ctk/detach-shape shape))]
; There is no solution that may recover it with confidence
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
;; shape)]
;; There is no solution that may recover it with confidence
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
;; shape)]
(log/dbg :hint "repairing shape :component-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
(-> (pcb/empty-changes nil page-id)
@@ -184,7 +185,7 @@
repair-component
(fn [component]
; Assign main instance in the component to current shape
;; Assign main instance in the component to current shape
(log/debug :hint " -> assign main-instance-id" :component-id (:id component))
(assoc component :main-instance-id (:id shape)))
@@ -207,7 +208,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-component
(fn [component]
; Assign main instance in the component to current shape
;; Assign main instance in the component to current shape
(log/debug :hint " -> assign main-instance-page" :component-id (:id component))
(assoc component :main-instance-page page-id))]
(log/dbg :hint "repairing shape :invalid-main-instance-page" :id (:id shape) :name (:name shape) :page-id page-id)
@@ -219,7 +220,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; There is no solution that may recover it with confidence
;; There is no solution that may recover it with confidence
(log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
shape)]
@@ -232,7 +233,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Unset the :shape as main instance root
;; Unset the :shape as main instance root
(log/debug :hint " -> unset :main-instance")
(dissoc shape :main-instance))]
@@ -245,7 +246,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a top copy root.
;; Convert the shape in a top copy root.
(log/debug :hint " -> set :component-root")
(assoc shape :component-root true))]
@@ -258,7 +259,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a nested copy root.
;; Convert the shape in a nested copy root.
(log/debug :hint " -> unset :component-root")
(dissoc shape :component-root))]
@@ -307,8 +308,8 @@
(log/debug :hint " -> detach shape" :shape-id (:id shape))
(ctk/detach-shape shape))]
; If the shape still refers to the remote component, try to find the corresponding near one
; and link to it. If not, detach the shape.
;; If the shape still refers to the remote component, try to find the corresponding near one
;; and link to it. If not, detach the shape.
(log/dbg :hint "repairing shape :ref-shape-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
(if (some? matching-shape)
(-> (pcb/empty-changes nil page-id)
@@ -324,7 +325,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert shape in a normal copy, removing nested copy status
;; Convert shape in a normal copy, removing nested copy status
(log/debug :hint " -> unhead shape")
(ctk/unhead-shape shape))]
@@ -337,7 +338,7 @@
[_ {:keys [shape page-id args] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert shape in a nested head, adding component info
;; Convert shape in a nested head, adding component info
(log/debug :hint " -> reroot shape")
(ctk/rehead-shape shape (:component-file args) (:component-id args)))]
@@ -350,8 +351,9 @@
[_ {:keys [shape args] :as error} file-data _]
(let [repair-component
(fn [component]
(let [objects (:objects component) ;; we only have encounter this on deleted components,
;; so the relevant objects are inside the component
(let [objects (:objects component)
;; we only have encounter this on deleted components,
;; so the relevant objects are inside the component
to-detach (->> (:cycles-ids args)
(map #(get objects %))
(map #(ctn/get-head-shape objects %))
@@ -378,7 +380,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Remove shape-ref
;; Remove shape-ref
(log/debug :hint " -> unset :shape-ref")
(dissoc shape :shape-ref))]
@@ -391,7 +393,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a nested main head.
;; Convert the shape in a nested main head.
(log/debug :hint " -> unset :component-root")
(dissoc shape :component-root))]
@@ -404,7 +406,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a top main head.
;; Convert the shape in a top main head.
(log/debug :hint " -> set :component-root")
(assoc shape :component-root true))]
@@ -418,7 +420,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a nested copy head.
;; Convert the shape in a nested copy head.
(log/debug :hint " -> unset :component-root")
(dissoc shape :component-root))]
@@ -431,7 +433,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a top copy root.
;; Convert the shape in a top copy root.
(log/debug :hint " -> set :component-root")
(assoc shape :component-root true))]
@@ -444,7 +446,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Detach the shape and convert it to non instance.
;; Detach the shape and convert it to non instance.
(log/debug :hint " -> detach shape" :shape-id (:id shape))
(ctk/detach-shape shape))]
@@ -457,7 +459,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Detach the shape and convert it to non instance.
;; Detach the shape and convert it to non instance.
(log/debug :hint " -> detach shape" :shape-id (:id shape))
(ctk/detach-shape shape))]
@@ -470,7 +472,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; There is no solution that may recover it with confidence
;; There is no solution that may recover it with confidence
(log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
shape)]
@@ -483,7 +485,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a frame.
;; Convert the shape in a frame.
(log/debug :hint " -> set :type :frame")
(assoc shape :type :frame
:fills []
@@ -502,7 +504,7 @@
[_ {:keys [shape] :as error} file-data _]
(let [repair-component
(fn [component]
; Remove the objects key, or set it to {} if the component is deleted
;; Remove the objects key, or set it to {} if the component is deleted
(if (:deleted component)
(do
(log/debug :hint " -> set :objects {}")

View File

@@ -430,8 +430,8 @@
(assoc :frame-id frame-id)
(assoc :svg-viewbox vbox)
(assoc :svg-attrs props)
;; We need to ensure fills are empty on import process
;; because setup-shape assings one by default.
;; We need to ensure fills are empty on import process
;; because setup-shape assings one by default.
(assoc :fills [])
(merge radius-attrs)))))

View File

@@ -124,33 +124,51 @@
(defn adjust-to-viewport
([viewport srect] (adjust-to-viewport viewport srect nil))
([viewport srect {:keys [padding] :or {padding 0}}]
([viewport srect {:keys [padding min-zoom] :or {padding 0 min-zoom nil}}]
(let [gprop (/ (:width viewport)
(:height viewport))
srect (-> srect
(update :x #(- % padding))
(update :y #(- % padding))
(update :width #(+ % padding padding))
(update :height #(+ % padding padding)))
width (:width srect)
height (:height srect)
lprop (/ width height)]
(cond
(> gprop lprop)
(let [width' (* (/ width lprop) gprop)
padding (/ (- width' width) 2)]
(-> srect
(update :x #(- % padding))
(assoc :width width')
(grc/update-rect :position)))
srect-padded (-> srect
(update :x #(- % padding))
(update :y #(- % padding))
(update :width #(+ % padding padding))
(update :height #(+ % padding padding)))
width (:width srect-padded)
height (:height srect-padded)
lprop (/ width height)
adjusted-rect
(cond
(> gprop lprop)
(let [width' (* (/ width lprop) gprop)
padding (/ (- width' width) 2)]
(-> srect-padded
(update :x #(- % padding))
(assoc :width width')
(grc/update-rect :position)))
(< gprop lprop)
(let [height' (/ (* height lprop) gprop)
padding (/ (- height' height) 2)]
(-> srect
(update :y #(- % padding))
(assoc :height height')
(grc/update-rect :position)))
(< gprop lprop)
(let [height' (/ (* height lprop) gprop)
padding (/ (- height' height) 2)]
(-> srect-padded
(update :y #(- % padding))
(assoc :height height')
(grc/update-rect :position)))
:else
(grc/update-rect srect :position)))))
:else
(grc/update-rect srect-padded :position))]
;; If min-zoom is specified and the resulting zoom would be below it,
;; return a rect with the original top-left corner centered in the viewport
;; instead of using the aspect-ratio-adjusted rect (which can push coords
;; extremely far with extreme aspect ratios).
(if (and (some? min-zoom)
(< (/ (:width viewport) (:width adjusted-rect)) min-zoom))
(let [anchor-x (:x srect)
anchor-y (:y srect)
vbox-width (/ (:width viewport) min-zoom)
vbox-height (/ (:height viewport) min-zoom)]
(-> adjusted-rect
(assoc :x (- anchor-x (/ vbox-width 2))
:y (- anchor-y (/ vbox-height 2))
:width vbox-width
:height vbox-height)
(grc/update-rect :position)))
adjusted-rect))))

View File

@@ -49,9 +49,9 @@
(def log-container-ids #{})
(def updatable-attrs (->> (seq (keys ctk/sync-attrs))
;; We don't update the flex-child attrs
;; We don't update the flex-child attrs
(remove ctk/swap-keep-attrs)
;; We don't do automatic update of the `layout-grid-cells` property.
;; We don't do automatic update of the `layout-grid-cells` property.
(remove #(= :layout-grid-cells %))))
(defn enabled-shape?
@@ -1898,10 +1898,10 @@
(gsh/absolute-move shape new-pos)))
(defn- switch-path-change-value
[prev-shape ;; The shape before the switch
current-shape ;; The shape after the switch (a clean copy)
ref-shape ;; The referenced shape on the main component
;; before the switch
[prev-shape ; The shape before the switch
current-shape ; The shape after the switch (a clean copy)
ref-shape ; The referenced shape on the main component
; before the switch
attr]
(let [old-width (-> ref-shape :selrect :width)
new-width (-> prev-shape :selrect :width)
@@ -1918,10 +1918,9 @@
(defn- switch-text-change-value
[prev-content ;; The :content of the text before the switch
current-content ;; The :content of the text after the switch (a clean copy)
ref-content touched] ;; The :content of the referenced text on the main component
;; before the switch
[prev-content ; The :content of the text before the switch
current-content ; The :content of the text after the switch (a clean copy)
ref-content touched] ; The :content of the referenced text on the main component before the switch
(let [;; We need the differences between the contents on the main
;; components. current-content is the content of a clean copy,
;; so for all effects its the same as the content on its main
@@ -2845,8 +2844,8 @@
duplicating-component?
true
(and remove-swap-slot?
;; only remove swap slot of children when the current shape
;; is not a subinstance head nor a instance root
;; only remove swap slot of children when the current shape
;; is not a subinstance head nor a instance root
(not subinstance-head?)
(not instance-root?))
variant-props))
@@ -2902,7 +2901,7 @@
variant-props)
changes))
;; We need to check the changes to get the ids-map
;; We need to check the changes to get the ids-map
ids-map
(into {}
(comp

View File

@@ -138,12 +138,12 @@
ids (cfh/clean-loops objects ids)
in-component-copy?
(fn [shape-id]
;; Look for shapes that are inside a component copy, but are
;; not the root. In this case, they must not be deleted,
;; but hidden (to be able to recover them more easily).
;; If we want to specifically allow altering the copies, this is
;; a special case, like a component swap, in which case we want
;; to delete the old shape
;; Look for shapes that are inside a component copy, but are
;; not the root. In this case, they must not be deleted,
;; but hidden (to be able to recover them more easily).
;; If we want to specifically allow altering the copies, this is
;; a special case, like a component swap, in which case we want
;; to delete the old shape
(let [shape (get objects shape-id)]
(and (ctn/has-any-copy-parent? objects shape)
(not allow-altering-copies))))
@@ -168,9 +168,9 @@
groups-to-unmask
(when-not ignore-mask
(reduce (fn [group-ids id]
;; When the shape to delete is the mask of a masked group,
;; the mask condition must be removed, and it must be
;; converted to a normal group.
;; When the shape to delete is the mask of a masked group,
;; the mask condition must be removed, and it must be
;; converted to a normal group.
(let [obj (lookup id)
parent (lookup (:parent-id obj))]
(if (and (:masked-group parent)
@@ -183,8 +183,8 @@
interacting-shapes
(filter (fn [shape]
;; If any of the deleted shapes is the destination of
;; some interaction, this must be deleted, too.
;; If any of the deleted shapes is the destination of
;; some interaction, this must be deleted, too.
(let [interactions (:interactions shape)]
(some #(and (ctsi/has-destination %)
(contains? ids-to-delete (:destination %)))
@@ -207,7 +207,7 @@
all-parents
(reduce (fn [res id]
;; All parents of any deleted shape must be resized.
;; All parents of any deleted shape must be resized.
(into res (cfh/get-parent-ids objects id)))
(d/ordered-set)
(concat ids-to-delete ids-to-hide))
@@ -239,10 +239,10 @@
(recursive-find-empty-parents parents))))
empty-parents
;; Any parent whose children are all deleted, must be deleted too.
;; If we want to specifically allow altering the copies, this is a special case,
;; for example during a component swap. in this case we are replacing a shape by
;; other one, so must not delete empty parents.
;; Any parent whose children are all deleted, must be deleted too.
;; If we want to specifically allow altering the copies, this is a special case,
;; for example during a component swap. in this case we are replacing a shape by
;; other one, so must not delete empty parents.
(if-not allow-altering-copies
(into (d/ordered-set) (find-all-empty-parents #{}))
#{})
@@ -274,8 +274,8 @@
guides-to-delete)
changes (reduce (fn [changes component-id]
;; It's important to delete the component before the main instance, because we
;; need to store the instance position if we want to restore it later.
;; It's important to delete the component before the main instance, because we
;; need to store the instance position if we want to restore it later.
(pcb/delete-component changes component-id (:id page)))
changes
components-to-delete)
@@ -327,7 +327,7 @@
result #{}]
(if-not current-id
;; Base case, no next element
;; Base case, no next element
result
(let [group (get objects current-id)]
@@ -335,14 +335,14 @@
(not= current-id parent-id)
(empty? (remove removed-id? (:shapes group))))
;; Adds group to the remove and check its parent
;; Adds group to the remove and check its parent
(let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])]
(recur (first to-check)
(rest to-check)
(conj removed-id? current-id)
(conj result current-id)))
;; otherwise recur
;; otherwise recur
(recur (first to-check)
(rest to-check)
removed-id?

View File

@@ -111,7 +111,7 @@
"Check if any ancestor of a shape (between base-parent-id and shape) was swapped"
[shape objects base-parent-id]
(let [ancestors (->> (ctn/get-parent-heads objects shape)
;; Ignore ancestors ahead of base-parent
;; Ignore ancestors ahead of base-parent
(drop-while #(not= base-parent-id (:id %)))
seq)
num-ancestors (count ancestors)

View File

@@ -222,7 +222,7 @@
:else
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
;; We add an end-of-line when finish a paragraph
;; We add an end-of-line when finish a paragraph
new-acc
(if (= (:type node) "paragraph")
(let [[hs ht] (first new-acc)]

View File

@@ -458,13 +458,13 @@
(map #(cfh/components-nesting-loop? objects (:id %) (:id parent)))
(every? nil?)))]
(or
;;We don't want to change the structure of component copies
;;We don't want to change the structure of component copies
(ctk/in-component-copy? parent)
(has-any-copy-parent? objects parent)
;; If we are moving something containing a main instance the container can't be part of a component (neither main nor copy)
;; If we are moving something containing a main instance the container can't be part of a component (neither main nor copy)
(and selected-main-instance? parent-in-component?)
;; Avoid placing a shape as a direct or indirect child of itself,
;; or inside its main component if it's in a copy.
;; Avoid placing a shape as a direct or indirect child of itself,
;; or inside its main component if it's in a copy.
comps-nesting-loop?)))
(defn find-valid-parent-and-frame-ids

View File

@@ -775,9 +775,9 @@
file-data (cond-> file-data
(d/not-empty? used-components)
(absorb-components used-components library-data))
;; Note that absorbed components may also be using colors
;; and typographies. This is the reason of doing this first
;; and accumulating file data for the next ones.
;; Note that absorbed components may also be using colors
;; and typographies. This is the reason of doing this first
;; and accumulating file data for the next ones.
used-colors (find-asset-type-usages file-data library-data :color)
file-data (cond-> file-data
@@ -1017,7 +1017,7 @@
libs-to-show
(-> libs-to-show
(add-component library-id component-id))))))
;; (find-used-components-cumulative page root)
;; (find-used-components-cumulative page root)
libs-to-show
components))

View File

@@ -306,7 +306,7 @@
(-write-to [_ heap offset]
(let [buffer' (.-buffer ^js/DataView dbuffer)
;; Calculate byte size: 4 bytes header + (size * FILL-U8-SIZE)
;; Calculate byte size: 4 bytes header + (size * FILL-U8-SIZE)
byte-size (+ 4 (* size FILL-U8-SIZE))
;; Create Uint32Array with exact size needed (convert bytes to u32 elements)
u32-array (js/Uint32Array. buffer' 0 (/ byte-size 4))]

View File

@@ -13,14 +13,15 @@
[app.common.schema :as sm]
[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.
;; WARNING: options are not deleted when changing event or action
;; type, so it can be restored if the user changes it back later.
;;
;; But that means that an interaction may have for example a delay or
;; destination, even if its type does not require it (but a previous type did).
;; But that means that an interaction may have for example a delay or
;; destination, even if its type does not require it (but a previous
;; type did).
;;
;; So make sure to use has-delay/has-destination... functions, or similar,
;; before reading them.
;; So make sure to use has-delay/has-destination... functions, or
;; similar, before reading them.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMA
@@ -452,16 +453,15 @@
(gpt/point 0 0)))
(defn calc-overlay-position
[interaction ;; interaction data
shape ;; Shape with the interaction
objects ;; the objects tree
relative-to-shape ;; the interaction position is realtive to this
;; sape
base-frame ;; the base frame of the current interaction
dest-frame ;; the frame to display with this interaction
frame-offset] ;; if this interaction starts in a frame opened
;; on another interaction, this is the position
;; of that frame
[interaction ; interaction data
shape ; Shape with the interaction
objects ; the objects tree
relative-to-shape ; the interaction position is realtive to this shape
base-frame ; the base frame of the current interaction
dest-frame ; the frame to display with this interaction
frame-offset] ; if this interaction starts in a frame opened
; on another interaction, this is the position
; of that frame
(assert (check-interaction interaction))
(assert (has-overlay-opts interaction)
"expected compatible interaction map")

View File

@@ -382,9 +382,9 @@
keep-ids? (:id shape)
:else (uuid/next))
;; Assign the correct frame-id for the given parent. It's the parent-id (if parent is frame)
;; or the parent's frame-id otherwise. Only for the first cloned shapes. In recursive calls
;; this is not needed.
;; Assign the correct frame-id for the given parent. It's the parent-id (if parent is frame)
;; or the parent's frame-id otherwise. Only for the first cloned shapes. In recursive calls
;; this is not needed.
frame-id (cond
(and (nil? frame-id) (cfh/frame-shape? dest-objects parent-id))
parent-id

View File

@@ -393,7 +393,7 @@
:else
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
;; We add an end-of-line when finish a paragraph
;; We add an end-of-line when finish a paragraph
new-acc
(if (= (:type node) "paragraph")
(let [[hs ht] (first new-acc)]

View File

@@ -111,7 +111,7 @@
(def token-name-ref
[:re {:title "TokenNameRef" :gen/gen sg/text}
#"^(?!\$)([a-zA-Z0-9-$_]+\.?)*(?<!\.)$"])
#"^[a-zA-Z0-9_-][a-zA-Z0-9$_-]*(\.[a-zA-Z0-9$_-]+)*$"])
(def ^:private schema:color
[:map

View File

@@ -693,7 +693,7 @@
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
;; Update the attrs on all the content tree
;; Update the attrs on all the content tree
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :font-size] "32")
@@ -851,7 +851,7 @@
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
;; Update the attrs on all the content tree
;; Update the attrs on all the content tree
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :font-size] "32")

View File

@@ -267,7 +267,7 @@
page' (thf/current-page file')
objects' (:objects page')]
;; ==== Check
;; ==== Check
(thf/validate-file! file')
(t/is (= (count (:components data)) 2))
(t/is (= (count (:components data')) 4))

View File

@@ -179,9 +179,9 @@ RUN set -eux; \
FROM base AS setup-utils
ENV CLJKONDO_VERSION=2025.07.28 \
ENV CLJKONDO_VERSION=2026.01.19 \
BABASHKA_VERSION=1.12.208 \
CLJFMT_VERSION=0.13.1
CLJFMT_VERSION=0.15.6
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
@@ -398,7 +398,6 @@ COPY files/Caddyfile /home/
COPY files/selfsigned.crt /home/
COPY files/selfsigned.key /home/
COPY files/start-tmux.sh /home/start-tmux.sh
COPY files/start-tmux-back.sh /home/start-tmux-back.sh
COPY files/entrypoint.sh /home/entrypoint.sh
COPY files/init.sh /home/init.sh

View File

@@ -1,16 +1,17 @@
{
auto_https off
auto_https off
}
localhost:3449 {
reverse_proxy localhost:4449
tls /home/selfsigned.crt /home/selfsigned.key
reverse_proxy localhost:4449
tls /home/selfsigned.crt /home/selfsigned.key
header -Strict-Transport-Security
}
http://localhost:3450 {
reverse_proxy localhost:4449
reverse_proxy localhost:4449
}
http://penpot-devenv-main:3450 {
reverse_proxy localhost:4449
reverse_proxy localhost:4449
}

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
"packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
"packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264",
"browserslist": [
"defaults"
],

View File

@@ -0,0 +1,155 @@
{
"~:features": {
"~#set": [
"fdata/path-data",
"design-tokens/v1",
"variants/v1",
"layout/grid",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:team-id": "~ud7430f09-4f59-8049-8007-6277bb7586f6",
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true,
"~:can-read": true,
"~:is-logged": true
},
"~:has-media-trimmed": false,
"~:comment-thread-seqn": 0,
"~:name": "flex_index_position",
"~:revn": 114,
"~:modified-at": "~m1769430362161",
"~:vern": 0,
"~:id": "~u31fe2e21-73e7-80f3-8007-73894fb58240",
"~:is-shared": false,
"~:migrations": {
"~#ordered-set": [
"legacy-2",
"legacy-3",
"legacy-5",
"legacy-6",
"legacy-7",
"legacy-8",
"legacy-9",
"legacy-10",
"legacy-11",
"legacy-12",
"legacy-13",
"legacy-14",
"legacy-16",
"legacy-17",
"legacy-18",
"legacy-19",
"legacy-25",
"legacy-26",
"legacy-27",
"legacy-28",
"legacy-29",
"legacy-31",
"legacy-32",
"legacy-33",
"legacy-34",
"legacy-36",
"legacy-37",
"legacy-38",
"legacy-39",
"legacy-40",
"legacy-41",
"legacy-42",
"legacy-43",
"legacy-44",
"legacy-45",
"legacy-46",
"legacy-47",
"legacy-48",
"legacy-49",
"legacy-50",
"legacy-51",
"legacy-52",
"legacy-53",
"legacy-54",
"legacy-55",
"legacy-56",
"legacy-57",
"legacy-59",
"legacy-62",
"legacy-65",
"legacy-66",
"legacy-67",
"0001-remove-tokens-from-groups",
"0002-normalize-bool-content-v2",
"0002-clean-shape-interactions",
"0003-fix-root-shape",
"0003-convert-path-content-v2",
"0005-deprecate-image-type",
"0006-fix-old-texts-fills",
"0008-fix-library-colors-v4",
"0009-clean-library-colors",
"0009-add-partial-text-touched-flags",
"0010-fix-swap-slots-pointing-non-existent-shapes",
"0011-fix-invalid-text-touched-flags",
"0012-fix-position-data",
"0013-fix-component-path",
"0013-clear-invalid-strokes-and-fills",
"0014-fix-tokens-lib-duplicate-ids",
"0014-clear-components-nil-objects",
"0015-fix-text-attrs-blank-strings",
"0015-clean-shadow-color",
"0016-copy-fills-from-position-data-to-text-node"
]
},
"~:version": 67,
"~:project-id": "~ud7430f09-4f59-8049-8007-6277bb765abd",
"~:created-at": "~m1769007798998",
"~:backend": "legacy-db",
"~:data": {
"~:pages": [
"~u02e9633d-4ce7-80da-8007-736558496fa8"
],
"~:pages-index": {
"~u02e9633d-4ce7-80da-8007-736558496fa8": {
"~:id": "~u02e9633d-4ce7-80da-8007-736558496fa8",
"~:name": "Page 1",
"~:objects": {
"~#penpot/objects-map/v2": {
"~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^I\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d50980078e\",\"~u94eaebe4-addd-80d1-8007-79d508a9dc2f\",\"~u94eaebe4-addd-80d1-8007-79d5055d6859\",\"~u77c71dba-32ee-804c-8007-736561cf857f\"]]]",
"~u77c71dba-32ee-804c-8007-736561cff457": "[\"~#shape\",[\"^ \",\"~:y\",396.00000357564704,\"~:rx\",8,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",80,\"~:transforming\",false,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",396.00000357564704]],[\"^>\",[\"^ \",\"~:x\",768.9999775886536,\"~:y\",396.00000357564704]],[\"^>\",[\"^ \",\"~:x\",768.9999775886536,\"~:y\",476.00000357564704]],[\"^>\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",476.00000357564704]]],\"~:r2\",8,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:r3\",8,\"~:r1\",8,\"~:id\",\"~u77c71dba-32ee-804c-8007-736561cff457\",\"~:parent-id\",\"~u77c71dba-32ee-804c-8007-736561cf8584\",\"~:frame-id\",\"~u77c71dba-32ee-804c-8007-736561cf8584\",\"~:strokes\",[],\"~:x\",688.9999775886536,\"~:proportion\",1,\"~:r4\",8,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",396.00000357564704,\"^9\",80,\"~:height\",80,\"~:x1\",688.9999775886536,\"~:y1\",396.00000357564704,\"~:x2\",768.9999775886536,\"~:y2\",476.00000357564704]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#e8e9ea\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"~:ry\",8,\"^M\",80,\"~:flip-y\",null]]",
"~u94eaebe4-addd-80d1-8007-79d508aa2885": "[\"~#shape\",[\"^ \",\"~:y\",612.0000188344361,\"~:rx\",8,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",80,\"~:transforming\",false,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",604.9999165534973,\"~:y\",612.0000188344361]],[\"^>\",[\"^ \",\"~:x\",684.9999165534973,\"~:y\",612.0000188344361]],[\"^>\",[\"^ \",\"~:x\",684.9999165534973,\"~:y\",692.0000188344361]],[\"^>\",[\"^ \",\"~:x\",604.9999165534973,\"~:y\",692.0000188344361]]],\"~:r2\",8,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:r3\",8,\"~:r1\",8,\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d508aa2885\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d508a9dc30\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d508a9dc30\",\"~:strokes\",[],\"~:x\",604.9999165534973,\"~:proportion\",1,\"~:r4\",8,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",604.9999165534973,\"~:y\",612.0000188344361,\"^9\",80,\"~:height\",80,\"~:x1\",604.9999165534973,\"~:y1\",612.0000188344361,\"~:x2\",684.9999165534973,\"~:y2\",692.0000188344361]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#e8e9ea\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"~:ry\",8,\"^M\",80,\"~:flip-y\",null]]",
"~u94eaebe4-addd-80d1-8007-79d508aa2886": "[\"~#shape\",[\"^ \",\"~:y\",636.0000188344361,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:rx\",8,\"~:layout-padding\",[\"^ \",\"~:p1\",8,\"~:p2\",12,\"~:p3\",8,\"~:p4\",12],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"Dark / Button / Primary / Text / Default\",\"~:layout-align-items\",\"~:center\",\"~:width\",66,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",611.9999165534973,\"~:y\",636.0000188344361]],[\"^K\",[\"^ \",\"~:x\",677.9999165534973,\"~:y\",636.0000188344361]],[\"^K\",[\"^ \",\"~:x\",677.9999165534973,\"~:y\",668.0000188344361]],[\"^K\",[\"^ \",\"~:x\",611.9999165534973,\"~:y\",668.0000188344361]]],\"~:r2\",8,\"~:show-content\",true,\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",4,\"~:column-gap\",4],\"~:transform-inverse\",[\"^;\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:r3\",8,\"~:layout-justify-content\",\"^D\",\"~:r1\",8,\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d508aa2886\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d508a9dc30\",\"~:layout-flex-dir\",\"~:row\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d508a9dc30\",\"~:strokes\",[],\"~:x\",611.9999165534973,\"~:proportion\",1,\"~:r4\",8,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",611.9999165534973,\"~:y\",636.0000188344361,\"^E\",66,\"~:height\",32,\"~:x1\",611.9999165534973,\"~:y1\",636.0000188344361,\"~:x2\",677.9999165534973,\"~:y2\",668.0000188344361]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#7efff5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"~:ry\",8,\"^17\",32,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d508aa2887\"]]]",
"~u94eaebe4-addd-80d1-8007-79d508aa2887": "[\"~#shape\",[\"^ \",\"~:y\",644.0000188344361,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",0,\"~:p2\",2,\"~:p3\",0,\"~:p4\",2],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"_Utilities / Text / White\",\"~:layout-align-items\",\"~:start\",\"~:width\",42,\"~:layout-padding-type\",\"~:simple\",\"~:transforming\",false,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",623.9999165534973,\"~:y\",644.0000188344361]],[\"^K\",[\"^ \",\"~:x\",665.9999165534973,\"~:y\",644.0000188344361]],[\"^K\",[\"^ \",\"~:x\",665.9999165534973,\"~:y\",660.0000188344361]],[\"^K\",[\"^ \",\"~:x\",623.9999165534973,\"~:y\",660.0000188344361]]],\"~:show-content\",true,\"~:layout-item-h-sizing\",\"~:auto\",\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",0,\"~:column-gap\",6],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:layout-justify-content\",\"~:center\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d508aa2887\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d508aa2886\",\"~:layout-flex-dir\",\"~:column\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d508aa2886\",\"~:strokes\",[],\"~:x\",623.9999165534973,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",623.9999165534973,\"~:y\",644.0000188344361,\"^D\",42,\"~:height\",16,\"~:x1\",623.9999165534973,\"~:y1\",644.0000188344361,\"~:x2\",665.9999165534973,\"~:y2\",660.0000188344361]],\"~:fills\",[],\"~:flip-x\",null,\"^16\",16,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d508aa2888\"]]]",
"~u94eaebe4-addd-80d1-8007-79d508aa2888": "[\"~#shape\",[\"^ \",\"~:y\",645.0000188344363,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:auto-width\",\"~:index\",null,\"~:content\",[\"^ \",\"~:type\",\"root\",\"~:children\",[[\"^ \",\"^8\",\"paragraph-set\",\"^9\",[[\"^ \",\"~:line-height\",\"1.2\",\"~:path\",\"\",\"~:font-style\",\"normal\",\"^9\",[[\"^ \",\"^:\",\"1.2\",\"^;\",\"\",\"^<\",\"normal\",\"~:text-transform\",\"uppercase\",\"~:text-align\",\"left\",\"~:font-id\",\"gfont-work-sans\",\"~:font-size\",\"12\",\"~:font-weight\",\"500\",\"~:modified-at\",\"2024-06-04T14:15:09.786Z\",\"~:font-variant-id\",\"500\",\"~:text-decoration\",\"underline\",\"~:letter-spacing\",\"0\",\"~:fills\",[[\"^ \",\"~:fill-color\",\"#000000\",\"~:fill-color-ref-file\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"~:fill-opacity\",1,\"~:fill-color-ref-id\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"~:font-family\",\"Work Sans\",\"~:text\",\"Label\"]],\"^=\",\"uppercase\",\"^>\",\"center\",\"^?\",\"gfont-work-sans\",\"^@\",\"12\",\"^A\",\"500\",\"^8\",\"paragraph\",\"^B\",\"2024-06-04T14:15:09.786Z\",\"^C\",\"500\",\"^D\",\"underline\",\"^E\",\"0\",\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^H\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"^I\",1,\"^J\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"^K\",\"Work Sans\"]]]],\"~:vertical-align\",\"center\",\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^I\",1]]],\"~:hide-in-viewer\",true,\"~:name\",\"Input\",\"~:saved-component-root\",null,\"~:width\",38,\"^8\",\"^L\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",625.9999165534973,\"~:y\",645.0000188344363]],[\"^S\",[\"^ \",\"~:x\",663.9999165534973,\"~:y\",645.0000188344363]],[\"^S\",[\"^ \",\"~:x\",663.9999165534973,\"~:y\",660.0000188344359]],[\"^S\",[\"^ \",\"~:x\",625.9999165534973,\"~:y\",660.0000188344363]]],\"~:layout-item-h-sizing\",\"~:fix\",\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d508aa2888\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d508aa2887\",\"~:position-data\",[[\"^ \",\"~:y\",659.3400268554688,\"^:\",\"1.2\",\"^<\",\"normal\",\"^=\",\"uppercase\",\"^>\",\"left\",\"^?\",\"sourcesanspro\",\"^@\",\"12\",\"^A\",\"500\",\"~:text-direction\",\"ltr\",\"^Q\",37.94000244140625,\"^C\",\"regular\",\"^D\",\"underline\",\"^E\",\"0\",\"~:x\",626.0299682617188,\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^H\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"^I\",1,\"^J\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"~:direction\",\"ltr\",\"^K\",\"Work Sans\",\"~:height\",14.08001708984375,\"^L\",\"Label\"]],\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d508aa2887\",\"~:strokes\",[],\"~:x\",625.9999165534973,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",625.9999165534973,\"~:y\",645.0000188344363,\"^Q\",38,\"^11\",15,\"~:x1\",625.9999165534973,\"~:y1\",645.0000188344363,\"~:x2\",663.9999165534973,\"~:y2\",660.0000188344363]],\"^F\",[],\"~:flip-x\",null,\"^11\",15,\"~:flip-y\",null]]",
"~u77c71dba-32ee-804c-8007-736561cff45a": "[\"~#shape\",[\"^ \",\"~:y\",429.00000357564727,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:auto-width\",\"~:index\",null,\"~:content\",[\"^ \",\"~:type\",\"root\",\"~:children\",[[\"^ \",\"^8\",\"paragraph-set\",\"^9\",[[\"^ \",\"~:line-height\",\"1.2\",\"~:path\",\"\",\"~:font-style\",\"normal\",\"^9\",[[\"^ \",\"^:\",\"1.2\",\"^;\",\"\",\"^<\",\"normal\",\"~:text-transform\",\"uppercase\",\"~:text-align\",\"left\",\"~:font-id\",\"gfont-work-sans\",\"~:font-size\",\"12\",\"~:font-weight\",\"500\",\"~:modified-at\",\"2024-06-04T14:15:09.786Z\",\"~:font-variant-id\",\"500\",\"~:text-decoration\",\"underline\",\"~:letter-spacing\",\"0\",\"~:fills\",[[\"^ \",\"~:fill-color\",\"#000000\",\"~:fill-color-ref-file\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"~:fill-opacity\",1,\"~:fill-color-ref-id\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"~:font-family\",\"Work Sans\",\"~:text\",\"Label\"]],\"^=\",\"uppercase\",\"^>\",\"center\",\"^?\",\"gfont-work-sans\",\"^@\",\"12\",\"^A\",\"500\",\"^8\",\"paragraph\",\"^B\",\"2024-06-04T14:15:09.786Z\",\"^C\",\"500\",\"^D\",\"underline\",\"^E\",\"0\",\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^H\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"^I\",1,\"^J\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"^K\",\"Work Sans\"]]]],\"~:vertical-align\",\"center\",\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^I\",1]]],\"~:hide-in-viewer\",true,\"~:name\",\"Input\",\"~:saved-component-root\",null,\"~:width\",38,\"^8\",\"^L\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",709.9999775886536,\"~:y\",429.00000357564727]],[\"^S\",[\"^ \",\"~:x\",747.9999775886536,\"~:y\",429.00000357564727]],[\"^S\",[\"^ \",\"~:x\",747.9999775886536,\"~:y\",444.0000035756468]],[\"^S\",[\"^ \",\"~:x\",709.9999775886536,\"~:y\",444.00000357564727]]],\"~:layout-item-h-sizing\",\"~:fix\",\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:id\",\"~u77c71dba-32ee-804c-8007-736561cff45a\",\"~:parent-id\",\"~u77c71dba-32ee-804c-8007-736561cff459\",\"~:position-data\",[[\"^ \",\"~:y\",443.3399963378906,\"^:\",\"1.2\",\"^<\",\"normal\",\"^=\",\"uppercase\",\"^>\",\"left\",\"^?\",\"sourcesanspro\",\"^@\",\"12\",\"^A\",\"500\",\"~:text-direction\",\"ltr\",\"^Q\",37.93994140625,\"^C\",\"regular\",\"^D\",\"underline\",\"^E\",\"0\",\"~:x\",710.030029296875,\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^H\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"^I\",1,\"^J\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"~:direction\",\"ltr\",\"^K\",\"Work Sans\",\"~:height\",14.079986572265625,\"^L\",\"Label\"]],\"~:frame-id\",\"~u77c71dba-32ee-804c-8007-736561cff459\",\"~:strokes\",[],\"~:x\",709.9999775886536,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",709.9999775886536,\"~:y\",429.00000357564727,\"^Q\",38,\"^11\",15,\"~:x1\",709.9999775886536,\"~:y1\",429.00000357564727,\"~:x2\",747.9999775886536,\"~:y2\",444.00000357564727]],\"^F\",[],\"~:flip-x\",null,\"^11\",15,\"~:flip-y\",null]]",
"~u77c71dba-32ee-804c-8007-736561cff459": "[\"~#shape\",[\"^ \",\"~:y\",428.00000357564704,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",0,\"~:p2\",2,\"~:p3\",0,\"~:p4\",2],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"_Utilities / Text / White\",\"~:layout-align-items\",\"~:start\",\"~:width\",42,\"~:layout-padding-type\",\"~:simple\",\"~:transforming\",false,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",707.9999775886536,\"~:y\",428.00000357564704]],[\"^K\",[\"^ \",\"~:x\",749.9999775886536,\"~:y\",428.00000357564704]],[\"^K\",[\"^ \",\"~:x\",749.9999775886536,\"~:y\",444.00000357564704]],[\"^K\",[\"^ \",\"~:x\",707.9999775886536,\"~:y\",444.00000357564704]]],\"~:show-content\",true,\"~:layout-item-h-sizing\",\"~:auto\",\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",0,\"~:column-gap\",6],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:layout-justify-content\",\"~:center\",\"~:id\",\"~u77c71dba-32ee-804c-8007-736561cff459\",\"~:parent-id\",\"~u77c71dba-32ee-804c-8007-736561cff458\",\"~:layout-flex-dir\",\"~:column\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u77c71dba-32ee-804c-8007-736561cff458\",\"~:strokes\",[],\"~:x\",707.9999775886536,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",707.9999775886536,\"~:y\",428.00000357564704,\"^D\",42,\"~:height\",16,\"~:x1\",707.9999775886536,\"~:y1\",428.00000357564704,\"~:x2\",749.9999775886536,\"~:y2\",444.00000357564704]],\"~:fills\",[],\"~:flip-x\",null,\"^16\",16,\"~:flip-y\",null,\"~:shapes\",[\"~u77c71dba-32ee-804c-8007-736561cff45a\"]]]",
"~u77c71dba-32ee-804c-8007-736561cff458": "[\"~#shape\",[\"^ \",\"~:y\",420.00000357564704,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:rx\",8,\"~:layout-padding\",[\"^ \",\"~:p1\",8,\"~:p2\",12,\"~:p3\",8,\"~:p4\",12],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"Dark / Button / Primary / Text / Default\",\"~:layout-align-items\",\"~:center\",\"~:width\",66,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",695.9999775886536,\"~:y\",420.00000357564704]],[\"^K\",[\"^ \",\"~:x\",761.9999775886536,\"~:y\",420.00000357564704]],[\"^K\",[\"^ \",\"~:x\",761.9999775886536,\"~:y\",452.00000357564704]],[\"^K\",[\"^ \",\"~:x\",695.9999775886536,\"~:y\",452.00000357564704]]],\"~:r2\",8,\"~:show-content\",true,\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",4,\"~:column-gap\",4],\"~:transform-inverse\",[\"^;\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:r3\",8,\"~:layout-justify-content\",\"^D\",\"~:r1\",8,\"~:id\",\"~u77c71dba-32ee-804c-8007-736561cff458\",\"~:parent-id\",\"~u77c71dba-32ee-804c-8007-736561cf8584\",\"~:layout-flex-dir\",\"~:row\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u77c71dba-32ee-804c-8007-736561cf8584\",\"~:strokes\",[],\"~:x\",695.9999775886536,\"~:proportion\",1,\"~:r4\",8,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",695.9999775886536,\"~:y\",420.00000357564704,\"^E\",66,\"~:height\",32,\"~:x1\",695.9999775886536,\"~:y1\",420.00000357564704,\"~:x2\",761.9999775886536,\"~:y2\",452.00000357564704]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#7efff5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"~:ry\",8,\"^17\",32,\"~:flip-y\",null,\"~:shapes\",[\"~u77c71dba-32ee-804c-8007-736561cff459\"]]]",
"~u77c71dba-32ee-804c-8007-736561cf857f": "[\"~#shape\",[\"^ \",\"~:y\",395.99997913999186,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",0,\"~:p2\",12,\"~:p3\",0,\"~:p4\",12],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:wrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"Board Parent 1\",\"~:layout-align-items\",\"~:start\",\"~:width\",272,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",593.0000386238098,\"~:y\",395.99997913999186]],[\"^J\",[\"^ \",\"~:x\",865.0000386238098,\"~:y\",395.99997913999186]],[\"^J\",[\"^ \",\"~:x\",865.0000386238098,\"~:y\",475.9999669761459]],[\"^J\",[\"^ \",\"~:x\",593.0000386238098,\"~:y\",475.9999669761459]]],\"~:show-content\",true,\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",4,\"~:column-gap\",4],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:layout-item-v-sizing\",\"~:fix\",\"~:layout-justify-content\",\"~:center\",\"~:id\",\"~u77c71dba-32ee-804c-8007-736561cf857f\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:layout-flex-dir\",\"~:row\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",593.0000386238098,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",593.0000386238098,\"~:y\",395.99997913999186,\"^D\",272,\"~:height\",79.99998783615405,\"~:x1\",593.0000386238098,\"~:y1\",395.99997913999186,\"~:x2\",865.0000386238098,\"~:y2\",475.9999669761459]],\"~:fills\",[],\"~:flip-x\",null,\"^15\",79.99998783615405,\"~:flip-y\",null,\"~:shapes\",[\"~u77c71dba-32ee-804c-8007-736561cf8584\"]]]",
"~u94eaebe4-addd-80d1-8007-79d50980078e": "[\"~#shape\",[\"^ \",\"~:y\",720.0000478045426,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",0,\"~:p2\",12,\"~:p3\",0,\"~:p4\",12],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:wrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"Board Parent 4\",\"~:layout-align-items\",\"~:start\",\"~:width\",272,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",592.9998555183411,\"~:y\",720.0000478045426]],[\"^J\",[\"^ \",\"~:x\",864.9998555183411,\"~:y\",720.0000478045426]],[\"^J\",[\"^ \",\"~:x\",864.9998555183411,\"~:y\",800.0000356406968]],[\"^J\",[\"^ \",\"~:x\",592.9998555183411,\"~:y\",800.0000356406968]]],\"~:show-content\",true,\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",4,\"~:column-gap\",4],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:layout-item-v-sizing\",\"~:fix\",\"~:layout-justify-content\",\"~:center\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d50980078e\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:layout-flex-dir\",\"~:column-reverse\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",592.9998555183411,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",592.9998555183411,\"~:y\",720.0000478045426,\"^D\",272,\"~:height\",79.9999878361541,\"~:x1\",592.9998555183411,\"~:y1\",720.0000478045426,\"~:x2\",864.9998555183411,\"~:y2\",800.0000356406968]],\"~:fills\",[],\"~:flip-x\",null,\"^15\",79.9999878361541,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d50980078f\"]]]",
"~u94eaebe4-addd-80d1-8007-79d50980078f": "[\"~#shape\",[\"^ \",\"~:y\",719.9999806874634,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:hide-in-viewer\",true,\"~:name\",\"Board Child\",\"~:width\",80,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",604.9999775886536,\"~:y\",719.9999806874634]],[\"^;\",[\"^ \",\"~:x\",684.9999775886536,\"~:y\",719.9999806874634]],[\"^;\",[\"^ \",\"~:x\",684.9999775886536,\"~:y\",799.9999806874634]],[\"^;\",[\"^ \",\"~:x\",604.9999775886536,\"~:y\",799.9999806874634]]],\"~:show-content\",true,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:constraints-v\",\"~:top\",\"~:constraints-h\",\"~:left\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d50980078f\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d50980078e\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d50980078e\",\"~:strokes\",[],\"~:x\",604.9999775886536,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",604.9999775886536,\"~:y\",719.9999806874634,\"^7\",80,\"~:height\",80,\"~:x1\",604.9999775886536,\"~:y1\",719.9999806874634,\"~:x2\",684.9999775886536,\"~:y2\",799.9999806874634]],\"~:fills\",[],\"~:flip-x\",null,\"^K\",80,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d509800790\",\"~u94eaebe4-addd-80d1-8007-79d509800791\"]]]",
"~u94eaebe4-addd-80d1-8007-79d508a9dc2f": "[\"~#shape\",[\"^ \",\"~:y\",612.000024916359,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",0,\"~:p2\",12,\"~:p3\",0,\"~:p4\",12],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:wrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"Board Parent 3\",\"~:layout-align-items\",\"~:start\",\"~:width\",272,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",592.9999165534973,\"~:y\",612.000024916359]],[\"^J\",[\"^ \",\"~:x\",864.9999165534973,\"~:y\",612.000024916359]],[\"^J\",[\"^ \",\"~:x\",864.9999165534973,\"~:y\",692.0000127525132]],[\"^J\",[\"^ \",\"~:x\",592.9999165534973,\"~:y\",692.0000127525132]]],\"~:show-content\",true,\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",4,\"~:column-gap\",4],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:layout-item-v-sizing\",\"~:fix\",\"~:layout-justify-content\",\"~:center\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d508a9dc2f\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:layout-flex-dir\",\"~:column\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",592.9999165534973,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",592.9999165534973,\"~:y\",612.000024916359,\"^D\",272,\"~:height\",79.9999878361541,\"~:x1\",592.9999165534973,\"~:y1\",612.000024916359,\"~:x2\",864.9999165534973,\"~:y2\",692.0000127525132]],\"~:fills\",[],\"~:flip-x\",null,\"^15\",79.9999878361541,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d508a9dc30\"]]]",
"~u94eaebe4-addd-80d1-8007-79d509800790": "[\"~#shape\",[\"^ \",\"~:y\",720.0000417226197,\"~:rx\",8,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",80,\"~:transforming\",false,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",604.9998555183411,\"~:y\",720.0000417226197]],[\"^>\",[\"^ \",\"~:x\",684.9998555183411,\"~:y\",720.0000417226197]],[\"^>\",[\"^ \",\"~:x\",684.9998555183411,\"~:y\",800.0000417226197]],[\"^>\",[\"^ \",\"~:x\",604.9998555183411,\"~:y\",800.0000417226197]]],\"~:r2\",8,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:r3\",8,\"~:r1\",8,\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d509800790\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d50980078f\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d50980078f\",\"~:strokes\",[],\"~:x\",604.9998555183411,\"~:proportion\",1,\"~:r4\",8,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",604.9998555183411,\"~:y\",720.0000417226197,\"^9\",80,\"~:height\",80,\"~:x1\",604.9998555183411,\"~:y1\",720.0000417226197,\"~:x2\",684.9998555183411,\"~:y2\",800.0000417226197]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#e8e9ea\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"~:ry\",8,\"^M\",80,\"~:flip-y\",null]]",
"~u94eaebe4-addd-80d1-8007-79d508a9dc30": "[\"~#shape\",[\"^ \",\"~:y\",612.0000188344361,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:hide-in-viewer\",true,\"~:name\",\"Board Child\",\"~:width\",80,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",604.9999775886536,\"~:y\",612.0000188344361]],[\"^;\",[\"^ \",\"~:x\",684.9999775886536,\"~:y\",612.0000188344361]],[\"^;\",[\"^ \",\"~:x\",684.9999775886536,\"~:y\",692.0000188344361]],[\"^;\",[\"^ \",\"~:x\",604.9999775886536,\"~:y\",692.0000188344361]]],\"~:show-content\",true,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:constraints-v\",\"~:top\",\"~:constraints-h\",\"~:left\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d508a9dc30\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d508a9dc2f\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d508a9dc2f\",\"~:strokes\",[],\"~:x\",604.9999775886536,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",604.9999775886536,\"~:y\",612.0000188344361,\"^7\",80,\"~:height\",80,\"~:x1\",604.9999775886536,\"~:y1\",612.0000188344361,\"~:x2\",684.9999775886536,\"~:y2\",692.0000188344361]],\"~:fills\",[],\"~:flip-x\",null,\"^K\",80,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d508aa2885\",\"~u94eaebe4-addd-80d1-8007-79d508aa2886\"]]]",
"~u94eaebe4-addd-80d1-8007-79d509800791": "[\"~#shape\",[\"^ \",\"~:y\",744.0000417226197,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:rx\",8,\"~:layout-padding\",[\"^ \",\"~:p1\",8,\"~:p2\",12,\"~:p3\",8,\"~:p4\",12],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"Dark / Button / Primary / Text / Default\",\"~:layout-align-items\",\"~:center\",\"~:width\",66,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",611.9998555183411,\"~:y\",744.0000417226197]],[\"^K\",[\"^ \",\"~:x\",677.9998555183411,\"~:y\",744.0000417226197]],[\"^K\",[\"^ \",\"~:x\",677.9998555183411,\"~:y\",776.0000417226197]],[\"^K\",[\"^ \",\"~:x\",611.9998555183411,\"~:y\",776.0000417226197]]],\"~:r2\",8,\"~:show-content\",true,\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",4,\"~:column-gap\",4],\"~:transform-inverse\",[\"^;\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:r3\",8,\"~:layout-justify-content\",\"^D\",\"~:r1\",8,\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d509800791\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d50980078f\",\"~:layout-flex-dir\",\"~:row\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d50980078f\",\"~:strokes\",[],\"~:x\",611.9998555183411,\"~:proportion\",1,\"~:r4\",8,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",611.9998555183411,\"~:y\",744.0000417226197,\"^E\",66,\"~:height\",32,\"~:x1\",611.9998555183411,\"~:y1\",744.0000417226197,\"~:x2\",677.9998555183411,\"~:y2\",776.0000417226197]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#7efff5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"~:ry\",8,\"^17\",32,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d509800792\"]]]",
"~u94eaebe4-addd-80d1-8007-79d509800792": "[\"~#shape\",[\"^ \",\"~:y\",752.0000417226197,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",0,\"~:p2\",2,\"~:p3\",0,\"~:p4\",2],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"_Utilities / Text / White\",\"~:layout-align-items\",\"~:start\",\"~:width\",42,\"~:layout-padding-type\",\"~:simple\",\"~:transforming\",false,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",623.9998555183411,\"~:y\",752.0000417226197]],[\"^K\",[\"^ \",\"~:x\",665.9998555183411,\"~:y\",752.0000417226197]],[\"^K\",[\"^ \",\"~:x\",665.9998555183411,\"~:y\",768.0000417226197]],[\"^K\",[\"^ \",\"~:x\",623.9998555183411,\"~:y\",768.0000417226197]]],\"~:show-content\",true,\"~:layout-item-h-sizing\",\"~:auto\",\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",0,\"~:column-gap\",6],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:layout-justify-content\",\"~:center\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d509800792\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d509800791\",\"~:layout-flex-dir\",\"~:column\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d509800791\",\"~:strokes\",[],\"~:x\",623.9998555183411,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",623.9998555183411,\"~:y\",752.0000417226197,\"^D\",42,\"~:height\",16,\"~:x1\",623.9998555183411,\"~:y1\",752.0000417226197,\"~:x2\",665.9998555183411,\"~:y2\",768.0000417226197]],\"~:fills\",[],\"~:flip-x\",null,\"^16\",16,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d509800793\"]]]",
"~u94eaebe4-addd-80d1-8007-79d509800793": "[\"~#shape\",[\"^ \",\"~:y\",753.0000417226199,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:auto-width\",\"~:index\",null,\"~:content\",[\"^ \",\"~:type\",\"root\",\"~:children\",[[\"^ \",\"^8\",\"paragraph-set\",\"^9\",[[\"^ \",\"~:line-height\",\"1.2\",\"~:path\",\"\",\"~:font-style\",\"normal\",\"^9\",[[\"^ \",\"^:\",\"1.2\",\"^;\",\"\",\"^<\",\"normal\",\"~:text-transform\",\"uppercase\",\"~:text-align\",\"left\",\"~:font-id\",\"gfont-work-sans\",\"~:font-size\",\"12\",\"~:font-weight\",\"500\",\"~:modified-at\",\"2024-06-04T14:15:09.786Z\",\"~:font-variant-id\",\"500\",\"~:text-decoration\",\"underline\",\"~:letter-spacing\",\"0\",\"~:fills\",[[\"^ \",\"~:fill-color\",\"#000000\",\"~:fill-color-ref-file\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"~:fill-opacity\",1,\"~:fill-color-ref-id\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"~:font-family\",\"Work Sans\",\"~:text\",\"Label\"]],\"^=\",\"uppercase\",\"^>\",\"center\",\"^?\",\"gfont-work-sans\",\"^@\",\"12\",\"^A\",\"500\",\"^8\",\"paragraph\",\"^B\",\"2024-06-04T14:15:09.786Z\",\"^C\",\"500\",\"^D\",\"underline\",\"^E\",\"0\",\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^H\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"^I\",1,\"^J\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"^K\",\"Work Sans\"]]]],\"~:vertical-align\",\"center\",\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^I\",1]]],\"~:hide-in-viewer\",true,\"~:name\",\"Input\",\"~:saved-component-root\",null,\"~:width\",38,\"^8\",\"^L\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",625.9998555183411,\"~:y\",753.0000417226199]],[\"^S\",[\"^ \",\"~:x\",663.9998555183411,\"~:y\",753.0000417226199]],[\"^S\",[\"^ \",\"~:x\",663.9998555183411,\"~:y\",768.0000417226195]],[\"^S\",[\"^ \",\"~:x\",625.9998555183411,\"~:y\",768.0000417226199]]],\"~:layout-item-h-sizing\",\"~:fix\",\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d509800793\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d509800792\",\"~:position-data\",[[\"^ \",\"~:y\",767.340087890625,\"^:\",\"1.2\",\"^<\",\"normal\",\"^=\",\"uppercase\",\"^>\",\"left\",\"^?\",\"sourcesanspro\",\"^@\",\"12\",\"^A\",\"500\",\"~:text-direction\",\"ltr\",\"^Q\",37.93994140625,\"^C\",\"regular\",\"^D\",\"underline\",\"^E\",\"0\",\"~:x\",626.0299072265625,\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^H\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"^I\",1,\"^J\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"~:direction\",\"ltr\",\"^K\",\"Work Sans\",\"~:height\",14.08001708984375,\"^L\",\"Label\"]],\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d509800792\",\"~:strokes\",[],\"~:x\",625.9998555183411,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",625.9998555183411,\"~:y\",753.0000417226199,\"^Q\",38,\"^11\",15,\"~:x1\",625.9998555183411,\"~:y1\",753.0000417226199,\"~:x2\",663.9998555183411,\"~:y2\",768.0000417226199]],\"^F\",[],\"~:flip-x\",null,\"^11\",15,\"~:flip-y\",null]]",
"~u77c71dba-32ee-804c-8007-736561cf8584": "[\"~#shape\",[\"^ \",\"~:y\",396.00000357564704,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:hide-in-viewer\",true,\"~:name\",\"Board Child\",\"~:width\",80,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",396.00000357564704]],[\"^;\",[\"^ \",\"~:x\",768.9999775886536,\"~:y\",396.00000357564704]],[\"^;\",[\"^ \",\"~:x\",768.9999775886536,\"~:y\",476.00000357564704]],[\"^;\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",476.00000357564704]]],\"~:show-content\",true,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:constraints-v\",\"~:top\",\"~:constraints-h\",\"~:left\",\"~:id\",\"~u77c71dba-32ee-804c-8007-736561cf8584\",\"~:parent-id\",\"~u77c71dba-32ee-804c-8007-736561cf857f\",\"~:frame-id\",\"~u77c71dba-32ee-804c-8007-736561cf857f\",\"~:strokes\",[],\"~:x\",688.9999775886536,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",396.00000357564704,\"^7\",80,\"~:height\",80,\"~:x1\",688.9999775886536,\"~:y1\",396.00000357564704,\"~:x2\",768.9999775886536,\"~:y2\",476.00000357564704]],\"~:fills\",[],\"~:flip-x\",null,\"^K\",80,\"~:flip-y\",null,\"~:shapes\",[\"~u77c71dba-32ee-804c-8007-736561cff457\",\"~u77c71dba-32ee-804c-8007-736561cff458\"]]]",
"~u94eaebe4-addd-80d1-8007-79d5055d6859": "[\"~#shape\",[\"^ \",\"~:y\",504.00000202817546,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",0,\"~:p2\",12,\"~:p3\",0,\"~:p4\",12],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:wrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"Board Parent 2\",\"~:layout-align-items\",\"~:start\",\"~:width\",272,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",592.9999775886536,\"~:y\",504.00000202817546]],[\"^J\",[\"^ \",\"~:x\",864.9999775886536,\"~:y\",504.00000202817546]],[\"^J\",[\"^ \",\"~:x\",864.9999775886536,\"~:y\",583.9999898643296]],[\"^J\",[\"^ \",\"~:x\",592.9999775886536,\"~:y\",583.9999898643296]]],\"~:show-content\",true,\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",4,\"~:column-gap\",4],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:layout-item-v-sizing\",\"~:fix\",\"~:layout-justify-content\",\"~:center\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d5055d6859\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:layout-flex-dir\",\"~:row-reverse\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",592.9999775886536,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",592.9999775886536,\"~:y\",504.00000202817546,\"^D\",272,\"~:height\",79.9999878361541,\"~:x1\",592.9999775886536,\"~:y1\",504.00000202817546,\"~:x2\",864.9999775886536,\"~:y2\",583.9999898643296]],\"~:fills\",[],\"~:flip-x\",null,\"^15\",79.9999878361541,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d5055d685a\"]]]",
"~u94eaebe4-addd-80d1-8007-79d5055d685a": "[\"~#shape\",[\"^ \",\"~:y\",503.9999959462525,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:hide-in-viewer\",true,\"~:name\",\"Board Child\",\"~:width\",80,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",503.9999959462525]],[\"^;\",[\"^ \",\"~:x\",768.9999775886536,\"~:y\",503.9999959462525]],[\"^;\",[\"^ \",\"~:x\",768.9999775886536,\"~:y\",583.9999959462525]],[\"^;\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",583.9999959462525]]],\"~:show-content\",true,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:constraints-v\",\"~:top\",\"~:constraints-h\",\"~:left\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685a\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d5055d6859\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d5055d6859\",\"~:strokes\",[],\"~:x\",688.9999775886536,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",503.9999959462525,\"^7\",80,\"~:height\",80,\"~:x1\",688.9999775886536,\"~:y1\",503.9999959462525,\"~:x2\",768.9999775886536,\"~:y2\",583.9999959462525]],\"~:fills\",[],\"~:flip-x\",null,\"^K\",80,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d5055d685b\",\"~u94eaebe4-addd-80d1-8007-79d5055d685c\"]]]",
"~u94eaebe4-addd-80d1-8007-79d5055d685b": "[\"~#shape\",[\"^ \",\"~:y\",503.9999959462525,\"~:rx\",8,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",80,\"~:transforming\",false,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",503.9999959462525]],[\"^>\",[\"^ \",\"~:x\",768.9999775886536,\"~:y\",503.9999959462525]],[\"^>\",[\"^ \",\"~:x\",768.9999775886536,\"~:y\",583.9999959462525]],[\"^>\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",583.9999959462525]]],\"~:r2\",8,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:r3\",8,\"~:r1\",8,\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685b\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685a\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685a\",\"~:strokes\",[],\"~:x\",688.9999775886536,\"~:proportion\",1,\"~:r4\",8,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",688.9999775886536,\"~:y\",503.9999959462525,\"^9\",80,\"~:height\",80,\"~:x1\",688.9999775886536,\"~:y1\",503.9999959462525,\"~:x2\",768.9999775886536,\"~:y2\",583.9999959462525]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#e8e9ea\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"~:ry\",8,\"^M\",80,\"~:flip-y\",null]]",
"~u94eaebe4-addd-80d1-8007-79d5055d685c": "[\"~#shape\",[\"^ \",\"~:y\",527.9999959462525,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:rx\",8,\"~:layout-padding\",[\"^ \",\"~:p1\",8,\"~:p2\",12,\"~:p3\",8,\"~:p4\",12],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"Dark / Button / Primary / Text / Default\",\"~:layout-align-items\",\"~:center\",\"~:width\",66,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",695.9999775886536,\"~:y\",527.9999959462525]],[\"^K\",[\"^ \",\"~:x\",761.9999775886536,\"~:y\",527.9999959462525]],[\"^K\",[\"^ \",\"~:x\",761.9999775886536,\"~:y\",559.9999959462525]],[\"^K\",[\"^ \",\"~:x\",695.9999775886536,\"~:y\",559.9999959462525]]],\"~:r2\",8,\"~:show-content\",true,\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",4,\"~:column-gap\",4],\"~:transform-inverse\",[\"^;\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:r3\",8,\"~:layout-justify-content\",\"^D\",\"~:r1\",8,\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685c\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685a\",\"~:layout-flex-dir\",\"~:row\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685a\",\"~:strokes\",[],\"~:x\",695.9999775886536,\"~:proportion\",1,\"~:r4\",8,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",695.9999775886536,\"~:y\",527.9999959462525,\"^E\",66,\"~:height\",32,\"~:x1\",695.9999775886536,\"~:y1\",527.9999959462525,\"~:x2\",761.9999775886536,\"~:y2\",559.9999959462525]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#7efff5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"~:ry\",8,\"^17\",32,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d5055d685d\"]]]",
"~u94eaebe4-addd-80d1-8007-79d5055d685d": "[\"~#shape\",[\"^ \",\"~:y\",535.9999959462525,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",0,\"~:p2\",2,\"~:p3\",0,\"~:p4\",2],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"_Utilities / Text / White\",\"~:layout-align-items\",\"~:start\",\"~:width\",42,\"~:layout-padding-type\",\"~:simple\",\"~:transforming\",false,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",707.9999775886536,\"~:y\",535.9999959462525]],[\"^K\",[\"^ \",\"~:x\",749.9999775886536,\"~:y\",535.9999959462525]],[\"^K\",[\"^ \",\"~:x\",749.9999775886536,\"~:y\",551.9999959462525]],[\"^K\",[\"^ \",\"~:x\",707.9999775886536,\"~:y\",551.9999959462525]]],\"~:show-content\",true,\"~:layout-item-h-sizing\",\"~:auto\",\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",0,\"~:column-gap\",6],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:layout-justify-content\",\"~:center\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685d\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685c\",\"~:layout-flex-dir\",\"~:column\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685c\",\"~:strokes\",[],\"~:x\",707.9999775886536,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",707.9999775886536,\"~:y\",535.9999959462525,\"^D\",42,\"~:height\",16,\"~:x1\",707.9999775886536,\"~:y1\",535.9999959462525,\"~:x2\",749.9999775886536,\"~:y2\",551.9999959462525]],\"~:fills\",[],\"~:flip-x\",null,\"^16\",16,\"~:flip-y\",null,\"~:shapes\",[\"~u94eaebe4-addd-80d1-8007-79d5055d685e\"]]]",
"~u94eaebe4-addd-80d1-8007-79d5055d685e": "[\"~#shape\",[\"^ \",\"~:y\",536.9999959462527,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:auto-width\",\"~:index\",null,\"~:content\",[\"^ \",\"~:type\",\"root\",\"~:children\",[[\"^ \",\"^8\",\"paragraph-set\",\"^9\",[[\"^ \",\"~:line-height\",\"1.2\",\"~:path\",\"\",\"~:font-style\",\"normal\",\"^9\",[[\"^ \",\"^:\",\"1.2\",\"^;\",\"\",\"^<\",\"normal\",\"~:text-transform\",\"uppercase\",\"~:text-align\",\"left\",\"~:font-id\",\"gfont-work-sans\",\"~:font-size\",\"12\",\"~:font-weight\",\"500\",\"~:modified-at\",\"2024-06-04T14:15:09.786Z\",\"~:font-variant-id\",\"500\",\"~:text-decoration\",\"underline\",\"~:letter-spacing\",\"0\",\"~:fills\",[[\"^ \",\"~:fill-color\",\"#000000\",\"~:fill-color-ref-file\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"~:fill-opacity\",1,\"~:fill-color-ref-id\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"~:font-family\",\"Work Sans\",\"~:text\",\"Label\"]],\"^=\",\"uppercase\",\"^>\",\"center\",\"^?\",\"gfont-work-sans\",\"^@\",\"12\",\"^A\",\"500\",\"^8\",\"paragraph\",\"^B\",\"2024-06-04T14:15:09.786Z\",\"^C\",\"500\",\"^D\",\"underline\",\"^E\",\"0\",\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^H\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"^I\",1,\"^J\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"^K\",\"Work Sans\"]]]],\"~:vertical-align\",\"center\",\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^I\",1]]],\"~:hide-in-viewer\",true,\"~:name\",\"Input\",\"~:saved-component-root\",null,\"~:width\",38,\"^8\",\"^L\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",709.9999775886536,\"~:y\",536.9999959462527]],[\"^S\",[\"^ \",\"~:x\",747.9999775886536,\"~:y\",536.9999959462527]],[\"^S\",[\"^ \",\"~:x\",747.9999775886536,\"~:y\",551.9999959462523]],[\"^S\",[\"^ \",\"~:x\",709.9999775886536,\"~:y\",551.9999959462527]]],\"~:layout-item-h-sizing\",\"~:fix\",\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u02e9633d-4ce7-80da-8007-736558496fa8\",\"~:id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685e\",\"~:parent-id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685d\",\"~:position-data\",[[\"^ \",\"~:y\",551.3400268554688,\"^:\",\"1.2\",\"^<\",\"normal\",\"^=\",\"uppercase\",\"^>\",\"left\",\"^?\",\"sourcesanspro\",\"^@\",\"12\",\"^A\",\"500\",\"~:text-direction\",\"ltr\",\"^Q\",37.93994140625,\"^C\",\"regular\",\"^D\",\"underline\",\"^E\",\"0\",\"~:x\",710.030029296875,\"^F\",[[\"^ \",\"^G\",\"#000000\",\"^H\",\"~ucaa70d02-51e1-81ae-8007-735e7de3d7bc\",\"^I\",1,\"^J\",\"~udfa92acf-7d18-8079-8003-baba8789d8af\"]],\"~:direction\",\"ltr\",\"^K\",\"Work Sans\",\"~:height\",14.08001708984375,\"^L\",\"Label\"]],\"~:frame-id\",\"~u94eaebe4-addd-80d1-8007-79d5055d685d\",\"~:strokes\",[],\"~:x\",709.9999775886536,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",709.9999775886536,\"~:y\",536.9999959462527,\"^Q\",38,\"^11\",15,\"~:x1\",709.9999775886536,\"~:y1\",536.9999959462527,\"~:x2\",747.9999775886536,\"~:y2\",551.9999959462527]],\"^F\",[],\"~:flip-x\",null,\"^11\",15,\"~:flip-y\",null]]"
}
}
}
},
"~:id": "~u31fe2e21-73e7-80f3-8007-73894fb58240",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
}
}
}

View File

@@ -58,10 +58,10 @@ export class WorkspacePage extends BaseWebSocketPage {
async waitForTextSpan(nth = 0) {
if (!nth) {
return this.page.waitForSelector('[data-itype="inline"]');
return this.page.waitForSelector('[data-itype="span"]');
}
return this.page.waitForSelector(
`[data-itype="inline"]:nth-child(${nth})`,
`[data-itype="span"]:nth-child(${nth})`,
);
}

View File

@@ -210,6 +210,22 @@ test("Renders a file with shadows applied to any kind of shape", async ({
await expect(workspace.canvas).toHaveScreenshot();
});
test("Renders a file with flex layouts and different directions", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-flex-layouts.json");
await workspace.goToWorkspace({
id: "31fe2e21-73e7-80f3-8007-73894fb58240",
pageId: "02e9633d-4ce7-80da-8007-736558496fa8",
});
await workspace.waitForFirstRenderWithoutUI();
await expect(workspace.canvas).toHaveScreenshot();
});
test("Renders a file with a closed path shape with multiple segments using strokes and shadow", async ({
page,
}) => {

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

226
frontend/pnpm-lock.yaml generated
View File

@@ -99,7 +99,7 @@ importers:
version: 1.15.4
jsdom:
specifier: ^27.4.0
version: 27.4.0
version: 27.4.0(canvas@3.2.1)
lodash:
specifier: ^4.17.21
version: 4.17.21
@@ -210,7 +210,7 @@ importers:
version: 7.3.1(@types/node@25.0.3)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2)
vitest:
specifier: ^4.0.18
version: 4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2)
version: 4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0(canvas@3.2.1))(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2)
wait-on:
specifier: ^9.0.3
version: 9.0.3
@@ -265,12 +265,15 @@ importers:
'@vitest/ui':
specifier: ^1.6.0
version: 1.6.1(vitest@1.6.1)
canvas:
specifier: ^3.2.1
version: 3.2.1
esbuild:
specifier: ^0.27.2
version: 0.27.2
jsdom:
specifier: ^27.4.0
version: 27.4.0
version: 27.4.0(canvas@3.2.1)
playwright:
specifier: ^1.45.1
version: 1.57.0
@@ -282,7 +285,7 @@ importers:
version: 5.4.21(@types/node@25.0.3)(sass-embedded@1.97.1)(sass@1.97.1)
vitest:
specifier: ^1.6.0
version: 1.6.1(@types/node@25.0.3)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0)(sass-embedded@1.97.1)(sass@1.97.1)
version: 1.6.1(@types/node@25.0.3)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0(canvas@3.2.1))(sass-embedded@1.97.1)(sass@1.97.1)
packages:
@@ -1751,6 +1754,9 @@ packages:
bintrees@1.0.2:
resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
body-parser@2.2.1:
resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==}
engines: {node: '>=18'}
@@ -1779,6 +1785,9 @@ packages:
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
@@ -1809,6 +1818,10 @@ packages:
caniuse-lite@1.0.30001762:
resolution: {integrity: sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==}
canvas@3.2.1:
resolution: {integrity: sha512-ej1sPFR5+0YWtaVp6S1N1FVz69TQCqmrkGeRvQxZeAB1nAIcjNTHVwrZtYtWFFBmQsF40/uDLehsW5KuYC99mg==}
engines: {node: ^18.12.0 || >= 20.9.0}
chai@4.5.0:
resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==}
engines: {node: '>=4'}
@@ -1851,6 +1864,9 @@ packages:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
@@ -2092,6 +2108,10 @@ packages:
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
decompress-response@6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'}
deep-eql@4.1.4:
resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==}
engines: {node: '>=6'}
@@ -2100,6 +2120,10 @@ packages:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
@@ -2144,6 +2168,10 @@ packages:
engines: {node: '>=0.10'}
hasBin: true
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
dettle@1.0.5:
resolution: {integrity: sha512-ZVyjhAJ7sCe1PNXEGveObOH9AC8QvMga3HJIghHawtG7mE4K5pW9nz/vDGAr/U7a3LWgdOzEE7ac9MURnyfaTA==}
@@ -2232,6 +2260,9 @@ packages:
encoding@0.1.13:
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
end-of-stream@1.4.5:
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
entities@2.2.0:
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
@@ -2329,6 +2360,10 @@ packages:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
engines: {node: '>=16.17'}
expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
@@ -2421,6 +2456,9 @@ packages:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
fs-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'}
@@ -2493,6 +2531,9 @@ packages:
resolution: {integrity: sha512-eFmhDi2xQ+2reMRY2AbJ2oa10uFOl1oyGbAKdCZiNOk94NJHi7aN0OBELSC9v35ZAPQdr+uRBi93/Gu4SlBdrA==}
engines: {node: '>=18'}
github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -3042,6 +3083,10 @@ packages:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
engines: {node: '>=12'}
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
min-indent@1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
@@ -3080,6 +3125,9 @@ packages:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
@@ -3112,6 +3160,9 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
napi-build-utils@2.0.0:
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
negotiator@0.6.4:
resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}
engines: {node: '>= 0.6'}
@@ -3123,6 +3174,10 @@ packages:
nice-try@1.0.5:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
node-abi@3.87.0:
resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==}
engines: {node: '>=10'}
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
@@ -3415,6 +3470,11 @@ packages:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
prebuild-install@7.1.3:
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
engines: {node: '>=10'}
hasBin: true
prettier@3.5.3:
resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
engines: {node: '>=14'}
@@ -3472,6 +3532,9 @@ packages:
pstree.remy@1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
pump@3.0.3:
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
punycode@1.4.1:
resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
@@ -3497,6 +3560,10 @@ packages:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
engines: {node: '>= 0.10'}
rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
react-docgen-typescript@2.4.0:
resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==}
peerDependencies:
@@ -3884,6 +3951,12 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
simple-concat@1.0.1:
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
simple-get@4.0.1:
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
simple-update-notifier@2.0.0:
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
engines: {node: '>=10'}
@@ -4016,6 +4089,10 @@ packages:
resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==}
engines: {node: '>=12'}
strip-json-comments@2.0.1:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
strip-literal@2.1.1:
resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==}
@@ -4069,6 +4146,13 @@ packages:
resolution: {integrity: sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==}
engines: {node: '>=16.0.0'}
tar-fs@2.1.4:
resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
tar-stream@2.2.0:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
@@ -4195,6 +4279,9 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
type-detect@4.1.0:
resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==}
engines: {node: '>=4'}
@@ -5411,7 +5498,7 @@ snapshots:
'@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.3)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2))(vitest@4.0.18)
'@vitest/browser-playwright': 4.0.18(playwright@1.58.0)(vite@7.3.1(@types/node@25.0.3)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2))(vitest@4.0.18)
'@vitest/runner': 4.0.18
vitest: 4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2)
vitest: 4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0(canvas@3.2.1))(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2)
transitivePeerDependencies:
- react
- react-dom
@@ -5583,7 +5670,7 @@ snapshots:
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.3)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2))
playwright: 1.58.0
tinyrainbow: 3.0.3
vitest: 4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2)
vitest: 4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0(canvas@3.2.1))(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2)
transitivePeerDependencies:
- bufferutil
- msw
@@ -5595,7 +5682,7 @@ snapshots:
'@vitest/utils': 1.6.1
magic-string: 0.30.21
sirv: 2.0.4
vitest: 1.6.1(@types/node@25.0.3)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0)(sass-embedded@1.97.1)(sass@1.97.1)
vitest: 1.6.1(@types/node@25.0.3)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0(canvas@3.2.1))(sass-embedded@1.97.1)(sass@1.97.1)
optionalDependencies:
playwright: 1.57.0
@@ -5608,7 +5695,7 @@ snapshots:
pngjs: 7.0.0
sirv: 3.0.2
tinyrainbow: 3.0.3
vitest: 4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2)
vitest: 4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0(canvas@3.2.1))(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2)
ws: 8.19.0
transitivePeerDependencies:
- bufferutil
@@ -5631,7 +5718,7 @@ snapshots:
std-env: 3.10.0
strip-literal: 2.1.1
test-exclude: 6.0.0
vitest: 1.6.1(@types/node@25.0.3)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0)(sass-embedded@1.97.1)(sass@1.97.1)
vitest: 1.6.1(@types/node@25.0.3)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0(canvas@3.2.1))(sass-embedded@1.97.1)(sass@1.97.1)
transitivePeerDependencies:
- supports-color
@@ -5647,7 +5734,7 @@ snapshots:
obug: 2.1.1
std-env: 3.10.0
tinyrainbow: 3.0.3
vitest: 4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2)
vitest: 4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0(canvas@3.2.1))(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2)
optionalDependencies:
'@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.3)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2))(vitest@4.0.18)
@@ -5740,7 +5827,7 @@ snapshots:
pathe: 1.1.2
picocolors: 1.1.1
sirv: 2.0.4
vitest: 1.6.1(@types/node@25.0.3)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0)(sass-embedded@1.97.1)(sass@1.97.1)
vitest: 1.6.1(@types/node@25.0.3)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0(canvas@3.2.1))(sass-embedded@1.97.1)(sass@1.97.1)
'@vitest/utils@1.6.1':
dependencies:
@@ -5908,6 +5995,12 @@ snapshots:
bintrees@1.0.2: {}
bl@4.1.0:
dependencies:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.2
body-parser@2.2.1:
dependencies:
bytes: 3.1.2
@@ -5949,6 +6042,11 @@ snapshots:
buffer-from@1.1.2: {}
buffer@5.7.1:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
buffer@6.0.3:
dependencies:
base64-js: 1.5.1
@@ -5981,6 +6079,11 @@ snapshots:
caniuse-lite@1.0.30001762: {}
canvas@3.2.1:
dependencies:
node-addon-api: 7.1.1
prebuild-install: 7.1.3
chai@4.5.0:
dependencies:
assertion-error: 1.1.0
@@ -6038,6 +6141,8 @@ snapshots:
dependencies:
readdirp: 4.1.2
chownr@1.1.4: {}
chownr@2.0.0: {}
ci-info@3.9.0: {}
@@ -6273,12 +6378,18 @@ snapshots:
decimal.js@10.6.0: {}
decompress-response@6.0.0:
dependencies:
mimic-response: 3.1.0
deep-eql@4.1.4:
dependencies:
type-detect: 4.1.0
deep-eql@5.0.2: {}
deep-extend@0.6.0: {}
deepmerge@4.3.1: {}
default-browser-id@5.0.1: {}
@@ -6313,6 +6424,8 @@ snapshots:
detect-libc@1.0.3:
optional: true
detect-libc@2.1.2: {}
dettle@1.0.5: {}
diff-sequences@29.6.3: {}
@@ -6407,6 +6520,10 @@ snapshots:
dependencies:
iconv-lite: 0.6.3
end-of-stream@1.4.5:
dependencies:
once: 1.4.0
entities@2.2.0: {}
entities@4.5.0: {}
@@ -6588,6 +6705,8 @@ snapshots:
signal-exit: 4.1.0
strip-final-newline: 3.0.0
expand-template@2.0.3: {}
expect-type@1.3.0: {}
expr-eval-fork@2.0.2: {}
@@ -6711,6 +6830,8 @@ snapshots:
fresh@2.0.0: {}
fs-constants@1.0.0: {}
fs-extra@10.1.0:
dependencies:
graceful-fs: 4.2.11
@@ -6789,6 +6910,8 @@ snapshots:
readable-stream: 4.7.0
safe-buffer: 5.2.1
github-from-package@0.0.0: {}
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -7163,7 +7286,7 @@ snapshots:
dependencies:
argparse: 2.0.1
jsdom@27.4.0:
jsdom@27.4.0(canvas@3.2.1):
dependencies:
'@acemir/cssom': 0.9.30
'@asamuzakjp/dom-selector': 6.7.6
@@ -7185,6 +7308,8 @@ snapshots:
whatwg-url: 15.1.0
ws: 8.18.3
xml-name-validator: 5.0.0
optionalDependencies:
canvas: 3.2.1
transitivePeerDependencies:
- '@exodus/crypto'
- bufferutil
@@ -7342,6 +7467,8 @@ snapshots:
mimic-fn@4.0.0: {}
mimic-response@3.1.0: {}
min-indent@1.0.1: {}
minimatch@10.1.1:
@@ -7375,6 +7502,8 @@ snapshots:
minipass: 3.3.6
yallist: 4.0.0
mkdirp-classic@0.5.3: {}
mkdirp@1.0.4: {}
mkdirp@3.0.1: {}
@@ -7396,14 +7525,19 @@ snapshots:
nanoid@3.3.11: {}
napi-build-utils@2.0.0: {}
negotiator@0.6.4: {}
negotiator@1.0.0: {}
nice-try@1.0.5: {}
node-addon-api@7.1.1:
optional: true
node-abi@3.87.0:
dependencies:
semver: 7.7.3
node-addon-api@7.1.1: {}
node-fetch@2.7.0(encoding@0.1.13):
dependencies:
@@ -7704,6 +7838,21 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
prebuild-install@7.1.3:
dependencies:
detect-libc: 2.1.2
expand-template: 2.0.3
github-from-package: 0.0.0
minimist: 1.2.8
mkdirp-classic: 0.5.3
napi-build-utils: 2.0.0
node-abi: 3.87.0
pump: 3.0.3
rc: 1.2.8
simple-get: 4.0.1
tar-fs: 2.1.4
tunnel-agent: 0.6.0
prettier@3.5.3: {}
prettier@3.7.4: {}
@@ -7755,6 +7904,11 @@ snapshots:
pstree.remy@1.1.8: {}
pump@3.0.3:
dependencies:
end-of-stream: 1.4.5
once: 1.4.0
punycode@1.4.1: {}
punycode@2.3.1: {}
@@ -7776,6 +7930,13 @@ snapshots:
iconv-lite: 0.7.1
unpipe: 1.0.0
rc@1.2.8:
dependencies:
deep-extend: 0.6.0
ini: 1.3.8
minimist: 1.2.8
strip-json-comments: 2.0.1
react-docgen-typescript@2.4.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
@@ -8249,6 +8410,14 @@ snapshots:
signal-exit@4.1.0: {}
simple-concat@1.0.1: {}
simple-get@4.0.1:
dependencies:
decompress-response: 6.0.0
once: 1.4.0
simple-concat: 1.0.1
simple-update-notifier@2.0.0:
dependencies:
semver: 7.7.3
@@ -8404,6 +8573,8 @@ snapshots:
strip-indent@4.1.1: {}
strip-json-comments@2.0.1: {}
strip-literal@2.1.1:
dependencies:
js-tokens: 9.0.1
@@ -8487,6 +8658,21 @@ snapshots:
sync-message-port@1.1.3: {}
tar-fs@2.1.4:
dependencies:
chownr: 1.1.4
mkdirp-classic: 0.5.3
pump: 3.0.3
tar-stream: 2.2.0
tar-stream@2.2.0:
dependencies:
bl: 4.1.0
end-of-stream: 1.4.5
fs-constants: 1.0.0
inherits: 2.0.4
readable-stream: 3.6.2
tar@6.2.1:
dependencies:
chownr: 2.0.0
@@ -8587,6 +8773,10 @@ snapshots:
tslib@2.8.1: {}
tunnel-agent@0.6.0:
dependencies:
safe-buffer: 5.2.1
type-detect@4.1.0: {}
type-is@2.0.1:
@@ -8757,7 +8947,7 @@ snapshots:
sass-embedded: 1.97.1
yaml: 2.8.2
vitest@1.6.1(@types/node@25.0.3)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0)(sass-embedded@1.97.1)(sass@1.97.1):
vitest@1.6.1(@types/node@25.0.3)(@vitest/browser@1.6.1)(@vitest/ui@1.6.1)(jsdom@27.4.0(canvas@3.2.1))(sass-embedded@1.97.1)(sass@1.97.1):
dependencies:
'@vitest/expect': 1.6.1
'@vitest/runner': 1.6.1
@@ -8783,7 +8973,7 @@ snapshots:
'@types/node': 25.0.3
'@vitest/browser': 1.6.1(playwright@1.57.0)(vitest@1.6.1)
'@vitest/ui': 1.6.1(vitest@1.6.1)
jsdom: 27.4.0
jsdom: 27.4.0(canvas@3.2.1)
transitivePeerDependencies:
- less
- lightningcss
@@ -8794,7 +8984,7 @@ snapshots:
- supports-color
- terser
vitest@4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2):
vitest@4.0.18(@types/node@25.0.3)(@vitest/browser-playwright@4.0.18)(jsdom@27.4.0(canvas@3.2.1))(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2):
dependencies:
'@vitest/expect': 4.0.18
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.3)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2))
@@ -8819,7 +9009,7 @@ snapshots:
optionalDependencies:
'@types/node': 25.0.3
'@vitest/browser-playwright': 4.0.18(playwright@1.58.0)(vite@7.3.1(@types/node@25.0.3)(sass-embedded@1.97.1)(sass@1.97.1)(yaml@2.8.2))(vitest@4.0.18)
jsdom: 27.4.0
jsdom: 27.4.0(canvas@3.2.1)
transitivePeerDependencies:
- jiti
- less

View File

@@ -65,8 +65,11 @@
ptk/WatchEvent
(watch [_ _ stream]
(rx/merge
(rx/of (ev/initialize)
(dp/refresh-profile))
(if (contains? cf/flags :audit-log)
(rx/of (ev/initialize))
(rx/empty))
(rx/of (dp/refresh-profile))
;; Watch for profile deletion events
(->> stream

View File

@@ -81,7 +81,7 @@
ptk/UpdateEvent
(update [_ state]
(reduce (fn [state {:keys [id] :as project}]
;; Replace completely instead of merge to ensure deleted-at is removed
;; Replace completely instead of merge to ensure deleted-at is removed
(assoc-in state [:projects id] project))
state
projects))))

View File

@@ -39,7 +39,6 @@
;; Defines the time window (in ms) within events belong to the same session.
(def session-timeout (* 1000 60 30))
;; Min time for a long task to be reported to telemetry
(def min-longtask-time 1000)
@@ -61,11 +60,6 @@
;; Def micro-benchmark iterations
(def micro-benchmark-iterations 1e6)
;; Performance logs
(defonce ^:private longtask-observer* (atom nil))
(defonce ^:private stall-timer* (atom nil))
(defonce ^:private current-op* (atom nil))
;; --- CONTEXT
(defn- collect-context
@@ -188,10 +182,25 @@
profile-id (aget result 1)]
(performance-payload profile-id props)))
([profile-id props]
(let [{:keys [performance-info]} @st/state]
(let [context (get @st/state :performance-info)]
{:type "action"
:name "performance"
:context (merge @context performance-info)
:context context
:props props
:profile-id profile-id})))
(defn make-long-task-event
([result]
(let [props (aget result 0)
profile-id (aget result 1)]
(make-long-task-event profile-id props)))
([profile-id props]
(prn "make-long-task-event")
(let [context (get @st/state :performance-info)]
{:type "fact"
;; FIXME: add file-id
:name "performance-long-task"
:context {:performance context}
:props props
:profile-id profile-id})))
@@ -298,17 +307,16 @@
(when (and (= "longtask" (.-entryType entry))
(> (.-duration entry) min-longtask-time))
(rx/push! subs
{::event :observer-longtask
:duration (.-duration entry)})))
{:duration (.-duration entry)})))
(.getEntries list))))]
(.observe observer #js {:entryTypes #js ["longtask"]})
(fn []
(.disconnect observer)))))
(rx/empty)))
(defn- save-performance-info
[]
(ptk/reify ::save-performance-info
(defn- snapshot-performance-info
[{:keys [file-id]}]
(ptk/reify ::snapshot-performance-info
ptk/UpdateEvent
(update [_ state]
(letfn [(count-shapes [file]
@@ -317,227 +325,144 @@
(fn [sum _ page]
(+ sum (count (:objects page))))
0)))
(count-library-data [files {:keys [id]}]
(let [data (dm/get-in files [id :data])]
{:components (count (:components data))
:colors (count (:colors data))
:typographies (count (:typographies data))}))]
(let [file-id (get state :current-file-id)
file (get-in state [:files file-id])
file-size (count-shapes file)
libraries
(-> (refs/select-libraries (:files state) (:id file))
(d/update-vals (partial count-library-data (:files state))))
(add-libraries-counters [state files]
(reduce (fn [state library-id]
(let [data (dm/get-in files [file-id :data])]
(-> state
(update :total-components + (count (:components data)))
(update :total-colors + (count (:colors data)))
(update :total-typographies + (count (:typographies data))))))
state
(refs/select-libraries files file-id)))]
lib-sizes
(->> libraries
(reduce-kv
(fn [acc _ {:keys [components colors typographies]}]
(-> acc
(update :components + components)
(update :colors + colors)
(update :typographies + typographies)))
{}))]
(update state :performance-info
(fn [info]
(update state :performance-info
(fn [info]
(let [files (get state :files)
file (get files file-id)]
(-> info
(assoc :file-size file-size)
(assoc :library-sizes lib-sizes)
(assoc :file-start-time (perf/now))))))))))
(assoc :total-shapes (count-shapes file))
(add-libraries-counters files)))))))))
(defn store-performace-info
(defn- store-performace-info
[]
(letfn [(micro-benchmark [state]
(let [start (perf/now)]
(loop [i micro-benchmark-iterations]
(when-not (zero? i)
(* (math/sin i) (math/sqrt i))
(recur (dec i))))
(let [end (perf/now)]
(update state :performance-info assoc :bench-result (- end start)))))]
(ptk/reify ::store-performace-info
ptk/UpdateEvent
(update [_ state]
(let [start (perf/now)
_ (loop [i micro-benchmark-iterations]
(when-not (zero? i)
(* (math/sin i) (math/sqrt i))
(recur (dec i))))
end (perf/now)]
(ptk/reify ::store-performace-info
ptk/UpdateEvent
(update [_ state]
(-> state
micro-benchmark
(assoc-in [:performance-info :app-start-time] (perf/now))))
(update state :performance-info assoc :bench (- end start))))
ptk/WatchEvent
(watch [_ _ stream]
(->> stream
(rx/filter (ptk/type? :app.main.data.workspace/all-libraries-resolved))
(rx/take 1)
(rx/map save-performance-info))))))
(rx/map deref)
(rx/map snapshot-performance-info)))))
(defn initialize
[]
(when (contains? cf/flags :audit-log)
(ptk/reify ::initialize
ptk/WatchEvent
(watch [_ _ _]
(rx/of (store-performace-info)))
(prn "initialize")
(ptk/reify ::initialize
ptk/WatchEvent
(watch [_ _ _]
(rx/of (store-performace-info)))
ptk/EffectEvent
(effect [_ _ stream]
(let [session (atom nil)
stopper (rx/filter (ptk/type? ::initialize) stream)
buffer (atom #queue [])
profile (->> (rx/from-atom storage/user {:emit-current-value? true})
(rx/map :profile)
(rx/map :id)
(rx/pipe (rxo/distinct-contiguous)))]
ptk/EffectEvent
(effect [_ _ stream]
(let [session (atom nil)
stopper (rx/filter (ptk/type? ::initialize) stream)
buffer (atom #queue [])
profile (->> (rx/from-atom storage/user {:emit-current-value? true})
(rx/map :profile)
(rx/map :id)
(rx/pipe (rxo/distinct-contiguous)))]
(l/debug :hint "event instrumentation initialized")
(l/debug :hint "event instrumentation initialized")
(->> (rx/merge
(->> (rx/from-atom buffer)
(rx/filter #(pos? (count %)))
(rx/debounce 2000))
(->> stream
(rx/filter (ptk/type? :app.main.data.profile/logout))
(rx/observe-on :async)))
(rx/map (fn [_]
(into [] (take max-buffer-size) @buffer)))
(rx/with-latest-from profile)
(rx/mapcat (fn [[chunk profile-id]]
(let [events (filterv #(= profile-id (:profile-id %)) chunk)]
(->> (persist-events events)
(rx/tap (fn [_]
(l/debug :hint "events chunk persisted" :total (count chunk))))
(rx/map (constantly chunk))))))
(rx/take-until stopper)
(rx/subs! (fn [chunk]
(swap! buffer remove-from-buffer (count chunk)))
(fn [cause]
(l/error :hint "unexpected error on audit persistence" :cause cause))
(fn []
(l/debug :hint "audit persistence terminated"))))
(->> (rx/merge
(->> (rx/from-atom buffer)
(rx/filter #(pos? (count %)))
(rx/debounce 2000))
(->> stream
(rx/filter (ptk/type? :app.main.data.profile/logout))
(rx/observe-on :async)))
(rx/map (fn [_]
(into [] (take max-buffer-size) @buffer)))
(rx/with-latest-from profile)
(rx/mapcat (fn [[chunk profile-id]]
(let [events (filterv #(= profile-id (:profile-id %)) chunk)]
(->> (persist-events events)
(rx/tap (fn [_]
(l/debug :hint "events chunk persisted" :total (count chunk))))
(rx/map (constantly chunk))))))
(rx/take-until stopper)
(rx/subs! (fn [chunk]
(swap! buffer remove-from-buffer (count chunk)))
(fn [cause]
(l/error :hint "unexpected error on audit persistence" :cause cause))
(fn []
(l/debug :hint "audit persistence terminated"))))
(->> (rx/merge
(->> stream
(rx/with-latest-from profile)
(rx/map (fn [result]
(let [event (aget result 0)
profile-id (aget result 1)]
(some-> (process-event event)
(update :profile-id #(or % profile-id)))))))
(->> (rx/merge
(->> stream
(rx/with-latest-from profile)
(rx/map (fn [result]
(let [event (aget result 0)
profile-id (aget result 1)]
(some-> (process-event event)
(update :profile-id #(or % profile-id)))))))
(->> (performance-observer-event-stream)
(rx/with-latest-from profile)
(rx/map performance-payload)
(rx/debounce debounce-browser-event-time))
(->> (performance-observer-event-stream)
(rx/with-latest-from profile)
(rx/map performance-payload)
(rx/debounce debounce-browser-event-time))
(->> (performance-observer-longtask-stream)
(rx/with-latest-from profile)
(rx/map performance-payload)
(rx/debounce debounce-longtask-time))
(->> (performance-observer-longtask-stream)
(rx/with-latest-from profile)
(rx/map make-long-task-event)
(rx/debounce debounce-longtask-time))
(->> stream
(rx/with-latest-from profile)
(rx/merge-map process-performance-event)
(rx/debounce debounce-performance-event-time)))
;; (->> stream
;; (rx/with-latest-from profile)
;; (rx/merge-map process-performance-event)
;; (rx/debounce debounce-performance-event-time))
)
(rx/filter :profile-id)
(rx/map (fn [event]
(let [session* (or @session (ct/now))
context (-> @context
(merge (:context event))
(assoc :session session*)
(assoc :external-session-id (cf/external-session-id))
(d/without-nils))]
(reset! session session*)
(-> event
(assoc :timestamp (ct/now))
(assoc :context context)))))
(rx/filter :profile-id)
(rx/map (fn [event]
(prn "AAAA" event)
(let [session* (or @session (ct/now))
context (-> @context
(merge (:context event))
(assoc :session session*)
(assoc :external-session-id (cf/external-session-id))
(d/without-nils))]
(reset! session session*)
(-> event
(assoc :timestamp (ct/now))
(assoc :context context)))))
(rx/tap (fn [event]
(l/debug :hint "event enqueued")
(swap! buffer append-to-buffer event)))
(rx/tap (fn [event]
(l/debug :hint "event enqueued")
(swap! buffer append-to-buffer event)))
(rx/switch-map #(rx/timer session-timeout))
(rx/take-until stopper)
(rx/subs! (fn [_]
(l/debug :hint "session reinitialized")
(reset! session nil))
(fn [cause]
(l/error :hint "error on event batching stream" :cause cause))
(fn []
(l/debug :hitn "events batching stream terminated")))))))))
(rx/switch-map #(rx/timer session-timeout))
(rx/take-until stopper)
(rx/subs! (fn [_]
(l/debug :hint "session reinitialized")
(reset! session nil))
(fn [cause]
(l/error :hint "error on event batching stream" :cause cause))
(fn []
(l/debug :hitn "events batching stream terminated"))))))))
(defn event
[props]
(ptk/data-event ::event props))
;; --- DEVTOOLS PERF LOGGING
(defn install-long-task-observer! []
(when (and (some? (.-PerformanceObserver js/window)) (nil? @longtask-observer*))
(let [observer (js/PerformanceObserver.
(fn [list _]
(when (contains? cf/flags :perf-logs)
(doseq [entry (.getEntries list)]
(let [dur (.-duration entry)
start (.-startTime entry)
attrib (.-attribution entry)
attrib-count (when attrib (.-length attrib))
first-attrib (when (and attrib-count (> attrib-count 0)) (aget attrib 0))
attrib-name (when first-attrib (.-name first-attrib))
attrib-ctype (when first-attrib (.-containerType first-attrib))
attrib-cid (when first-attrib (.-containerId first-attrib))
attrib-csrc (when first-attrib (.-containerSrc first-attrib))]
(.warn js/console (str "[perf] long task " (Math/round dur) "ms at " (Math/round start) "ms"
(when first-attrib
(str " attrib:name=" attrib-name
" ctype=" attrib-ctype
" cid=" attrib-cid
" csrc=" attrib-csrc)))))))))]
(.observe observer #js{:entryTypes #js["longtask"]})
(reset! longtask-observer* observer))))
(defn start-event-loop-stall-logger!
"Log event loop stalls by measuring setInterval drift.
interval-ms: base interval
threshold-ms: drift over which we report"
[interval-ms threshold-ms]
(when (nil? @stall-timer*)
(let [last (atom (.now js/performance))
id (js/setInterval
(fn []
(when (contains? cf/flags :perf-logs)
(let [now (.now js/performance)
expected (+ @last interval-ms)
drift (- now expected)
current-op @current-op*
measures (.getEntriesByType js/performance "measure")
mlen (.-length measures)
last-measure (when (> mlen 0) (aget measures (dec mlen)))
meas-name (when last-measure (.-name last-measure))
meas-detail (when last-measure (.-detail last-measure))
meas-count (when meas-detail (unchecked-get meas-detail "count"))]
(reset! last now)
(when (> drift threshold-ms)
(.warn js/console
(str "[perf] event loop stall: " (Math/round drift) "ms"
(when current-op (str " op=" current-op))
(when meas-name (str " last=" meas-name))
(when meas-count (str " count=" meas-count))))))))
interval-ms)]
(reset! stall-timer* id))))
(defn init!
"Install perf observers in dev builds. Safe to call multiple times.
Perf logs are disabled by default. Enable them with the :perf-logs flag in config."
[]
(when ^boolean js/goog.DEBUG
(install-long-task-observer!)
(start-event-loop-stall-logger! 50 100)
;; Expose simple API on window for manual control in devtools
(let [api #js {:reset (fn []
(try
(.clearMarks js/performance)
(.clearMeasures js/performance)
(catch :default _ nil)))}]
(aset js/window "PenpotPerf" api))))

View File

@@ -104,15 +104,15 @@
variant (or (.getEnglishName ^js font "preferredSubfamily")
(.getEnglishName ^js font "fontSubfamily"))
;; Vertical metrics determine the baseline in a text and the space between lines of
;; text. For historical reasons, there are three pairs of ascender/descender
;; values, known as hhea, OS/2 and uSWin metrics. Depending on the font, operating
;; system and application a different set will be used to render text on the
;; screen. On Mac, Safari and Chrome use the hhea values to render text. Firefox
;; will respect the useTypoMetrics setting and will use the OS/2 if it is set. If
;; the useTypoMetrics is not set, Firefox will also use metrics from the hhea
;; table. On Windows, all browsers use the usWin metrics, but respect the
;; useTypoMetrics setting and if set will use the OS/2 values.
;; Vertical metrics determine the baseline in a text and the space between lines of
;; text. For historical reasons, there are three pairs of ascender/descender
;; values, known as hhea, OS/2 and uSWin metrics. Depending on the font, operating
;; system and application a different set will be used to render text on the
;; screen. On Mac, Safari and Chrome use the hhea values to render text. Firefox
;; will respect the useTypoMetrics setting and will use the OS/2 if it is set. If
;; the useTypoMetrics is not set, Firefox will also use metrics from the hhea
;; table. On Windows, all browsers use the usWin metrics, but respect the
;; useTypoMetrics setting and if set will use the OS/2 values.
hhea-ascender (abs (-> ^js font .-tables .-hhea .-ascender))
hhea-descender (abs (-> ^js font .-tables .-hhea .-descender))

View File

@@ -156,8 +156,8 @@
(defn- update-plugin-permissions-peek
[{:keys [plugin-id url]}]
(when url
;; If the saved manifest has a URL we fetch the manifest to check
;; for updates
;; If the saved manifest has a URL we fetch the manifest to check
;; for updates
(->> (fetch-manifest url)
(rx/subs!
(fn [new-manifest]

View File

@@ -410,25 +410,25 @@
(string? value)
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-shadow value)]}
;; Empty value
;; Empty value
(nil? value) {:errors [(wte/get-error-code :error.token/empty-input)]}
;; Invalid value
;; Invalid value
(not (js/Array.isArray value)) {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}
;; Array of shadows
;; Array of shadows
:else
(let [converted (js->clj value :keywordize-keys true)
;; Parse each shadow with its index
;; Parse each shadow with its index
parsed-shadows (map-indexed
(fn [idx shadow-map]
(parse-single-shadow shadow-map idx))
converted)
;; Collect all errors from all shadows
;; Collect all errors from all shadows
all-errors (mapcat :errors parsed-shadows)
;; Collect all values from shadows that have values
;; Collect all values from shadows that have values
all-values (into [] (keep :value parsed-shadows))]
(if (seq all-errors)

View File

@@ -24,6 +24,7 @@
[app.common.types.shape :as cts]
[app.common.types.variant :as ctv]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.changes :as dch]
[app.main.data.comments :as dcmt]
[app.main.data.common :as dcm]
@@ -75,6 +76,7 @@
[app.util.dom :as dom]
[app.util.globals :as ug]
[app.util.http :as http]
[app.util.perf :as perf]
[app.util.storage :as storage]
[app.util.timers :as tm]
[app.util.webapi :as wapi]
@@ -195,7 +197,7 @@
(rx/of (check-libraries-synchronization file-id libraries))))))
;; This events marks that all the libraries have been resolved
(rx/of (ptk/data-event ::all-libraries-resolved)))
(rx/of (ptk/data-event ::all-libraries-resolved {:file-id file-id})))
(rx/take-until stopper-s))))))
(defn- workspace-initialized
@@ -348,10 +350,11 @@
:file-id file-id}))))))
;; Install dev perf observers once the workspace is ready
(->> stream
(rx/filter (ptk/type? ::workspace-initialized))
(rx/take 1)
(rx/map (fn [_] (ev/init!))))
(when (contains? cf/flags :perf-logs)
(->> stream
(rx/filter (ptk/type? ::workspace-initialized))
(rx/take 1)
(rx/tap (fn [_] (perf/setup)))))
(->> stream
(rx/filter (ptk/type? ::dps/persistence-notification))

View File

@@ -214,8 +214,8 @@
ptk/WatchEvent
(watch [_ state _]
(let [change-fn
(fn [shape attrs]
(update shape :fills types.fills/prepend attrs))
(fn [node attrs]
(update node :fills types.fills/prepend attrs))
undo-id
(js/Symbol)]
(rx/concat

View File

@@ -1292,9 +1292,9 @@
(rx/take 1 workspace-data-s)
(rx/take 1 workspace-data-s)
workspace-data-s)
;; Need to get the file data before the change, so deleted shapes
;; still exist, for example. We initialize the buffer with three
;; copies of the initial state
;; Need to get the file data before the change, so deleted shapes
;; still exist, for example. We initialize the buffer with three
;; copies of the initial state
(rx/buffer 3 1))
changes-s

View File

@@ -231,12 +231,12 @@
:timeout nil
:tag :media-loading}))
(->> (if (seq uris)
;; Media objects is a list of URL's pointing to the path
;; Media objects is a list of URL's pointing to the path
(process-uris params)
;; Media objects are blob of data to be upload
;; Media objects are blob of data to be upload
(process-blobs params))
;; Every stream has its own sideeffect. We need to ignore the result
;; Every stream has its own sideeffect. We need to ignore the result
(rx/ignore)
(rx/catch #(handle-media-error % on-error))
(rx/finalize #(st/emit! (ntf/hide :tag :media-loading))))))))

View File

@@ -793,8 +793,8 @@
(-> options
(assoc :reg-objects? true)
(assoc :ignore-tree ignore-tree)
;; Attributes that can change in the transform. This
;; way we don't have to check all the attributes
;; Attributes that can change in the transform. This
;; way we don't have to check all the attributes
(assoc :attrs transform-attrs))
update-shape

View File

@@ -439,7 +439,7 @@
add-new-variant? (and
;; The parent is a variant container
(-> parent-id objects ctc/is-variant-container?)
;; Any of the shapes is a main instance
;; Any of the shapes is a main instance
(some (comp ctc/main-instance? objects) ids))
undo-id (js/Symbol)]

View File

@@ -143,7 +143,7 @@
([value shape-ids attributes]
(update-stroke-color value shape-ids attributes nil))
;; The attributes param is needed to have the same arity that other update functions
;; The attributes param is needed to have the same arity that other update functions
([value shape-ids _attributes page-id]
(when-let [color (value->color value)]
(dwsh/update-shapes shape-ids

View File

@@ -1121,15 +1121,15 @@
:cell cell))
add-component-to-variant? (and
;; Any of the shapes is a head
;; Any of the shapes is a head
(some (comp ctk/instance-head? objects) ids)
;; Any ancestor of the destination parent is a variant
(->> (cfh/get-parents-with-self objects frame-id)
(some ctk/is-variant?)))
add-new-variant? (and
;; The parent is a variant container
;; The parent is a variant container
(-> frame-id objects ctk/is-variant-container?)
;; Any of the shapes is a main instance
;; Any of the shapes is a main instance
(some (comp ctk/main-instance? objects) ids))]
(rx/concat

View File

@@ -447,8 +447,8 @@
:stroke-opacity 1
:stroke-width 2}
;; Move the position of the variant container so the main shape doesn't
;; change its position
;; Move the position of the variant container so the main shape doesn't
;; change its position
delta (or delta
(if (ctsl/any-layout? parent)
(gpt/point 0 0)
@@ -456,8 +456,8 @@
undo-id (js/Symbol)]
;;TODO Refactor all called methods in order to be able to
;;generate changes instead of call the events
;;TODO Refactor all called methods in order to be able to
;;generate changes instead of call the events
(rx/concat
@@ -467,7 +467,7 @@
(when (not= name (:name main))
(dwl/rename-component component-id name))
;; Create variant container
;; Create variant container
(dwsh/create-artboard-from-shapes [main-instance-id] variant-id nil nil nil delta flex?)
(cl/remove-all-fills variant-vec {:color clr/black :opacity 1})
(when flex? (dwsl/create-layout-from-id variant-id :flex))
@@ -476,12 +476,12 @@
(cl/add-stroke variant-vec stroke-props)
(set-variant-id component-id variant-id))
;; Add the necessary number of new properties, with default values
;; Add the necessary number of new properties, with default values
(rx/from
(repeatedly num-props
#(add-new-property variant-id {:fill-values? true})))
;; When the component has path, set the path items as properties values
;; When the component has path, set the path items as properties values
(when (> (count cpath) 1)
(rx/from
(map
@@ -634,7 +634,7 @@
prefix (->> shapes
(mapv #(cpn/split-path (:name %)))
(common-prefix))
;; When the common parent is root, add a wrapper
;; When the common parent is root, add a wrapper
add-wrapper? (empty? prefix)
first-shape (first shapes)
delta (gpt/point (- (:x rect) (:x first-shape) 30)
@@ -667,10 +667,10 @@
(dwt/update-dimensions [variant-id] :height (+ (:height rect) 60))
(ev/event {::ev/name "combine-as-variants" ::ev/origin trigger :number-of-combined (count ids)}))
;; NOTE: we need to schedule a commit into a
;; microtask for ensure that all the scheduled
;; microtask of previous events execute before the
;; commit
;; NOTE: we need to schedule a commit into a
;; microtask for ensure that all the scheduled
;; microtask of previous events execute before the
;; commit
(->> (rx/of (dwu/commit-undo-transaction undo-id))
(rx/observe-on :async)))))))
@@ -705,7 +705,7 @@
(let [libraries (dsh/lookup-libraries state)
component-id (:component-id shape)
component (ctf/get-component libraries (:component-file shape) component-id :include-deleted? false)]
;; If the value is already val, do nothing
;; If the value is already val, do nothing
(when (not= val (dm/get-in component [:variant-properties pos :value]))
(let [current-page-objects (dsh/lookup-page-objects state)
variant-id (:variant-id component)

View File

@@ -51,7 +51,7 @@
(or (> (:width srect) width)
(> (:height srect) height))
(let [srect (gal/adjust-to-viewport size srect {:padding 40})
(let [srect (gal/adjust-to-viewport size srect {:padding 40 :min-zoom 0.01})
zoom (/ (:width size) (:width srect))]
(-> local

View File

@@ -97,7 +97,7 @@
state
(update state :workspace-local
(fn [{:keys [vport] :as local}]
(let [srect (gal/adjust-to-viewport vport srect {:padding 160})
(let [srect (gal/adjust-to-viewport vport srect {:padding 160 :min-zoom 0.01})
zoom (/ (:width vport) (:width srect))]
(-> local
(assoc :zoom zoom)
@@ -118,7 +118,7 @@
(gsh/shapes->rect))]
(update state :workspace-local
(fn [{:keys [vport] :as local}]
(let [srect (gal/adjust-to-viewport vport srect {:padding 40})
(let [srect (gal/adjust-to-viewport vport srect {:padding 40 :min-zoom 0.01})
zoom (/ (:width vport) (:width srect))]
(-> local
(assoc :zoom zoom)
@@ -142,7 +142,7 @@
(fn [{:keys [vport] :as local}]
(let [srect (gal/adjust-to-viewport
vport srect
{:padding 40})
{:padding 40 :min-zoom 0.01})
zoom (/ (:width vport)
(:width srect))]
(-> local

View File

@@ -214,8 +214,8 @@
([font-id variant-id]
(log/dbg :action "try-ensure-loaded!" :font-id font-id :variant-id variant-id)
(if-not (exists? js/window)
;; If we are in the worker environment, we just mark it as loaded
;; without really loading it.
;; If we are in the worker environment, we just mark it as loaded
;; without really loading it.
(do
(swap! loaded-hints conj {:font-id font-id :font-variant-id variant-id})
(p/resolved font-id))

View File

@@ -133,7 +133,7 @@
[{:keys [shape] :as props}]
(let [childs (mapv #(get objects %) (:shapes shape))]
(if (and (map? (:content shape))
;; tspan shouldn't be contained in a group or have svg defs
;; tspan shouldn't be contained in a group or have svg defs
(not= :tspan (get-in shape [:content :tag]))
(or (= :svg (get-in shape [:content :tag]))
(contains? shape :svg-attrs)))

View File

@@ -762,7 +762,7 @@
h (:height viewport)
comment-width 284 ;; TODO: this is the width set via CSS in an outer container…
;; We should probably do this in a different way.
;; We should probably do this in a different way.
orientation-left? (>= (+ base-x comment-width (:x bubble-margin)) w)
orientation-top? (>= base-y (/ h 2))

View File

@@ -198,7 +198,7 @@
:valid (and touched? (not error))
:invalid (and touched? error)
:disabled disabled)
;; :empty (str/empty? value)
;; :empty (str/empty? value)
on-focus #(reset! focus? true)

View File

@@ -225,10 +225,10 @@
(and
(= subscription-type "unlimited")
(or
;; common: seats < 25 and diff >= 4
;; common: seats < 25 and diff >= 4
(and (< seats 25)
(>= (- editors seats) 4))
;; special: reached 25+ editors, seats < 25 and there is overuse
;; special: reached 25+ editors, seats < 25 and there is overuse
(and (< seats 25)
(>= editors 25)
(> editors seats)))))))

View File

@@ -135,8 +135,8 @@
:version "1.1"
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"
;; Fix Chromium bug about color of html texts
;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5
;; Fix Chromium bug about color of html texts
;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5
:style {:-webkit-print-color-adjust :exact}
:fill "none"}

View File

@@ -71,11 +71,11 @@
start-size-ref (mf/use-ref nil)
start-ref (mf/use-ref nil)
;; Since Penpot is not responsive designed, this value will only refer to vertical axis.
;; Since Penpot is not responsive designed, this value will only refer to vertical axis.
window-height* (mf/use-state #(dom/get-window-height))
window-height (deref window-height*)
;; In case max-val is a string, we need to parse it as a double.
;; In case max-val is a string, we need to parse it as a double.
max-val (mf/with-memo [max-val window-height]
(let [parsed-max-val (when (string? max-val) (d/parse-double max-val))]
(if parsed-max-val

View File

@@ -82,7 +82,7 @@
:style #js {"--bullet-size" "16px"}}
[:& cb/color-bullet {:color color
:mini true}]]
;; REMOVE this conditional when :inspect-styles flag is removed
;; REMOVE this conditional when :inspect-styles flag is removed
(if (contains? cf/flags :inspect-styles)
[:div {:class (stl/css :global/attr-label)} property]
[:div {:class (stl/css :format-wrapper)}

View File

@@ -107,10 +107,10 @@
(when (:text-decoration style)
[:div {:class (stl/css :text-row)}
[:div {:class (stl/css :global/attr-label)} "Text Decoration"]
;; Execution time translation strings:
;; (tr "inspect.attributes.typography.text-decoration.none")
;; (tr "inspect.attributes.typography.text-decoration.strikethrough")
;; (tr "inspect.attributes.typography.text-decoration.underline")
;; Execution time translation strings:
;; (tr "inspect.attributes.typography.text-decoration.none")
;; (tr "inspect.attributes.typography.text-decoration.strikethrough")
;; (tr "inspect.attributes.typography.text-decoration.underline")
[:div {:class (stl/css :global/attr-value)}
[:> copy-button* {:data (copy-style-data style :text-decoration)}
[:div {:class (stl/css :button-children)}
@@ -119,12 +119,12 @@
(when (:text-transform style)
[:div {:class (stl/css :text-row)}
[:div {:class (stl/css :global/attr-label)} "Text Transform"]
;; Execution time translation strings:
;; (tr "inspect.attributes.typography.text-transform.lowercase")
;; (tr "inspect.attributes.typography.text-transform.none")
;; (tr "inspect.attributes.typography.text-transform.capitalize")
;; (tr "inspect.attributes.typography.text-transform.uppercase")
;; (tr "inspect.attributes.typography.text-transform.unset")
;; Execution time translation strings:
;; (tr "inspect.attributes.typography.text-transform.lowercase")
;; (tr "inspect.attributes.typography.text-transform.none")
;; (tr "inspect.attributes.typography.text-transform.capitalize")
;; (tr "inspect.attributes.typography.text-transform.uppercase")
;; (tr "inspect.attributes.typography.text-transform.unset")
[:div {:class (stl/css :global/attr-value)}
[:> copy-button* {:data (copy-style-data style :text-transform)}
[:div {:class (stl/css :button-children)}

View File

@@ -141,14 +141,14 @@
(for [panel panels]
[:li {:key (d/name panel)}
(case panel
;; VARIANTS PANEL
;; VARIANTS PANEL
:variant
[:> style-box* {:panel :variant}
[:> variants-panel* {:component first-component
:objects objects
:shape first-shape
:data data}]]
;; GEOMETRY PANEL
;; GEOMETRY PANEL
:geometry
[:> style-box* {:panel :geometry
:shorthand (:geometry shorthands)}
@@ -156,7 +156,7 @@
:objects objects
:resolved-tokens resolved-active-tokens
:on-geometry-shorthand set-shorthands}]]
;; LAYOUT PANEL
;; LAYOUT PANEL
:layout
(let [layout-shapes (->> shapes (filter ctl/any-layout?))]
(when (seq layout-shapes)
@@ -166,7 +166,7 @@
:objects objects
:resolved-tokens resolved-active-tokens
:on-layout-shorthand set-shorthands}]]))
;; LAYOUT ELEMENT PANEL
;; LAYOUT ELEMENT PANEL
:layout-element
(let [shapes (->> shapes (filter #(ctl/any-layout-immediate-child? objects %)))
some-layout-prop? (->> shapes

View File

@@ -101,7 +101,7 @@
:resolved-tokens resolved-tokens
:color-space color-space}]))
;; Typography style
;; Typography style
(when (and (not composite-typography-token)
(:typography-ref-id style))
[:> typography-name-block* {:style style}])

View File

@@ -61,7 +61,7 @@
content
(when (some? links)
(for [[index link] (d/enumerate links)]
;; TODO Review this component
;; TODO Review this component
[:& lb/link-button {:class (stl/css :link)
:on-click (:callback link)
:value (:label link)

View File

@@ -171,8 +171,8 @@
:fillOpacity opacity}
[:path {:d "M 3 0 L 6 3 L 3 6 L 0 3 z"}]])
;; If the user wants line caps but different in each end,
;; simulate it with markers.
;; If the user wants line caps but different in each end,
;; simulate it with markers.
(when (and (or (= cap-start :round)
(= cap-end :round))
(not= cap-start cap-end))

View File

@@ -100,14 +100,14 @@
[:& filters/filters {:shape (dissoc shape :blur) :filter-id filter-id-shadows}]
[:& filters/filters {:shape (assoc shape :shadow []) :filter-id filter-id-blur}]]
;; This need to be separated in two layers so the clip doesn't affect the shadow filters
;; otherwise the shadow will be clipped and not visible
;; This need to be separated in two layers so the clip doesn't affect the shadow filters
;; otherwise the shadow will be clipped and not visible
[:g.frame-container-shadows {:filter filter-str-shadows}
[:g {:clip-path (when-not ^boolean show-content? (frame-clip-url shape render-id))
;; A frame sets back normal fill behavior (default
;; transparent). It may have been changed to default black
;; if a shape coming from an imported SVG file is
;; rendered. See main.ui.shapes.attrs/add-style-attrs.
;; A frame sets back normal fill behavior (default
;; transparent). It may have been changed to default black
;; if a shape coming from an imported SVG file is
;; rendered. See main.ui.shapes.attrs/add-style-attrs.
:fill "none"}
[:& shape-fills {:shape shape}

View File

@@ -447,7 +447,7 @@
(mf/use-effect
(mf/deps nav-scroll)
(fn []
;; Set scroll position after navigate
;; Set scroll position after navigate
(when (number? nav-scroll)
(let [viewer-section (dom/get-element "viewer-section")]
(st/emit! (dv/reset-nav-scroll))
@@ -481,8 +481,8 @@
:fit (st/emit! dv/zoom-to-fit)
:fill (st/emit! dv/zoom-to-fill)
nil)
;; Navigate animation needs to be started after navigation
;; is complete, and we have the next page index.
;; Navigate animation needs to be started after navigation
;; is complete, and we have the next page index.
(let [nav-animation (d/seek #(= (:kind %) :go-to-frame) (vals current-animations))]
(when nav-animation
(let [orig-viewport (mf/ref-val orig-viewport-ref)
@@ -498,7 +498,7 @@
(mf/use-effect
(mf/deps current-animations)
(fn []
;; Overlay animations may be started when needed.
;; Overlay animations may be started when needed.
(when current-animations
(doseq [[overlay-frame-id animation-vals] current-animations]
(let [overlay-viewport (dom/get-element (str "overlay-" (str (:overlay-id animation-vals))))

View File

@@ -47,7 +47,7 @@
(defn- ignore-frame-shape
[shape objects manual?]
(let [shape (cond-> shape ;; When the the interaction is not manual and its origin is a frame,
;; we need to ignore it on all the find-frame calculations
;; we need to ignore it on all the find-frame calculations
(and (:frame-id shape) (not manual?))
(assoc :type :rect))
objects (assoc objects (:id shape) shape)]

View File

@@ -112,7 +112,7 @@
{:class (stl/css :file-name)
:title file-name
:on-double-click start-editing-name}
;;-- Persistende state widget
;;-- Persistende state widget
[:div {:class (case persistence-status
:pending (stl/css :status-notification :pending-status)
:saving (stl/css :status-notification :saving-status)

View File

@@ -191,7 +191,7 @@
:key "frame-container"
:opacity (when ^boolean hidden? 0)}
;; When there is no thumbnail, we generate a empty rect.
;; When there is no thumbnail, we generate a empty rect.
(when (and (not ^boolean content-visible?) (not @imposter-loaded))
[:g.frame-placeholder
[:rect {:x x

View File

@@ -106,7 +106,7 @@
:title (if is-local
(mf/html [:div {:class (stl/css :special-title)}
(tr "workspace.assets.local-library")])
;; Do we need to add shared info here?
;; Do we need to add shared info here?
(mf/html [:div {:class (stl/css :special-title)}
file-name]))}
(when-not ^boolean is-local

View File

@@ -67,7 +67,7 @@
(let [data (into [] (remove nil?) (dwc/extract-all-colors shapes file-id libraries))
groups (d/group-by :attrs #(dissoc % :attrs) data)
;; Unique color attribute maps
;; Unique color attribute maps
all-colors (distinct (mapv :attrs data))
;; Split into: library colors, token colors, and plain colors

View File

@@ -346,17 +346,19 @@
{:value (:id variant)
:key (pr-str variant)
:label (:name variant)})))
variant-options (if (= font-variant-id :multiple)
variant-options (if (or (= font-variant-id :multiple) (= font-variant-id "mixed"))
(conj basic-variant-options
{:value ""
:key :multiple-variants
:label "--"})
basic-variant-options)]
basic-variant-options)
font-variant-value (attr->string font-variant-id)
font-variant-value (if (= font-variant-value "mixed") "" font-variant-value)]
;; TODO Add disabled mode
[:& select
{:class (stl/css :font-variant-select)
:default-value (attr->string font-variant-id)
:default-value font-variant-value
:options variant-options
:on-change on-font-variant-change
:on-blur on-blur}])]]]))

View File

@@ -281,7 +281,7 @@
:options stroke-style-options
:on-change on-style-change}]])]
;; Stroke Caps
;; Stroke Caps
(when show-caps
[:div {:class (stl/css :stroke-caps-options)}
[:& select {:default-value (:stroke-cap-start stroke)

View File

@@ -211,7 +211,7 @@
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
prev-input-color (some-> value
(tinycolor/valid-color))
;; If the input is a reference we will take the format from the computed value
;; If the input is a reference we will take the format from the computed value
prev-computed-color (when-not prev-input-color
(some-> value (tinycolor/valid-color)))
prev-format (some-> (or prev-input-color prev-computed-color)
@@ -380,7 +380,7 @@
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
prev-input-color (some-> value
(tinycolor/valid-color))
;; If the input is a reference we will take the format from the computed value
;; If the input is a reference we will take the format from the computed value
prev-computed-color (when-not prev-input-color
(some-> value (tinycolor/valid-color)))
prev-format (some-> (or prev-input-color prev-computed-color)

View File

@@ -146,7 +146,7 @@
:active-theme-tokens active-theme-tokens
:on-click on-token-pill-click
:on-context-menu on-pill-context-menu}])
;; Render segment folder
;; Render segment folder
[:ul {:class (stl/css :node-parent)
:key (:path node)}
[:> folder-node* {:node node

View File

@@ -245,7 +245,7 @@
(dom/child? (dom/get-target event) (dom/query ".grid-layout-editor"))
(dom/class? (dom/get-target event) "viewport-selrect"))
(let [position (dom/get-client-position event)]
;; Delayed callback because we need to wait to the previous context menu to be closed
;; Delayed callback because we need to wait to the previous context menu to be closed
(ts/schedule
#(st/emit!
(if (and (not read-only?) (some? @hover))

View File

@@ -123,7 +123,7 @@
:aria-label (tr "workspace.path.actions.draw-nodes" (sc/get-tooltip :draw-nodes))
:tooltip-placement "bottom"
:on-click on-select-draw-mode}]
;; Edit mode
;; Edit mode
[:> icon-button* {:variant "ghost"
:class (stl/css :topbar-btn)
:icon i/move

View File

@@ -200,7 +200,7 @@
is-variant? [:use {:href "#icon-component"}])])
(if ^boolean edition?
;; Case when edition? is true
;; Case when edition? is true
[:foreignObject {:x text-pos-x
:y -15
:width (max 0 (- text-width text-pos-x))
@@ -217,7 +217,7 @@
:ref ref
:default-value (:name frame)
:on-blur accept-edit}]]
;; Case when edition? is false
;; Case when edition? is false
[:foreignObject {:x text-pos-x
:y -11
:width (max 0 (- text-width text-pos-x))

View File

@@ -215,7 +215,7 @@
:meroitic #"\uD802[\uDD80-\uDD9F]"
;; Arrows, Mathematical Operators, Misc Technical, Geometric Shapes, Misc Symbols, Dingbats, Supplemental Arrows, etc.
:symbols #"[\u2190-\u21FF\u2200-\u22FF\u2300-\u23FF\u25A0-\u25FF\u2600-\u26FF\u2700-\u27BF\u2B00-\u2BFF]"
;; Additional arrows, math, technical, geometric, and symbol blocks
;; Additional arrows, math, technical, geometric, and symbol blocks
:symbols-2 #"[\u2190-\u21FF\u2200-\u22FF\u2300-\u23FF\u25A0-\u25FF\u2600-\u26FF\u2700-\u27BF\u2B00-\u2BFF]"
:music #"[\u2669-\u267B]|\uD834[\uDD00-\uDD1F]"})

View File

@@ -108,7 +108,7 @@ void main() {
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. positions) (.-STATIC_DRAW ^js gl))
(.enableVertexAttribArray ^js gl position-location)
(.vertexAttribPointer ^js gl position-location 2 (.-FLOAT ^js gl) false 0 0)
;; Set up texcoord buffer
;; Set up texcoord buffer
(.bindBuffer ^js gl (.-ARRAY_BUFFER ^js gl) texcoord-buffer)
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. texcoords) (.-STATIC_DRAW ^js gl))
(.enableVertexAttribArray ^js gl texcoord-location)

View File

@@ -258,8 +258,8 @@
(let [values (unchecked-get wasm/serializers "font-style")
default (unchecked-get values "normal")]
(case font-style
;; NOTE: normal == regular!
;; is it OK to keep those two values in our cljs model?
;; NOTE: normal == regular!
;; is it OK to keep those two values in our cljs model?
"normal" (unchecked-get values "normal")
"regular" (unchecked-get values "normal")
"italic" (unchecked-get values "italic")

View File

@@ -7,5 +7,5 @@
[hex opacity]
(let [rgb (js/parseInt (subs hex 1) 16)
a (mth/floor (* (or opacity 1) 0xff))]
;; rgba >>> 0 so we have an unsigned representation
;; rgba >>> 0 so we have an unsigned representation
(unsigned-bit-shift-right (bit-or (bit-shift-left a 24) rgb) 0)))

View File

@@ -56,7 +56,7 @@
is-input?
(= (dom/get-tag-name target) "INPUT")]
;; ignore when pasting into an editable control
;; ignore when pasting into an editable control
(if-not (or content-editable? is-input?)
(-> event
(dom/event->browser-event)

View File

@@ -44,9 +44,9 @@
(js/console.log
label
"[" (:name data) "]"
;; (if currentTarget
;; (str "<" (.-localName currentTarget) " " (.-textContent currentTarget) ">")
;; "null")
;; (if currentTarget
;; (str "<" (.-localName currentTarget) " " (.-textContent currentTarget) ">")
;; "null")
(if relatedTarget
(str "<" (.-localName relatedTarget) " " (.-textContent relatedTarget) ">")
"null"))))

View File

@@ -169,3 +169,81 @@
(let [end (timestamp)]
(println (str "[" event "]" (- end start)))))
#js {"priority" "user-blocking"})))))
;; --- DEVTOOLS PERF LOGGING
(defonce ^:private longtask-observer* (atom nil))
(defonce ^:private stall-timer* (atom nil))
(defonce ^:private current-op* (atom nil))
(defn- install-long-task-observer
[]
(when (and (some? (.-PerformanceObserver js/window)) (nil? @longtask-observer*))
(let [observer (js/PerformanceObserver.
(fn [list _]
(doseq [entry (.getEntries list)]
(let [dur (.-duration entry)
start (.-startTime entry)
attrib (.-attribution entry)
attrib-count (when attrib (.-length attrib))
first-attrib (when (and attrib-count (> attrib-count 0)) (aget attrib 0))
attrib-name (when first-attrib (.-name first-attrib))
attrib-ctype (when first-attrib (.-containerType first-attrib))
attrib-cid (when first-attrib (.-containerId first-attrib))
attrib-csrc (when first-attrib (.-containerSrc first-attrib))]
(.warn js/console (str "[perf] long task " (Math/round dur) "ms at " (Math/round start) "ms"
(when first-attrib
(str " attrib:name=" attrib-name
" ctype=" attrib-ctype
" cid=" attrib-cid
" csrc=" attrib-csrc))))))))]
(.observe observer #js{:entryTypes #js["longtask"]})
(reset! longtask-observer* observer))))
(defn- start-event-loop-stall-logger
"Log event loop stalls by measuring setInterval drift.
Params:
- interval-ms: base interval
- threshold-ms: drift over which we report
"
[interval-ms threshold-ms]
(when (nil? @stall-timer*)
(let [last (atom (.now js/performance))
id (js/setInterval
(fn []
(let [now (.now js/performance)
expected (+ @last interval-ms)
drift (- now expected)
current-op @current-op*
measures (.getEntriesByType js/performance "measure")
mlen (.-length measures)
last-measure (when (> mlen 0) (aget measures (dec mlen)))
meas-name (when last-measure (.-name last-measure))
meas-detail (when last-measure (.-detail last-measure))
meas-count (when meas-detail (unchecked-get meas-detail "count"))]
(reset! last now)
(when (> drift threshold-ms)
(.warn js/console
(str "[perf] event loop stall: " (Math/round drift) "ms"
(when current-op (str " op=" current-op))
(when meas-name (str " last=" meas-name))
(when meas-count (str " count=" meas-count)))))))
interval-ms)]
(reset! stall-timer* id))))
(defn setup
"Install perf observers in dev builds. Safe to call multiple times.
Perf logs are disabled by default. Enable them with the :perf-logs
flag in config."
[]
(install-long-task-observer)
(start-event-loop-stall-logger 50 100)
;; Expose simple API on window for manual control in devtools
(let [api #js {:reset (fn []
(try
(.clearMarks js/performance)
(.clearMeasures js/performance)
(catch :default _ nil)))}]
(unchecked-set js/window "PenpotPerf" api)))

View File

@@ -23,15 +23,15 @@
[node]
(is-element node "br"))
(defn is-inline-child
(defn is-text-span-child
[node]
(or (is-line-break node)
(is-text-node node)))
(defn get-inline-text
(defn get-text-span-text
[element]
(when-not (is-inline-child (.-firstChild element))
(throw (js/TypeError. "Invalid inline child")))
(when-not (is-text-span-child (.-firstChild element))
(throw (js/TypeError. "Invalid text span child")))
(if (is-line-break (.-firstChild element))
""
(.-textContent element)))
@@ -54,7 +54,7 @@
(assoc acc key (if (value-empty? value) (get defaults key) value))))
{} attrs)))
(defn get-inline-styles
(defn get-text-span-styles
[element]
(get-attrs-from-styles element txt/text-node-attrs (txt/get-default-text-attrs)))
@@ -66,18 +66,18 @@
[element]
(get-attrs-from-styles element txt/root-attrs txt/default-root-attrs))
(defn create-inline
(defn create-text-span
[element]
(let [text (get-inline-text element)]
(let [text (get-text-span-text element)]
(d/merge {:text text
:key (.-id element)}
(get-inline-styles element))))
(get-text-span-styles element))))
(defn create-paragraph
[element]
(d/merge {:type "paragraph"
:key (.-id element)
:children (mapv create-inline (.-children element))}
:children (mapv create-text-span (.-children element))}
(get-paragraph-styles element)))
(defn create-root

View File

@@ -54,8 +54,8 @@
(assoc acc key (get node key default-value)))) {} attrs)
fills
(cond
;; DEPRECATED: still here for backward compatibility with
;; old penpot files that still has a single color.
;; DEPRECATED: still here for backward compatibility with
;; old penpot files that still has a single color.
(or (some? (:fill-color node))
(some? (:fill-opacity node))
(some? (:fill-color-gradient node)))
@@ -92,7 +92,7 @@
[root]
(get-styles-from-attrs root txt/root-attrs txt/default-text-attrs))
(defn get-inline-styles
(defn get-text-span-styles
[inline paragraph]
(let [node (if (= "" (:text inline)) paragraph inline)
styles (get-styles-from-attrs node txt/text-node-attrs txt/default-text-attrs)]
@@ -104,7 +104,7 @@
(when text
(.replace text (js/RegExp "/" "g") "/\u200B")))
(defn get-inline-children
(defn get-text-span-children
[inline paragraph]
[(if (and (= "" (:text inline))
(= 1 (count (:children paragraph))))
@@ -119,14 +119,14 @@
[paragraph]
(some #(not= "" (:text % "")) (:children paragraph)))
(defn create-inline
(defn create-text-span
[inline paragraph]
(create-element
"span"
{:id (or (:key inline) (create-random-key))
:data {:itype "inline"}
:style (get-inline-styles inline paragraph)}
(get-inline-children inline paragraph)))
:data {:itype "span"}
:style (get-text-span-styles inline paragraph)}
(get-text-span-children inline paragraph)))
(defn create-paragraph
[paragraph]
@@ -135,7 +135,7 @@
{:id (or (:key paragraph) (create-random-key))
:data {:itype "paragraph"}
:style (get-paragraph-styles paragraph)}
(mapv #(create-inline % paragraph) (:children paragraph))))
(mapv #(create-text-span % paragraph) (:children paragraph))))
(defn create-root
[root]

View File

@@ -100,7 +100,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
blue1 (cths/get-shape file :blue1)
features #{"components/v2"}
@@ -121,12 +121,12 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' uuid/zero)]
;; ==== Check
;; ==== Check
;; blue1 has swap-id
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -138,7 +138,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b1 (cths/get-shape file :frame-b1)
blue1 (cths/get-shape file :blue1)
@@ -161,11 +161,11 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id b1'))]
;; ==== Check
;; blue1 has swap-id
;; ==== Check
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -176,7 +176,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
blue1 (cths/get-shape file :blue1)
@@ -199,11 +199,11 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id yellow'))]
;; ==== Check
;; blue1 has swap-id
;; ==== Check
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -215,7 +215,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b2 (cths/get-shape file :frame-b2)
blue1 (cths/get-shape file :blue1)
@@ -238,11 +238,11 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id b2'))]
;; ==== Check
;; blue1 has swap-id
;; ==== Check
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -254,7 +254,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
blue1 (cths/get-shape file :blue1)
features #{"components/v2"}
@@ -276,8 +276,8 @@
page' (cthf/current-page file')
copied-blue1' (find-copied-shape blue1 page' uuid/zero)]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -290,7 +290,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
blue1 (cths/get-shape file :blue1)
@@ -314,8 +314,8 @@
yellow' (cths/get-shape file' :frame-yellow)
copied-blue1' (find-copied-shape blue1 page' (:id yellow'))]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -327,7 +327,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b2 (cths/get-shape file :frame-b2)
blue1 (cths/get-shape file :blue1)
@@ -351,8 +351,8 @@
b2' (cths/get-shape file' :frame-b2)
copied-blue1' (find-copied-shape blue1 page' (:id b2'))]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -364,7 +364,7 @@
file (setup-file-blue1-in-yellow)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
features #{"components/v2"}
@@ -386,12 +386,12 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))]
;; ==== Check
;; ==== Check
;; blue1 has swap-id
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -402,7 +402,7 @@
file (setup-file-blue1-in-yellow)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
b1 (cths/get-shape file :frame-b1)
@@ -426,12 +426,12 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))]
;; ==== Check
;; ==== Check
;; blue1 has swap-id
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -442,7 +442,7 @@
file (setup-file-blue1-in-yellow)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
b2 (cths/get-shape file :frame-b2)
@@ -466,11 +466,11 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))]
;; ==== Check
;; ==== Check
;; blue1 has swap-id
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -482,7 +482,7 @@
store (ths/setup-store file)
blue1 (cths/get-shape file :blue1)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
features #{"components/v2"}
@@ -505,8 +505,8 @@
copied-yellow' (find-copied-shape yellow page' uuid/zero)
copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -518,7 +518,7 @@
store (ths/setup-store file)
blue1 (cths/get-shape file :blue1)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
b1 (cths/get-shape file :frame-b1)
@@ -543,8 +543,8 @@
copied-yellow' (find-copied-shape yellow page' (:id b1'))
copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -556,7 +556,7 @@
store (ths/setup-store file)
blue1 (cths/get-shape file :blue1)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
b2 (cths/get-shape file :frame-b2)
@@ -581,8 +581,8 @@
copied-yellow' (find-copied-shape yellow page' (:id b2'))
copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -593,7 +593,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
green (cths/get-shape file :green-copy)
features #{"components/v2"}
@@ -615,12 +615,12 @@
copied-green' (find-copied-shape green page' uuid/zero)
copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))]
;; ==== Check
;; ==== Check
;; blue2 has swap-id
;; blue2 has swap-id
(t/is (some? (ctk/get-swap-slot blue2')))
;; copied-blue2 also has swap-id
;; copied-blue2 also has swap-id
(t/is (some? copied-blue2'))
(t/is (some? (ctk/get-swap-slot copied-blue2')))))))))
@@ -631,7 +631,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b1 (cths/get-shape file :frame-b1)
green (cths/get-shape file :green-copy)
@@ -655,12 +655,12 @@
copied-green' (find-copied-shape green page' (:id b1'))
copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))]
;; ==== Check
;; ==== Check
;; blue1 has swap-id
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue2')))
;; copied-blue1 also has swap-id
;; copied-blue1 also has swap-id
(t/is (some? copied-blue2'))
(t/is (some? (ctk/get-swap-slot copied-blue2')))))))))
@@ -671,7 +671,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b2 (cths/get-shape file :frame-b2)
green (cths/get-shape file :green-copy)
@@ -695,12 +695,12 @@
copied-green' (find-copied-shape green page' (:id b2'))
copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))]
;; ==== Check
;; ==== Check
;; blue2 has swap-id
;; blue2 has swap-id
(t/is (some? (ctk/get-swap-slot blue2')))
;; copied-blue1 also has swap-id
;; copied-blue1 also has swap-id
(t/is (some? copied-blue2'))
(t/is (some? (ctk/get-swap-slot copied-blue2')))))))))
@@ -712,7 +712,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
green (cths/get-shape file :green-copy)
blue2 (cths/get-shape file :blue-copy-in-green-copy)
@@ -736,8 +736,8 @@
copied-green' (find-copied-shape green page' uuid/zero)
copied-blue1' (find-copied-shape blue2 page' (:id copied-green'))]
;; ==== Check
;; copied-blue1 has swap-id
;; ==== Check
;; copied-blue1 has swap-id
(t/is (some? copied-blue1'))
(t/is (some? (ctk/get-swap-slot copied-blue1')))))))))
@@ -748,7 +748,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b1 (cths/get-shape file :frame-b1)
green (cths/get-shape file :green-copy)
@@ -774,8 +774,8 @@
copied-green' (find-copied-shape green page' (:id b1'))
copied-blue2' (find-copied-shape blue2 page' (:id copied-green'))]
;; ==== Check
;; copied-blue1 has swap-id
;; ==== Check
;; copied-blue1 has swap-id
(t/is (some? copied-blue2'))
(t/is (some? (ctk/get-swap-slot copied-blue2')))))))))
@@ -786,7 +786,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b2 (cths/get-shape file :frame-b2)
blue2 (cths/get-shape file :blue-copy-in-green-copy)
@@ -812,8 +812,8 @@
copied-green' (find-copied-shape green page' (:id b2'))
copied-blue2' (find-copied-shape blue2 page' (:id copied-green'))]
;; ==== Check
;; copied-blue1 has swap-id
;; ==== Check
;; copied-blue1 has swap-id
(t/is (some? copied-blue2'))
(t/is (some? (ctk/get-swap-slot copied-blue2')))))))))
@@ -823,10 +823,10 @@
(t/async
done
(let [;; ==== Setup
;; {:frame-red} [:name frame-blue] # [Component :red]
;; {:frame-blue} [:name frame-blue] #[Component :blue]
;; {:frame-green} [:name frame-green] #[Component :green]
;; :blue1 [:name frame-blue, :swap-slot-label :red-copy-green] @--> frame-blue
;; {:frame-red} [:name frame-blue] # [Component :red]
;; {:frame-blue} [:name frame-blue] #[Component :blue]
;; {:frame-green} [:name frame-green] #[Component :green]
;; :blue1 [:name frame-blue, :swap-slot-label :red-copy-green] @--> frame-blue
file (-> (cthf/sample-file :file1)
(ctho/add-frame :frame-red :name "frame-blue")
@@ -839,7 +839,7 @@
(cthc/component-swap :red-copy-green :blue :blue1))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
green (cths/get-shape file :frame-green)
features #{"components/v2"}
@@ -862,11 +862,11 @@
copied-green' (find-copied-shape green' page' uuid/zero)
copied-blue1' (find-copied-shape blue1' page' (:id copied-green'))]
;; ==== Check
;; blue1 has swap-id
;; ==== Check
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))

View File

@@ -74,7 +74,7 @@
file (setup-base-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events
[(dws/select-shape (cthi/id :frame1))
(dwl/add-component)]]
@@ -87,7 +87,7 @@
frame1' (cths/get-shape file' :frame1)
tokens-frame1' (:applied-tokens frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 4))
(t/is (= (get tokens-frame1' :r1) "test-token-1"))
(t/is (= (get tokens-frame1' :r2) "test-token-1"))
@@ -105,7 +105,7 @@
file (setup-file-with-main)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events
[(dwl/instantiate-component (:id file)
(cthi/id :component1)
@@ -119,7 +119,7 @@
c-frame1' (dsh/lookup-shape new-state (first selected))
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 4))
(t/is (= (get tokens-frame1' :r1) "test-token-1"))
(t/is (= (get tokens-frame1' :r2) "test-token-1"))
@@ -137,7 +137,7 @@
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events [(dwta/apply-token {:shape-ids [(cthi/id :frame1)]
:attributes #{:r1 :r2 :r3 :r4}
:token (toht/get-token file "test-token-2")
@@ -153,7 +153,7 @@
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 4))
(t/is (= (get tokens-frame1' :r1) "test-token-2"))
(t/is (= (get tokens-frame1' :r2) "test-token-2"))
@@ -174,7 +174,7 @@
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events [(dwta/unapply-token {:shape-ids [(cthi/id :frame1)]
:attributes #{:r1 :r2 :r3 :r4}
:token (toht/get-token file "test-token-1")})]
@@ -189,7 +189,7 @@
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 0))
(t/is (= (get c-frame1' :r1) 25))
(t/is (= (get c-frame1' :r2) 25))
@@ -245,7 +245,7 @@
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events [(dwta/apply-token {:shape-ids [(cthi/id :c-frame1)]
:attributes #{:r1 :r2 :r3 :r4}
:token (toht/get-token file "test-token-2")
@@ -265,7 +265,7 @@
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 4))
(t/is (= (get tokens-frame1' :r1) "test-token-2"))
(t/is (= (get tokens-frame1' :r2) "test-token-2"))
@@ -286,7 +286,7 @@
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events [(dwta/unapply-token {:shape-ids [(cthi/id :c-frame1)]
:attributes #{:r1 :r2 :r3 :r4}
:token (toht/get-token file "test-token-1")})
@@ -305,7 +305,7 @@
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 0))
(t/is (= (get c-frame1' :r1) 25))
(t/is (= (get c-frame1' :r2) 25))
@@ -369,7 +369,7 @@
(cthc/instantiate-component :component1 :c-frame1))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events [(dwtl/set-selected-token-set-id (cthi/id :test-token-set))
(dwtl/update-token (cthi/id :token-radius)
{:name "token-radius"

View File

@@ -162,17 +162,17 @@
(let [;; ==== Setup
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
events
(concat
(duplicate-each-main-and-first-level-copy file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the nearest main and check propagation to duplicated.
;; - Change color of the nearest main and check propagation to duplicated.
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"}))]
(ths/run-store
@@ -188,17 +188,17 @@
(let [;; ==== Setup
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
events
(concat
(duplicate-each-main-and-first-level-copy file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the nearest main and check propagation to duplicated.
;; - Change color of the nearest main and check propagation to duplicated.
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
(set-color-bottom-shape :frame-composed-2 file {:color "#333333"}))]
@@ -216,17 +216,17 @@
(let [;; ==== Setup
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
events
(concat
(duplicate-each-main-and-first-level-copy file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the nearest main and check propagation to duplicated.
;; - Change color of the nearest main and check propagation to duplicated.
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
(set-color-bottom-shape :frame-composed-2 file {:color "#333333"})
(set-color-bottom-shape :frame-composed-3 file {:color "#444444"}))]
@@ -251,14 +251,14 @@
events
(concat
(duplicate-simple-nested-in-main-and-group file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))]
(ths/run-store
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)]
;; Check propagation to all copies.
;; Check propagation to all copies.
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 28)))))))))
(t/deftest copy-nested-in-main-2
@@ -275,14 +275,14 @@
events
(concat
(duplicate-simple-nested-in-main-and-group file)
;; - Change color of the nearest main
;; - Change color of the nearest main
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"}))]
(ths/run-store
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)]
;; Check propagation to duplicated.
;; Check propagation to duplicated.
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 9)))))))))
(t/deftest copy-nested-in-main-3
@@ -299,14 +299,14 @@
events
(concat
(duplicate-simple-nested-in-main-and-group file)
;; - Change color of the copy you duplicated from.
;; - Change color of the copy you duplicated from.
(set-color-bottom-shape :group-3 file {:color "#333333"}))]
(ths/run-store
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)]
;; Check that it's NOT PROPAGATED.
;; Check that it's NOT PROPAGATED.
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 2)))))))))
(t/deftest copy-nested-1
@@ -324,14 +324,14 @@
events
(concat
(duplicate-copy-nested-and-group-out-of-the-main file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))]
(ths/run-store
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)]
;; Check propagation to all copies.
;; Check propagation to all copies.
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 20)))))))))
@@ -350,9 +350,9 @@
events
(concat
(duplicate-copy-nested-and-group-out-of-the-main file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the previous main
;; - Change color of the previous main
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
(set-color-bottom-shape :group-3 file {:color "#333333"}))]
@@ -360,7 +360,7 @@
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)]
;; Check that it's NOT PROPAGATED.
;; Check that it's NOT PROPAGATED.
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 11))
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 7))
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 2)))))))))
@@ -380,9 +380,9 @@
events
(concat
(duplicate-copy-nested-and-group-out-of-the-main file :target-page-label :page-2)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the previous main
;; - Change color of the previous main
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
(set-color-bottom-shape :group-3 file {:color "#333333"}))]
@@ -391,7 +391,7 @@
(fn [new-state]
(let [file' (-> (ths/get-file-from-state new-state)
(cthf/switch-to-page :page-2))]
;; Check that it's NOT PROPAGATED.
;; Check that it's NOT PROPAGATED.
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 10))
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 4))
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 0)))))))))

View File

@@ -33,7 +33,7 @@
:id (uuid/next)
:position 0}
;; ==== Action
;; ==== Action
events
[(dw/update-guides guide)
(dw/update-position (:id frame1) {:x 100})]]

View File

@@ -76,7 +76,7 @@
(ctho/add-frame :frame-blue {:name "frame-blue"}))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
frame-blue (cths/get-shape file :frame-blue)
@@ -99,7 +99,7 @@
frame-blue' (cths/get-shape file' :frame-blue)
copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]
;; ==== Check
;; ==== Check
(t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))
(t/deftest copy-shape-to-component
@@ -113,7 +113,7 @@
(ctho/add-frame :frame-blue {:name "frame-blue"}))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
frame-blue (cths/get-shape file :frame-blue)
@@ -136,7 +136,7 @@
frame-blue' (cths/get-shape file' :frame-blue)
copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]
;; ==== Check
;; ==== Check
(t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))
(t/deftest copy-component-to-frame
@@ -150,7 +150,7 @@
(cthc/make-component :blue :frame-blue))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
frame-blue (cths/get-shape file :frame-blue)
@@ -173,7 +173,7 @@
frame-blue' (cths/get-shape file' :frame-blue)
copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]
;; ==== Check
;; ==== Check
(t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))
(t/deftest copy-component-to-component
@@ -188,7 +188,7 @@
(cthc/make-component :blue :frame-blue))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
frame-blue (cths/get-shape file :frame-blue)
@@ -211,7 +211,7 @@
frame-blue' (cths/get-shape file' :frame-blue)
copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]
;; ==== Check
;; ==== Check
(t/is (not (ctk/main-instance? copied-blue1')))
(t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))
@@ -228,7 +228,7 @@
(cthc/make-component :blue :frame-blue))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
frame-blue (cths/get-shape file :frame-blue)
@@ -254,7 +254,7 @@
copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))
copied-blue2' (find-copied-shape frame-blue' page' uuid/zero true)]
;; ==== Check
;; ==== Check
(t/is (nil? copied-blue1'))
(t/is (ctk/main-instance? copied-blue2'))
(t/is (= (:parent-id copied-blue2') uuid/zero))))))))
@@ -272,7 +272,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
root (cths/get-shape-by-id file uuid/zero)
frame-red (cths/get-shape file :frame-red)
@@ -297,13 +297,13 @@
root-children' (->> (:shapes root')
(map #(cths/get-shape-by-id file' %)))]
;; ==== Check
;; The main shape of the component have no children
;; ==== Check
;; The main shape of the component have no children
(t/is (= 0 (count (:shapes frame-red'))))
;; Root had two children, now have 3
;; Root had two children, now have 3
(t/is (= 2 (count (:shapes root))))
(t/is (= 3 (count (:shapes root'))))
;; Two of the children of root are variant-containers
;; Two of the children of root are variant-containers
(t/is (= 2 (count (filter ctk/is-variant-container? root-children'))))))))))
(t/deftest cut-paste-variant-container-into-component
@@ -319,7 +319,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
root (cths/get-shape-by-id file uuid/zero)
frame-red (cths/get-shape file :frame-red)
@@ -346,13 +346,13 @@
root-children' (->> (:shapes root')
(map #(cths/get-shape-by-id file' %)))]
;; ==== Check
;; The main shape of the component have no children
;; ==== Check
;; The main shape of the component have no children
(t/is (= 0 (count (:shapes frame-red'))))
;; Root had two children, now it still have two (because we have cutted one of them, and then created a new one)
;; Root had two children, now it still have two (because we have cutted one of them, and then created a new one)
(t/is (= 2 (count (:shapes root))))
(t/is (= 2 (count (:shapes root'))))
;; One of the children of root is a variant-container
;; One of the children of root is a variant-container
(t/is (= 1 (count (filter ctk/is-variant-container? root-children'))))))))))
(t/deftest copy-variant-into-different-variant-container
@@ -366,7 +366,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
m01 (cths/get-shape file :m01)
v02 (cths/get-shape file :v02)
@@ -389,12 +389,12 @@
v02' (cths/get-shape file' :v02)
components' (cthc/get-components file')]
;; ==== Check
;; v02 had two children, now it have 3
;; ==== Check
;; v02 had two children, now it have 3
(t/is (= 2 (count (:shapes v02))))
(t/is (= 3 (count (:shapes v02'))))
;;There was 4 components, now there are 5
;;There was 4 components, now there are 5
(t/is (= 4 (count components)))
(t/is (= 5 (count components')))))))))
@@ -409,7 +409,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
m01 (cths/get-shape file :m01)
v02 (cths/get-shape file :v02)
@@ -434,16 +434,16 @@
m03' (cths/get-shape file' :m03)
components' (cthc/get-components file')]
;; ==== Check
;; v02 had two children, now it have still 2
;; ==== Check
;; v02 had two children, now it have still 2
(t/is (= 2 (count (:shapes v02))))
(t/is (= 2 (count (:shapes v02'))))
;; m03 had no children, now it have 1
;; m03 had no children, now it have 1
(t/is (= 0 (count (:shapes m03))))
(t/is (= 1 (count (:shapes m03'))))
;;There was 4 components, now there is still 4
;;There was 4 components, now there is still 4
(t/is (= 4 (count components)))
(t/is (= 4 (count components')))))))))
@@ -457,7 +457,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
m01 (cths/get-shape file :m01)
v01 (cths/get-shape file :v01)
@@ -480,12 +480,12 @@
v01' (cths/get-shape file' :v01)
components' (cthc/get-components file')]
;; ==== Check
;; v01 had two children, now it have 3
;; ==== Check
;; v01 had two children, now it have 3
(t/is (= 2 (count (:shapes v01))))
(t/is (= 3 (count (:shapes v01'))))
;;There was 2 components, now there are 3
;;There was 2 components, now there are 3
(t/is (= 2 (count components)))
(t/is (= 3 (count components')))))))))
@@ -499,7 +499,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
m01 (cths/get-shape file :m01)
v01 (cths/get-shape file :v01)
@@ -522,12 +522,12 @@
v01' (cths/get-shape file' :v01)
components' (cthc/get-components file')]
;; ==== Check
;; v01 had two children, now it have 3
;; ==== Check
;; v01 had two children, now it have 3
(t/is (= 2 (count (:shapes v01))))
(t/is (= 3 (count (:shapes v01'))))
;;There was 2 components, now there are 3
;;There was 2 components, now there are 3
(t/is (= 2 (count components)))
(t/is (= 3 (count components')))))))))
@@ -541,7 +541,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
m01 (cths/get-shape file :m01)
m02 (cths/get-shape file :m02)
@@ -565,12 +565,12 @@
v01' (cths/get-shape file' :v01)
components' (cthc/get-components file')]
;; ==== Check
;; v01 had two children, now it have 3
;; ==== Check
;; v01 had two children, now it have 3
(t/is (= 2 (count (:shapes v01))))
(t/is (= 3 (count (:shapes v01'))))
;;There was 2 components, now there are 3
;;There was 2 components, now there are 3
(t/is (= 2 (count components)))
(t/is (= 3 (count components')))))))))
@@ -586,7 +586,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
v01 (cths/get-shape file :v01)
@@ -609,12 +609,12 @@
v01' (cths/get-shape file' :v01)
components' (cthc/get-components file')]
;; ==== Check
;; v01 had two children, now it have 3
;; ==== Check
;; v01 had two children, now it have 3
(t/is (= 2 (count (:shapes v01))))
(t/is (= 3 (count (:shapes v01'))))
;;There was 3 components, now there are 4
;;There was 3 components, now there are 4
(t/is (= 3 (count components)))
(t/is (= 4 (count components')))))))))
@@ -630,7 +630,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
v01 (cths/get-shape file :v01)
@@ -655,15 +655,15 @@
m01' (cths/get-shape file' :m01)
components' (cthc/get-components file')]
;; ==== Check
;; v01 had two children, now it have still 2
;; ==== Check
;; v01 had two children, now it have still 2
(t/is (= 2 (count (:shapes v01))))
(t/is (= 2 (count (:shapes v01'))))
;; m01 had no children, now it have 1
;; m01 had no children, now it have 1
(t/is (= 0 (count (:shapes m01))))
(t/is (= 1 (count (:shapes m01'))))
;;There was 3 components, now there are still 3
;;There was 3 components, now there are still 3
(t/is (= 3 (count components)))
(t/is (= 3 (count components')))))))))

View File

@@ -118,9 +118,9 @@
:token (toht/get-token file "borderRadius.sm")
:shape-ids [(:id rect-1)]
:on-update-shape dwta/update-shape-radius-all})
;; Apply single `:r1` attribute to same shape
;; while removing other attributes from the border-radius set
;; but keep `:r4` for testing purposes
;; Apply single `:r1` attribute to same shape
;; while removing other attributes from the border-radius set
;; but keep `:r4` for testing purposes
(dwta/apply-token {:attributes #{:r1 :r2 :r3}
:token (toht/get-token file "borderRadius.md")
:shape-ids [(:id rect-1)]

View File

@@ -22,10 +22,11 @@
"@vitest/ui": "^1.6.0",
"esbuild": "^0.27.2",
"jsdom": "^27.4.0",
"canvas": "^3.2.1",
"playwright": "^1.45.1",
"prettier": "^3.7.4",
"vite": "^5.3.1",
"vitest": "^1.6.0"
},
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6"
"packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264"
}

View File

@@ -130,9 +130,9 @@ export class TextEditor extends EventTarget {
cut: this.#onCut,
copy: this.#onCopy,
keydown: this.#onKeyDown,
beforeinput: this.#onBeforeInput,
input: this.#onInput,
keydown: this.#onKeyDown,
};
this.#styleDefaults = options?.styleDefaults;
this.#options = options;
@@ -160,7 +160,7 @@ export class TextEditor extends EventTarget {
if (this.#element.ariaAutoComplete) this.#element.ariaAutoComplete = false;
if (!this.#element.ariaMultiLine) this.#element.ariaMultiLine = true;
this.#element.dataset.itype = "editor";
if (options.shouldUpdatePositionOnScroll) {
if (options?.shouldUpdatePositionOnScroll) {
this.#updatePositionFromCanvas();
}
}
@@ -186,7 +186,7 @@ export class TextEditor extends EventTarget {
"stylechange",
this.#onStyleChange,
);
if (options.shouldUpdatePositionOnScroll) {
if (options?.shouldUpdatePositionOnScroll) {
window.addEventListener("scroll", this.#onScroll);
}
addEventListeners(this.#element, this.#events, {
@@ -218,7 +218,7 @@ export class TextEditor extends EventTarget {
// Disposes the rest of event listeners.
removeEventListeners(this.#element, this.#events);
if (this.#options.shouldUpdatePositionOnScroll) {
if (this.#options?.shouldUpdatePositionOnScroll) {
window.removeEventListener("scroll", this.#onScroll);
}
@@ -385,7 +385,8 @@ export class TextEditor extends EventTarget {
* @param {InputEvent} e
*/
#onBeforeInput = (e) => {
if (e.inputType === "historyUndo" || e.inputType === "historyRedo") {
if (e.inputType === "historyUndo"
|| e.inputType === "historyRedo") {
return;
}
@@ -419,7 +420,8 @@ export class TextEditor extends EventTarget {
* @param {InputEvent} e
*/
#onInput = (e) => {
if (e.inputType === "historyUndo" || e.inputType === "historyRedo") {
if (e.inputType === "historyUndo"
|| e.inputType === "historyRedo") {
return;
}

View File

@@ -0,0 +1,11 @@
import { describe, test, expect } from "vitest";
import { getFills } from "./Color.js";
/* @vitest-environment jsdom */
describe("Color", () => {
test("getFills", () => {
expect(getFills("#aa0000")).toBe(
'[["^ ","~:fill-color","#aa0000","~:fill-opacity",1]]',
);
});
});

Some files were not shown because too many files have changed in this diff Show More