🌌 Migrate app to svelte-5
This commit is contained in:
@@ -1,43 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { slide } from 'svelte/transition';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { Down } from './icons';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { Down } from './icons';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
function openCollapsible() {
|
||||
open = !open;
|
||||
if (open) {
|
||||
opened();
|
||||
} else {
|
||||
closed();
|
||||
}
|
||||
}
|
||||
|
||||
function openCollapsible() {
|
||||
open = !open;
|
||||
if (open) {
|
||||
dispatch('opened');
|
||||
} else {
|
||||
dispatch('closed');
|
||||
}
|
||||
}
|
||||
|
||||
export let open = false;
|
||||
let { icon, title, children, open, opened, closed, class: klass } = $props();
|
||||
</script>
|
||||
|
||||
<div class="{$$props.class || ''} 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">
|
||||
<span class="inline-flex items-baseline">
|
||||
<slot name="icon" />
|
||||
<slot name="title" />
|
||||
</span>
|
||||
<button class="btn btn-circle btn-ghost btn-sm" on:click={() => openCollapsible()}>
|
||||
<Down
|
||||
class="text-base-content h-auto w-6 transition-transform duration-300 ease-in-out {open
|
||||
? 'rotate-180'
|
||||
: ''}"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{#if open}
|
||||
<div
|
||||
class="flex flex-col gap-2 p-4 pt-0"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
<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"
|
||||
>
|
||||
<span class="inline-flex items-baseline">
|
||||
{@render icon?.()}
|
||||
{@render title?.()}
|
||||
</span>
|
||||
<button class="btn btn-circle btn-ghost btn-sm" onclick={() => openCollapsible()}>
|
||||
<Down
|
||||
class="text-base-content h-auto w-6 transition-transform duration-300 ease-in-out {(
|
||||
open
|
||||
) ?
|
||||
'rotate-180'
|
||||
: ''}"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{#if open}
|
||||
<div
|
||||
class="flex flex-col gap-2 p-4 pt-0"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,47 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { createBubbler } from 'svelte/legacy';
|
||||
|
||||
const bubble = createBubbler();
|
||||
import { closeModal } from 'svelte-modals';
|
||||
import { focusTrap } from 'svelte-focus-trap';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { Cancel, Check } from '$lib/components/icons';
|
||||
|
||||
// provided by <Modals />
|
||||
export let isOpen: boolean;
|
||||
|
||||
export let title: string;
|
||||
export let message: string;
|
||||
export let onConfirm: any;
|
||||
export let labels = {
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
onConfirm: any;
|
||||
labels?: any;
|
||||
}
|
||||
|
||||
let {
|
||||
isOpen,
|
||||
title,
|
||||
message,
|
||||
onConfirm,
|
||||
labels = {
|
||||
cancel: { label: 'Cancel', icon: Cancel },
|
||||
confirm: { label: 'OK', icon: Check }
|
||||
};
|
||||
}
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if isOpen}
|
||||
{@const SvelteComponent = labels?.confirm.icon}
|
||||
<div
|
||||
role="dialog"
|
||||
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
|
||||
transition:fly={{ y: 50 }}
|
||||
on:introstart
|
||||
on:outroend
|
||||
onintrostart={bubble('introstart')}
|
||||
onoutroend={bubble('outroend')}
|
||||
use:focusTrap
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
<div class="divider my-2" />
|
||||
<div class="divider my-2"></div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button class="btn btn-primary inline-flex items-center" on:click={closeModal}
|
||||
><svelte:component this={labels.cancel.icon} class="mr-2 h-5 w-5" /><span
|
||||
<button class="btn btn-primary inline-flex items-center" onclick={closeModal}
|
||||
><labels.cancel.icon class="mr-2 h-5 w-5" /><span
|
||||
>{labels?.cancel.label}</span
|
||||
></button
|
||||
>
|
||||
<button
|
||||
class="btn btn-warning text-warning-content inline-flex items-center"
|
||||
on:click={onConfirm}
|
||||
><svelte:component this={labels?.confirm.icon} class="mr-2 h-5 w-5" /><span
|
||||
onclick={onConfirm}
|
||||
><SvelteComponent class="mr-2 h-5 w-5" /><span
|
||||
>{labels?.confirm.label}</span
|
||||
></button
|
||||
>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { run, createBubbler } from 'svelte/legacy';
|
||||
|
||||
const bubble = createBubbler();
|
||||
import { closeAllModals, onBeforeClose } from 'svelte-modals';
|
||||
import { focusTrap } from 'svelte-focus-trap';
|
||||
import { fly } from 'svelte/transition';
|
||||
@@ -6,35 +9,45 @@
|
||||
import { Cancel } from './icons';
|
||||
|
||||
// provided by <Modals />
|
||||
export let isOpen: boolean;
|
||||
|
||||
let updating = true;
|
||||
|
||||
let progress = 0;
|
||||
$: if ($telemetry.download_ota.status == 'progress') {
|
||||
progress = $telemetry.download_ota.progress;
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
$: if ($telemetry.download_ota.status == 'error') {
|
||||
updating = false;
|
||||
}
|
||||
let { isOpen }: Props = $props();
|
||||
|
||||
let message = 'Preparing ...';
|
||||
let timerId: number;
|
||||
let updating = $state(true);
|
||||
|
||||
$: if ($telemetry.download_ota.status == 'progress') {
|
||||
message = 'Downloading ...';
|
||||
} else if ($telemetry.download_ota.status == 'error') {
|
||||
message = $telemetry.download_ota.error;
|
||||
} else if ($telemetry.download_ota.status == 'finished') {
|
||||
message = 'Restarting ...';
|
||||
progress = 0;
|
||||
// Reload page after 5 sec
|
||||
timerId = setTimeout(() => {
|
||||
closeAllModals();
|
||||
location.reload();
|
||||
}, 5000);
|
||||
}
|
||||
let progress = $state(0);
|
||||
run(() => {
|
||||
if ($telemetry.download_ota.status == 'progress') {
|
||||
progress = $telemetry.download_ota.progress;
|
||||
}
|
||||
});
|
||||
|
||||
run(() => {
|
||||
if ($telemetry.download_ota.status == 'error') {
|
||||
updating = false;
|
||||
}
|
||||
});
|
||||
|
||||
let message = $state('Preparing ...');
|
||||
let timerId: number = $state();
|
||||
|
||||
run(() => {
|
||||
if ($telemetry.download_ota.status == 'progress') {
|
||||
message = 'Downloading ...';
|
||||
} else if ($telemetry.download_ota.status == 'error') {
|
||||
message = $telemetry.download_ota.error;
|
||||
} else if ($telemetry.download_ota.status == 'finished') {
|
||||
message = 'Restarting ...';
|
||||
progress = 0;
|
||||
// Reload page after 5 sec
|
||||
timerId = setTimeout(() => {
|
||||
closeAllModals();
|
||||
location.reload();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeClose(() => {
|
||||
if (updating) {
|
||||
@@ -54,32 +67,32 @@
|
||||
role="dialog"
|
||||
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
|
||||
transition:fly={{ y: 50 }}
|
||||
on:introstart
|
||||
on:outroend
|
||||
onintrostart={bubble('introstart')}
|
||||
onoutroend={bubble('outroend')}
|
||||
use:focusTrap
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<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="bg-base-100 flex flex-col items-center justify-center p-6">
|
||||
{#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}
|
||||
<progress class="progress progress-primary w-56" />
|
||||
<progress class="progress progress-primary w-56"></progress>
|
||||
{/if}
|
||||
<p class="mt-8 text-2xl">{message}</p>
|
||||
</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-grow" />
|
||||
<div class="flex-grow"></div>
|
||||
<button
|
||||
class="btn btn-warning text-warning-content inline-flex flex-none items-center"
|
||||
disabled={updating}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
closeAllModals();
|
||||
location.reload();
|
||||
}}
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { createBubbler } from 'svelte/legacy';
|
||||
|
||||
const bubble = createBubbler();
|
||||
import { focusTrap } from 'svelte-focus-trap';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { Check } from './icons';
|
||||
|
||||
// provided by <Modals />
|
||||
export let isOpen: boolean;
|
||||
|
||||
export let title: string;
|
||||
export let message: string;
|
||||
export let onDismiss: any;
|
||||
export let dismiss = { label: 'Dismiss', icon: Check };
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
onDismiss: any;
|
||||
dismiss?: any;
|
||||
}
|
||||
|
||||
let {
|
||||
isOpen,
|
||||
title,
|
||||
message,
|
||||
onDismiss,
|
||||
dismiss = { label: 'Dismiss', icon: Check }
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if isOpen}
|
||||
@@ -17,22 +30,22 @@
|
||||
role="dialog"
|
||||
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
|
||||
transition:fly={{ y: 50 }}
|
||||
on:introstart
|
||||
on:outroend
|
||||
onintrostart={bubble('introstart')}
|
||||
onoutroend={bubble('outroend')}
|
||||
use:focusTrap
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
<div class="divider my-2" />
|
||||
<div class="divider my-2"></div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button
|
||||
class="btn btn-warning text-warning-content inline-flex items-center"
|
||||
on:click={onDismiss}
|
||||
><svelte:component this={dismiss.icon} class="mr-2 h-5 w-5" /><span>{dismiss.label}</span
|
||||
onclick={onDismiss}
|
||||
><dismiss.icon class="mr-2 h-5 w-5" /><span>{dismiss.label}</span
|
||||
></button
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,21 @@
|
||||
import { slide } from 'svelte/transition';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { Down } from './icons';
|
||||
export let open = true;
|
||||
export let collapsible = true;
|
||||
interface Props {
|
||||
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>
|
||||
|
||||
{#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"
|
||||
>
|
||||
<span class="inline-flex items-baseline">
|
||||
<slot name="icon" />
|
||||
<slot name="title" />
|
||||
{@render icon?.()}
|
||||
{@render title?.()}
|
||||
</span>
|
||||
<button
|
||||
class="btn btn-circle btn-ghost btn-sm"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
open = !open;
|
||||
}}
|
||||
>
|
||||
@@ -35,7 +48,7 @@
|
||||
class="flex flex-col gap-2 p-4 pt-0"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -45,12 +58,12 @@
|
||||
>
|
||||
<div class="min-h-16 w-full p-4 text-xl font-medium">
|
||||
<span class="inline-flex items-baseline">
|
||||
<slot name="icon" />
|
||||
<slot name="title" />
|
||||
{@render icon?.()}
|
||||
{@render title?.()}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 p-4 pt-0">
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { onDestroy } from 'svelte';
|
||||
import { location } from '$lib/stores';
|
||||
|
||||
let source = `${$location}/api/camera/stream`;
|
||||
let source = $state(`${$location}/api/camera/stream`);
|
||||
|
||||
onDestroy(() => (source = '#'));
|
||||
</script>
|
||||
|
||||
@@ -4,30 +4,31 @@
|
||||
import { notifications } from '$lib/components/toasts/notifications';
|
||||
import { error, info, success, warning } from './icons';
|
||||
|
||||
export let theme = {
|
||||
|
||||
/** @type {{theme?: any, icon?: any}} */
|
||||
let { theme = {
|
||||
error: 'alert-error',
|
||||
success: 'alert-success',
|
||||
warning: 'alert-warning',
|
||||
info: 'alert-info'
|
||||
};
|
||||
|
||||
export let icon = {
|
||||
}, icon = {
|
||||
error: error,
|
||||
success: success,
|
||||
warning: warning,
|
||||
info: info
|
||||
};
|
||||
} } = $props();
|
||||
</script>
|
||||
|
||||
<div class="toast toast-end mr-4">
|
||||
{#each $notifications as notification (notification.id)}
|
||||
{@const SvelteComponent = icon[notification.type]}
|
||||
<div
|
||||
animate:flip={{ duration: 400 }}
|
||||
class="alert animate-none {theme[notification.type]}"
|
||||
in:fly={{ y: 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>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -41,14 +41,24 @@
|
||||
import type { URDFRobot } from 'urdf-loader';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export let sky = true;
|
||||
export let orbit = false;
|
||||
export let panel = true;
|
||||
export let debug = false;
|
||||
export let ground = true;
|
||||
interface Props {
|
||||
sky?: boolean;
|
||||
orbit?: boolean;
|
||||
panel?: boolean;
|
||||
debug?: boolean;
|
||||
ground?: boolean;
|
||||
}
|
||||
|
||||
let sceneManager = new SceneBuilder();
|
||||
let canvas: HTMLCanvasElement;
|
||||
let {
|
||||
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 modelTargetAngles: number[] = new Array(12).fill(0);
|
||||
@@ -332,6 +342,6 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={sceneManager.fillParent} />
|
||||
<svelte:window onresize={sceneManager.fillParent} />
|
||||
|
||||
<canvas bind:this={canvas}></canvas>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { MdiEyeOffOutline, MdiEyeOutline } from "../icons";
|
||||
|
||||
export let show = false;
|
||||
export let value = '';
|
||||
export let id = '';
|
||||
interface Props {
|
||||
show?: boolean;
|
||||
value?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
let { show = $bindable(false), value = $bindable(''), id = '' }: Props = $props();
|
||||
|
||||
$: type = show ? 'text' : 'password';
|
||||
let type = $derived(show ? 'text' : 'password');
|
||||
|
||||
const handleInput = (e: any) => value = e.target.value
|
||||
|
||||
@@ -13,9 +17,9 @@
|
||||
</script>
|
||||
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<input {type} class="grow" {value} on:input={handleInput} {id} />
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div on:click={togglePassword} role="button" tabindex="0">
|
||||
<input {type} class="grow" {value} oninput={handleInput} {id} />
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div onclick={togglePassword} role="button" tabindex="0">
|
||||
<MdiEyeOffOutline class="text-base-content/50 h-6 {show ? 'block' : 'hidden'}" />
|
||||
<MdiEyeOutline class="text-base-content/50 h-6 {show ? 'hidden' : 'block'}" />
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
<script lang="ts">
|
||||
export let min = 0;
|
||||
export let max = 100;
|
||||
export let step = 1;
|
||||
export let value = (max - min) / 2;
|
||||
import { createBubbler } from 'svelte/legacy';
|
||||
|
||||
const bubble = createBubbler();
|
||||
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>
|
||||
|
||||
<input
|
||||
@@ -13,8 +25,8 @@
|
||||
max={max}
|
||||
step={step}
|
||||
bind:value
|
||||
on:input
|
||||
on:change
|
||||
oninput={bubble('input')}
|
||||
onchange={bubble('change')}
|
||||
/>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="box-border overflow-hidden flex-1">
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<script lang="ts">
|
||||
import WidgetContainer from './WidgetContainer.svelte';
|
||||
import { WidgetComponents, type WidgetContainerConfig, isWidgetConfig } from '$lib/stores/application';
|
||||
import Widget from './Widget.svelte';
|
||||
|
||||
export let container: WidgetContainerConfig;
|
||||
interface Props {
|
||||
container: WidgetContainerConfig;
|
||||
}
|
||||
|
||||
let { container }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full flex flex-col overflow-hidden">
|
||||
@@ -15,9 +20,10 @@
|
||||
{#each container.widgets as widget, index (widget.id + '-' + index)}
|
||||
<Widget>
|
||||
{#if isWidgetConfig(widget)}
|
||||
<svelte:component this={WidgetComponents[widget.component]} {...widget.props} />
|
||||
{@const SvelteComponent = WidgetComponents[widget.component]}
|
||||
<SvelteComponent {...widget.props} />
|
||||
{:else if widget.widgets}
|
||||
<svelte:self container={widget} />
|
||||
<WidgetContainer container={widget} />
|
||||
{/if}
|
||||
</Widget>
|
||||
{#if index !== container.widgets.length - 1}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Github } from "../icons";
|
||||
|
||||
export let github;
|
||||
interface Props {
|
||||
github: any;
|
||||
}
|
||||
|
||||
let { github }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if github.active}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script>
|
||||
import logo from '$lib/assets/logo512.png';
|
||||
|
||||
export let appName;
|
||||
/** @type {{appName: any}} */
|
||||
let { appName } = $props();
|
||||
</script>
|
||||
|
||||
<a
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { useFeatureFlags } from '$lib/stores/featureFlags';
|
||||
@@ -42,102 +44,105 @@
|
||||
submenu?: menuItem[];
|
||||
};
|
||||
|
||||
$: menuItems = [
|
||||
{
|
||||
title: 'Connection',
|
||||
icon: WiFi,
|
||||
href: '/connection',
|
||||
feature: !appEnv.VITE_USE_HOST_NAME
|
||||
},
|
||||
{
|
||||
title: 'Controller',
|
||||
icon: MdiController,
|
||||
href: '/controller',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'Peripherals',
|
||||
icon: Devices,
|
||||
feature: true,
|
||||
submenu: [
|
||||
{
|
||||
title: 'I2C',
|
||||
icon: Connection,
|
||||
href: '/peripherals/i2c',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'Camera',
|
||||
icon: Camera,
|
||||
href: '/peripherals/camera',
|
||||
feature: $features.camera
|
||||
},
|
||||
{
|
||||
title: 'Servo',
|
||||
icon: MotorOutline,
|
||||
href: '/peripherals/servo',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'IMU',
|
||||
icon: Rotate3d,
|
||||
href: '/peripherals/imu',
|
||||
feature: $features.imu || $features.mag || $features.bmp
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'WiFi',
|
||||
icon: WiFi,
|
||||
feature: true,
|
||||
submenu: [
|
||||
{
|
||||
title: 'WiFi Station',
|
||||
icon: Router,
|
||||
href: '/wifi/sta',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'Access Point',
|
||||
icon: AP,
|
||||
href: '/wifi/ap',
|
||||
feature: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'System',
|
||||
icon: Settings,
|
||||
feature: true,
|
||||
submenu: [
|
||||
{
|
||||
title: 'System Status',
|
||||
icon: Health,
|
||||
href: '/system/status',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'File System',
|
||||
icon: Folder,
|
||||
href: '/system/filesystem',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'System Metrics',
|
||||
icon: Metrics,
|
||||
href: '/system/metrics',
|
||||
feature: $features.analytics
|
||||
},
|
||||
{
|
||||
title: 'Firmware Update',
|
||||
icon: Update,
|
||||
href: '/system/update',
|
||||
feature:
|
||||
$features.ota || $features.upload_firmware || $features.download_firmware
|
||||
}
|
||||
]
|
||||
}
|
||||
] as menuItem[];
|
||||
let menuItems = $state();
|
||||
run(() => {
|
||||
menuItems = [
|
||||
{
|
||||
title: 'Connection',
|
||||
icon: WiFi,
|
||||
href: '/connection',
|
||||
feature: !appEnv.VITE_USE_HOST_NAME
|
||||
},
|
||||
{
|
||||
title: 'Controller',
|
||||
icon: MdiController,
|
||||
href: '/controller',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'Peripherals',
|
||||
icon: Devices,
|
||||
feature: true,
|
||||
submenu: [
|
||||
{
|
||||
title: 'I2C',
|
||||
icon: Connection,
|
||||
href: '/peripherals/i2c',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'Camera',
|
||||
icon: Camera,
|
||||
href: '/peripherals/camera',
|
||||
feature: $features.camera
|
||||
},
|
||||
{
|
||||
title: 'Servo',
|
||||
icon: MotorOutline,
|
||||
href: '/peripherals/servo',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'IMU',
|
||||
icon: Rotate3d,
|
||||
href: '/peripherals/imu',
|
||||
feature: $features.imu || $features.mag || $features.bmp
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'WiFi',
|
||||
icon: WiFi,
|
||||
feature: true,
|
||||
submenu: [
|
||||
{
|
||||
title: 'WiFi Station',
|
||||
icon: Router,
|
||||
href: '/wifi/sta',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'Access Point',
|
||||
icon: AP,
|
||||
href: '/wifi/ap',
|
||||
feature: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'System',
|
||||
icon: Settings,
|
||||
feature: true,
|
||||
submenu: [
|
||||
{
|
||||
title: 'System Status',
|
||||
icon: Health,
|
||||
href: '/system/status',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'File System',
|
||||
icon: Folder,
|
||||
href: '/system/filesystem',
|
||||
feature: true
|
||||
},
|
||||
{
|
||||
title: 'System Metrics',
|
||||
icon: Metrics,
|
||||
href: '/system/metrics',
|
||||
feature: $features.analytics
|
||||
},
|
||||
{
|
||||
title: 'Firmware Update',
|
||||
icon: Update,
|
||||
href: '/system/update',
|
||||
feature:
|
||||
$features.ota || $features.upload_firmware || $features.download_firmware
|
||||
}
|
||||
]
|
||||
}
|
||||
] as menuItem[];
|
||||
});
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -152,7 +157,9 @@
|
||||
dispatch('menuClicked');
|
||||
}
|
||||
|
||||
$: setActiveMenuItem($page.data.title);
|
||||
run(() => {
|
||||
setActiveMenuItem($page.data.title);
|
||||
});
|
||||
|
||||
const updateMenu = (event: any) => {
|
||||
setActiveMenuItem(event.details);
|
||||
@@ -164,7 +171,7 @@
|
||||
|
||||
<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">
|
||||
<GithubButton {github} />
|
||||
|
||||
@@ -1,38 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
type menuItem = {
|
||||
import MenuList from './MenuList.svelte';
|
||||
type MenuItem = {
|
||||
title: string;
|
||||
icon: ConstructorOfATypedSvelteComponent;
|
||||
href?: string;
|
||||
feature: boolean;
|
||||
active?: boolean;
|
||||
submenu?: menuItem[];
|
||||
submenu?: MenuItem[];
|
||||
};
|
||||
|
||||
export let menuItems: menuItem[];
|
||||
|
||||
export let level = 0;
|
||||
let { level, menuItems, select, class: klass } = $props();
|
||||
|
||||
const selectMenuItem = (title: string) => {
|
||||
dispatch('select', title);
|
||||
select(title);
|
||||
};
|
||||
</script>
|
||||
|
||||
<ul class={$$props.class + ' menu'}>
|
||||
{#each menuItems as menuItem, i (menuItem.title)}
|
||||
<ul class={klass + ' menu'}>
|
||||
{#each menuItems as MenuItem[] as menuItem, i (menuItem.title)}
|
||||
{#if menuItem.feature}
|
||||
<li>
|
||||
{#if menuItem.submenu}
|
||||
<details open={menuItem.submenu.some(subItem => subItem.active)}>
|
||||
<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}
|
||||
</summary>
|
||||
<div class="pl-4">
|
||||
<svelte:self menuItems={menuItem.submenu} level={level + 1} />
|
||||
<MenuList menuItems={menuItem.submenu} level={level + 1} />
|
||||
</div>
|
||||
</details>
|
||||
{:else}
|
||||
@@ -42,9 +37,9 @@
|
||||
class:bg-base-100={menuItem.active}
|
||||
class:text-lg={level === 0}
|
||||
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}
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
|
||||
const features = useFeatureFlags();
|
||||
|
||||
export let battery:Battery;
|
||||
interface Props {
|
||||
battery: Battery;
|
||||
}
|
||||
|
||||
let { battery }: Props = $props();
|
||||
|
||||
const getBatteryIcon = () => {
|
||||
if (battery.voltage === 0) return BatteryCharging;
|
||||
@@ -18,9 +22,9 @@
|
||||
</script>
|
||||
|
||||
{#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">
|
||||
<svelte:component
|
||||
this={getBatteryIcon()}
|
||||
<SvelteComponent
|
||||
class="h-7 w-7 -rotate-90 {battery.voltage === 0 || battery.voltage <= 7.6 ? 'animate-pulse' : ''} {battery.voltage <= 7.6 ? 'text-error' : ''}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { isFullscreen, toggleFullscreen } from '$lib/stores';
|
||||
import { MdiFullscreenExit, MdiFullscreen } from '../icons';
|
||||
|
||||
const SvelteComponent = $derived($isFullscreen ? MdiFullscreenExit : MdiFullscreen);
|
||||
</script>
|
||||
|
||||
<button on:click={toggleFullscreen}>
|
||||
<svelte:component this={$isFullscreen ? MdiFullscreenExit : MdiFullscreen} class="h-7 w-7" />
|
||||
<button onclick={toggleFullscreen}>
|
||||
<SvelteComponent class="h-7 w-7" />
|
||||
</button>
|
||||
@@ -1,8 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { WiFi, WiFi0, WiFi1, WiFi2, WifiOff } from "../icons";
|
||||
|
||||
export let showDBm = false;
|
||||
export let rssi = 0;
|
||||
interface Props {
|
||||
showDBm?: boolean;
|
||||
rssi?: number;
|
||||
}
|
||||
|
||||
let { showDBm = false, rssi = 0 }: Props = $props();
|
||||
|
||||
const getWiFiIcon = () => {
|
||||
if (rssi === 0) return WifiOff;
|
||||
@@ -11,6 +15,8 @@
|
||||
if (rssi >= -85) return WiFi1;
|
||||
return WiFi0;
|
||||
};
|
||||
|
||||
const SvelteComponent = $derived(getWiFiIcon());
|
||||
</script>
|
||||
|
||||
<div class="indicator">
|
||||
@@ -21,7 +27,7 @@
|
||||
</span>
|
||||
{/if}
|
||||
<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>
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
{#if $features.sleep}
|
||||
<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" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
</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="flex gap-2 p-2">
|
||||
<a href="/">
|
||||
<svelte:component this={Hamburger} class="h-8 w-8"/>
|
||||
<Hamburger class="h-8 w-8"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,10 +14,14 @@
|
||||
|
||||
const features = useFeatureFlags();
|
||||
|
||||
export let update = false;
|
||||
interface Props {
|
||||
update?: boolean;
|
||||
}
|
||||
|
||||
let firmwareVersion: string;
|
||||
let firmwareDownloadLink: string;
|
||||
let { update = $bindable(false) }: Props = $props();
|
||||
|
||||
let firmwareVersion: string = $state();
|
||||
let firmwareDownloadLink: string = $state();
|
||||
|
||||
async function getGithubAPI() {
|
||||
const headers = {
|
||||
@@ -100,7 +104,7 @@
|
||||
<div class="indicator flex-none">
|
||||
<button
|
||||
class="btn btn-square btn-ghost h-9 w-9"
|
||||
on:click={() => confirmGithubUpdate(firmwareDownloadLink)}
|
||||
onclick={() => confirmGithubUpdate(firmwareDownloadLink)}
|
||||
>
|
||||
<span
|
||||
class="indicator-item indicator-top indicator-center badge badge-info badge-xs top-2 scale-75 lg:top-1"
|
||||
|
||||
@@ -4,30 +4,31 @@
|
||||
import { notifications } from '$lib/components/toasts/notifications';
|
||||
import { error, info, success, warning } from '../icons';
|
||||
|
||||
export let theme = {
|
||||
|
||||
/** @type {{theme?: any, icon?: any}} */
|
||||
let { theme = {
|
||||
error: 'alert-error',
|
||||
success: 'alert-success',
|
||||
warning: 'alert-warning',
|
||||
info: 'alert-info'
|
||||
};
|
||||
|
||||
export let icon = {
|
||||
}, icon = {
|
||||
error: error,
|
||||
success: success,
|
||||
warning: warning,
|
||||
info: info
|
||||
};
|
||||
} } = $props();
|
||||
</script>
|
||||
|
||||
<div class="toast toast-end mr-4 z-20">
|
||||
{#each $notifications as notification (notification.id)}
|
||||
{@const SvelteComponent = icon[notification.type]}
|
||||
<div
|
||||
animate:flip={{ duration: 400 }}
|
||||
class="alert animate-none {theme[notification.type]}"
|
||||
in:fly={{ y: 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>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
import { cubicOut } from "svelte/easing";
|
||||
import { slide } from "svelte/transition";
|
||||
|
||||
let chartElement: HTMLCanvasElement;
|
||||
let chartElement: HTMLCanvasElement = $state();
|
||||
let chart: Chart;
|
||||
|
||||
export let label
|
||||
export let data:number[]
|
||||
export let title
|
||||
interface Props {
|
||||
label: any;
|
||||
data: number[];
|
||||
title: any;
|
||||
}
|
||||
|
||||
let { label, data, title }: Props = $props();
|
||||
|
||||
Chart.register(...registerables);
|
||||
|
||||
@@ -94,6 +98,6 @@
|
||||
class="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<canvas bind:this={chartElement} />
|
||||
<canvas bind:this={chartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,12 +1,20 @@
|
||||
<script lang="ts">
|
||||
export let options: string[] = [];
|
||||
export let selectedOption: string = '';
|
||||
import { createBubbler } from 'svelte/legacy';
|
||||
|
||||
const bubble = createBubbler();
|
||||
interface Props {
|
||||
options?: string[];
|
||||
selectedOption?: string;
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
let { options = [], selectedOption = $bindable(''), ...rest }: Props = $props();
|
||||
</script>
|
||||
|
||||
<select
|
||||
bind:value={selectedOption}
|
||||
on:change
|
||||
class="select select-bordered select-sm lg:select-md max-w-xs {$$restProps.class || ''}"
|
||||
onchange={bubble('change')}
|
||||
class="select select-bordered select-sm lg:select-md max-w-xs {rest.class || ''}"
|
||||
>
|
||||
{#each options as option}
|
||||
<option value={option}>{option}</option>
|
||||
|
||||
Reference in New Issue
Block a user