Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ node_modules
lib/framework/WWWData.h
ssl_certs/cacert.pem
/logs
.aid
.aid
/scripts/__pycache__
15 changes: 9 additions & 6 deletions interface/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions interface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.2.0",
"private": true,
"scripts": {
"dev": "vite dev",
"dev": "vite dev --host",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
Expand Down Expand Up @@ -44,7 +44,7 @@
"jwt-decode": "^4.0.0",
"luxon": "^3.7.1",
"msgpack-lite": "^0.1.26",
"svelte-dnd-list": "^0.1.8",
"svelte-dnd-action": "^0.9.65",
"svelte-modals": "^2.0.1"
}
}
38 changes: 34 additions & 4 deletions interface/src/lib/components/Collapsible.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,54 @@
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import Down from '~icons/tabler/chevron-down';
import Alert from '~icons/tabler/alert-hexagon';

let { icon, title, children, opened, closed, open = false, class: className = '' } = $props();
interface Props {
open?: boolean;
opened?: any;
closed?: any;
collapsible?: boolean;
icon?: import('svelte').Snippet;
title?: import('svelte').Snippet;
children?: import('svelte').Snippet;
class?: string;
isDirty?: boolean;
}

let {
open = $bindable(false),
opened,
closed,
icon,
title,
children,
class: className = '',
isDirty = false
}: Props = $props();

function openCollapsible() {
open = !open;
if (open) {
opened();
if (opened) opened();
} else {
closed();
if (closed) closed();
}
}
</script>

<div class="{className} relative grid w-full max-w-2xl self-center overflow-hidden">
{#if isDirty}
<div class="absolute left-0 top-0 w-1.5 h-full bg-red-300"></div>
{/if}
<div class="min-h-16 flex w-full items-center justify-between space-x-3 p-4 text-xl font-medium">
<span class="inline-flex items-baseline">
<span class="inline-flex items-center">
{@render icon?.()}
{@render title?.()}
{#if isDirty}
<div data-tip="There are unsaved changes." class="tooltip tooltip-right tooltip-error">
<Alert class="text-error lex-shrink-0 ml-2 h-6 w-6 self-end cursor-help" />
</div>
{/if}
</span>
<button class="btn btn-circle btn-ghost btn-sm" onclick={() => openCollapsible()}>
<Down
Expand Down
8 changes: 4 additions & 4 deletions interface/src/lib/components/ConfirmDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// provided by <Modals />

interface Props {
isOpen: boolean;
isOpen?: boolean;
title: string;
message: string;
onConfirm: any;
Expand Down Expand Up @@ -40,19 +40,19 @@
>
<h2 class="text-base-content text-start text-2xl font-bold">{title}</h2>
<div class="divider my-2"></div>
<p class="text-base-content mb-1 text-start">{message}</p>
<p class="text-base-content mb-1 text-start">{@html message}</p>
<div class="divider my-2"></div>
<div class="flex justify-end gap-2">
<button
class="btn btn-primary inline-flex items-center"
onclick={() => {
modals.close();
}}><labels.cancel.icon class="mr-2 h-5 w-5" /><span>{labels?.cancel.label}</span></button
}}><labels.cancel.icon class="h-5 w-5" /><span>{labels?.cancel.label}</span></button
>
<button
class="btn btn-warning text-warning-content inline-flex items-center"
onclick={onConfirm}
><SvelteComponent class="mr-2 h-5 w-5" /><span>{labels?.confirm.label}</span></button
><SvelteComponent class="h-5 w-5" /><span>{labels?.confirm.label}</span></button
>
</div>
</div>
Expand Down
83 changes: 83 additions & 0 deletions interface/src/lib/components/DraggableList.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<script lang="ts">
import { dndzone } from 'svelte-dnd-action';
import type { Snippet } from 'svelte';

interface Props {
items: any[];
onReorder?: (reorderedItems: any[]) => void;
flipDurationMs?: number;
dragDisabled?: boolean;
class?: string;
children: Snippet<[{ item: any; index: number; originalItem: any }]>;
}

let {
items,
onReorder = () => {},
flipDurationMs = 200,
dragDisabled = false,
class: className = '',
children
}: Props = $props();

// Create a state array with IDs for drag-and-drop functionality
let itemsWithIds: any[] = $state([]);

// Update the drag-and-drop array whenever items change
$effect(() => {
itemsWithIds = items.map((item, index) => ({
...item,
id: item.id || `dnd-item-${index}-${Date.now()}` // Generate unique ID with timestamp
}));
});

function handleSort(e: any) {
// Update the visual drag-and-drop array immediately
itemsWithIds = e.detail.items;
}

function handleFinalizeSort(e: any) {
// Remove only temporary IDs, preserve original device IDs
const reorderedItems = e.detail.items.map((item: any) => {
// If this is a temporary ID we added (string starting with 'dnd-item-'), remove it
if (typeof item.id === 'string' && item.id.startsWith('dnd-item-')) {
const { id, ...itemWithoutTempId } = item;
return itemWithoutTempId;
}
// Otherwise, keep the item as-is (preserving original numeric IDs)
return item;
});

// Call the parent's reorder handler
onReorder(reorderedItems);
}

</script>

<section
use:dndzone={{
items: itemsWithIds,
flipDurationMs,
dropTargetStyle: {}, // This is to actively clear default styles
dropTargetClasses: ['dragzone-outline'], // This applies custom styling
dragDisabled
}}
onconsider={handleSort}
onfinalize={handleFinalizeSort}
class={className}
>
{#each itemsWithIds as item, index (item.id)}
{@render children({ item, index, originalItem: items[index] })}
{/each}
</section>

<style>
@reference "$src/app.css";
:global(.dragzone-outline) {
@apply outline-solid outline-2 outline-(--color-primary);
}
:global(#dnd-action-dragged-el) {
@apply outline-solid outline-2 outline-current;

}
</style>
2 changes: 1 addition & 1 deletion interface/src/lib/components/InfoDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
>
<h2 class="text-base-content text-start text-2xl font-bold">{title}</h2>
<div class="divider my-2"></div>
<p class="text-base-content mb-1 text-start">{message}</p>
<p class="text-base-content mb-1 text-start">{@html message}</p>
<div class="divider my-2"></div>
<div class="flex justify-end gap-2">
<button
Expand Down
11 changes: 9 additions & 2 deletions interface/src/lib/components/InputPassword.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,24 @@
id?: string;
}

let { value = $bindable(''), id = '' }: Props = $props();
let { value = $bindable('') as string, id = '' as string }: Props = $props();

function handleInput(e: any) {
value = e.target.value;
}
</script>

<div class="relative">
<input {type} class="input w-full" {value} oninput={handleInput} {id} />
<input {type} class="input input-bordered w-full" {value} oninput={handleInput} {id} />
<div class="absolute inset-y-0 right-0 flex items-center pr-1">
<!-- svelte-ignore a11y_click_events_have_key_events -->
<svg
xmlns="http://www.w3.org/2000/svg"
class="text-base-content/50 h-6 {show ? 'block' : 'hidden'}"
onclick={() => (show = false)}
role="button"
aria-label="Hide password"
tabindex="0"
width="40"
height="40"
viewBox="0 0 24 24"
Expand All @@ -43,6 +47,9 @@
xmlns="http://www.w3.org/2000/svg"
class="text-base-content/50 h-6 {show ? 'hidden' : 'block'}"
onclick={() => (show = true)}
role="button"
aria-label="Show password"
tabindex="0"
width="40"
height="40"
viewBox="0 0 24 24"
Expand Down
32 changes: 27 additions & 5 deletions interface/src/lib/components/SettingsCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,47 @@
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import Down from '~icons/tabler/chevron-down';
import Alert from '~icons/tabler/alert-hexagon';

interface Props {
open?: boolean;
collapsible?: boolean;
icon?: import('svelte').Snippet;
title?: import('svelte').Snippet;
children?: import('svelte').Snippet;
maxwidth?: string;
isDirty?: boolean;
}

let {
open = $bindable(true),
collapsible = true,
icon,
title,
children
children,
maxwidth = 'max-w-2xl',
isDirty = false
}: Props = $props();
</script>

{#if collapsible}
<div
class="bg-base-200 rounded-box shadow-primary/50 relative grid w-full max-w-2xl self-center overflow-hidden shadow-lg"
class="bg-base-200 rounded-box shadow-primary/50 relative grid w-full {maxwidth} self-center overflow-hidden shadow-lg m-10"
>
{#if isDirty}
<div class="absolute left-0 top-0 w-1.5 h-full bg-red-300"></div>
{/if}
<div
class="min-h-16 flex w-full items-center justify-between space-x-3 p-4 text-xl font-medium"
>
<span class="inline-flex items-baseline">
<span class="inline-flex items-center">
{@render icon?.()}
{@render title?.()}
{#if isDirty}
<div data-tip="There are unsaved changes." class="tooltip tooltip-right tooltip-error">
<Alert class="text-error lex-shrink-0 ml-2 h-6 w-6 self-end cursor-help" />
</div>
{/if}
</span>
<button
class="btn btn-circle btn-ghost btn-sm"
Expand All @@ -54,12 +68,20 @@
</div>
{:else}
<div
class="bg-base-200 rounded-box shadow-primary/50 relative grid w-full max-w-2xl self-center overflow-hidden shadow-lg"
class="bg-base-200 rounded-box shadow-primary/50 relative grid w-full {maxwidth} self-center overflow-hidden shadow-lg m-10"
>
{#if isDirty}
<div class="absolute left-0 top-0 w-1.5 h-full bg-red-300"></div>
{/if}
<div class="min-h-16 w-full p-4 text-xl font-medium">
<span class="inline-flex items-baseline">
<span class="inline-flex items-center">
{@render icon?.()}
{@render title?.()}
{#if isDirty}
<div data-tip="There are unsaved changes." class="tooltip tooltip-right tooltip-error">
<Alert class="text-error lex-shrink-0 ml-2 h-6 w-6 self-end cursor-help" />
</div>
{/if}
</span>
</div>
<div class="flex flex-col gap-2 p-4 pt-0">
Expand Down
7 changes: 5 additions & 2 deletions interface/src/lib/components/Spinner.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<script lang="ts">
import Loader from '~icons/tabler/loader-2';

let { text = "Loading..."} = $props();

</script>

<div class="flex h-full w-full flex-col items-center justify-center p-6">
<div class="flex w-full flex-col items-center justify-center p-6">
<Loader class="text-primary h-14 w-auto animate-spin stroke-2" />
<p class="text-xl">Loading...</p>
<p class="text-xl">{text}</p>
</div>
7 changes: 4 additions & 3 deletions interface/src/lib/components/UpdateIndicator.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

let { update = $bindable(false) }: Props = $props();

let firmwareVersion: string;
let firmwareVersion: string = $state('');
let firmwareDownloadLink: string;

async function getGithubAPI() {
Expand All @@ -31,6 +31,7 @@
}
});
if (response.status !== 200) {
notifications.error('Failed to fetch latest release from GitHub.', 5000);
throw new Error(`Failed to fetch latest release from ${githubUrl}`);
}
const results = await response.json();
Expand All @@ -54,7 +55,7 @@
}
}
} catch (error) {
console.error('Error:', error);
console.warn(error);
}
}

Expand Down Expand Up @@ -112,6 +113,6 @@
class="indicator-item indicator-top indicator-center badge badge-info badge-xs top-2 scale-75 lg:top-1"
>{firmwareVersion}</span
>
<Firmware class="inline-block h-7 w-7" />
<Firmware class="h-7 w-7" />
</button>
{/if}
Loading