From 9fe86b62b9e5dbd3a133316ab258a06b76022a26 Mon Sep 17 00:00:00 2001
From: th
Date: Fri, 24 Apr 2026 08:33:20 +0200
Subject: [PATCH] 0.14.0: multi-folder media drawer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Clipart store holds an array of root folders instead of a single
handle, plus an activeHandleIndex for the in-folder view. Getters:
activeHandle, inOverview, hasAnyFolder, canGoBack, folderName.
- Actions: addFolder (appends a root), enterFolder, exitFolder,
removeFolder. Single-folder installs auto-drill into the sole root
and behave exactly like the pre-refactor drawer; adding a 2nd+
folder pops back to the root overview.
- IDB persistence migrated from the 'clipartDir' single-handle key to
a 'clipartDirs' array. Legacy records are migrated on first load.
saveClipartFolders unwraps Pinia proxies (toRaw per element) so
IDB's structured clone accepts the payload.
- ClipartItem union gained a 'folder' kind so root tiles render as
first-class media items. listClipart emits nested directories too,
but the drawer keeps drill-down one level deep for now.
- ClipartDrawer header: three equally-sized .header-btn chips —
back (only when canGoBack), refresh (when viewing a folder), and a
new `+` (when any folder is attached). No more big 'Choose folder'
button — empty state is a centred 96px dashed + with a wrapped hint.
- Folder tiles carry a hover `×` to remove a root from the library.
- DocumentSettings media tab lists all configured folders with a per-
folder remove action; primary button is now 'Add folder'.
- Pinia store now calls acceptHMRUpdate so Vite HMR actually swaps
the store definition between reloads instead of leaving the first-
load state shape behind.
- Tests: store actions (addFolder / enterFolder / exitFolder / remove
/ IDB round-trip), drawer UI (centred + empty state, overview with
folder tiles + click-to-enter, back visibility, single-root hides
back, header + button triggers addFolder). Tests isolated with
try/finally so a failing store assertion can't leak error state
into subsequent drawer tests.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
package.json | 2 +-
src/components/CanvasStage.vue | 2 +-
src/components/ClipartDrawer.vue | 224 +++++++++++++++++++-----
src/components/DocumentSettings.vue | 50 ++++--
src/devtools/tests.ts | 261 +++++++++++++++++++++++++---
src/i18n/messages.ts | 26 ++-
src/lib/clipartLibrary.ts | 102 ++++++++---
src/stores/clipart.ts | 171 ++++++++++++++----
src/version.ts | 2 +-
9 files changed, 698 insertions(+), 142 deletions(-)
diff --git a/package.json b/package.json
index 4d593c2..aeb293b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "ruwio",
"private": true,
- "version": "0.13.1",
+ "version": "0.14.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src/components/CanvasStage.vue b/src/components/CanvasStage.vue
index 05f42d3..461d8ae 100644
--- a/src/components/CanvasStage.vue
+++ b/src/components/CanvasStage.vue
@@ -2809,7 +2809,7 @@ function onContainerDrop(e: DragEvent) {
e.preventDefault()
const idx = Number.parseInt(clipartIdx, 10)
const item = Number.isFinite(idx) ? clipart.items[idx] : null
- if (item) {
+ if (item && item.kind !== 'folder') {
void importClipartAt(item.file, worldX, worldY)
}
return
diff --git a/src/components/ClipartDrawer.vue b/src/components/ClipartDrawer.vue
index 415388b..c189665 100644
--- a/src/components/ClipartDrawer.vue
+++ b/src/components/ClipartDrawer.vue
@@ -1,6 +1,16 @@
@@ -117,35 +154,56 @@ function onItemDragStart(e: DragEvent, index: number) {