🌌 Migrate app to svelte-5

This commit is contained in:
Rune Harlyk
2025-02-26 22:28:30 +01:00
committed by Rune Harlyk
parent d9285bbdc0
commit 788f4ffea3
51 changed files with 1512 additions and 1348 deletions
+8 -8
View File
@@ -21,8 +21,8 @@
"@playwright/test": "^1.49.1", "@playwright/test": "^1.49.1",
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-static": "^3.0.1", "@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.5.5", "@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0",
"@types/eslint": "^8.56.0", "@types/eslint": "^8.56.0",
"@types/three": "^0.162.0", "@types/three": "^0.162.0",
"@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0",
@@ -30,19 +30,19 @@
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1", "eslint-plugin-svelte": "^2.45.1",
"jsdom": "^24.0.0", "jsdom": "^24.0.0",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2", "prettier-plugin-svelte": "^3.2.6",
"svelte": "^4.2.7", "svelte": "^5.0.0",
"svelte-check": "^3.6.0", "svelte-check": "^4.0.0",
"svelte-focus-trap": "^1.2.0", "svelte-focus-trap": "^1.2.0",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"tslib": "^2.6.1", "tslib": "^2.6.1",
"typescript": "^5.1.6", "typescript": "^5.5.0",
"unplugin-icons": "^0.18.5", "unplugin-icons": "^0.18.5",
"vite": "^5.0.3", "vite": "^5.4.4",
"vitest": "^1.2.0" "vitest": "^1.2.0"
}, },
"type": "module", "type": "module",
+509 -548
View File
File diff suppressed because it is too large Load Diff
+15 -14
View File
@@ -1,33 +1,34 @@
<script lang="ts"> <script lang="ts">
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';
import { createEventDispatcher } from 'svelte';
import { Down } from './icons'; import { Down } from './icons';
const dispatch = createEventDispatcher();
function openCollapsible() { function openCollapsible() {
open = !open; open = !open;
if (open) { if (open) {
dispatch('opened'); opened();
} else { } else {
dispatch('closed'); closed();
} }
} }
export let open = false; let { icon, title, children, open, opened, closed, class: klass } = $props();
</script> </script>
<div class="{$$props.class || ''} relative grid w-full max-w-2xl self-center overflow-hidden"> <div class="{klass} relative grid w-full max-w-2xl self-center overflow-hidden">
<div class="min-h-16 flex w-full items-center justify-between space-x-3 p-4 text-xl font-medium"> <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-baseline">
<slot name="icon" /> {@render icon?.()}
<slot name="title" /> {@render title?.()}
</span> </span>
<button class="btn btn-circle btn-ghost btn-sm" on:click={() => openCollapsible()}> <button class="btn btn-circle btn-ghost btn-sm" onclick={() => openCollapsible()}>
<Down <Down
class="text-base-content h-auto w-6 transition-transform duration-300 ease-in-out {open class="text-base-content h-auto w-6 transition-transform duration-300 ease-in-out {(
? 'rotate-180' open
) ?
'rotate-180'
: ''}" : ''}"
/> />
</button> </button>
@@ -37,7 +38,7 @@
class="flex flex-col gap-2 p-4 pt-0" class="flex flex-col gap-2 p-4 pt-0"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
> >
<slot /> {@render children?.()}
</div> </div>
{/if} {/if}
</div> </div>
+28 -14
View File
@@ -1,47 +1,61 @@
<script lang="ts"> <script lang="ts">
import { createBubbler } from 'svelte/legacy';
const bubble = createBubbler();
import { closeModal } from 'svelte-modals'; import { closeModal } from 'svelte-modals';
import { focusTrap } from 'svelte-focus-trap'; import { focusTrap } from 'svelte-focus-trap';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { Cancel, Check } from '$lib/components/icons'; import { Cancel, Check } from '$lib/components/icons';
// provided by <Modals /> // provided by <Modals />
export let isOpen: boolean;
export let title: string; interface Props {
export let message: string; isOpen: boolean;
export let onConfirm: any; title: string;
export let labels = { message: string;
onConfirm: any;
labels?: any;
}
let {
isOpen,
title,
message,
onConfirm,
labels = {
cancel: { label: 'Cancel', icon: Cancel }, cancel: { label: 'Cancel', icon: Cancel },
confirm: { label: 'OK', icon: Check } confirm: { label: 'OK', icon: Check }
}; }
}: Props = $props();
</script> </script>
{#if isOpen} {#if isOpen}
{@const SvelteComponent = labels?.confirm.icon}
<div <div
role="dialog" role="dialog"
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center" class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
transition:fly={{ y: 50 }} transition:fly={{ y: 50 }}
on:introstart onintrostart={bubble('introstart')}
on:outroend onoutroend={bubble('outroend')}
use:focusTrap use:focusTrap
> >
<div <div
class="rounded-box bg-base-100 pointer-events-auto flex min-w-fit max-w-md flex-col justify-between p-4 shadow-lg" class="rounded-box bg-base-100 pointer-events-auto flex min-w-fit max-w-md flex-col justify-between p-4 shadow-lg"
> >
<h2 class="text-base-content text-start text-2xl font-bold">{title}</h2> <h2 class="text-base-content text-start text-2xl font-bold">{title}</h2>
<div class="divider my-2" /> <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">{message}</p>
<div class="divider my-2" /> <div class="divider my-2"></div>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<button class="btn btn-primary inline-flex items-center" on:click={closeModal} <button class="btn btn-primary inline-flex items-center" onclick={closeModal}
><svelte:component this={labels.cancel.icon} class="mr-2 h-5 w-5" /><span ><labels.cancel.icon class="mr-2 h-5 w-5" /><span
>{labels?.cancel.label}</span >{labels?.cancel.label}</span
></button ></button
> >
<button <button
class="btn btn-warning text-warning-content inline-flex items-center" class="btn btn-warning text-warning-content inline-flex items-center"
on:click={onConfirm} onclick={onConfirm}
><svelte:component this={labels?.confirm.icon} class="mr-2 h-5 w-5" /><span ><SvelteComponent class="mr-2 h-5 w-5" /><span
>{labels?.confirm.label}</span >{labels?.confirm.label}</span
></button ></button
> >
@@ -1,4 +1,7 @@
<script lang="ts"> <script lang="ts">
import { run, createBubbler } from 'svelte/legacy';
const bubble = createBubbler();
import { closeAllModals, onBeforeClose } from 'svelte-modals'; import { closeAllModals, onBeforeClose } from 'svelte-modals';
import { focusTrap } from 'svelte-focus-trap'; import { focusTrap } from 'svelte-focus-trap';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
@@ -6,23 +9,32 @@
import { Cancel } from './icons'; import { Cancel } from './icons';
// provided by <Modals /> // provided by <Modals />
export let isOpen: boolean; interface Props {
isOpen: boolean;
}
let updating = true; let { isOpen }: Props = $props();
let progress = 0; let updating = $state(true);
$: if ($telemetry.download_ota.status == 'progress') {
let progress = $state(0);
run(() => {
if ($telemetry.download_ota.status == 'progress') {
progress = $telemetry.download_ota.progress; progress = $telemetry.download_ota.progress;
} }
});
$: if ($telemetry.download_ota.status == 'error') { run(() => {
if ($telemetry.download_ota.status == 'error') {
updating = false; updating = false;
} }
});
let message = 'Preparing ...'; let message = $state('Preparing ...');
let timerId: number; let timerId: number = $state();
$: if ($telemetry.download_ota.status == 'progress') { run(() => {
if ($telemetry.download_ota.status == 'progress') {
message = 'Downloading ...'; message = 'Downloading ...';
} else if ($telemetry.download_ota.status == 'error') { } else if ($telemetry.download_ota.status == 'error') {
message = $telemetry.download_ota.error; message = $telemetry.download_ota.error;
@@ -35,6 +47,7 @@
location.reload(); location.reload();
}, 5000); }, 5000);
} }
});
onBeforeClose(() => { onBeforeClose(() => {
if (updating) { if (updating) {
@@ -54,32 +67,32 @@
role="dialog" role="dialog"
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center" class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
transition:fly={{ y: 50 }} transition:fly={{ y: 50 }}
on:introstart onintrostart={bubble('introstart')}
on:outroend onoutroend={bubble('outroend')}
use:focusTrap use:focusTrap
> >
<div <div
class="bg-base-100 rounded-box pointer-events-auto flex max-h-full min-w-fit max-w-md flex-col justify-between p-4 shadow-lg" class="bg-base-100 rounded-box pointer-events-auto flex max-h-full min-w-fit max-w-md flex-col justify-between p-4 shadow-lg"
> >
<h2 class="text-base-content text-start text-2xl font-bold">Updating Firmware</h2> <h2 class="text-base-content text-start text-2xl font-bold">Updating Firmware</h2>
<div class="divider my-2" /> <div class="divider my-2"></div>
<div class="overflow-y-auto"> <div class="overflow-y-auto">
<div class="bg-base-100 flex flex-col items-center justify-center p-6"> <div class="bg-base-100 flex flex-col items-center justify-center p-6">
{#if $telemetry.download_ota.status == 'progress'} {#if $telemetry.download_ota.status == 'progress'}
<progress class="progress progress-primary w-56" value={progress} max="100" /> <progress class="progress progress-primary w-56" value={progress} max="100"></progress>
{:else} {:else}
<progress class="progress progress-primary w-56" /> <progress class="progress progress-primary w-56"></progress>
{/if} {/if}
<p class="mt-8 text-2xl">{message}</p> <p class="mt-8 text-2xl">{message}</p>
</div> </div>
</div> </div>
<div class="divider my-2" /> <div class="divider my-2"></div>
<div class="flex flex-wrap justify-end gap-2"> <div class="flex flex-wrap justify-end gap-2">
<div class="flex-grow" /> <div class="flex-grow"></div>
<button <button
class="btn btn-warning text-warning-content inline-flex flex-none items-center" class="btn btn-warning text-warning-content inline-flex flex-none items-center"
disabled={updating} disabled={updating}
on:click={() => { onclick={() => {
closeAllModals(); closeAllModals();
location.reload(); location.reload();
}} }}
+24 -11
View File
@@ -1,15 +1,28 @@
<script lang="ts"> <script lang="ts">
import { createBubbler } from 'svelte/legacy';
const bubble = createBubbler();
import { focusTrap } from 'svelte-focus-trap'; import { focusTrap } from 'svelte-focus-trap';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { Check } from './icons'; import { Check } from './icons';
// provided by <Modals /> // provided by <Modals />
export let isOpen: boolean;
export let title: string; interface Props {
export let message: string; isOpen: boolean;
export let onDismiss: any; title: string;
export let dismiss = { label: 'Dismiss', icon: Check }; message: string;
onDismiss: any;
dismiss?: any;
}
let {
isOpen,
title,
message,
onDismiss,
dismiss = { label: 'Dismiss', icon: Check }
}: Props = $props();
</script> </script>
{#if isOpen} {#if isOpen}
@@ -17,22 +30,22 @@
role="dialog" role="dialog"
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center" class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
transition:fly={{ y: 50 }} transition:fly={{ y: 50 }}
on:introstart onintrostart={bubble('introstart')}
on:outroend onoutroend={bubble('outroend')}
use:focusTrap use:focusTrap
> >
<div <div
class="rounded-box bg-base-100 pointer-events-auto flex min-w-fit max-w-md flex-col justify-between p-4 shadow-lg" class="rounded-box bg-base-100 pointer-events-auto flex min-w-fit max-w-md flex-col justify-between p-4 shadow-lg"
> >
<h2 class="text-base-content text-start text-2xl font-bold">{title}</h2> <h2 class="text-base-content text-start text-2xl font-bold">{title}</h2>
<div class="divider my-2" /> <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">{message}</p>
<div class="divider my-2" /> <div class="divider my-2"></div>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<button <button
class="btn btn-warning text-warning-content inline-flex items-center" class="btn btn-warning text-warning-content inline-flex items-center"
on:click={onDismiss} onclick={onDismiss}
><svelte:component this={dismiss.icon} class="mr-2 h-5 w-5" /><span>{dismiss.label}</span ><dismiss.icon class="mr-2 h-5 w-5" /><span>{dismiss.label}</span
></button ></button
> >
</div> </div>
+22 -9
View File
@@ -2,8 +2,21 @@
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';
import { Down } from './icons'; import { Down } from './icons';
export let open = true; interface Props {
export let collapsible = true; open?: boolean;
collapsible?: boolean;
icon?: import('svelte').Snippet;
title?: import('svelte').Snippet;
children?: import('svelte').Snippet;
}
let {
open = $bindable(true),
collapsible = true,
icon,
title,
children
}: Props = $props();
</script> </script>
{#if collapsible} {#if collapsible}
@@ -14,12 +27,12 @@
class="min-h-16 flex w-full items-center justify-between space-x-3 p-4 text-xl font-medium" 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-baseline">
<slot name="icon" /> {@render icon?.()}
<slot name="title" /> {@render title?.()}
</span> </span>
<button <button
class="btn btn-circle btn-ghost btn-sm" class="btn btn-circle btn-ghost btn-sm"
on:click={() => { onclick={() => {
open = !open; open = !open;
}} }}
> >
@@ -35,7 +48,7 @@
class="flex flex-col gap-2 p-4 pt-0" class="flex flex-col gap-2 p-4 pt-0"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
> >
<slot /> {@render children?.()}
</div> </div>
{/if} {/if}
</div> </div>
@@ -45,12 +58,12 @@
> >
<div class="min-h-16 w-full p-4 text-xl font-medium"> <div class="min-h-16 w-full p-4 text-xl font-medium">
<span class="inline-flex items-baseline"> <span class="inline-flex items-baseline">
<slot name="icon" /> {@render icon?.()}
<slot name="title" /> {@render title?.()}
</span> </span>
</div> </div>
<div class="flex flex-col gap-2 p-4 pt-0"> <div class="flex flex-col gap-2 p-4 pt-0">
<slot /> {@render children?.()}
</div> </div>
</div> </div>
{/if} {/if}
+1 -1
View File
@@ -2,7 +2,7 @@
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { location } from '$lib/stores'; import { location } from '$lib/stores';
let source = `${$location}/api/camera/stream`; let source = $state(`${$location}/api/camera/stream`);
onDestroy(() => (source = '#')); onDestroy(() => (source = '#'));
</script> </script>
+7 -6
View File
@@ -4,30 +4,31 @@
import { notifications } from '$lib/components/toasts/notifications'; import { notifications } from '$lib/components/toasts/notifications';
import { error, info, success, warning } from './icons'; import { error, info, success, warning } from './icons';
export let theme = {
/** @type {{theme?: any, icon?: any}} */
let { theme = {
error: 'alert-error', error: 'alert-error',
success: 'alert-success', success: 'alert-success',
warning: 'alert-warning', warning: 'alert-warning',
info: 'alert-info' info: 'alert-info'
}; }, icon = {
export let icon = {
error: error, error: error,
success: success, success: success,
warning: warning, warning: warning,
info: info info: info
}; } } = $props();
</script> </script>
<div class="toast toast-end mr-4"> <div class="toast toast-end mr-4">
{#each $notifications as notification (notification.id)} {#each $notifications as notification (notification.id)}
{@const SvelteComponent = icon[notification.type]}
<div <div
animate:flip={{ duration: 400 }} animate:flip={{ duration: 400 }}
class="alert animate-none {theme[notification.type]}" class="alert animate-none {theme[notification.type]}"
in:fly={{ y: 100, duration: 400 }} in:fly={{ y: 100, duration: 400 }}
out:fly={{ x: 100, duration: 400 }} out:fly={{ x: 100, duration: 400 }}
> >
<svelte:component this={icon[notification.type]} class="h-6 w-6 flex-shrink-0" /> <SvelteComponent class="h-6 w-6 flex-shrink-0" />
<span>{notification.message}</span> <span>{notification.message}</span>
</div> </div>
{/each} {/each}
+18 -8
View File
@@ -41,14 +41,24 @@
import type { URDFRobot } from 'urdf-loader'; import type { URDFRobot } from 'urdf-loader';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
export let sky = true; interface Props {
export let orbit = false; sky?: boolean;
export let panel = true; orbit?: boolean;
export let debug = false; panel?: boolean;
export let ground = true; debug?: boolean;
ground?: boolean;
}
let sceneManager = new SceneBuilder(); let {
let canvas: HTMLCanvasElement; sky = true,
orbit = false,
panel = true,
debug = false,
ground = true
}: Props = $props();
let sceneManager = $state(new SceneBuilder());
let canvas: HTMLCanvasElement = $state();
let currentModelAngles: number[] = new Array(12).fill(0); let currentModelAngles: number[] = new Array(12).fill(0);
let modelTargetAngles: number[] = new Array(12).fill(0); let modelTargetAngles: number[] = new Array(12).fill(0);
@@ -332,6 +342,6 @@
}; };
</script> </script>
<svelte:window on:resize={sceneManager.fillParent} /> <svelte:window onresize={sceneManager.fillParent} />
<canvas bind:this={canvas}></canvas> <canvas bind:this={canvas}></canvas>
@@ -1,11 +1,15 @@
<script lang="ts"> <script lang="ts">
import { MdiEyeOffOutline, MdiEyeOutline } from "../icons"; import { MdiEyeOffOutline, MdiEyeOutline } from "../icons";
export let show = false; interface Props {
export let value = ''; show?: boolean;
export let id = ''; value?: string;
id?: string;
}
$: type = show ? 'text' : 'password'; let { show = $bindable(false), value = $bindable(''), id = '' }: Props = $props();
let type = $derived(show ? 'text' : 'password');
const handleInput = (e: any) => value = e.target.value const handleInput = (e: any) => value = e.target.value
@@ -13,9 +17,9 @@
</script> </script>
<label class="input input-bordered flex items-center gap-2"> <label class="input input-bordered flex items-center gap-2">
<input {type} class="grow" {value} on:input={handleInput} {id} /> <input {type} class="grow" {value} oninput={handleInput} {id} />
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<div on:click={togglePassword} role="button" tabindex="0"> <div onclick={togglePassword} role="button" tabindex="0">
<MdiEyeOffOutline class="text-base-content/50 h-6 {show ? 'block' : 'hidden'}" /> <MdiEyeOffOutline class="text-base-content/50 h-6 {show ? 'block' : 'hidden'}" />
<MdiEyeOutline class="text-base-content/50 h-6 {show ? 'hidden' : 'block'}" /> <MdiEyeOutline class="text-base-content/50 h-6 {show ? 'hidden' : 'block'}" />
</div> </div>
@@ -1,8 +1,20 @@
<script lang="ts"> <script lang="ts">
export let min = 0; import { createBubbler } from 'svelte/legacy';
export let max = 100;
export let step = 1; const bubble = createBubbler();
export let value = (max - min) / 2; interface Props {
min?: number;
max?: number;
step?: number;
value?: any;
}
let {
min = 0,
max = 100,
step = 1,
value = $bindable((max - min) / 2)
}: Props = $props();
</script> </script>
<input <input
@@ -13,8 +25,8 @@
max={max} max={max}
step={step} step={step}
bind:value bind:value
on:input oninput={bubble('input')}
on:change onchange={bubble('change')}
/> />
<style> <style>
+6 -1
View File
@@ -1,6 +1,11 @@
<script lang="ts"> <script lang="ts">
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
</script> </script>
<div class="box-border overflow-hidden flex-1"> <div class="box-border overflow-hidden flex-1">
<slot></slot> {@render children?.()}
</div> </div>
@@ -1,8 +1,13 @@
<script lang="ts"> <script lang="ts">
import WidgetContainer from './WidgetContainer.svelte';
import { WidgetComponents, type WidgetContainerConfig, isWidgetConfig } from '$lib/stores/application'; import { WidgetComponents, type WidgetContainerConfig, isWidgetConfig } from '$lib/stores/application';
import Widget from './Widget.svelte'; import Widget from './Widget.svelte';
export let container: WidgetContainerConfig; interface Props {
container: WidgetContainerConfig;
}
let { container }: Props = $props();
</script> </script>
<div class="w-full h-full flex flex-col overflow-hidden"> <div class="w-full h-full flex flex-col overflow-hidden">
@@ -15,9 +20,10 @@
{#each container.widgets as widget, index (widget.id + '-' + index)} {#each container.widgets as widget, index (widget.id + '-' + index)}
<Widget> <Widget>
{#if isWidgetConfig(widget)} {#if isWidgetConfig(widget)}
<svelte:component this={WidgetComponents[widget.component]} {...widget.props} /> {@const SvelteComponent = WidgetComponents[widget.component]}
<SvelteComponent {...widget.props} />
{:else if widget.widgets} {:else if widget.widgets}
<svelte:self container={widget} /> <WidgetContainer container={widget} />
{/if} {/if}
</Widget> </Widget>
{#if index !== container.widgets.length - 1} {#if index !== container.widgets.length - 1}
@@ -1,7 +1,11 @@
<script lang="ts"> <script lang="ts">
import { Github } from "../icons"; import { Github } from "../icons";
export let github; interface Props {
github: any;
}
let { github }: Props = $props();
</script> </script>
{#if github.active} {#if github.active}
@@ -1,7 +1,8 @@
<script> <script>
import logo from '$lib/assets/logo512.png'; import logo from '$lib/assets/logo512.png';
export let appName; /** @type {{appName: any}} */
let { appName } = $props();
</script> </script>
<a <a
+10 -3
View File
@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { useFeatureFlags } from '$lib/stores/featureFlags'; import { useFeatureFlags } from '$lib/stores/featureFlags';
@@ -42,7 +44,9 @@
submenu?: menuItem[]; submenu?: menuItem[];
}; };
$: menuItems = [ let menuItems = $state();
run(() => {
menuItems = [
{ {
title: 'Connection', title: 'Connection',
icon: WiFi, icon: WiFi,
@@ -138,6 +142,7 @@
] ]
} }
] as menuItem[]; ] as menuItem[];
});
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@@ -152,7 +157,9 @@
dispatch('menuClicked'); dispatch('menuClicked');
} }
$: setActiveMenuItem($page.data.title); run(() => {
setActiveMenuItem($page.data.title);
});
const updateMenu = (event: any) => { const updateMenu = (event: any) => {
setActiveMenuItem(event.details); setActiveMenuItem(event.details);
@@ -164,7 +171,7 @@
<MenuList {menuItems} on:select{updateMenu} class="flex-grow flex-nowrap overflow-y-auto" /> <MenuList {menuItems} on:select{updateMenu} class="flex-grow flex-nowrap overflow-y-auto" />
<div class="divider my-0" /> <div class="divider my-0"></div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<GithubButton {github} /> <GithubButton {github} />
+11 -16
View File
@@ -1,38 +1,33 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'; import MenuList from './MenuList.svelte';
type MenuItem = {
const dispatch = createEventDispatcher();
type menuItem = {
title: string; title: string;
icon: ConstructorOfATypedSvelteComponent; icon: ConstructorOfATypedSvelteComponent;
href?: string; href?: string;
feature: boolean; feature: boolean;
active?: boolean; active?: boolean;
submenu?: menuItem[]; submenu?: MenuItem[];
}; };
export let menuItems: menuItem[]; let { level, menuItems, select, class: klass } = $props();
export let level = 0;
const selectMenuItem = (title: string) => { const selectMenuItem = (title: string) => {
dispatch('select', title); select(title);
}; };
</script> </script>
<ul class={$$props.class + ' menu'}> <ul class={klass + ' menu'}>
{#each menuItems as menuItem, i (menuItem.title)} {#each menuItems as MenuItem[] as menuItem, i (menuItem.title)}
{#if menuItem.feature} {#if menuItem.feature}
<li> <li>
{#if menuItem.submenu} {#if menuItem.submenu}
<details open={menuItem.submenu.some(subItem => subItem.active)}> <details open={menuItem.submenu.some(subItem => subItem.active)}>
<summary class="text-lg font-bold"> <summary class="text-lg font-bold">
<svelte:component this={menuItem.icon} class="h-6 w-6" /> <menuItem.icon class="h-6 w-6" />
{menuItem.title} {menuItem.title}
</summary> </summary>
<div class="pl-4"> <div class="pl-4">
<svelte:self menuItems={menuItem.submenu} level={level + 1} /> <MenuList menuItems={menuItem.submenu} level={level + 1} />
</div> </div>
</details> </details>
{:else} {:else}
@@ -42,9 +37,9 @@
class:bg-base-100={menuItem.active} class:bg-base-100={menuItem.active}
class:text-lg={level === 0} class:text-lg={level === 0}
class:text-md={level === 1} class:text-md={level === 1}
on:click={() => selectMenuItem(menuItem.title)} onclick={() => selectMenuItem(menuItem.title)}
> >
<svelte:component this={menuItem.icon} class="h-6 w-6" /> <menuItem.icon class="h-6 w-6" />
{menuItem.title} {menuItem.title}
</a> </a>
{/if} {/if}
@@ -5,7 +5,11 @@
const features = useFeatureFlags(); const features = useFeatureFlags();
export let battery:Battery; interface Props {
battery: Battery;
}
let { battery }: Props = $props();
const getBatteryIcon = () => { const getBatteryIcon = () => {
if (battery.voltage === 0) return BatteryCharging; if (battery.voltage === 0) return BatteryCharging;
@@ -18,9 +22,9 @@
</script> </script>
{#if $features.battery} {#if $features.battery}
{@const SvelteComponent = getBatteryIcon()}
<div class="tooltip tooltip-left z-10" data-tip="{battery.voltage}V {Math.floor(battery.current*10)/10} mA"> <div class="tooltip tooltip-left z-10" data-tip="{battery.voltage}V {Math.floor(battery.current*10)/10} mA">
<svelte:component <SvelteComponent
this={getBatteryIcon()}
class="h-7 w-7 -rotate-90 {battery.voltage === 0 || battery.voltage <= 7.6 ? 'animate-pulse' : ''} {battery.voltage <= 7.6 ? 'text-error' : ''}" class="h-7 w-7 -rotate-90 {battery.voltage === 0 || battery.voltage <= 7.6 ? 'animate-pulse' : ''} {battery.voltage <= 7.6 ? 'text-error' : ''}"
/> />
</div> </div>
@@ -1,8 +1,10 @@
<script lang="ts"> <script lang="ts">
import { isFullscreen, toggleFullscreen } from '$lib/stores'; import { isFullscreen, toggleFullscreen } from '$lib/stores';
import { MdiFullscreenExit, MdiFullscreen } from '../icons'; import { MdiFullscreenExit, MdiFullscreen } from '../icons';
const SvelteComponent = $derived($isFullscreen ? MdiFullscreenExit : MdiFullscreen);
</script> </script>
<button on:click={toggleFullscreen}> <button onclick={toggleFullscreen}>
<svelte:component this={$isFullscreen ? MdiFullscreenExit : MdiFullscreen} class="h-7 w-7" /> <SvelteComponent class="h-7 w-7" />
</button> </button>
@@ -1,8 +1,12 @@
<script lang="ts"> <script lang="ts">
import { WiFi, WiFi0, WiFi1, WiFi2, WifiOff } from "../icons"; import { WiFi, WiFi0, WiFi1, WiFi2, WifiOff } from "../icons";
export let showDBm = false; interface Props {
export let rssi = 0; showDBm?: boolean;
rssi?: number;
}
let { showDBm = false, rssi = 0 }: Props = $props();
const getWiFiIcon = () => { const getWiFiIcon = () => {
if (rssi === 0) return WifiOff; if (rssi === 0) return WifiOff;
@@ -11,6 +15,8 @@
if (rssi >= -85) return WiFi1; if (rssi >= -85) return WiFi1;
return WiFi0; return WiFi0;
}; };
const SvelteComponent = $derived(getWiFiIcon());
</script> </script>
<div class="indicator"> <div class="indicator">
@@ -21,7 +27,7 @@
</span> </span>
{/if} {/if}
<div class="h-7 w-7"> <div class="h-7 w-7">
<svelte:component this={getWiFiIcon()} class="absolute inset-0 h-full w-full" /> <SvelteComponent class="absolute inset-0 h-full w-full" />
</div> </div>
</div> </div>
</div> </div>
@@ -27,7 +27,7 @@
{#if $features.sleep} {#if $features.sleep}
<div class="flex-none"> <div class="flex-none">
<button class="btn btn-square btn-ghost h-9 w-10" on:click={confirmSleep}> <button class="btn btn-square btn-ghost h-9 w-10" onclick={confirmSleep}>
<Power class="text-error h-9 w-9" /> <Power class="text-error h-9 w-9" />
</button> </button>
</div> </div>
@@ -7,4 +7,4 @@
</script> </script>
<button on:click={deactivate} class="bg-error text-white btn rounded-none">STOP</button> <button onclick={deactivate} class="bg-error text-white btn rounded-none">STOP</button>
@@ -5,7 +5,7 @@
<div class="topbar absolute left-0 top-0 w-full z-20 flex justify-between bg-zinc-800"> <div class="topbar absolute left-0 top-0 w-full z-20 flex justify-between bg-zinc-800">
<div class="flex gap-2 p-2"> <div class="flex gap-2 p-2">
<a href="/"> <a href="/">
<svelte:component this={Hamburger} class="h-8 w-8"/> <Hamburger class="h-8 w-8"/>
</a> </a>
</div> </div>
</div> </div>
@@ -14,10 +14,14 @@
const features = useFeatureFlags(); const features = useFeatureFlags();
export let update = false; interface Props {
update?: boolean;
}
let firmwareVersion: string; let { update = $bindable(false) }: Props = $props();
let firmwareDownloadLink: string;
let firmwareVersion: string = $state();
let firmwareDownloadLink: string = $state();
async function getGithubAPI() { async function getGithubAPI() {
const headers = { const headers = {
@@ -100,7 +104,7 @@
<div class="indicator flex-none"> <div class="indicator flex-none">
<button <button
class="btn btn-square btn-ghost h-9 w-9" class="btn btn-square btn-ghost h-9 w-9"
on:click={() => confirmGithubUpdate(firmwareDownloadLink)} onclick={() => confirmGithubUpdate(firmwareDownloadLink)}
> >
<span <span
class="indicator-item indicator-top indicator-center badge badge-info badge-xs top-2 scale-75 lg:top-1" class="indicator-item indicator-top indicator-center badge badge-info badge-xs top-2 scale-75 lg:top-1"
+7 -6
View File
@@ -4,30 +4,31 @@
import { notifications } from '$lib/components/toasts/notifications'; import { notifications } from '$lib/components/toasts/notifications';
import { error, info, success, warning } from '../icons'; import { error, info, success, warning } from '../icons';
export let theme = {
/** @type {{theme?: any, icon?: any}} */
let { theme = {
error: 'alert-error', error: 'alert-error',
success: 'alert-success', success: 'alert-success',
warning: 'alert-warning', warning: 'alert-warning',
info: 'alert-info' info: 'alert-info'
}; }, icon = {
export let icon = {
error: error, error: error,
success: success, success: success,
warning: warning, warning: warning,
info: info info: info
}; } } = $props();
</script> </script>
<div class="toast toast-end mr-4 z-20"> <div class="toast toast-end mr-4 z-20">
{#each $notifications as notification (notification.id)} {#each $notifications as notification (notification.id)}
{@const SvelteComponent = icon[notification.type]}
<div <div
animate:flip={{ duration: 400 }} animate:flip={{ duration: 400 }}
class="alert animate-none {theme[notification.type]}" class="alert animate-none {theme[notification.type]}"
in:fly={{ y: 100, duration: 400 }} in:fly={{ y: 100, duration: 400 }}
out:fly={{ x: 100, duration: 400 }} out:fly={{ x: 100, duration: 400 }}
> >
<svelte:component this={icon[notification.type]} class="h-6 w-6 flex-shrink-0" /> <SvelteComponent class="h-6 w-6 flex-shrink-0" />
<span>{notification.message}</span> <span>{notification.message}</span>
</div> </div>
{/each} {/each}
@@ -5,12 +5,16 @@
import { cubicOut } from "svelte/easing"; import { cubicOut } from "svelte/easing";
import { slide } from "svelte/transition"; import { slide } from "svelte/transition";
let chartElement: HTMLCanvasElement; let chartElement: HTMLCanvasElement = $state();
let chart: Chart; let chart: Chart;
export let label interface Props {
export let data:number[] label: any;
export let title data: number[];
title: any;
}
let { label, data, title }: Props = $props();
Chart.register(...registerables); Chart.register(...registerables);
@@ -94,6 +98,6 @@
class="flex w-full flex-col space-y-1 h-60" class="flex w-full flex-col space-y-1 h-60"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
> >
<canvas bind:this={chartElement} /> <canvas bind:this={chartElement}></canvas>
</div> </div>
</div> </div>
+12 -4
View File
@@ -1,12 +1,20 @@
<script lang="ts"> <script lang="ts">
export let options: string[] = []; import { createBubbler } from 'svelte/legacy';
export let selectedOption: string = '';
const bubble = createBubbler();
interface Props {
options?: string[];
selectedOption?: string;
[key: string]: any
}
let { options = [], selectedOption = $bindable(''), ...rest }: Props = $props();
</script> </script>
<select <select
bind:value={selectedOption} bind:value={selectedOption}
on:change onchange={bubble('change')}
class="select select-bordered select-sm lg:select-md max-w-xs {$$restProps.class || ''}" class="select select-bordered select-sm lg:select-md max-w-xs {rest.class || ''}"
> >
{#each options as option} {#each options as option}
<option value={option}>{option}</option> <option value={option}>{option}</option>
+5 -5
View File
@@ -7,7 +7,7 @@ import {
AmbientLight, AmbientLight,
DirectionalLight, DirectionalLight,
PCFSoftShadowMap, PCFSoftShadowMap,
GridHelper, type GridHelper,
ArrowHelper, ArrowHelper,
Vector3, Vector3,
FogExp2, FogExp2,
@@ -350,17 +350,17 @@ export default class SceneBuilder {
this.renderer.domElement.addEventListener( this.renderer.domElement.addEventListener(
'touchstart', 'touchstart',
(data) => dragControls._mouseDown(data.touches[0]), data => dragControls._mouseDown(data.touches[0]),
{ passive: true } { passive: true }
); );
this.renderer.domElement.addEventListener( this.renderer.domElement.addEventListener(
'touchmove', 'touchmove',
(data) => dragControls._mouseMove(data.touches[0]), data => dragControls._mouseMove(data.touches[0]),
{ passive: true } { passive: true }
); );
this.renderer.domElement.addEventListener( this.renderer.domElement.addEventListener(
'touchend', 'touchend',
(data) => dragControls._mouseUp(data.touches[0]), data => dragControls._mouseUp(data.touches[0]),
{ passive: true } { passive: true }
); );
return this; return this;
@@ -372,7 +372,7 @@ export default class SceneBuilder {
private handleRobotShadow = () => { private handleRobotShadow = () => {
if (this.isLoaded) return; if (this.isLoaded) return;
const intervalId = setInterval(() => this.model?.traverse((c) => (c.castShadow = true)), 10); const intervalId = setInterval(() => this.model?.traverse(c => (c.castShadow = true)), 10);
setTimeout(() => clearInterval(intervalId), 1000); setTimeout(() => clearInterval(intervalId), 1000);
this.isLoaded = true; this.isLoaded = true;
}; };
+14 -8
View File
@@ -22,6 +22,11 @@
useFeatureFlags useFeatureFlags
} from '$lib/stores'; } from '$lib/stores';
import type { Analytics, DownloadOTA } from '$lib/types/models'; import type { Analytics, DownloadOTA } from '$lib/types/models';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
const features = useFeatureFlags(); const features = useFeatureFlags();
@@ -82,7 +87,7 @@
const handleOAT = (data: DownloadOTA) => telemetry.setDownloadOTA(data); const handleOAT = (data: DownloadOTA) => telemetry.setDownloadOTA(data);
let menuOpen = false; let menuOpen = $state(false);
</script> </script>
<svelte:head> <svelte:head>
@@ -96,24 +101,25 @@
<Statusbar /> <Statusbar />
<!-- Main page content here --> <!-- Main page content here -->
<slot /> {@render children?.()}
</div> </div>
<!-- Side Navigation --> <!-- Side Navigation -->
<div class="drawer-side z-30 shadow-lg"> <div class="drawer-side z-30 shadow-lg">
<label for="main-menu" class="drawer-overlay" /> <label for="main-menu" class="drawer-overlay"></label>
<Menu on:menuClicked={() => (menuOpen = false)} /> <Menu on:menuClicked={() => (menuOpen = false)} />
</div> </div>
</div> </div>
<Modals> <Modals>
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
{#snippet backdrop()}
<div <div
slot="backdrop"
class="fixed inset-0 z-40 max-h-full max-w-full bg-black/20 backdrop-blur" class="fixed inset-0 z-40 max-h-full max-w-full bg-black/20 backdrop-blur"
transition:fade transition:fade
on:click={closeModal} onclick={closeModal}
/> ></div>
{/snippet}
</Modals> </Modals>
<Toast /> <Toast />
+6 -2
View File
@@ -3,7 +3,11 @@
import { notifications } from '$lib/components/toasts/notifications'; import { notifications } from '$lib/components/toasts/notifications';
import Visualization from '$lib/components/Visualization.svelte'; import Visualization from '$lib/components/Visualization.svelte';
export let data: PageData; interface Props {
data: PageData;
}
let { data }: Props = $props();
</script> </script>
<div class="hero bg-base-100 h-screen"> <div class="hero bg-base-100 h-screen">
@@ -17,7 +21,7 @@
<a <a
class="btn btn-primary" class="btn btn-primary"
href="/controller" href="/controller"
on:click={() => notifications.success('You did it!', 1000)}>Begin</a onclick={() => notifications.success('You did it!', 1000)}>Begin</a
> >
</div> </div>
</div> </div>
+7 -3
View File
@@ -12,13 +12,17 @@
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<WiFi slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" /> {#snippet icon()}
<span slot="title">Connection</span> <WiFi class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
{/snippet}
{#snippet title()}
<span >Connection</span>
{/snippet}
<div class="flex"> <div class="flex">
<label class="label w-32" for="server">Address:</label> <label class="label w-32" for="server">Address:</label>
<input class="input" bind:value={$location} /> <input class="input" bind:value={$location} />
</div> </div>
<button class="btn btn-primary" on:click={update}>Update</button> <button class="btn btn-primary" onclick={update}>Update</button>
</SettingsCard> </SettingsCard>
+1 -1
View File
@@ -7,7 +7,7 @@
import { imu } from '$lib/stores/imu'; import { imu } from '$lib/stores/imu';
import type { IMU } from '$lib/types/models'; import type { IMU } from '$lib/types/models';
$: layout = $views.find(v => v.name === $selectedView)!; let layout = $derived($views.find(v => v.name === $selectedView)!);
onMount(() => { onMount(() => {
socket.on('imu', (data: IMU) => { socket.on('imu', (data: IMU) => {
+7 -7
View File
@@ -86,9 +86,9 @@
<div class="absolute top-0 left-0 w-screen h-screen"> <div class="absolute top-0 left-0 w-screen h-screen">
<div class="absolute top-0 left-0 h-full w-full flex portrait:hidden"> <div class="absolute top-0 left-0 h-full w-full flex portrait:hidden">
<div id="left" class="flex w-60 items-center justify-end" /> <div id="left" class="flex w-60 items-center justify-end"></div>
<div class="flex-1" /> <div class="flex-1"></div>
<div id="right" class="flex w-60 items-center" /> <div id="right" class="flex w-60 items-center"></div>
</div> </div>
<div class="absolute bottom-0 right-0 p-4 z-10 gap-2 flex-col hidden lg:flex"> <div class="absolute bottom-0 right-0 p-4 z-10 gap-2 flex-col hidden lg:flex">
<div class="flex justify-center w-full"> <div class="flex justify-center w-full">
@@ -112,7 +112,7 @@
<button <button
class="btn join-item" class="btn join-item"
class:btn-primary={$mode === modes.indexOf(modeValue)} class:btn-primary={$mode === modes.indexOf(modeValue)}
on:click={() => changeMode(modeValue)} onclick={() => changeMode(modeValue)}
> >
{capitalize(modeValue)} {capitalize(modeValue)}
</button> </button>
@@ -128,13 +128,13 @@
name="s1" name="s1"
min="0" min="0"
max="100" max="100"
on:input={(e) => handleRange(e, 's1')} oninput={(e) => handleRange(e, 's1')}
class="range range-sm range-primary" class="range range-sm range-primary"
/> />
</div> </div>
<div> <div>
<label for="speed">Speed</label> <label for="speed">Speed</label>
<input type="range" name="speed" min="0" max="100" on:input={(e) => handleRange(e, 'speed')} class="range range-sm range-primary" /> <input type="range" name="speed" min="0" max="100" oninput={(e) => handleRange(e, 'speed')} class="range range-sm range-primary" />
</div> </div>
</div> </div>
{/if} {/if}
@@ -142,4 +142,4 @@
</div> </div>
</div> </div>
<svelte:window on:keyup={handleKeyup} on:keydown={handleKeyup} /> <svelte:window onkeyup={handleKeyup} onkeydown={handleKeyup} />
@@ -6,8 +6,12 @@
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<Camera slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" /> {#snippet icon()}
<span slot="title">Camera</span> <Camera class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
{/snippet}
{#snippet title()}
<span >Camera</span>
{/snippet}
<Stream /> <Stream />
<CameraSetting /> <CameraSetting />
</SettingsCard> </SettingsCard>
@@ -2,7 +2,7 @@
import { api } from '$lib/api'; import { api } from '$lib/api';
import Spinner from '$lib/components/Spinner.svelte'; import Spinner from '$lib/components/Spinner.svelte';
import type { CameraSettings } from '$lib/types/models'; import type { CameraSettings } from '$lib/types/models';
let settings:CameraSettings let settings:CameraSettings = $state()
const getCameraSettings = async () => { const getCameraSettings = async () => {
const result = await api.get<CameraSettings>('/api/camera/settings') const result = await api.get<CameraSettings>('/api/camera/settings')
@@ -27,7 +27,7 @@
<Spinner /> <Spinner />
{:then _} {:then _}
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<button class="btn btn-primary" type="button" on:click={updateCameraSettings}>Update camera settings</button> <button class="btn btn-primary" type="button" onclick={updateCameraSettings}>Update camera settings</button>
<label for="brightness"> <label for="brightness">
Brightness {settings.brightness} Brightness {settings.brightness}
+7 -3
View File
@@ -17,7 +17,7 @@
{ address: 119, part_number: 'BMP085', name: 'Temp/Barometric' } { address: 119, part_number: 'BMP085', name: 'Temp/Barometric' }
]; ];
let active_devices: I2CDevice[] = []; let active_devices: I2CDevice[] = $state([]);
onMount(() => { onMount(() => {
socket.on('i2cScan', handleScan); socket.on('i2cScan', handleScan);
@@ -38,8 +38,12 @@
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<Connection slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" /> {#snippet icon()}
<span slot="title">I<sup>2</sup>C</span> <Connection class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
{/snippet}
{#snippet title()}
<span >I<sup>2</sup>C</span>
{/snippet}
<div class="grid"> <div class="grid">
{#if active_devices.length === 0} {#if active_devices.length === 0}
+12 -8
View File
@@ -15,13 +15,13 @@
Chart.register(...registerables); Chart.register(...registerables);
let angleChartElement: HTMLCanvasElement; let angleChartElement: HTMLCanvasElement = $state();
let angleChart: Chart; let angleChart: Chart;
let tempChartElement: HTMLCanvasElement; let tempChartElement: HTMLCanvasElement = $state();
let tempChart: Chart; let tempChart: Chart;
let altitudeChartElement: HTMLCanvasElement; let altitudeChartElement: HTMLCanvasElement = $state();
let altitudeChart: Chart; let altitudeChart: Chart;
const handleImu = (data: IMU) => { const handleImu = (data: IMU) => {
@@ -272,15 +272,19 @@
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<Rotate3d slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" /> {#snippet icon()}
<span slot="title">IMU</span> <Rotate3d class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
{/snippet}
{#snippet title()}
<span >IMU</span>
{/snippet}
{#if $features.imu} {#if $features.imu}
<div class="w-full overflow-x-auto"> <div class="w-full overflow-x-auto">
<div <div
class="flex w-full flex-col space-y-1 h-60" class="flex w-full flex-col space-y-1 h-60"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
> >
<canvas bind:this={angleChartElement} /> <canvas bind:this={angleChartElement}></canvas>
</div> </div>
</div> </div>
{/if} {/if}
@@ -290,7 +294,7 @@
class="flex w-full flex-col space-y-1 h-60" class="flex w-full flex-col space-y-1 h-60"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
> >
<canvas bind:this={tempChartElement} /> <canvas bind:this={tempChartElement}></canvas>
</div> </div>
</div> </div>
<div class="w-full overflow-x-auto"> <div class="w-full overflow-x-auto">
@@ -298,7 +302,7 @@
class="flex w-full flex-col space-y-1 h-60" class="flex w-full flex-col space-y-1 h-60"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
> >
<canvas bind:this={altitudeChartElement} /> <canvas bind:this={altitudeChartElement}></canvas>
</div> </div>
</div> </div>
{/if} {/if}
@@ -1,9 +1,13 @@
<script lang="ts"> <script lang="ts">
import { api } from '$lib/api'; import { api } from '$lib/api';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
export let data = { interface Props {
data?: any;
}
let { data = $bindable({
servos: [] servos: []
}; }) }: Props = $props();
const updateValue = (event, index, key) => { const updateValue = (event, index, key) => {
data.servos[index][key] = event.target.innerText; data.servos[index][key] = event.target.innerText;
@@ -36,29 +40,29 @@
<tr> <tr>
<td <td
contenteditable="true" contenteditable="true"
on:blur={syncConfig} onblur={syncConfig}
on:input={event => updateValue(event, index, 'center_pwm')} oninput={event => updateValue(event, index, 'center_pwm')}
> >
{servo.center_pwm} {servo.center_pwm}
</td> </td>
<td <td
contenteditable="true" contenteditable="true"
on:blur={syncConfig} onblur={syncConfig}
on:input={event => updateValue(event, index, 'center_angle')} oninput={event => updateValue(event, index, 'center_angle')}
> >
{servo.center_angle} {servo.center_angle}
</td> </td>
<td <td
contenteditable="true" contenteditable="true"
on:blur={syncConfig} onblur={syncConfig}
on:input={event => updateValue(event, index, 'direction')} oninput={event => updateValue(event, index, 'direction')}
> >
{servo.direction} {servo.direction}
</td> </td>
<td <td
contenteditable="true" contenteditable="true"
on:blur={syncConfig} onblur={syncConfig}
on:input={event => updateValue(event, index, 'conversion')} oninput={event => updateValue(event, index, 'conversion')}
> >
{servo.conversion} {servo.conversion}
</td> </td>
+11 -7
View File
@@ -10,9 +10,9 @@
let isLoading = false; let isLoading = false;
let active = false; let active = $state(false);
let servoId = 0; let servoId = $state(0);
const throttler = new Throttler(); const throttler = new Throttler();
@@ -29,7 +29,7 @@
socket.sendEvent('servoState', { active: 0 }); socket.sendEvent('servoState', { active: 0 });
}; };
let pwm = 306; let pwm = $state(306);
const updatePWM = () => { const updatePWM = () => {
throttler.throttle(() => { throttler.throttle(() => {
@@ -39,15 +39,19 @@
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<MotorOutline slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" /> {#snippet icon()}
<span slot="title">Servo</span> <MotorOutline class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
{/snippet}
{#snippet title()}
<span >Servo</span>
{/snippet}
{pwm} {pwm}
<input <input
type="range" type="range"
min="80" min="80"
max="600" max="600"
bind:value={pwm} bind:value={pwm}
on:input={updatePWM} oninput={updatePWM}
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/> />
@@ -63,7 +67,7 @@
type="checkbox" type="checkbox"
class="toggle" class="toggle"
bind:checked={active} bind:checked={active}
on:change={active ? activateServo : deactivateServo} onchange={active ? activateServo : deactivateServo}
/> />
</span> </span>
</div> </div>
+5 -4
View File
@@ -2,7 +2,8 @@
import { FileIcon } from '$lib/components/icons'; import { FileIcon } from '$lib/components/icons';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
export let name; /** @type {{name: any}} */
let { name } = $props();
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@@ -11,8 +12,8 @@
} }
</script> </script>
<!-- svelte-ignore a11y-interactive-supports-focus --> <!-- svelte-ignore a11y_interactive_supports_focus -->
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<span role="button" class="flex pl-4 gap-2 items-center" on:click={updateSelected}> <span role="button" class="flex pl-4 gap-2 items-center" onclick={updateSelected}>
<FileIcon/>{name} <FileIcon/>{name}
</span> </span>
@@ -6,7 +6,7 @@
import type { Directory } from "$lib/types/models"; import type { Directory } from "$lib/types/models";
import { FolderIcon } from "$lib/components/icons"; import { FolderIcon } from "$lib/components/icons";
let filename = ''; let filename = $state('');
const getFiles = async () => { const getFiles = async () => {
const result = await api.get<Directory>('/api/files') const result = await api.get<Directory>('/api/files')
@@ -38,8 +38,12 @@
} }
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<FolderIcon slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" /> {#snippet icon()}
<span slot="title">File System</span> <FolderIcon class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
{/snippet}
{#snippet title()}
<span >File System</span>
{/snippet}
<div class="w-full overflow-x-auto"> <div class="w-full overflow-x-auto">
{#await getFiles()} {#await getFiles()}
<Spinner /> <Spinner />
+10 -5
View File
@@ -1,11 +1,16 @@
<script lang="ts"> <script lang="ts">
import Folder from './Folder.svelte';
import File from './File.svelte'; import File from './File.svelte';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { FolderIcon, FolderOpenOutline } from '$lib/components/icons'; import { FolderIcon, FolderOpenOutline } from '$lib/components/icons';
export let expanded = false; interface Props {
export let name; expanded?: boolean;
export let files; name: any;
files: any;
}
let { expanded = $bindable(false), name, files }: Props = $props();
function toggle() { function toggle() {
expanded = !expanded; expanded = !expanded;
@@ -18,7 +23,7 @@
} }
</script> </script>
<button class="flex pl-2" on:click={toggle}> <button class="flex pl-2" onclick={toggle}>
{#if expanded} {#if expanded}
<FolderOpenOutline class="w-6 h-6" /> <FolderOpenOutline class="w-6 h-6" />
{:else} {:else}
@@ -32,7 +37,7 @@
{#each Object.entries(files) as [name, content]} {#each Object.entries(files) as [name, content]}
<li class="p-1"> <li class="p-1">
{#if typeof content == 'object'} {#if typeof content == 'object'}
<svelte:self {name} files={content} on:selected={updateSelected} /> <Folder {name} files={content} on:selected={updateSelected} />
{:else} {:else}
<File {name} on:selected={updateSelected}/> <File {name} on:selected={updateSelected}/>
{/if} {/if}
@@ -12,16 +12,16 @@
Chart.register(...registerables); Chart.register(...registerables);
let cpuChartElement: HTMLCanvasElement; let cpuChartElement: HTMLCanvasElement = $state();
let cpuChart: Chart; let cpuChart: Chart;
let heapChartElement: HTMLCanvasElement; let heapChartElement: HTMLCanvasElement = $state();
let heapChart: Chart; let heapChart: Chart;
let filesystemChartElement: HTMLCanvasElement; let filesystemChartElement: HTMLCanvasElement = $state();
let filesystemChart: Chart; let filesystemChart: Chart;
let temperatureChartElement: HTMLCanvasElement; let temperatureChartElement: HTMLCanvasElement = $state();
let temperatureChart: Chart; let temperatureChart: Chart;
onMount(() => { onMount(() => {
@@ -330,15 +330,19 @@
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<Metrics slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" /> {#snippet icon()}
<span slot="title">System Metrics</span> <Metrics class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
{/snippet}
{#snippet title()}
<span >System Metrics</span>
{/snippet}
<div class="w-full overflow-x-auto"> <div class="w-full overflow-x-auto">
<div <div
class="flex w-full flex-col space-y-1 h-60" class="flex w-full flex-col space-y-1 h-60"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
> >
<canvas bind:this={cpuChartElement} /> <canvas bind:this={cpuChartElement}></canvas>
</div> </div>
</div> </div>
@@ -347,7 +351,7 @@
class="flex w-full flex-col space-y-1 h-60" class="flex w-full flex-col space-y-1 h-60"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
> >
<canvas bind:this={heapChartElement} /> <canvas bind:this={heapChartElement}></canvas>
</div> </div>
</div> </div>
<div class="w-full overflow-x-auto"> <div class="w-full overflow-x-auto">
@@ -355,7 +359,7 @@
class="flex w-full flex-col space-y-1 h-52" class="flex w-full flex-col space-y-1 h-52"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
> >
<canvas bind:this={filesystemChartElement} /> <canvas bind:this={filesystemChartElement}></canvas>
</div> </div>
</div> </div>
<div class="w-full overflow-x-auto"> <div class="w-full overflow-x-auto">
@@ -363,7 +367,7 @@
class="flex w-full flex-col space-y-1 h-52" class="flex w-full flex-col space-y-1 h-52"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
> >
<canvas bind:this={temperatureChartElement} /> <canvas bind:this={temperatureChartElement}></canvas>
</div> </div>
</div> </div>
</SettingsCard> </SettingsCard>
@@ -34,7 +34,7 @@
const features = useFeatureFlags(); const features = useFeatureFlags();
let systemInformation: SystemInformation; let systemInformation: SystemInformation = $state();
async function getSystemStatus() { async function getSystemStatus() {
const result = await api.get<SystemInformation>('/api/system/status'); const result = await api.get<SystemInformation>('/api/system/status');
@@ -106,8 +106,12 @@
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<Health slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" /> {#snippet icon()}
<span slot="title">System Status</span> <Health class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
{/snippet}
{#snippet title()}
<span >System Status</span>
{/snippet}
<div class="w-full overflow-x-auto"> <div class="w-full overflow-x-auto">
{#await getSystemStatus()} {#await getSystemStatus()}
@@ -309,14 +313,14 @@
<div class="mt-4 flex flex-wrap justify-end gap-2"> <div class="mt-4 flex flex-wrap justify-end gap-2">
{#if $features.sleep} {#if $features.sleep}
<button class="btn btn-primary inline-flex items-center" on:click={confirmSleep}> <button class="btn btn-primary inline-flex items-center" onclick={confirmSleep}>
<Sleep class="mr-2 h-5 w-5" /><span>Sleep</span> <Sleep class="mr-2 h-5 w-5" /><span>Sleep</span>
</button> </button>
{/if} {/if}
<button class="btn btn-primary inline-flex items-center" on:click={confirmRestart}> <button class="btn btn-primary inline-flex items-center" onclick={confirmRestart}>
<Power class="mr-2 h-5 w-5" /><span>Restart</span> <Power class="mr-2 h-5 w-5" /><span>Restart</span>
</button> </button>
<button class="btn btn-secondary inline-flex items-center" on:click={confirmReset}> <button class="btn btn-secondary inline-flex items-center" onclick={confirmReset}>
<FactoryReset class="mr-2 h-5 w-5" /><span>Factory Reset</span> <FactoryReset class="mr-2 h-5 w-5" /><span>Factory Reset</span>
</button> </button>
</div> </div>
@@ -81,8 +81,12 @@
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<Github slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end rounded-full" /> {#snippet icon()}
<span slot="title">Github Firmware Manager</span> <Github class="lex-shrink-0 mr-2 h-6 w-6 self-end rounded-full" />
{/snippet}
{#snippet title()}
<span >Github Firmware Manager</span>
{/snippet}
{#await getGithubAPI()} {#await getGithubAPI()}
<Spinner /> <Spinner />
{:then githubReleases} {:then githubReleases}
@@ -136,7 +140,7 @@
{#if compareVersions($features.firmware_version, release.tag_name) != 0} {#if compareVersions($features.firmware_version, release.tag_name) != 0}
<button <button
class="btn btn-ghost btn-circle btn-sm" class="btn btn-ghost btn-circle btn-sm"
on:click={() => { onclick={() => {
confirmGithubUpdate(release.assets); confirmGithubUpdate(release.assets);
}} }}
> >
@@ -6,7 +6,7 @@
import { api } from '$lib/api'; import { api } from '$lib/api';
import { Cancel, OTA, Warning } from '$lib/components/icons'; import { Cancel, OTA, Warning } from '$lib/components/icons';
let files: FileList; let files: FileList = $state();
async function uploadBIN() { async function uploadBIN() {
const formData = new FormData(); const formData = new FormData();
@@ -32,8 +32,12 @@
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<OTA slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end rounded-full" /> {#snippet icon()}
<span slot="title">Upload Firmware</span> <OTA class="lex-shrink-0 mr-2 h-6 w-6 self-end rounded-full" />
{/snippet}
{#snippet title()}
<span >Upload Firmware</span>
{/snippet}
<div class="alert alert-warning shadow-lg"> <div class="alert alert-warning shadow-lg">
<Warning class="h-6 w-6 flex-shrink-0" /> <Warning class="h-6 w-6 flex-shrink-0" />
<span <span
@@ -48,6 +52,6 @@
class="file-input file-input-bordered file-input-secondary mt-4 w-full" class="file-input file-input-bordered file-input-secondary mt-4 w-full"
bind:files bind:files
accept=".bin,.md5" accept=".bin,.md5"
on:change={confirmBinUpload} onchange={confirmBinUpload}
/> />
</SettingsCard> </SettingsCard>
+14 -8
View File
@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';
@@ -13,10 +15,10 @@
const features = useFeatureFlags(); const features = useFeatureFlags();
let apSettings: ApSettings; let apSettings: ApSettings = $state();
let apStatus: ApStatus; let apStatus: ApStatus = $state();
let formField: any; let formField: any = $state();
async function getAPStatus() { async function getAPStatus() {
const result = await api.get<ApStatus>('/api/wifi/ap/status'); const result = await api.get<ApStatus>('/api/wifi/ap/status');
@@ -67,14 +69,14 @@
{ bg_color: 'bg-warning', text_color: 'text-warning-content', description: 'Lingering' } { bg_color: 'bg-warning', text_color: 'text-warning-content', description: 'Lingering' }
]; ];
let formErrors = { let formErrors = $state({
ssid: false, ssid: false,
channel: false, channel: false,
max_clients: false, max_clients: false,
local_ip: false, local_ip: false,
gateway_ip: false, gateway_ip: false,
subnet_mask: false subnet_mask: false
}; });
async function postAPSettings(data: ApSettings) { async function postAPSettings(data: ApSettings) {
const result = await api.post<ApSettings>('/api/wifi/ap/settings', data); const result = await api.post<ApSettings>('/api/wifi/ap/settings', data);
@@ -152,8 +154,12 @@
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<AP slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" /> {#snippet icon()}
<span slot="title">Access Point</span> <AP class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
{/snippet}
{#snippet title()}
<span >Access Point</span>
{/snippet}
<div class="w-full overflow-x-auto"> <div class="w-full overflow-x-auto">
{#await getAPStatus()} {#await getAPStatus()}
<Spinner /> <Spinner />
@@ -234,7 +240,7 @@
> >
<form <form
class="grid w-full grid-cols-1 content-center gap-x-4 p-0s sm:grid-cols-2" class="grid w-full grid-cols-1 content-center gap-x-4 p-0s sm:grid-cols-2"
on:submit|preventDefault={handleSubmitAP} onsubmit={preventDefault(handleSubmitAP)}
novalidate novalidate
bind:this={formField} bind:this={formField}
> >
+21 -14
View File
@@ -1,4 +1,7 @@
<script lang="ts"> <script lang="ts">
import { createBubbler } from 'svelte/legacy';
const bubble = createBubbler();
import { closeModal } from 'svelte-modals'; import { closeModal } from 'svelte-modals';
import { focusTrap } from 'svelte-focus-trap'; import { focusTrap } from 'svelte-focus-trap';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
@@ -10,8 +13,12 @@
import { AP, Network, Reload, Cancel } from '$lib/components/icons'; import { AP, Network, Reload, Cancel } from '$lib/components/icons';
// provided by <Modals /> // provided by <Modals />
export let isOpen: boolean; interface Props {
export let storeNetwork: any; isOpen: boolean;
storeNetwork: any;
}
let { isOpen, storeNetwork }: Props = $props();
const encryptionType = [ const encryptionType = [
'Open', 'Open',
@@ -25,9 +32,9 @@
'WAPI PSK' 'WAPI PSK'
]; ];
let listOfNetworks: NetworkItem[] = []; let listOfNetworks: NetworkItem[] = $state([]);
let scanActive = false; let scanActive = $state(false);
let pollingId: number; let pollingId: number;
@@ -73,15 +80,15 @@
role="dialog" role="dialog"
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center" class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
transition:fly={{ y: 50 }} transition:fly={{ y: 50 }}
on:introstart onintrostart={bubble('introstart')}
on:outroend onoutroend={bubble('outroend')}
use:focusTrap use:focusTrap
> >
<div <div
class="bg-base-100 rounded-box pointer-events-auto flex max-h-full min-w-fit max-w-md flex-col justify-between p-4 shadow-lg" class="bg-base-100 rounded-box pointer-events-auto flex max-h-full min-w-fit max-w-md flex-col justify-between p-4 shadow-lg"
> >
<h2 class="text-base-content text-start text-2xl font-bold">Scan Networks</h2> <h2 class="text-base-content text-start text-2xl font-bold">Scan Networks</h2>
<div class="divider my-2" /> <div class="divider my-2"></div>
<div class="overflow-y-auto"> <div class="overflow-y-auto">
{#if scanActive}<div {#if scanActive}<div
class="bg-base-100 flex flex-col items-center justify-center p-6" class="bg-base-100 flex flex-col items-center justify-center p-6"
@@ -93,10 +100,10 @@
<ul class="menu"> <ul class="menu">
{#each listOfNetworks as network, i} {#each listOfNetworks as network, i}
<li> <li>
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<div <div
class="bg-base-200 rounded-btn my-1 flex items-center space-x-3 hover:scale-[1.02] active:scale-[0.98]" class="bg-base-200 rounded-btn my-1 flex items-center space-x-3 hover:scale-[1.02] active:scale-[0.98]"
on:click={() => { onclick={() => {
storeNetwork(network.ssid); storeNetwork(network.ssid);
}} }}
role="button" role="button"
@@ -114,7 +121,7 @@
Channel: {network.channel} Channel: {network.channel}
</div> </div>
</div> </div>
<div class="flex-grow" /> <div class="flex-grow"></div>
<RssiIndicator showDBm={true} rssi={network.rssi} /> <RssiIndicator showDBm={true} rssi={network.rssi} />
</div> </div>
</li> </li>
@@ -122,19 +129,19 @@
</ul> </ul>
{/if} {/if}
</div> </div>
<div class="divider my-2" /> <div class="divider my-2"></div>
<div class="flex flex-wrap justify-end gap-2"> <div class="flex flex-wrap justify-end gap-2">
<button <button
class="btn btn-primary inline-flex flex-none items-center" class="btn btn-primary inline-flex flex-none items-center"
disabled={scanActive} disabled={scanActive}
on:click={scanNetworks} onclick={scanNetworks}
><Reload class="mr-2 h-5 w-5" /><span>Scan again</span></button ><Reload class="mr-2 h-5 w-5" /><span>Scan again</span></button
> >
<div class="flex-grow" /> <div class="flex-grow"></div>
<button <button
class="btn btn-warning text-warning-content inline-flex flex-none items-center" class="btn btn-warning text-warning-content inline-flex flex-none items-center"
on:click={closeModal}><Cancel class="mr-2 h-5 w-5" /><span>Cancel</span></button onclick={closeModal}><Cancel class="mr-2 h-5 w-5" /><span>Cancel</span></button
> >
</div> </div>
</div> </div>
+39 -38
View File
@@ -1,5 +1,3 @@
<svelte:options immutable={true} />
<script lang="ts"> <script lang="ts">
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { openModal, closeModal } from 'svelte-modals'; import { openModal, closeModal } from 'svelte-modals';
@@ -38,7 +36,7 @@
const features = useFeatureFlags(); const features = useFeatureFlags();
let networkEditable: KnownNetworkItem = { let networkEditable: KnownNetworkItem = $state({
ssid: '', ssid: '',
password: '', password: '',
static_ip_config: false, static_ip_config: false,
@@ -47,32 +45,32 @@
gateway_ip: undefined, gateway_ip: undefined,
dns_ip_1: undefined, dns_ip_1: undefined,
dns_ip_2: undefined dns_ip_2: undefined
}; });
let static_ip_config = false; let static_ip_config = $state(false);
let newNetwork: boolean = true; let newNetwork: boolean = $state(true);
let showNetworkEditor: boolean = false; let showNetworkEditor: boolean = $state(false);
let wifiStatus: WifiStatus; let wifiStatus: WifiStatus = $state();
let wifiSettings: WifiSettings; let wifiSettings: WifiSettings = $state();
let dndNetworkList: KnownNetworkItem[] = []; let dndNetworkList: KnownNetworkItem[] = $state([]);
let showWifiDetails = false; let showWifiDetails = $state(false);
let formField: any; let formField: any = $state();
let formErrors = { let formErrors = $state({
ssid: false, ssid: false,
local_ip: false, local_ip: false,
gateway_ip: false, gateway_ip: false,
subnet_mask: false, subnet_mask: false,
dns_1: false, dns_1: false,
dns_2: false dns_2: false
}; });
let formErrorhostname = false; let formErrorhostname = $state(false);
async function getWifiStatus() { async function getWifiStatus() {
const result = await api.get<WifiStatus>('/api/wifi/sta/status'); const result = await api.get<WifiStatus>('/api/wifi/sta/status');
@@ -128,7 +126,8 @@
} }
} }
function validateWiFiForm() { function validateWiFiForm(event: SubmitEvent) {
event.preventDefault();
let valid = true; let valid = true;
// Validate SSID // Validate SSID
@@ -286,8 +285,12 @@
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
<Router slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" /> {#snippet icon()}
<span slot="title">WiFi Connection</span> <Router class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
{/snippet}
{#snippet title()}
<span>WiFi Connection</span>
{/snippet}
<div class="w-full overflow-x-auto"> <div class="w-full overflow-x-auto">
{#await getWifiStatus()} {#await getWifiStatus()}
<Spinner /> <Spinner />
@@ -350,10 +353,10 @@
{wifiStatus.rssi} dBm {wifiStatus.rssi} dBm
</div> </div>
</div> </div>
<div class="grow" /> <div class="grow"></div>
<button <button
class="btn btn-circle btn-ghost btn-sm modal-button" class="btn btn-circle btn-ghost btn-sm modal-button"
on:click={() => { onclick={() => {
showWifiDetails = !showWifiDetails; showWifiDetails = !showWifiDetails;
}} }}
> >
@@ -451,7 +454,7 @@
<div class="relative w-full overflow-visible"> <div class="relative w-full overflow-visible">
<button <button
class="btn btn-primary text-primary-content btn-md absolute -top-14 right-16" class="btn btn-primary text-primary-content btn-md absolute -top-14 right-16"
on:click={() => { onclick={() => {
if (checkNetworkList()) { if (checkNetworkList()) {
addNetwork(); addNetwork();
showNetworkEditor = true; showNetworkEditor = true;
@@ -462,7 +465,7 @@
> >
<button <button
class="btn btn-primary text-primary-content btn-md absolute -top-14 right-0" class="btn btn-primary text-primary-content btn-md absolute -top-14 right-0"
on:click={() => { onclick={() => {
if (checkNetworkList()) { if (checkNetworkList()) {
scanForNetworks(); scanForNetworks();
showNetworkEditor = true; showNetworkEditor = true;
@@ -482,21 +485,23 @@
itemSize={60} itemSize={60}
itemCount={dndNetworkList.length} itemCount={dndNetworkList.length}
on:drop={onDrop} on:drop={onDrop}
let:index
> >
<!-- svelte-ignore a11y-click-events-have-key-events --> {#snippet children({ index })}
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <!-- svelte-ignore a11y_click_events_have_key_events -->
<div
class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"
>
<div class="mask mask-hexagon bg-primary h-auto w-10 shrink-0"> <div class="mask mask-hexagon bg-primary h-auto w-10 shrink-0">
<Router class="text-primary-content h-auto w-full scale-75" /> <Router class="text-primary-content h-auto w-full scale-75" />
</div> </div>
<div> <div>
<div class="font-bold">{dndNetworkList[index].ssid}</div> <div class="font-bold">{dndNetworkList[index].ssid}</div>
</div> </div>
<div class="flex-grow" /> <div class="flex-grow"></div>
<div class="space-x-0 px-0 mx-0"> <div class="space-x-0 px-0 mx-0">
<button <button
class="btn btn-ghost btn-sm" class="btn btn-ghost btn-sm"
on:click={() => { onclick={() => {
handleEdit(index); handleEdit(index);
}} }}
> >
@@ -504,7 +509,7 @@
> >
<button <button
class="btn btn-ghost btn-sm" class="btn btn-ghost btn-sm"
on:click={() => { onclick={() => {
confirmDelete(index); confirmDelete(index);
}} }}
> >
@@ -512,21 +517,17 @@
</button> </button>
</div> </div>
</div> </div>
{/snippet}
</DragDropList> </DragDropList>
</div> </div>
</div> </div>
<div class="divider mb-0" /> <div class="divider mb-0"></div>
<div <div
class="flex flex-col gap-2 p-0" class="flex flex-col gap-2 p-0"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
> >
<form <form class="" onsubmit={validateWiFiForm} novalidate bind:this={formField}>
class=""
on:submit|preventDefault={validateWiFiForm}
novalidate
bind:this={formField}
>
<div class="grid w-full grid-cols-1 content-center gap-x-4 px-4 sm:grid-cols-2"> <div class="grid w-full grid-cols-1 content-center gap-x-4 px-4 sm:grid-cols-2">
<div> <div>
<label class="label" for="channel"> <label class="label" for="channel">
@@ -566,7 +567,7 @@
</div> </div>
{#if showNetworkEditor} {#if showNetworkEditor}
<div class="divider my-0" /> <div class="divider my-0"></div>
<div <div
class="grid w-full grid-cols-1 content-center gap-x-4 px-4 sm:grid-cols-2" class="grid w-full grid-cols-1 content-center gap-x-4 px-4 sm:grid-cols-2"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}
@@ -747,12 +748,12 @@
{/if} {/if}
{/if} {/if}
<div class="divider mb-2 mt-0" /> <div class="divider mb-2 mt-0"></div>
<div class="mx-4 flex flex-wrap justify-end gap-2"> <div class="mx-4 flex flex-wrap justify-end gap-2">
<button class="btn btn-primary" type="submit" disabled={!showNetworkEditor}> <button class="btn btn-primary" type="submit" disabled={!showNetworkEditor}>
{newNetwork ? 'Add Network' : 'Update Network'} {newNetwork ? 'Add Network' : 'Update Network'}
</button> </button>
<button class="btn btn-primary" type="button" on:click={validateHostName}> <button class="btn btn-primary" type="button" onclick={validateHostName}>
Apply Settings Apply Settings
</button> </button>
</div> </div>
+1
View File
@@ -10,6 +10,7 @@
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"moduleResolution": "bundler", "moduleResolution": "bundler",
"module": "preserve",
"types": ["unplugin-icons/types/svelte"] "types": ["unplugin-icons/types/svelte"]
} }
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias