Remove the :canary flag from the flags definition and make all
features gated behind it always available:
- Enable "download font" option in dashboard fonts context menu
- Enable Tab/Shift+Tab keyboard navigation for renaming shapes
in layer items
- Enable "duplicate color" option in asset panel when applicable
- Enable "duplicate typography" option in asset panel when applicable
- Enable "copy as image" context menu option for frame shapes
Also remove unused [app.config :as cf] requires from files that
no longer reference it after the materialization.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Reviewer follow-up on PR #9151. The "Delete group" handler was
duplicated across the three assets-panel sections (colors,
typographies, components), each carrying the same skeleton — filter
by group path, build an undo-id, run the deletes inside one
transaction, and show the same confirm modal — with only the path
predicate and the per-asset delete event differing.
Add `app.main.ui.workspace.sidebar.assets.common/make-delete-asset-group-fn`
that takes the differing parts as options:
- `:assets` collection to filter.
- `:on-clear-selection` invoked before the deletes.
- `:delete-events` `(fn [matching-assets] => seq-of-events)`.
- `:path-filter` predicate (defaults to `str/starts-with?`),
overridden to `cpn/inside-path?` for
components so nested group paths match the
same way the existing ungroup/combine helpers
do.
The factory returns `(fn [path] …)` so each call site stays a
straight `mf/use-fn`. The variant-container dedup in components
(one `delete-shapes` per container, not one per sibling variant)
moves into that section's `:delete-events` fn and is unchanged in
behavior.
Cleanup
-------
The `:as i18n` alias is no longer needed in any of the three section
files (its only use was `i18n/c` for the modal count, which the
helper now handles); reduced to `:refer [tr]`.
Github #9141
Signed-off-by: FairyPigDev <luislee3108@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
* 🐛 Preserve renamed layer name when re-entering edit mode
When a layer was renamed and the user clicked its name again to edit
it, the input opened with the type-based default name instead of the user's saved name. Pressing Enter then
silently overwrote the saved name with the default. Read the current
shape :name when seeding the rename input so the user's previous
rename is preserved.
Signed-off-by: jack-stormentswe <crazycoder131@gmail.com>
* 💄 Remove redundant DOM-refresh effect from layer rename input
Signed-off-by: jack-stormentswe <crazycoder131@gmail.com>
---------
Signed-off-by: jack-stormentswe <crazycoder131@gmail.com>
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
When working with large asset groups, users asked for a one-click way
to remove every asset under a group path. Multi-select across hundreds
of items is impractical, and ungrouping first and then deleting leaves
the orphaned items in the flat list.
This change adds a "Delete group" option to the assets-panel
context-menu for three asset types that already carry group structure:
- Components (including variants — sibling variants sharing a variant
container are deduplicated, and the container is deleted once via
the same dispatch the per-item delete uses in file_library.cljs).
- Colors.
- Typographies.
A confirmation modal is shown before deletion, with the count of
assets to be removed, so the action is never silent. All deletes run
inside a single undo transaction, so one Cmd+Z restores the whole
group.
Changes
-------
- `assets/groups.cljs` — `asset-group-title*` accepts an optional
`on-delete-group` prop and conditionally adds the menu entry
between "Ungroup" and "Combine as variants". When the callback is
not supplied the option is hidden, so asset sections that do not
implement it stay unaffected.
- `assets/components.cljs` — threads `on-delete-group` through the
recursive `components-group*` and defines the section-level
handler, dispatching to `dwsh/delete-shapes` for variant containers
and `dwl/delete-component` for plain components.
- `assets/colors.cljs` — same threading + a simple `dwl/delete-color`
dispatch per color in the group.
- `assets/typographies.cljs` — same threading + a
`dwl/delete-typography` dispatch per typography in the group.
- `translations/en.po` — three new strings: the menu label
(`workspace.assets.delete-group`) and the modal title/message
(`modals.delete-asset-group.title`/`.message`, plural-aware).
Github #9141
Signed-off-by: FairyPigDev <luislee3108@gmail.com>
Signed-off-by: FairyPiggyDev <luislee3108@gmail.com>
The ::mf/props and ::mf/wrap-props metadata keys are no-ops on modern
components (those defined with mf/defc and the * suffix) since the *
suffix already triggers the props behavior these keys attempt to
configure. This cleanup removes the redundant metadata from modern
components across all UI directories.
Changes:
- comments/: comments
- dashboard/: comments, deleted, files, fonts, grid, import, libraries,
pin_button, projects, search, sidebar, subscription, team, templates
- exports/: files
- modal/: modal
- settings/: subscription
- static/: static
- viewer/: comments, interactions, viewer
- workspace/: context_menu, libraries, sidebar/assets,
viewport/gradients, tokens/settings/menu
Let users pick the pixel grid color from a standard color picker.
The grid color was previously hardcoded, making it invisible on
mid-tone canvases. Choice is stored on the file so it persists
across sessions. Defaults preserve the current appearance when
unset.
Closes#7750
Signed-off-by: jack-stormentswe <crazycoder131@gmail.com>
Closes#7736.
The Layers sidebar offered no way to expand every nested level of a
single subtree at once. Unfolding a layer that wraps a deep tree
required clicking each disclosure indicator one level at a time -
O(siblings * depth) clicks. The asymmetry was particularly visible
next to the existing Shift+click gesture, which collapses every
layer in the panel in a single action via `dwc/collapse-all`, with
no expand counterpart for either a single subtree or the whole
tree.
Add a new `dwc/expand-subtree` event in
`app.main.data.workspace.collapse` that uses
`cfh/get-children-ids-with-self` to gather the shape's id together
with every descendant id, then merges `{descendant-id true}` entries
into `[:workspace-local :expanded]` so the entire subtree opens in
one update. Existing expansion state on unrelated branches is left
untouched (`merge`, not `assoc`), matching the per-key shape used by
`toggle-collapse` and `expand-collapse`.
Wire the gesture into `layer_item.cljs` `toggle-collapse` callback as
a third branch:
- Shift+click while expanded - collapse every layer (existing).
- Alt+click while collapsed - expand the entire subtree (new).
- Otherwise - toggle this single level (existing).
Alt is chosen instead of Shift to avoid the ambiguity the issue
author flagged: "for a layer of middle depth it is unclear whether
[Shift+click] should fold all (up to the topmost parent) or expand
all (only the current subtree)". Alt is a common platform
convention for "do this recursively" (Finder, file managers,
several IDEs), so the asymmetric mapping matches user expectations.
The callback's `mf/deps` vector is extended with `id` and `objects`
so the closure refreshes when the shape tree changes.
CHANGES.md entry added under the 2.17.0 New features section.
* ✨ Remove the need to navigate to page for deletion operation
* 🐛 Fix multiple selection with applied-tokens on stroke-color
* 🐛 Fix button position on page header
---------
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
* ✨ Add read-only preview mode for saved versions (#7622)
* 🔧 Address review feedback on version preview (#7622)
* 🐛 Fix version preview for WASM renderer (#7622)
* 🐛 Fix stylelint color-named and color-function-notation in preview banner (#7622)
* 🐛 Fix invalid-arity call to initialize-workspace in exit-preview (#7622)
* 🐛 Fix unclosed defn paren in exit-preview (#7622)
* ♻️ Refactor version preview/restore flow
Separate enter-preview and enter-restore flows with dedicated dialogs
instead of a persistent banner. Removes preview-banner component in favor
of inline actions dialog. Uses backup/restore pattern for exit-preview
instead of full workspace reinitialization. Adds analytics events for
preview/restore actions.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* ⚡ Extract on-name-input-focus as namespace-level private function
The callback had no dependencies on component-local state or props,
making it a pure function that can be hoisted to a defn-. This avoids
recreating the same callback identity on every render of version-entry*.
* ⚡ Extract extract-id-from-event helper to deduplicate snapshot callbacks
Three callbacks in snapshot-entry* shared the same DOM extraction logic
(get current target, read data-id, parse UUID). Extracted into a private
defn- to remove the duplication and simplify each callback.
* ⚡ Extract pure state-update callbacks from versions-toolbox* to namespace level
Eight callbacks that only emit fixed Potok events with no meaningful
deps were hoisted out of the component as defn- functions:
- on-create-version
- on-edit-version
- on-cancel-version-edition
- on-rename-version
- on-delete-version
- on-pin-version
- on-lock-version
- on-unlock-version
These no longer need mf/use-fn wrappers since namespace-level functions
have stable identity across renders, avoiding unnecessary callback
recreation on each render cycle.
* ✨ Rename filter parameter to filter-value in on-change-filter to avoid core shadowing
The parameter name 'filter' shadowed clojure.core/filter within the
function scope. Renamed to 'filter-value' for clarity and to prevent
potential bugs if core/filter were needed in future changes.
* 🔧 Fix linter warnings and errors across version-related namespaces
frontend/src/app/main/ui/workspace.cljs:
- Remove unused requires: app.common.data, app.main.data.notifications,
app.main.data.workspace.versions
frontend/src/app/main/data/workspace/versions.cljs:
- Remove unused require: app.common.uuid
- Fix duplicate reify type: enter-restore used ::restore-version
(same as the private restore-version fn), renamed to ::enter-restore
- Remove unused bindings: state in enter-restore, team-id in
exit-preview and restore-version-from-plugin
---------
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Signed-off-by: wdeveloper16 <wdeveloer16@protonmail.com>
Co-authored-by: wdeveloper16 <wdeveloer16@protonmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
* 🐛 Ensure typography-ref attrs are always present and fix nil encoding
Add :typography-ref-file and :typography-ref-id (both defaulting to nil)
to default-text-attrs so these keys are always present in text node maps,
whether or not a typography is attached.
Skip nil values in attrs-to-styles (Draft.js style encoder) and in
attrs->styles (v2 CSS custom-property mapper) so nil typography-ref
entries are never serialised to CSS.
Replace when with if/acc in get-styles-from-style-declaration to prevent
the accumulator from being clobbered to nil when a mixed-value entry is
skipped during style decoding.
* 🎉 Add test
---------
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
The inline (fn [] (ts/schedule ...)) passed as :on-blur to text-options
was an exact copy of the on-text-blur callback already defined via
mf/use-fn earlier in the same let block. Pass on-text-blur directly
to avoid allocating a new function object on every render.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
toggle-main-menu and toggle-more-options close over a state atom and
need no external deps. The declared deps (main-menu-open? /
more-options-open?) were unused inside the function bodies, causing
each callback to be reallocated on every toggle — self-reinforcing
churn. Drop the mf/deps calls to make both callbacks stable.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
(contains? cf/flags :token-typography-row) is a pure constant:
cf/flags is immutable after startup. Define it once as a private
namespace-level var token-typography-row? instead of re-evaluating
the check on every render in text-decoration-options* and text-menu*.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Wrap the (case type (tr ...) ...) expression in mf/with-memo [type]
so the translation is resolved only when the type prop changes
instead of on every render.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Wrap the two radio-button option maps in mf/with-memo [token-applied]
so the vector and its (tr ...) calls are evaluated only when the
token-applied prop changes, not on every render.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Wrap the three radio-button option maps in mf/with-memo [] so the
vector and its (tr ...) calls are evaluated once per mount instead
of on every render.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Wrap the two radio-button option maps in mf/with-memo [] so the
vector and its (tr ...) calls are evaluated once per mount instead
of on every render.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Wrap the four radio-button option maps in mf/with-memo [] so the
vector and its (tr ...) calls are evaluated once per mount instead
of on every render.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Wrap the radio-button options vector in `mf/with-memo []` so the
vector allocation and `(tr ...)` calls happen once per component
mount instead of on every render.
Also document the translation memoization rule in frontend/AGENTS.md:
`(tr ...)` must never be called at namespace level (locale is
runtime-only), and static option lists should always be wrapped in
`mf/with-memo []`.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Rename text-align-options, text-direction-options, vertical-align,
grow-options, text-decoration-options and text-menu to their * variants.
Update all call sites in shapes/text.cljs, shapes/group.cljs and
shapes/multiple.cljs.
Rename components to blur-menu*, constraints-menu* and stroke-menu*.
Update :refer imports and all [:& ...] call sites to [:> ...*] for
blur-menu*, constraints-menu* and stroke-menu* across all nine
shapes-specific options panels.
- Add session ID tracking to RPC layer (backend and frontend)
- Send session ID header with RPC requests for request correlation
- Rename file-restore to file-restored for consistency
- Extract initialize-file function from initialize-workspace flow
- Improve file restoration initialization with wait-for-persistence
- Extract initialize-version event handler for version restoration
- Fix viewport key generation with file version numbers for proper re-renders
- Update layout item schema and constraints to use internal sizing state
- Add v-sizing state retrieval in layout-size-constraints component
- Refactor file-change notifications stream handling with rx/map
- Fix team-id lookup in restore-version-from-plugins
Improves request traceability across frontend/backend sessions and streamlines
the workspace initialization flow for file restoration scenarios.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Guard imperative DOM operations (removeChild, RAF callbacks) against
race conditions where React has already unmounted the target nodes.
- assets/common.cljs: add dom/child? guard before removeChild in RAF
- dynamic_modifiers.cljs: capture RAF IDs and cancel them on cleanup;
add null guards for DOM nodes that may no longer exist
- hooks.cljs: guard portal container removal with dom/child? check
- errors.cljs: extract is-ignorable-exception? to a top-level defn
and add NotFoundError/removeChild to ignorable exceptions, since
these are caused by browser extensions modifying React-managed DOM
- Add unit tests for is-ignorable-exception? predicate
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* 🐛 Fix blur input after enter value
* 🐛 Catch error on invalid maths
* 🐛 Fix race condition
* 🎉 Add tests that cover issues
* 🐛 Fix padding applying only to one side
* 🐛 Fix show broken pill when reference is on not active set