@selvajs/ui 0.9.0 → 0.9.2
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.
|
@@ -63,7 +63,8 @@
|
|
|
63
63
|
|
|
64
64
|
function buildTree(files: FileData[]): SvelteMap<string, TreeNode> {
|
|
65
65
|
const root = new SvelteMap<string, TreeNode>();
|
|
66
|
-
for (
|
|
66
|
+
for (let i = 0; i < files.length; i++) {
|
|
67
|
+
const file = files[i];
|
|
67
68
|
const parts = (file.subFolder || '').split('/').filter(Boolean);
|
|
68
69
|
let current = root;
|
|
69
70
|
for (const part of parts) {
|
|
@@ -73,7 +74,9 @@
|
|
|
73
74
|
const node = current.get(part)!;
|
|
74
75
|
if (node.type === 'folder') current = node.children;
|
|
75
76
|
}
|
|
76
|
-
|
|
77
|
+
const baseKey = `${file.fileName}${file.fileType ?? ''}`;
|
|
78
|
+
const key = current.has(baseKey) ? `${baseKey}#${i}` : baseKey;
|
|
79
|
+
current.set(key, { type: 'file', file });
|
|
77
80
|
}
|
|
78
81
|
return root;
|
|
79
82
|
}
|
|
@@ -93,6 +96,24 @@
|
|
|
93
96
|
const hasSubFolders = $derived(filesArray.some((f) => f.subFolder && f.subFolder.length > 0));
|
|
94
97
|
const fileTree = $derived(hasSubFolders ? buildTree(filesArray) : null);
|
|
95
98
|
|
|
99
|
+
function fullPath(f: FileData): string {
|
|
100
|
+
const folder = (f.subFolder || '').replace(/^\/+|\/+$/g, '');
|
|
101
|
+
const name = `${f.fileName}${f.fileType ?? ''}`;
|
|
102
|
+
return folder ? `${folder}/${name}` : name;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const duplicatePaths = $derived.by(() => {
|
|
106
|
+
const seen = new Map<string, number>();
|
|
107
|
+
for (const f of filesArray) {
|
|
108
|
+
const key = fullPath(f);
|
|
109
|
+
seen.set(key, (seen.get(key) ?? 0) + 1);
|
|
110
|
+
}
|
|
111
|
+
return Array.from(seen.entries())
|
|
112
|
+
.filter(([, n]) => n > 1)
|
|
113
|
+
.map(([path]) => path);
|
|
114
|
+
});
|
|
115
|
+
const hasDuplicates = $derived(duplicatePaths.length > 0);
|
|
116
|
+
|
|
96
117
|
// All folders expanded by default
|
|
97
118
|
let expandedFolders = new SvelteSet<string>();
|
|
98
119
|
|
|
@@ -220,7 +241,7 @@
|
|
|
220
241
|
{@render treeNodes(fileTree, '')}
|
|
221
242
|
{:else}
|
|
222
243
|
<div class="gap-1 flex flex-col">
|
|
223
|
-
{#each filesArray as file (file
|
|
244
|
+
{#each filesArray as file, i (fullPath(file) + '#' + i)}
|
|
224
245
|
<div
|
|
225
246
|
class="gap-2 text-xs flex items-center justify-between text-muted-foreground"
|
|
226
247
|
>
|
|
@@ -233,6 +254,24 @@
|
|
|
233
254
|
</div>
|
|
234
255
|
{/if}
|
|
235
256
|
</div>
|
|
257
|
+
{#if hasDuplicates}
|
|
258
|
+
<div
|
|
259
|
+
class="rounded px-3 py-2 text-sm border border-destructive bg-destructive/10 text-destructive"
|
|
260
|
+
>
|
|
261
|
+
<div class="font-medium">Duplicate file names detected</div>
|
|
262
|
+
<div class="mt-1 text-xs">
|
|
263
|
+
The following {duplicatePaths.length === 1 ? 'path appears' : 'paths appear'} more than once:
|
|
264
|
+
</div>
|
|
265
|
+
<ul class="mt-1 ml-4 text-xs list-disc">
|
|
266
|
+
{#each duplicatePaths as path (path)}
|
|
267
|
+
<li class="truncate"><span class="font-mono">{path}</span></li>
|
|
268
|
+
{/each}
|
|
269
|
+
</ul>
|
|
270
|
+
<div class="mt-1 text-xs">
|
|
271
|
+
Rename outputs in your Grasshopper definition so each file has a unique name.
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
{/if}
|
|
236
275
|
{#if downloadError}
|
|
237
276
|
<div
|
|
238
277
|
class="rounded px-3 py-2 text-sm border border-destructive bg-destructive/10 text-destructive"
|
|
@@ -240,7 +279,11 @@
|
|
|
240
279
|
{downloadError}
|
|
241
280
|
</div>
|
|
242
281
|
{/if}
|
|
243
|
-
<Button
|
|
282
|
+
<Button
|
|
283
|
+
onclick={handleDownload}
|
|
284
|
+
disabled={downloading || hasDuplicates}
|
|
285
|
+
class="w-full"
|
|
286
|
+
>
|
|
244
287
|
{downloading
|
|
245
288
|
? 'Downloading...'
|
|
246
289
|
: `Download ${fileCount === 1 ? 'File' : `${fileCount} Files`}`}
|
package/package.json
CHANGED
|
@@ -63,7 +63,8 @@
|
|
|
63
63
|
|
|
64
64
|
function buildTree(files: FileData[]): SvelteMap<string, TreeNode> {
|
|
65
65
|
const root = new SvelteMap<string, TreeNode>();
|
|
66
|
-
for (
|
|
66
|
+
for (let i = 0; i < files.length; i++) {
|
|
67
|
+
const file = files[i];
|
|
67
68
|
const parts = (file.subFolder || '').split('/').filter(Boolean);
|
|
68
69
|
let current = root;
|
|
69
70
|
for (const part of parts) {
|
|
@@ -73,7 +74,9 @@
|
|
|
73
74
|
const node = current.get(part)!;
|
|
74
75
|
if (node.type === 'folder') current = node.children;
|
|
75
76
|
}
|
|
76
|
-
|
|
77
|
+
const baseKey = `${file.fileName}${file.fileType ?? ''}`;
|
|
78
|
+
const key = current.has(baseKey) ? `${baseKey}#${i}` : baseKey;
|
|
79
|
+
current.set(key, { type: 'file', file });
|
|
77
80
|
}
|
|
78
81
|
return root;
|
|
79
82
|
}
|
|
@@ -93,6 +96,24 @@
|
|
|
93
96
|
const hasSubFolders = $derived(filesArray.some((f) => f.subFolder && f.subFolder.length > 0));
|
|
94
97
|
const fileTree = $derived(hasSubFolders ? buildTree(filesArray) : null);
|
|
95
98
|
|
|
99
|
+
function fullPath(f: FileData): string {
|
|
100
|
+
const folder = (f.subFolder || '').replace(/^\/+|\/+$/g, '');
|
|
101
|
+
const name = `${f.fileName}${f.fileType ?? ''}`;
|
|
102
|
+
return folder ? `${folder}/${name}` : name;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const duplicatePaths = $derived.by(() => {
|
|
106
|
+
const seen = new Map<string, number>();
|
|
107
|
+
for (const f of filesArray) {
|
|
108
|
+
const key = fullPath(f);
|
|
109
|
+
seen.set(key, (seen.get(key) ?? 0) + 1);
|
|
110
|
+
}
|
|
111
|
+
return Array.from(seen.entries())
|
|
112
|
+
.filter(([, n]) => n > 1)
|
|
113
|
+
.map(([path]) => path);
|
|
114
|
+
});
|
|
115
|
+
const hasDuplicates = $derived(duplicatePaths.length > 0);
|
|
116
|
+
|
|
96
117
|
// All folders expanded by default
|
|
97
118
|
let expandedFolders = new SvelteSet<string>();
|
|
98
119
|
|
|
@@ -220,7 +241,7 @@
|
|
|
220
241
|
{@render treeNodes(fileTree, '')}
|
|
221
242
|
{:else}
|
|
222
243
|
<div class="gap-1 flex flex-col">
|
|
223
|
-
{#each filesArray as file (file
|
|
244
|
+
{#each filesArray as file, i (fullPath(file) + '#' + i)}
|
|
224
245
|
<div
|
|
225
246
|
class="gap-2 text-xs flex items-center justify-between text-muted-foreground"
|
|
226
247
|
>
|
|
@@ -233,6 +254,24 @@
|
|
|
233
254
|
</div>
|
|
234
255
|
{/if}
|
|
235
256
|
</div>
|
|
257
|
+
{#if hasDuplicates}
|
|
258
|
+
<div
|
|
259
|
+
class="rounded px-3 py-2 text-sm border border-destructive bg-destructive/10 text-destructive"
|
|
260
|
+
>
|
|
261
|
+
<div class="font-medium">Duplicate file names detected</div>
|
|
262
|
+
<div class="mt-1 text-xs">
|
|
263
|
+
The following {duplicatePaths.length === 1 ? 'path appears' : 'paths appear'} more than once:
|
|
264
|
+
</div>
|
|
265
|
+
<ul class="mt-1 ml-4 text-xs list-disc">
|
|
266
|
+
{#each duplicatePaths as path (path)}
|
|
267
|
+
<li class="truncate"><span class="font-mono">{path}</span></li>
|
|
268
|
+
{/each}
|
|
269
|
+
</ul>
|
|
270
|
+
<div class="mt-1 text-xs">
|
|
271
|
+
Rename outputs in your Grasshopper definition so each file has a unique name.
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
{/if}
|
|
236
275
|
{#if downloadError}
|
|
237
276
|
<div
|
|
238
277
|
class="rounded px-3 py-2 text-sm border border-destructive bg-destructive/10 text-destructive"
|
|
@@ -240,7 +279,11 @@
|
|
|
240
279
|
{downloadError}
|
|
241
280
|
</div>
|
|
242
281
|
{/if}
|
|
243
|
-
<Button
|
|
282
|
+
<Button
|
|
283
|
+
onclick={handleDownload}
|
|
284
|
+
disabled={downloading || hasDuplicates}
|
|
285
|
+
class="w-full"
|
|
286
|
+
>
|
|
244
287
|
{downloading
|
|
245
288
|
? 'Downloading...'
|
|
246
289
|
: `Download ${fileCount === 1 ? 'File' : `${fileCount} Files`}`}
|