Skip to content

Commit b36e0bc

Browse files
committed
UI: Recursive details. Animations: add controls
UI == - Array.svelte: New! component to recursively show an array of records - WIP - Module.svelte: remove array code and call Array.svelte - Files.svelte: remove breadCrumbsString, calculate instead Server ===== - pio.ini: app version is 0.5.4 - ModuleAnimations: add controls per node
1 parent e76238d commit b36e0bc

File tree

8 files changed

+12886
-12776
lines changed

8 files changed

+12886
-12776
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<!--
2+
@title MoonLight
3+
@file MultiInput.svelte
4+
@repo https://github.com/MoonModules/MoonLight, submit changes to this file as PRs
5+
@Authors https://github.com/MoonModules/MoonLight/commits/main
6+
@Copyright © 2025 Github MoonLight Commit Authors
7+
@license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
8+
@license For non GPL-v3 usage, commercial licenses must be purchased. Contact moonmodules@icloud.com
9+
10+
Not w-full!
11+
-->
12+
13+
14+
<script lang="ts">
15+
import FileEdit from '$lib/components/custom/FileEdit.svelte';
16+
import DragDropList, { VerticalDropZone, reorder, type DropEvent } from 'svelte-dnd-list';
17+
import Add from '~icons/tabler/circle-plus';
18+
import { user } from '$lib/stores/user';
19+
import { page } from '$app/state';
20+
import Router from '~icons/tabler/router';
21+
import { slide } from 'svelte/transition';
22+
import { cubicOut } from 'svelte/easing';
23+
import Edit from '~icons/tabler/pencil';
24+
import Delete from '~icons/tabler/trash';
25+
import MultiInput from '$lib/components/custom/MultiInput.svelte';
26+
import Array from '$lib/components/custom/Array.svelte';
27+
import ArrayLight from '$lib/components/custom/ArrayLight.svelte';
28+
29+
let { property, data, definition, showEditor = false, onChange, changeOnInput, value1=$bindable(), value2 } = $props();
30+
31+
let dataEditable: any = $state({});
32+
33+
let propertyEditable: string = $state("");
34+
35+
//if no records added yet, add an empty array
36+
if (data[property.name] == undefined) {
37+
data[property.name] = [];
38+
}
39+
40+
console.log("Array property", property, data, definition, showEditor, changeOnInput, data[property.name], value1, value2);
41+
for (let i = 0; i < definition.length; i++) {
42+
// console.log("addItem def", propertyName, property)
43+
if (property.name == definition[i].name) {
44+
console.log("def[i]", property.name, definition[i].n)
45+
}
46+
}
47+
48+
function onDrop(propertyName: string, { detail: { from, to } }: CustomEvent<DropEvent>) {
49+
50+
if (!to || from === to) {
51+
return;
52+
}
53+
54+
data[propertyName] = reorder(data[propertyName], from.index, to.index);
55+
onChange();
56+
// console.log(onDrop, data[propertyName]);
57+
}
58+
59+
function addItem(propertyName: string) {
60+
propertyEditable = propertyName;
61+
//set the default values from the definition...
62+
dataEditable = {};
63+
64+
//set properties with their defaults
65+
for (let i = 0; i < definition.length; i++) {
66+
let property = definition[i];
67+
// console.log("addItem def", propertyName, property)
68+
if (property.name == propertyName) {
69+
for (let i=0; i < property.n.length; i++) {
70+
let propertyN = property.n[i];
71+
// console.log("propertyN", propertyN)
72+
dataEditable[propertyN.name] = propertyN.default;
73+
}
74+
}
75+
}
76+
}
77+
78+
function handleEdit(propertyName: string, index: number) {
79+
console.log("handleEdit", propertyName, index)
80+
propertyEditable = propertyName;
81+
showEditor = true;
82+
dataEditable = data[propertyName][index];
83+
}
84+
85+
function deleteItem(propertyName: string, index: number) {
86+
// Check if item is currently been edited and delete as well
87+
if (data[propertyName][index].animation === dataEditable.animation) {
88+
addItem(propertyName);
89+
}
90+
// Remove item from array
91+
data[propertyName].splice(index, 1);
92+
data[propertyName] = [...data[propertyName]]; //Trigger reactivity
93+
showEditor = false;
94+
onChange();
95+
}
96+
97+
</script>
98+
99+
<div class="divider mb-2 mt-0"></div>
100+
<div class="h-16 flex w-full items-center justify-between space-x-3 p-0 text-xl font-medium">
101+
{property.name}
102+
</div>
103+
<div class="relative w-full overflow-visible">
104+
<!-- <div class="mx-4 mb-4 flex flex-wrap justify-end gap-2"> -->
105+
<button
106+
class="btn btn-primary text-primary-content btn-md absolute -top-14 right-0"
107+
onclick={() => {
108+
addItem(property.name);
109+
onChange();
110+
111+
//add the new item to the data
112+
data[property.name].push(dataEditable);
113+
showEditor = true;
114+
}}
115+
>
116+
<Add class="h-6 w-6" /></button
117+
>
118+
</div>
119+
120+
<div
121+
class="overflow-x-auto space-y-1"
122+
transition:slide|local={{ duration: 300, easing: cubicOut }}
123+
>
124+
<DragDropList
125+
id={property.name}
126+
type={VerticalDropZone}
127+
itemSize={60}
128+
itemCount={data[property.name].length}
129+
on:drop={(event) => {
130+
onDrop(property.name, event);
131+
}}
132+
133+
>
134+
{#snippet children({ index })}
135+
<!-- svelte-ignore a11y_click_events_have_key_events -->
136+
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
137+
<div class="mask mask-hexagon bg-primary h-auto w-10 shrink-0">
138+
<Router class="text-primary-content h-auto w-full scale-75" />
139+
</div>
140+
{#each property.n as propertyN}
141+
{#if propertyN.type == "array"}
142+
<div>
143+
<div class="font-bold">{data[property.name][index][propertyN.name].length}</div>
144+
</div>
145+
{:else if propertyN.type != "password"}
146+
<div>
147+
<div class="font-bold">{data[property.name][index][propertyN.name]}</div>
148+
</div>
149+
{/if}
150+
{/each}
151+
{#if !page.data.features.security || $user.admin}
152+
<div class="flex-grow"></div>
153+
<div class="space-x-0 px-0 mx-0">
154+
<button
155+
class="btn btn-ghost btn-sm"
156+
onclick={() => {
157+
handleEdit(property.name, index);
158+
}}
159+
>
160+
<Edit class="h-6 w-6" /></button
161+
>
162+
<button
163+
class="btn btn-ghost btn-sm"
164+
onclick={() => {
165+
deleteItem(property.name, index);
166+
}}
167+
>
168+
<Delete class="text-error h-6 w-6" />
169+
</button>
170+
</div>
171+
{/if}
172+
</div>
173+
{/snippet}
174+
</DragDropList>
175+
</div>
176+
{#if showEditor && property.name == propertyEditable}
177+
<div class="divider my-0"></div>
178+
{#each property.n as propertyN}
179+
{#if propertyN.type != "array"}
180+
<div>
181+
<MultiInput property={propertyN} bind:value={dataEditable[propertyN.name]} onChange={onChange} changeOnInput={changeOnInput}></MultiInput>
182+
</div>
183+
{:else if propertyN.type == "array"}
184+
<label for="{propertyN.name}">{propertyN.name}</label>
185+
<Array property={propertyN} bind:value1={data[propertyN.name]} value2={data} data={dataEditable} definition={definition} onChange={onChange} changeOnInput={changeOnInput}></Array>
186+
{/if}
187+
{/each}
188+
{/if}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!--
2+
@title MoonLight
3+
@file MultiInput.svelte
4+
@repo https://github.com/MoonModules/MoonLight, submit changes to this file as PRs
5+
@Authors https://github.com/MoonModules/MoonLight/commits/main
6+
@Copyright © 2025 Github MoonLight Commit Authors
7+
@license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
8+
@license For non GPL-v3 usage, commercial licenses must be purchased. Contact moonmodules@icloud.com
9+
10+
Not w-full!
11+
-->
12+
13+
14+
<script lang="ts">
15+
import FileEdit from '$lib/components/custom/FileEdit.svelte';
16+
import DragDropList, { VerticalDropZone, reorder, type DropEvent } from 'svelte-dnd-list';
17+
import Add from '~icons/tabler/circle-plus';
18+
import { user } from '$lib/stores/user';
19+
import { page } from '$app/state';
20+
import Router from '~icons/tabler/router';
21+
import { slide } from 'svelte/transition';
22+
import { cubicOut } from 'svelte/easing';
23+
import Edit from '~icons/tabler/pencil';
24+
import Delete from '~icons/tabler/trash';
25+
import MultiInput from '$lib/components/custom/MultiInput.svelte';
26+
import Array from '$lib/components/custom/Array.svelte';
27+
28+
let { property, data, definition, showEditor = false, onChange, changeOnInput, value1=$bindable(), value2 } = $props();
29+
30+
let dataEditable: any = $state({});
31+
32+
let propertyEditable: string = $state("");
33+
34+
console.log("ArrayLight property", property, "data", data, "definition", definition, "value1", value1, "value2", value2, showEditor, changeOnInput, data[property.name]);
35+
// for (let i = 0; i < definition.length; i++) {
36+
// // console.log("addItem def", propertyName, property)
37+
// if (property.name == definition[i].name) {
38+
// console.log("def[i]", property.name, definition[i].n)
39+
// }
40+
// }
41+
42+
</script>

interface/src/routes/custom/files/Files.svelte

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
fs_used: 0,
4747
});
4848
let breadCrumbs:string[] = $state([]);
49-
let breadCrumbsString:string = $state(""); //as breadCrumbs.join("/") is not reactive for some reason
5049
5150
let newItem: boolean = $state(true);
5251
let showEditor: boolean = $state(false);
@@ -56,7 +55,6 @@
5655
// console.log("cookie", cookieValue);
5756
if (cookieValue) {
5857
breadCrumbs = JSON.parse(cookieValue);
59-
breadCrumbsString = breadCrumbs.join("/");
6058
}
6159
6260
async function getState() {
@@ -101,7 +99,7 @@
10199
function addFile() {
102100
console.log("addFile")
103101
newItem = true;
104-
path = "/" + breadCrumbsString
102+
path = "/" + breadCrumbs.join("/");
105103
editableFile = {
106104
name: '',
107105
path: '',
@@ -117,7 +115,7 @@
117115
function addFolder() {
118116
console.log("addFolder")
119117
newItem = true;
120-
path = "/" + breadCrumbsString
118+
path = "/" + breadCrumbs.join("/");
121119
editableFile = {
122120
name: '',
123121
path: '',
@@ -145,7 +143,6 @@
145143
}
146144
if (!found) { //e.g. old coookie, reset
147145
breadCrumbs = [];
148-
breadCrumbsString = "";
149146
folderList = filesState.files;
150147
return;
151148
}
@@ -160,7 +157,6 @@
160157
161158
if (breadCrumbs.length > 0 && editableFile.name === breadCrumbs[breadCrumbs.length-1]) { //if parent folder
162159
breadCrumbs.pop(); //remove last folder
163-
breadCrumbsString = breadCrumbs.join("/");
164160
folderListFromBreadCrumbs();
165161
166162
setCookie('breadCrumbs', JSON.stringify(breadCrumbs), 7);
@@ -171,7 +167,6 @@
171167
showEditor = false; await tick(); showEditor = true; //Trigger reactivity
172168
} else { //if folder, go to folder
173169
breadCrumbs.push(editableFile.name);
174-
breadCrumbsString = breadCrumbs.join("/");
175170
setCookie('breadCrumbs', JSON.stringify(breadCrumbs), 7);
176171
// folderList = [folderList[index], ...editableFile.files];
177172
folderListFromBreadCrumbs();
@@ -267,7 +262,7 @@
267262
{#if !page.data.features.security || $user.admin}
268263
<div class="bg-base-200 shadow-lg relative grid w-full max-w-2xl self-center overflow-hidden">
269264
<div class="h-16 flex w-full items-center justify-between space-x-3 p-0 text-xl font-medium">
270-
Files /{breadCrumbsString}
265+
Files /{breadCrumbs.join("/")}
271266
</div>
272267
{#await getState()}
273268
<Spinner />
@@ -312,7 +307,6 @@
312307
>
313308
{#each folderList as item, index}
314309

315-
<!-- svelte-ignore a11y-click-events-have-key-events -->
316310
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
317311
<div class="mask mask-hexagon bg-primary h-auto w-10 shrink-0">
318312
{#if item.isFile}

0 commit comments

Comments
 (0)