* 🔧 Validate only after propagation in tests
* 💄 Enhance some component sync traces
* 🔧 Add fake uuid generator for debugging
* 🐛 Remove old feature of advancing references when reset changes
Since long time ago, we only allow to reset changes in the top copy
shape. In this case the near and the remote shapes are the same, so
the advance-ref has no effect.
* 🐛 Fix some bugs and add validations, repair and migrations
Also added several utilities to debug and to create scripts that
processes files
* 🐛 Fix misplaced parenthesis passing propagate-fn to wrong function
The :propagate-fn keyword argument was incorrectly placed inside the
ths/get-shape call instead of being passed to tho/reset-overrides.
This caused reset-overrides to never propagate component changes,
making the test not validate what it intended.
* 🐛 Accept and forward :include-deleted? in find-near-match
Callers were passing :include-deleted? true but the parameter was not
in the destructuring, so it was silently ignored and the function
always hardcoded true. This made the API misleading and would cause
incorrect behavior if called with :include-deleted? false.
* 💄 Use set/union alias instead of fully-qualified clojure.set/union
The namespace already requires [clojure.set :as set], so use the alias
for consistency.
* 🐛 Add tests for reset-overrides with and without propagate-fn
Add two focused tests to comp_reset_test to cover the propagate-fn
path in reset-overrides:
- test-reset-with-propagation-updates-copies: verifies that resetting
an override on a nested copy inside a main and supplying propagate-fn
causes the canonical color to appear in all downstream copies.
- test-reset-without-propagation-does-not-update-copies: regression
guard for the misplaced-parenthesis bug; confirms that omitting
propagate-fn leaves copies with the overridden value because the
component sync never runs.
---------
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
* ✨ Add delete and duplicate buttons to typography dialog
Add delete and duplicate action buttons to the expanded typography
editing panel, allowing users to quickly manage typographies without
needing to close the panel and use the context menu.
Fixes#5270
* ♻️ Use DS icon-button for typography dialog actions
Address review feedback: replace raw `:button`/`:div` elements and
deprecated-icon usage with the design system `icon-button*` and
non-deprecated icons (`i/add`, `i/delete`, `i/tick`).
* ♻️ Only show typography delete/duplicate buttons in assets sidebar
`typography-entry` is reused from the right sidebar text options
panel, where the delete and duplicate actions don't make sense.
Add an `is-asset?` opt-in prop and gate the `on-delete`/`on-duplicate`
handlers behind it, so the buttons only appear when the entry is
rendered from the assets sidebar.
* ♻️ Move typography delete/duplicate handlers next to their use site
Refine the previous opt-in: instead of plumbing on-delete/on-duplicate
function props through typography-entry, build them directly inside
typography-advanced-options where they're actually rendered. The
component now takes :file-id and :is-asset? and gates the action
buttons on a single `show-actions?` flag.
---------
Signed-off-by: eureka0928 <meobius123@gmail.com>
Tests that exercise app.common.types.color were living inside
common-tests.colors-test alongside the app.common.colors tests. Move
them to common-tests.types.color-test so the test namespace mirrors
the source namespace structure, consistent with the rest of the
types/ test suite.
The [app.common.types.color :as colors] require is removed from
colors_test.cljc; the new file is registered in runner.cljc.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
The five functions interpolate-color, offset-spread, uniform-spread?,
uniform-spread, and interpolate-gradient duplicated the canonical
implementations in app.common.types.color. The copies in colors.cljc
also contained two bugs: a division-by-zero in offset-spread when
num=1, and a crash on nil idx in interpolate-gradient.
All production callers already use app.common.types.color. The
duplicate tests that exercised the old copies are removed; their
coverage is absorbed into expanded tests under the types-* suite,
including a new nil-idx guard test and a single-stop no-crash test.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Add indexed-access-with-default in fill_test.cljc to cover the two-arity
(nth fills i default) form on both valid and out-of-range indices, directly
exercising the CLJS Fills -nth path fixed in 593cf125.
Add segment-content->selrect-multi-line in path_data_test.cljc to cover
content->selrect on a subpath with multiple consecutive line-to commands
where move-p diverges from from-p, confirming the bounding box matches
both the expected coordinates and the reference implementation; this
guards the calculate-extremities fix in bb5a04c7.
Add types-uniform-spread? in colors_test.cljc to cover
app.common.types.color/uniform-spread?, which had no dedicated tests.
Exercises the uniform case (via uniform-spread), the two-stop edge case,
wrong-offset detection, and wrong-color detection.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
In the :line-to branch of calculate-extremities, move-p (the subpath
start point) was being added to the extremities set instead of from-p
(the actual previous point). For all line segments beyond the first one
in a subpath this produced an incorrect bounding-box start point.
The :curve-to branch correctly used from-p; align :line-to to match.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
In the ClojureScript Fills deftype, the two-arity -nth implementation
called (d/in-range? i size) but the signature is (d/in-range? size i).
This meant -nth always fell through to the default value for any valid
index when called with an explicit default, since i < size is the
condition but the args were swapped.
The no-default -nth sibling on line 378 and both CLJ nth impls on
lines 286 and 291 had the correct argument order.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
In the CLJS -lookup implementation, when a key is absent from data the
negative cache entry was stored under 'key' (the built-in map-entry
key function) rather than the 'k' parameter. As a result every
subsequent lookup of any missing key bypassed the cache and repeated
the full lookup path, making the negative-cache optimization entirely
ineffective.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
`(cfh/frame-shape? current-id)` passes a UUID to the single-arity
overload of `frame-shape?`, which expects a shape map; it always
returns false. Fix by passing `current` (the resolved shape) instead.
Update the test to assert the correct behaviour.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
`(mapcat collect-main-shapes children objects)` passes `objects` as a
second parallel collection instead of threading it as the second
argument to `collect-main-shapes` for each child. Fix by using an
anonymous fn: `(mapcat #(collect-main-shapes % objects) children)`.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
\`(get "type" shadow)\` always returns nil because the map and key
arguments were swapped. The correct call is \`(get shadow "type")\`,
which allows the legacy innerShadow detection to work correctly.
Update the test expectation accordingly.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
font-weight-keys was listed twice in the set/union call for
typography-keys, a copy-paste error. The duplicate entry has no
functional effect (sets deduplicate), but it is misleading and
suggests a missing key such as font-style-keys in its place.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
get-children-rec passed the original children vector to each recursive
call instead of the updated one that already includes the current
shape. This caused descendant results to be accumulated from the wrong
starting point, losing intermediate shapes. Pass children' (which
includes the current shape) into every recursive call.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
When no gradient stop satisfies (<= offset (:offset %)),
d/index-of-pred returns nil. The previous code called (dec nil) in
the start binding before the nil check, throwing a
NullPointerException/ClassCastException. Guard the start binding with
a cond that handles nil before attempting dec.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
The key :podition was used instead of :position when updating the
id-from cell in swap-shapes, silently discarding the position value
and leaving the cell's :position as nil after every swap.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
When several library colors share the same RGB value but differ only
in opacity, append the alpha percentage (e.g. "#ff0000 50%") next to
the displayed default name and in the color bullet tooltip so users
can tell them apart at a glance.
Closes#6328
Signed-off-by: rockchris99 <chrisleo0721@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
* ♻️ Convert snap-points components to modern rumext format
Migrate snap-point, snap-line, snap-feedback, and snap-points from
legacy mf/defc format to modern * suffix format. This enables
optimized props handling by the rumext macro, eliminating implicit
JS object wrapping overhead on each render. All internal and
external call sites updated to use [:> component* props] syntax.
* ♻️ Convert frame-title to modern rumext format
Migrate frame-title from legacy mf/defc format to modern * suffix
format. The component was using legacy implicit props wrapping without
::mf/wrap-props false or ::mf/props :obj, causing unnecessary JS
object conversion overhead on each render. The parent frame-titles*
call site updated to use [:> frame-title* props] syntax.
* ♻️ Convert interactions components to modern rumext format
Migrate interaction-marker, interaction-path, interaction-handle,
overlay-marker, and interactions from legacy mf/defc format to modern
* suffix format. These five components had zero props optimization
applied, causing implicit JS object wrapping on every render. All
internal and external call sites updated to use [:> component* props]
syntax.
* ♻️ Convert rulers components to modern rumext format
Migrate rulers-text, viewport-frame, and selection-area from legacy
mf/defc format to modern * suffix format. These three components in
the always-visible rulers layer had zero props optimization applied.
Internal call sites in the parent rulers component updated to use
[:> component* props] syntax.
* ♻️ Convert frame-grid components to modern rumext format
Migrate square-grid, layout-grid, grid-display-frame, and frame-grid
from legacy mf/defc format to modern * suffix format. These four
components render grid patterns per-frame with zero props optimization.
All internal and external call sites updated to use [:> component* props]
syntax.
* ♻️ Convert gradient handler components to modern rumext format
Migrate shadow, gradient-color-handler, and gradient-handler-transformed
from legacy mf/defc format to modern * suffix format. These components
are rendered during gradient editing with zero props optimization applied.
Internal call sites in gradient-handler-transformed and
gradient-handlers-impl updated to use [:> component* props] syntax.
* ♻️ Rename ?-ending props in modernized workspace viewport components
Apply prop naming rules to all * components migrated in the previous batch:
- remove-snap? -> remove-snap in snap-feedback* (and get-snap helper)
- selected? -> is-selected in interaction-path*
- hover-disabled? -> is-hover-disabled in overlay-marker* and interactions*
- show-rulers? -> show-rulers in viewport-frame*
Update all internal and external call sites consistently.
* 🐛 Fix get-snap call in snap-feedback* using JS props object
Modern rumext *-suffix components receive props as JS objects, not
Clojure maps. snap-feedback* was pushing the raw props object into the
rx/subject and get-snap was destructuring it as a Clojure map, causing
all keys to resolve to nil.
Fix by:
- Changing get-snap to take positional arguments (coord, shapes,
page-id, remove-snap, zoom) instead of a map-destructured opts arg
- Building an explicit Clojure map from the bound locals before pushing
to the subject
- Destructuring that map inside the rx/switch-map callback and calling
get-snap with positional args
Also mark get-snap and add-point-to-snaps as private (defn-), consistent
with the other helpers in the namespace — none are referenced externally.
The 'Move to' menu in the dashboard file context menu only filtered
out the first selected file's project from the available target list.
When multiple files from different projects were selected, the other
files' projects still appeared as valid targets, causing a 400
'cant-move-to-same-project' backend error.
Now all selected files' project IDs are collected and excluded from
the available target projects.
lambdaisland/uri's query-string->map uses :multikeys :duplicates by
default: a key that appears once yields a plain string, but the same
key repeated yields a vector. cljs.core/parse-long only accepts
strings and therefore threw "Expected string, got: object" whenever
a URL contained a duplicate 'index' parameter.
Add rt/get-query-param to app.main.router. The helper returns the
scalar value of a query param key, taking the last element when the
value is a sequential (i.e. the key was repeated). Use it at every
call site that feeds a query-param value into parse-long, in both
app.main.ui (page*) and app.main.data.viewer.
* 🐛 Allow viewers to select locked elements in canvas
* ✨ Add ability to lock guides to prevent accidental movement
---------
Signed-off-by: Dexterity104 <hatanokanjiro@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Replace str/slug with a targeted regex that only removes
filesystem-unsafe characters when generating export filenames.
The slug function strips all non-word characters including hyphens,
causing names like "my-board" to become "myboard" on export.
Fixes#8901
Signed-off-by: jamesrayammons <jamesrayammons@outlook.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
When users switch between the Layers and Assets sidebar tabs, the
`assets-toolbox*` component unmounts and its local `use-state` is
discarded, so the search query and section filter are lost.
Lift the search term and section filter into a per-file, in-memory
session atom that survives tab switches but doesn't leak across files
or persist across reloads. Ordering and list-style continue to use
localStorage as before.
Closes#2913
Signed-off-by: eureka0928 <meobius123@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Subgroups in asset libraries were rendered in hash-map order because
update-in descends into plain maps instead of sorted ones. Add a
recursive post-process that rebuilds every level as a sorted-map so
subfolders are alphabetical at every nesting depth.
Closes#2572
Signed-off-by: eureka928 <meobius123@gmail.com>
Refactor use-portal-container to allocate one persistent <div> per
logical category (:modal, :popup, :tooltip, :default) instead of
creating a new div for every component instance. This keeps the DOM
clean with at most four fixed portal containers and eliminates the
arbitrary growth of empty <div> elements on document.body while
preserving the removeChild race condition fix.
* ✨ Add visibility toggle for strokes
* ♻️ Use single emit! call for stroke visibility toggle
* 💄 Disable stroke controls when hidden, matching shadow/blur pattern
When a stroke is hidden, the alignment/style selects, cap selects, and
cap switch button are now disabled. A .hidden CSS class dims the
options area with reduced opacity. This matches the existing behavior
in shadow_row and blur menu where controls are disabled when the
effect is hidden.
* 💄 Move stroke hide button before remove button
---------
Signed-off-by: eureka928 <meobius123@gmail.com>
Add z-index to the sticky .nav element in the dashboard so that
section titles (Recent, Deleted) stay above scrolling content
instead of being obscured by project cards and file thumbnails.
Fixes#8577
Signed-off-by: rockchris99 <chrisleo0721@gmail.com>