@autumnsgrove/groveengine 0.8.6 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/admin/GutterManager.svelte +213 -101
- package/dist/components/admin/MarkdownEditor.svelte +6 -3
- package/dist/components/custom/GutterItem.svelte +8 -2
- package/dist/components/quota/UpgradePrompt.svelte +1 -0
- package/dist/config/domain-blocklist.d.ts +59 -0
- package/dist/config/domain-blocklist.js +731 -0
- package/dist/config/index.d.ts +3 -1
- package/dist/config/index.js +2 -1
- package/dist/config/offensive-blocklist.d.ts +44 -0
- package/dist/config/offensive-blocklist.js +751 -0
- package/dist/config/terrarium.d.ts +109 -0
- package/dist/config/terrarium.js +125 -0
- package/dist/styles/tokens.css +90 -0
- package/dist/types/dom-to-image-more.d.ts +39 -0
- package/dist/ui/components/chrome/Footer.svelte +137 -0
- package/dist/ui/components/chrome/Footer.svelte.d.ts +11 -0
- package/dist/ui/components/chrome/FooterMinimal.svelte +75 -0
- package/dist/ui/components/chrome/FooterMinimal.svelte.d.ts +10 -0
- package/dist/ui/components/chrome/Header.svelte +113 -0
- package/dist/ui/components/chrome/Header.svelte.d.ts +11 -0
- package/dist/ui/components/chrome/HeaderMinimal.svelte +68 -0
- package/dist/ui/components/chrome/HeaderMinimal.svelte.d.ts +9 -0
- package/dist/ui/components/chrome/MobileMenu.svelte +145 -0
- package/dist/ui/components/chrome/MobileMenu.svelte.d.ts +9 -0
- package/dist/ui/components/chrome/ThemeToggle.svelte +34 -0
- package/dist/ui/components/chrome/ThemeToggle.svelte.d.ts +3 -0
- package/dist/ui/components/chrome/defaults.d.ts +6 -0
- package/dist/ui/components/chrome/defaults.js +65 -0
- package/dist/ui/components/chrome/index.d.ts +13 -0
- package/dist/ui/components/chrome/index.js +14 -0
- package/dist/ui/components/chrome/types.d.ts +19 -0
- package/dist/ui/components/chrome/types.js +8 -0
- package/dist/ui/components/content/RoadmapPreview.svelte +2 -1
- package/dist/ui/components/forms/ContentSearch.svelte +406 -0
- package/dist/ui/components/forms/ContentSearch.svelte.d.ts +71 -0
- package/dist/ui/components/forms/filterUtils.d.ts +138 -0
- package/dist/ui/components/forms/filterUtils.js +240 -0
- package/dist/ui/components/forms/index.d.ts +2 -0
- package/dist/ui/components/forms/index.js +5 -1
- package/dist/ui/components/gallery/ImageGallery.svelte +3 -0
- package/dist/ui/components/gallery/Lightbox.svelte +3 -0
- package/dist/ui/components/gallery/ZoomableImage.svelte +1 -0
- package/dist/ui/components/icons/index.d.ts +2 -1
- package/dist/ui/components/icons/index.js +14 -3
- package/dist/ui/components/icons/lucide.d.ts +213 -0
- package/dist/ui/components/icons/lucide.js +224 -0
- package/dist/ui/components/terrarium/AssetPalette.svelte +207 -0
- package/dist/ui/components/terrarium/AssetPalette.svelte.d.ts +7 -0
- package/dist/ui/components/terrarium/Canvas.svelte +231 -0
- package/dist/ui/components/terrarium/Canvas.svelte.d.ts +14 -0
- package/dist/ui/components/terrarium/ExportDialog.svelte +307 -0
- package/dist/ui/components/terrarium/ExportDialog.svelte.d.ts +18 -0
- package/dist/ui/components/terrarium/PaletteItem.svelte +169 -0
- package/dist/ui/components/terrarium/PaletteItem.svelte.d.ts +9 -0
- package/dist/ui/components/terrarium/PlacedAsset.svelte +222 -0
- package/dist/ui/components/terrarium/PlacedAsset.svelte.d.ts +11 -0
- package/dist/ui/components/terrarium/Terrarium.svelte +266 -0
- package/dist/ui/components/terrarium/Terrarium.svelte.d.ts +3 -0
- package/dist/ui/components/terrarium/Toolbar.svelte +299 -0
- package/dist/ui/components/terrarium/Toolbar.svelte.d.ts +24 -0
- package/dist/ui/components/terrarium/index.d.ts +31 -0
- package/dist/ui/components/terrarium/index.js +33 -0
- package/dist/ui/components/terrarium/terrariumState.svelte.d.ts +45 -0
- package/dist/ui/components/terrarium/terrariumState.svelte.js +291 -0
- package/dist/ui/components/terrarium/types.d.ts +139 -0
- package/dist/ui/components/terrarium/types.js +43 -0
- package/dist/ui/components/terrarium/utils/export.d.ts +48 -0
- package/dist/ui/components/terrarium/utils/export.js +148 -0
- package/dist/ui/components/ui/CollapsibleSection.svelte +2 -0
- package/dist/ui/components/ui/GlassConfirmDialog.svelte +9 -0
- package/dist/ui/components/ui/GlassOverlay.svelte +2 -1
- package/dist/ui/components/ui/Input.svelte +9 -1
- package/dist/ui/components/ui/Input.svelte.d.ts +2 -0
- package/dist/ui/components/ui/Textarea.svelte +9 -1
- package/dist/ui/components/ui/Textarea.svelte.d.ts +2 -0
- package/dist/ui/stores/index.d.ts +6 -0
- package/dist/ui/stores/index.js +6 -0
- package/dist/ui/stores/season.d.ts +14 -0
- package/dist/ui/stores/season.js +65 -0
- package/package.json +27 -4
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Grove — A place to Be
|
|
3
|
+
Copyright (c) 2025 Autumn Brown
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published
|
|
7
|
+
by the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
-->
|
|
18
|
+
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
import { Play, Pause, Grid3X3, Trash2, Copy, Download, Save } from 'lucide-svelte';
|
|
21
|
+
import { cn } from '../../utils';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Terrarium Toolbar
|
|
25
|
+
*
|
|
26
|
+
* Top toolbar for Terrarium canvas actions and settings.
|
|
27
|
+
* Features scene naming, animation/grid controls, and action buttons.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
interface Props {
|
|
31
|
+
sceneName: string;
|
|
32
|
+
animationsEnabled: boolean;
|
|
33
|
+
gridEnabled: boolean;
|
|
34
|
+
gridSize: 16 | 32 | 64;
|
|
35
|
+
hasSelection: boolean;
|
|
36
|
+
onToggleAnimations: () => void;
|
|
37
|
+
onToggleGrid: () => void;
|
|
38
|
+
onSetGridSize: (size: 16 | 32 | 64) => void;
|
|
39
|
+
onDelete: () => void;
|
|
40
|
+
onDuplicate: () => void;
|
|
41
|
+
onExport: () => void;
|
|
42
|
+
onSave: () => void;
|
|
43
|
+
onRename: (name: string) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let {
|
|
47
|
+
sceneName = $bindable(),
|
|
48
|
+
animationsEnabled,
|
|
49
|
+
gridEnabled,
|
|
50
|
+
gridSize,
|
|
51
|
+
hasSelection,
|
|
52
|
+
onToggleAnimations,
|
|
53
|
+
onToggleGrid,
|
|
54
|
+
onSetGridSize,
|
|
55
|
+
onDelete,
|
|
56
|
+
onDuplicate,
|
|
57
|
+
onExport,
|
|
58
|
+
onSave,
|
|
59
|
+
onRename
|
|
60
|
+
}: Props = $props();
|
|
61
|
+
|
|
62
|
+
let isEditingName = $state(false);
|
|
63
|
+
let nameInput: HTMLInputElement | null = $state(null);
|
|
64
|
+
let editedName = $state(sceneName);
|
|
65
|
+
|
|
66
|
+
// Grid size options
|
|
67
|
+
const gridSizes: Array<16 | 32 | 64> = [16, 32, 64];
|
|
68
|
+
|
|
69
|
+
// Start editing scene name
|
|
70
|
+
function startEditingName() {
|
|
71
|
+
isEditingName = true;
|
|
72
|
+
editedName = sceneName;
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
nameInput?.focus();
|
|
75
|
+
nameInput?.select();
|
|
76
|
+
}, 0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Finish editing scene name
|
|
80
|
+
function finishEditingName() {
|
|
81
|
+
if (isEditingName) {
|
|
82
|
+
const trimmed = editedName.trim();
|
|
83
|
+
if (trimmed && trimmed !== sceneName) {
|
|
84
|
+
onRename(trimmed);
|
|
85
|
+
} else {
|
|
86
|
+
editedName = sceneName;
|
|
87
|
+
}
|
|
88
|
+
isEditingName = false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Handle name input keydown
|
|
93
|
+
function handleNameKeydown(e: KeyboardEvent) {
|
|
94
|
+
if (e.key === 'Enter') {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
finishEditingName();
|
|
97
|
+
} else if (e.key === 'Escape') {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
editedName = sceneName;
|
|
100
|
+
isEditingName = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Glass button styles
|
|
105
|
+
const buttonClass = cn(
|
|
106
|
+
'inline-flex items-center justify-center',
|
|
107
|
+
'h-9 px-3 rounded-lg',
|
|
108
|
+
'bg-white/60 dark:bg-emerald-950/25',
|
|
109
|
+
'border border-white/40 dark:border-emerald-800/25',
|
|
110
|
+
'text-foreground text-sm font-medium',
|
|
111
|
+
'hover:bg-white/75 dark:hover:bg-emerald-950/35',
|
|
112
|
+
'hover:border-white/50 dark:hover:border-emerald-700/30',
|
|
113
|
+
'transition-all duration-200',
|
|
114
|
+
'backdrop-blur-md shadow-sm hover:shadow-md',
|
|
115
|
+
'disabled:opacity-50 disabled:pointer-events-none',
|
|
116
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50',
|
|
117
|
+
'[&_svg]:w-4 [&_svg]:h-4'
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const iconButtonClass = cn(
|
|
121
|
+
'inline-flex items-center justify-center',
|
|
122
|
+
'h-9 w-9 rounded-lg',
|
|
123
|
+
'bg-white/60 dark:bg-emerald-950/25',
|
|
124
|
+
'border border-white/40 dark:border-emerald-800/25',
|
|
125
|
+
'text-foreground',
|
|
126
|
+
'hover:bg-white/75 dark:hover:bg-emerald-950/35',
|
|
127
|
+
'hover:border-white/50 dark:hover:border-emerald-700/30',
|
|
128
|
+
'transition-all duration-200',
|
|
129
|
+
'backdrop-blur-md shadow-sm hover:shadow-md',
|
|
130
|
+
'disabled:opacity-50 disabled:pointer-events-none',
|
|
131
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50',
|
|
132
|
+
'[&_svg]:w-4 [&_svg]:h-4'
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const activeButtonClass = cn(
|
|
136
|
+
iconButtonClass,
|
|
137
|
+
'bg-accent/70 dark:bg-accent/60',
|
|
138
|
+
'border-accent/40 dark:border-accent/30',
|
|
139
|
+
'text-white',
|
|
140
|
+
'hover:bg-accent/85 dark:hover:bg-accent/75',
|
|
141
|
+
'hover:border-accent/60 dark:hover:border-accent/50'
|
|
142
|
+
);
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<nav
|
|
146
|
+
class={cn(
|
|
147
|
+
'flex items-center gap-3 px-4 py-3',
|
|
148
|
+
'bg-white/60 dark:bg-emerald-950/25',
|
|
149
|
+
'border-b border-white/40 dark:border-emerald-800/25',
|
|
150
|
+
'backdrop-blur-md shadow-sm'
|
|
151
|
+
)}
|
|
152
|
+
>
|
|
153
|
+
<!-- Scene Name -->
|
|
154
|
+
<div class="flex items-center gap-2 min-w-0">
|
|
155
|
+
{#if isEditingName}
|
|
156
|
+
<input
|
|
157
|
+
bind:this={nameInput}
|
|
158
|
+
bind:value={editedName}
|
|
159
|
+
onblur={finishEditingName}
|
|
160
|
+
onkeydown={handleNameKeydown}
|
|
161
|
+
class={cn(
|
|
162
|
+
'px-2 py-1 rounded text-lg font-semibold',
|
|
163
|
+
'bg-white/80 dark:bg-emerald-950/40',
|
|
164
|
+
'border border-accent/40 dark:border-accent/30',
|
|
165
|
+
'focus:outline-none focus:ring-2 focus:ring-accent/50',
|
|
166
|
+
'min-w-[200px] max-w-[400px]'
|
|
167
|
+
)}
|
|
168
|
+
maxlength="100"
|
|
169
|
+
/>
|
|
170
|
+
{:else}
|
|
171
|
+
<button
|
|
172
|
+
onclick={startEditingName}
|
|
173
|
+
class={cn(
|
|
174
|
+
'px-2 py-1 rounded text-lg font-semibold truncate',
|
|
175
|
+
'hover:bg-white/40 dark:hover:bg-emerald-950/20',
|
|
176
|
+
'transition-colors duration-200',
|
|
177
|
+
'text-left max-w-[400px]'
|
|
178
|
+
)}
|
|
179
|
+
title="Click to rename scene"
|
|
180
|
+
>
|
|
181
|
+
{sceneName}
|
|
182
|
+
</button>
|
|
183
|
+
{/if}
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div class="flex-1"></div>
|
|
187
|
+
|
|
188
|
+
<!-- Controls Section -->
|
|
189
|
+
<div class="flex items-center gap-2">
|
|
190
|
+
<!-- Animation Toggle -->
|
|
191
|
+
<button
|
|
192
|
+
onclick={onToggleAnimations}
|
|
193
|
+
class={animationsEnabled ? activeButtonClass : iconButtonClass}
|
|
194
|
+
title={animationsEnabled ? 'Pause animations (Space)' : 'Play animations (Space)'}
|
|
195
|
+
aria-label={animationsEnabled ? 'Pause animations' : 'Play animations'}
|
|
196
|
+
>
|
|
197
|
+
{#if animationsEnabled}
|
|
198
|
+
<Pause />
|
|
199
|
+
{:else}
|
|
200
|
+
<Play />
|
|
201
|
+
{/if}
|
|
202
|
+
</button>
|
|
203
|
+
|
|
204
|
+
<!-- Grid Toggle -->
|
|
205
|
+
<button
|
|
206
|
+
onclick={onToggleGrid}
|
|
207
|
+
class={gridEnabled ? activeButtonClass : iconButtonClass}
|
|
208
|
+
title={gridEnabled ? 'Hide grid (G)' : 'Show grid (G)'}
|
|
209
|
+
aria-label={gridEnabled ? 'Hide grid' : 'Show grid'}
|
|
210
|
+
>
|
|
211
|
+
<Grid3X3 />
|
|
212
|
+
</button>
|
|
213
|
+
|
|
214
|
+
<!-- Grid Size Dropdown -->
|
|
215
|
+
{#if gridEnabled}
|
|
216
|
+
<select
|
|
217
|
+
value={gridSize}
|
|
218
|
+
onchange={(e) => onSetGridSize(Number(e.currentTarget.value) as 16 | 32 | 64)}
|
|
219
|
+
class={cn(
|
|
220
|
+
'h-9 px-3 rounded-lg',
|
|
221
|
+
'bg-white/60 dark:bg-emerald-950/25',
|
|
222
|
+
'border border-white/40 dark:border-emerald-800/25',
|
|
223
|
+
'text-foreground text-sm font-medium',
|
|
224
|
+
'hover:bg-white/75 dark:hover:bg-emerald-950/35',
|
|
225
|
+
'transition-all duration-200',
|
|
226
|
+
'backdrop-blur-md shadow-sm',
|
|
227
|
+
'focus:outline-none focus:ring-2 focus:ring-accent/50',
|
|
228
|
+
'cursor-pointer'
|
|
229
|
+
)}
|
|
230
|
+
title="Grid size"
|
|
231
|
+
aria-label="Select grid size"
|
|
232
|
+
>
|
|
233
|
+
{#each gridSizes as size}
|
|
234
|
+
<option value={size}>{size}px</option>
|
|
235
|
+
{/each}
|
|
236
|
+
</select>
|
|
237
|
+
{/if}
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<!-- Divider -->
|
|
241
|
+
<div class="w-px h-6 bg-white/40 dark:bg-emerald-800/25"></div>
|
|
242
|
+
|
|
243
|
+
<!-- Action Buttons Section -->
|
|
244
|
+
<div class="flex items-center gap-2">
|
|
245
|
+
<!-- Delete -->
|
|
246
|
+
<button
|
|
247
|
+
onclick={onDelete}
|
|
248
|
+
disabled={!hasSelection}
|
|
249
|
+
class={iconButtonClass}
|
|
250
|
+
title="Delete selected (⌫)"
|
|
251
|
+
aria-label="Delete selected asset"
|
|
252
|
+
>
|
|
253
|
+
<Trash2 />
|
|
254
|
+
</button>
|
|
255
|
+
|
|
256
|
+
<!-- Duplicate -->
|
|
257
|
+
<button
|
|
258
|
+
onclick={onDuplicate}
|
|
259
|
+
disabled={!hasSelection}
|
|
260
|
+
class={iconButtonClass}
|
|
261
|
+
title="Duplicate selected (⌘D)"
|
|
262
|
+
aria-label="Duplicate selected asset"
|
|
263
|
+
>
|
|
264
|
+
<Copy />
|
|
265
|
+
</button>
|
|
266
|
+
|
|
267
|
+
<!-- Export PNG -->
|
|
268
|
+
<button
|
|
269
|
+
onclick={onExport}
|
|
270
|
+
class={buttonClass}
|
|
271
|
+
title="Export as PNG (⌘E)"
|
|
272
|
+
aria-label="Export scene as PNG"
|
|
273
|
+
>
|
|
274
|
+
<Download />
|
|
275
|
+
<span>Export PNG</span>
|
|
276
|
+
</button>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<!-- Divider -->
|
|
280
|
+
<div class="w-px h-6 bg-white/40 dark:bg-emerald-800/25"></div>
|
|
281
|
+
|
|
282
|
+
<!-- Save Button -->
|
|
283
|
+
<button
|
|
284
|
+
onclick={onSave}
|
|
285
|
+
class={cn(
|
|
286
|
+
buttonClass,
|
|
287
|
+
'bg-accent/70 dark:bg-accent/60',
|
|
288
|
+
'border-accent/40 dark:border-accent/30',
|
|
289
|
+
'text-white',
|
|
290
|
+
'hover:bg-accent/85 dark:hover:bg-accent/75',
|
|
291
|
+
'hover:border-accent/60 dark:hover:border-accent/50'
|
|
292
|
+
)}
|
|
293
|
+
title="Save scene (⌘S)"
|
|
294
|
+
aria-label="Save scene"
|
|
295
|
+
>
|
|
296
|
+
<Save />
|
|
297
|
+
<span>Save</span>
|
|
298
|
+
</button>
|
|
299
|
+
</nav>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terrarium Toolbar
|
|
3
|
+
*
|
|
4
|
+
* Top toolbar for Terrarium canvas actions and settings.
|
|
5
|
+
* Features scene naming, animation/grid controls, and action buttons.
|
|
6
|
+
*/
|
|
7
|
+
interface Props {
|
|
8
|
+
sceneName: string;
|
|
9
|
+
animationsEnabled: boolean;
|
|
10
|
+
gridEnabled: boolean;
|
|
11
|
+
gridSize: 16 | 32 | 64;
|
|
12
|
+
hasSelection: boolean;
|
|
13
|
+
onToggleAnimations: () => void;
|
|
14
|
+
onToggleGrid: () => void;
|
|
15
|
+
onSetGridSize: (size: 16 | 32 | 64) => void;
|
|
16
|
+
onDelete: () => void;
|
|
17
|
+
onDuplicate: () => void;
|
|
18
|
+
onExport: () => void;
|
|
19
|
+
onSave: () => void;
|
|
20
|
+
onRename: (name: string) => void;
|
|
21
|
+
}
|
|
22
|
+
declare const Toolbar: import("svelte").Component<Props, {}, "sceneName">;
|
|
23
|
+
type Toolbar = ReturnType<typeof Toolbar>;
|
|
24
|
+
export default Toolbar;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terrarium — Creative Canvas for Grove
|
|
3
|
+
*
|
|
4
|
+
* A drag-and-drop canvas where users compose nature scenes
|
|
5
|
+
* that become decorations for their blogs.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```svelte
|
|
9
|
+
* <script>
|
|
10
|
+
* import { Terrarium } from '@autumnsgrove/groveengine/ui/terrarium';
|
|
11
|
+
* </script>
|
|
12
|
+
*
|
|
13
|
+
* <Terrarium />
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Grove — A place to Be
|
|
17
|
+
* Copyright (c) 2025 Autumn Brown
|
|
18
|
+
* Licensed under AGPL-3.0
|
|
19
|
+
*/
|
|
20
|
+
export { default as Terrarium } from './Terrarium.svelte';
|
|
21
|
+
export { default as Canvas } from './Canvas.svelte';
|
|
22
|
+
export { default as AssetPalette } from './AssetPalette.svelte';
|
|
23
|
+
export { default as PaletteItem } from './PaletteItem.svelte';
|
|
24
|
+
export { default as PlacedAssetComponent } from './PlacedAsset.svelte';
|
|
25
|
+
export { default as Toolbar } from './Toolbar.svelte';
|
|
26
|
+
export { default as ExportDialog } from './ExportDialog.svelte';
|
|
27
|
+
export { createTerrariumState } from './terrariumState.svelte';
|
|
28
|
+
export type { TerrariumState } from './terrariumState.svelte';
|
|
29
|
+
export type { AssetCategory, AssetDefinition, AssetMeta, CanvasSettings, Decoration, DecorationOptions, DecorationZone, DragState, ExportOptions, PanState, PlacedAsset, Point, PropDefinition, SelectionState, Size, TerrariumScene, ToolbarAction, ToolMode } from './types';
|
|
30
|
+
export { CANVAS_BACKGROUNDS, DEFAULT_SCENE } from './types';
|
|
31
|
+
export { exportSceneAsPNG, generateDataUrl, sanitizeFilename } from './utils/export';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terrarium — Creative Canvas for Grove
|
|
3
|
+
*
|
|
4
|
+
* A drag-and-drop canvas where users compose nature scenes
|
|
5
|
+
* that become decorations for their blogs.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```svelte
|
|
9
|
+
* <script>
|
|
10
|
+
* import { Terrarium } from '@autumnsgrove/groveengine/ui/terrarium';
|
|
11
|
+
* </script>
|
|
12
|
+
*
|
|
13
|
+
* <Terrarium />
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Grove — A place to Be
|
|
17
|
+
* Copyright (c) 2025 Autumn Brown
|
|
18
|
+
* Licensed under AGPL-3.0
|
|
19
|
+
*/
|
|
20
|
+
// Main component
|
|
21
|
+
export { default as Terrarium } from './Terrarium.svelte';
|
|
22
|
+
// Sub-components (for advanced usage)
|
|
23
|
+
export { default as Canvas } from './Canvas.svelte';
|
|
24
|
+
export { default as AssetPalette } from './AssetPalette.svelte';
|
|
25
|
+
export { default as PaletteItem } from './PaletteItem.svelte';
|
|
26
|
+
export { default as PlacedAssetComponent } from './PlacedAsset.svelte';
|
|
27
|
+
export { default as Toolbar } from './Toolbar.svelte';
|
|
28
|
+
export { default as ExportDialog } from './ExportDialog.svelte';
|
|
29
|
+
// State management
|
|
30
|
+
export { createTerrariumState } from './terrariumState.svelte';
|
|
31
|
+
export { CANVAS_BACKGROUNDS, DEFAULT_SCENE } from './types';
|
|
32
|
+
// Utilities
|
|
33
|
+
export { exportSceneAsPNG, generateDataUrl, sanitizeFilename } from './utils/export';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grove — A place to Be
|
|
3
|
+
* Copyright (c) 2025 Autumn Brown
|
|
4
|
+
* Licensed under AGPL-3.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Terrarium State Management
|
|
8
|
+
*
|
|
9
|
+
* Centralized state management for the Terrarium creative canvas using Svelte 5 runes.
|
|
10
|
+
* Manages scene, assets, selection, and canvas interaction state.
|
|
11
|
+
*
|
|
12
|
+
* Note on mutation strategy: Svelte 5 runes track mutations deeply, so we use direct
|
|
13
|
+
* mutations (push, splice, property assignment) for performance. The setScene function
|
|
14
|
+
* uses immutable patterns to ensure a clean state when loading external data.
|
|
15
|
+
*/
|
|
16
|
+
import type { TerrariumScene, PlacedAsset, Point, AssetCategory, ToolMode } from './types';
|
|
17
|
+
/**
|
|
18
|
+
* Create the Terrarium state manager
|
|
19
|
+
*/
|
|
20
|
+
export declare function createTerrariumState(): {
|
|
21
|
+
readonly scene: TerrariumScene;
|
|
22
|
+
readonly selectedAssetId: string | null;
|
|
23
|
+
isDragging: boolean;
|
|
24
|
+
readonly animationsEnabled: boolean;
|
|
25
|
+
readonly panOffset: Point;
|
|
26
|
+
readonly toolMode: ToolMode;
|
|
27
|
+
readonly selectedAsset: PlacedAsset | null;
|
|
28
|
+
readonly assetCount: number;
|
|
29
|
+
readonly canAddAsset: boolean;
|
|
30
|
+
readonly complexityUsage: number;
|
|
31
|
+
addAsset: (componentName: string, category: AssetCategory, position: Point) => string;
|
|
32
|
+
updateAsset: (id: string, updates: Partial<PlacedAsset>) => void;
|
|
33
|
+
deleteAsset: (id: string) => void;
|
|
34
|
+
duplicateAsset: (id: string) => string;
|
|
35
|
+
selectAsset: (id: string | null) => void;
|
|
36
|
+
moveLayer: (id: string, direction: "up" | "down" | "top" | "bottom") => void;
|
|
37
|
+
setScene: (newScene: TerrariumScene) => void;
|
|
38
|
+
resetScene: () => void;
|
|
39
|
+
toggleAnimations: () => void;
|
|
40
|
+
toggleGrid: () => void;
|
|
41
|
+
setGridSize: (size: 16 | 32 | 64) => void;
|
|
42
|
+
setPanOffset: (offset: Point) => void;
|
|
43
|
+
setToolMode: (mode: ToolMode) => void;
|
|
44
|
+
};
|
|
45
|
+
export type TerrariumState = ReturnType<typeof createTerrariumState>;
|