🌌 Migrate app to svelte-5
This commit is contained in:
@@ -22,6 +22,11 @@
|
||||
useFeatureFlags
|
||||
} from '$lib/stores';
|
||||
import type { Analytics, DownloadOTA } from '$lib/types/models';
|
||||
interface Props {
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let { children }: Props = $props();
|
||||
|
||||
const features = useFeatureFlags();
|
||||
|
||||
@@ -82,7 +87,7 @@
|
||||
|
||||
const handleOAT = (data: DownloadOTA) => telemetry.setDownloadOTA(data);
|
||||
|
||||
let menuOpen = false;
|
||||
let menuOpen = $state(false);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -96,24 +101,25 @@
|
||||
<Statusbar />
|
||||
|
||||
<!-- Main page content here -->
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
<!-- Side Navigation -->
|
||||
<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)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modals>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
slot="backdrop"
|
||||
class="fixed inset-0 z-40 max-h-full max-w-full bg-black/20 backdrop-blur"
|
||||
transition:fade
|
||||
on:click={closeModal}
|
||||
/>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
{#snippet backdrop()}
|
||||
<div
|
||||
class="fixed inset-0 z-40 max-h-full max-w-full bg-black/20 backdrop-blur"
|
||||
transition:fade
|
||||
onclick={closeModal}
|
||||
></div>
|
||||
{/snippet}
|
||||
</Modals>
|
||||
|
||||
<Toast />
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
import { notifications } from '$lib/components/toasts/notifications';
|
||||
import Visualization from '$lib/components/Visualization.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
interface Props {
|
||||
data: PageData;
|
||||
}
|
||||
|
||||
let { data }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="hero bg-base-100 h-screen">
|
||||
@@ -17,7 +21,7 @@
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
href="/controller"
|
||||
on:click={() => notifications.success('You did it!', 1000)}>Begin</a
|
||||
onclick={() => notifications.success('You did it!', 1000)}>Begin</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,13 +12,17 @@
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
<WiFi slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
<span slot="title">Connection</span>
|
||||
{#snippet icon()}
|
||||
<WiFi class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<span >Connection</span>
|
||||
{/snippet}
|
||||
|
||||
<div class="flex">
|
||||
<label class="label w-32" for="server">Address:</label>
|
||||
<input class="input" bind:value={$location} />
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" on:click={update}>Update</button>
|
||||
<button class="btn btn-primary" onclick={update}>Update</button>
|
||||
</SettingsCard>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { imu } from '$lib/stores/imu';
|
||||
import type { IMU } from '$lib/types/models';
|
||||
|
||||
$: layout = $views.find(v => v.name === $selectedView)!;
|
||||
let layout = $derived($views.find(v => v.name === $selectedView)!);
|
||||
|
||||
onMount(() => {
|
||||
socket.on('imu', (data: IMU) => {
|
||||
|
||||
@@ -86,9 +86,9 @@
|
||||
|
||||
<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 id="left" class="flex w-60 items-center justify-end" />
|
||||
<div class="flex-1" />
|
||||
<div id="right" class="flex w-60 items-center" />
|
||||
<div id="left" class="flex w-60 items-center justify-end"></div>
|
||||
<div class="flex-1"></div>
|
||||
<div id="right" class="flex w-60 items-center"></div>
|
||||
</div>
|
||||
<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">
|
||||
@@ -112,7 +112,7 @@
|
||||
<button
|
||||
class="btn join-item"
|
||||
class:btn-primary={$mode === modes.indexOf(modeValue)}
|
||||
on:click={() => changeMode(modeValue)}
|
||||
onclick={() => changeMode(modeValue)}
|
||||
>
|
||||
{capitalize(modeValue)}
|
||||
</button>
|
||||
@@ -128,13 +128,13 @@
|
||||
name="s1"
|
||||
min="0"
|
||||
max="100"
|
||||
on:input={(e) => handleRange(e, 's1')}
|
||||
oninput={(e) => handleRange(e, 's1')}
|
||||
class="range range-sm range-primary"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
{/if}
|
||||
@@ -142,4 +142,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svelte:window on:keyup={handleKeyup} on:keydown={handleKeyup} />
|
||||
<svelte:window onkeyup={handleKeyup} onkeydown={handleKeyup} />
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
<Camera slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
<span slot="title">Camera</span>
|
||||
{#snippet icon()}
|
||||
<Camera class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<span >Camera</span>
|
||||
{/snippet}
|
||||
<Stream />
|
||||
<CameraSetting />
|
||||
</SettingsCard>
|
||||
@@ -2,7 +2,7 @@
|
||||
import { api } from '$lib/api';
|
||||
import Spinner from '$lib/components/Spinner.svelte';
|
||||
import type { CameraSettings } from '$lib/types/models';
|
||||
let settings:CameraSettings
|
||||
let settings:CameraSettings = $state()
|
||||
|
||||
const getCameraSettings = async () => {
|
||||
const result = await api.get<CameraSettings>('/api/camera/settings')
|
||||
@@ -27,7 +27,7 @@
|
||||
<Spinner />
|
||||
{:then _}
|
||||
<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">
|
||||
Brightness {settings.brightness}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{ address: 119, part_number: 'BMP085', name: 'Temp/Barometric' }
|
||||
];
|
||||
|
||||
let active_devices: I2CDevice[] = [];
|
||||
let active_devices: I2CDevice[] = $state([]);
|
||||
|
||||
onMount(() => {
|
||||
socket.on('i2cScan', handleScan);
|
||||
@@ -38,8 +38,12 @@
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
<Connection slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
<span slot="title">I<sup>2</sup>C</span>
|
||||
{#snippet icon()}
|
||||
<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">
|
||||
{#if active_devices.length === 0}
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
|
||||
Chart.register(...registerables);
|
||||
|
||||
let angleChartElement: HTMLCanvasElement;
|
||||
let angleChartElement: HTMLCanvasElement = $state();
|
||||
let angleChart: Chart;
|
||||
|
||||
let tempChartElement: HTMLCanvasElement;
|
||||
let tempChartElement: HTMLCanvasElement = $state();
|
||||
let tempChart: Chart;
|
||||
|
||||
let altitudeChartElement: HTMLCanvasElement;
|
||||
let altitudeChartElement: HTMLCanvasElement = $state();
|
||||
let altitudeChart: Chart;
|
||||
|
||||
const handleImu = (data: IMU) => {
|
||||
@@ -272,15 +272,19 @@
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
<Rotate3d slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
<span slot="title">IMU</span>
|
||||
{#snippet icon()}
|
||||
<Rotate3d class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<span >IMU</span>
|
||||
{/snippet}
|
||||
{#if $features.imu}
|
||||
<div class="w-full overflow-x-auto">
|
||||
<div
|
||||
class="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<canvas bind:this={angleChartElement} />
|
||||
<canvas bind:this={angleChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -290,7 +294,7 @@
|
||||
class="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<canvas bind:this={tempChartElement} />
|
||||
<canvas bind:this={tempChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full overflow-x-auto">
|
||||
@@ -298,7 +302,7 @@
|
||||
class="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<canvas bind:this={altitudeChartElement} />
|
||||
<canvas bind:this={altitudeChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { api } from '$lib/api';
|
||||
import { onMount } from 'svelte';
|
||||
export let data = {
|
||||
interface Props {
|
||||
data?: any;
|
||||
}
|
||||
|
||||
let { data = $bindable({
|
||||
servos: []
|
||||
};
|
||||
}) }: Props = $props();
|
||||
|
||||
const updateValue = (event, index, key) => {
|
||||
data.servos[index][key] = event.target.innerText;
|
||||
@@ -36,29 +40,29 @@
|
||||
<tr>
|
||||
<td
|
||||
contenteditable="true"
|
||||
on:blur={syncConfig}
|
||||
on:input={event => updateValue(event, index, 'center_pwm')}
|
||||
onblur={syncConfig}
|
||||
oninput={event => updateValue(event, index, 'center_pwm')}
|
||||
>
|
||||
{servo.center_pwm}
|
||||
</td>
|
||||
<td
|
||||
contenteditable="true"
|
||||
on:blur={syncConfig}
|
||||
on:input={event => updateValue(event, index, 'center_angle')}
|
||||
onblur={syncConfig}
|
||||
oninput={event => updateValue(event, index, 'center_angle')}
|
||||
>
|
||||
{servo.center_angle}
|
||||
</td>
|
||||
<td
|
||||
contenteditable="true"
|
||||
on:blur={syncConfig}
|
||||
on:input={event => updateValue(event, index, 'direction')}
|
||||
onblur={syncConfig}
|
||||
oninput={event => updateValue(event, index, 'direction')}
|
||||
>
|
||||
{servo.direction}
|
||||
</td>
|
||||
<td
|
||||
contenteditable="true"
|
||||
on:blur={syncConfig}
|
||||
on:input={event => updateValue(event, index, 'conversion')}
|
||||
onblur={syncConfig}
|
||||
oninput={event => updateValue(event, index, 'conversion')}
|
||||
>
|
||||
{servo.conversion}
|
||||
</td>
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
let isLoading = false;
|
||||
|
||||
let active = false;
|
||||
let active = $state(false);
|
||||
|
||||
let servoId = 0;
|
||||
let servoId = $state(0);
|
||||
|
||||
const throttler = new Throttler();
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
socket.sendEvent('servoState', { active: 0 });
|
||||
};
|
||||
|
||||
let pwm = 306;
|
||||
let pwm = $state(306);
|
||||
|
||||
const updatePWM = () => {
|
||||
throttler.throttle(() => {
|
||||
@@ -39,15 +39,19 @@
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
<MotorOutline slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
<span slot="title">Servo</span>
|
||||
{#snippet icon()}
|
||||
<MotorOutline class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<span >Servo</span>
|
||||
{/snippet}
|
||||
{pwm}
|
||||
<input
|
||||
type="range"
|
||||
min="80"
|
||||
max="600"
|
||||
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"
|
||||
/>
|
||||
|
||||
@@ -63,7 +67,7 @@
|
||||
type="checkbox"
|
||||
class="toggle"
|
||||
bind:checked={active}
|
||||
on:change={active ? activateServo : deactivateServo}
|
||||
onchange={active ? activateServo : deactivateServo}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
import { FileIcon } from '$lib/components/icons';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let name;
|
||||
/** @type {{name: any}} */
|
||||
let { name } = $props();
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -11,8 +12,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-interactive-supports-focus -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<span role="button" class="flex pl-4 gap-2 items-center" on:click={updateSelected}>
|
||||
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<span role="button" class="flex pl-4 gap-2 items-center" onclick={updateSelected}>
|
||||
<FileIcon/>{name}
|
||||
</span>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import type { Directory } from "$lib/types/models";
|
||||
import { FolderIcon } from "$lib/components/icons";
|
||||
|
||||
let filename = '';
|
||||
let filename = $state('');
|
||||
|
||||
const getFiles = async () => {
|
||||
const result = await api.get<Directory>('/api/files')
|
||||
@@ -38,8 +38,12 @@
|
||||
}
|
||||
</script>
|
||||
<SettingsCard collapsible={false}>
|
||||
<FolderIcon slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
<span slot="title">File System</span>
|
||||
{#snippet icon()}
|
||||
<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">
|
||||
{#await getFiles()}
|
||||
<Spinner />
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Folder from './Folder.svelte';
|
||||
import File from './File.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { FolderIcon, FolderOpenOutline } from '$lib/components/icons';
|
||||
|
||||
export let expanded = false;
|
||||
export let name;
|
||||
export let files;
|
||||
interface Props {
|
||||
expanded?: boolean;
|
||||
name: any;
|
||||
files: any;
|
||||
}
|
||||
|
||||
let { expanded = $bindable(false), name, files }: Props = $props();
|
||||
|
||||
function toggle() {
|
||||
expanded = !expanded;
|
||||
@@ -18,7 +23,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<button class="flex pl-2" on:click={toggle}>
|
||||
<button class="flex pl-2" onclick={toggle}>
|
||||
{#if expanded}
|
||||
<FolderOpenOutline class="w-6 h-6" />
|
||||
{:else}
|
||||
@@ -32,7 +37,7 @@
|
||||
{#each Object.entries(files) as [name, content]}
|
||||
<li class="p-1">
|
||||
{#if typeof content == 'object'}
|
||||
<svelte:self {name} files={content} on:selected={updateSelected} />
|
||||
<Folder {name} files={content} on:selected={updateSelected} />
|
||||
{:else}
|
||||
<File {name} on:selected={updateSelected}/>
|
||||
{/if}
|
||||
|
||||
@@ -12,16 +12,16 @@
|
||||
|
||||
Chart.register(...registerables);
|
||||
|
||||
let cpuChartElement: HTMLCanvasElement;
|
||||
let cpuChartElement: HTMLCanvasElement = $state();
|
||||
let cpuChart: Chart;
|
||||
|
||||
let heapChartElement: HTMLCanvasElement;
|
||||
let heapChartElement: HTMLCanvasElement = $state();
|
||||
let heapChart: Chart;
|
||||
|
||||
let filesystemChartElement: HTMLCanvasElement;
|
||||
let filesystemChartElement: HTMLCanvasElement = $state();
|
||||
let filesystemChart: Chart;
|
||||
|
||||
let temperatureChartElement: HTMLCanvasElement;
|
||||
let temperatureChartElement: HTMLCanvasElement = $state();
|
||||
let temperatureChart: Chart;
|
||||
|
||||
onMount(() => {
|
||||
@@ -330,15 +330,19 @@
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
<Metrics slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
<span slot="title">System Metrics</span>
|
||||
{#snippet icon()}
|
||||
<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="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<canvas bind:this={cpuChartElement} />
|
||||
<canvas bind:this={cpuChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -347,7 +351,7 @@
|
||||
class="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<canvas bind:this={heapChartElement} />
|
||||
<canvas bind:this={heapChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full overflow-x-auto">
|
||||
@@ -355,7 +359,7 @@
|
||||
class="flex w-full flex-col space-y-1 h-52"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<canvas bind:this={filesystemChartElement} />
|
||||
<canvas bind:this={filesystemChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full overflow-x-auto">
|
||||
@@ -363,7 +367,7 @@
|
||||
class="flex w-full flex-col space-y-1 h-52"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<canvas bind:this={temperatureChartElement} />
|
||||
<canvas bind:this={temperatureChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
const features = useFeatureFlags();
|
||||
|
||||
let systemInformation: SystemInformation;
|
||||
let systemInformation: SystemInformation = $state();
|
||||
|
||||
async function getSystemStatus() {
|
||||
const result = await api.get<SystemInformation>('/api/system/status');
|
||||
@@ -106,8 +106,12 @@
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
<Health slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
<span slot="title">System Status</span>
|
||||
{#snippet icon()}
|
||||
<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">
|
||||
{#await getSystemStatus()}
|
||||
@@ -309,14 +313,14 @@
|
||||
|
||||
<div class="mt-4 flex flex-wrap justify-end gap-2">
|
||||
{#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>
|
||||
</button>
|
||||
{/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>
|
||||
</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>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -81,8 +81,12 @@
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
<Github slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end rounded-full" />
|
||||
<span slot="title">Github Firmware Manager</span>
|
||||
{#snippet icon()}
|
||||
<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()}
|
||||
<Spinner />
|
||||
{:then githubReleases}
|
||||
@@ -136,7 +140,7 @@
|
||||
{#if compareVersions($features.firmware_version, release.tag_name) != 0}
|
||||
<button
|
||||
class="btn btn-ghost btn-circle btn-sm"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
confirmGithubUpdate(release.assets);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { api } from '$lib/api';
|
||||
import { Cancel, OTA, Warning } from '$lib/components/icons';
|
||||
|
||||
let files: FileList;
|
||||
let files: FileList = $state();
|
||||
|
||||
async function uploadBIN() {
|
||||
const formData = new FormData();
|
||||
@@ -32,8 +32,12 @@
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
<OTA slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end rounded-full" />
|
||||
<span slot="title">Upload Firmware</span>
|
||||
{#snippet icon()}
|
||||
<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">
|
||||
<Warning class="h-6 w-6 flex-shrink-0" />
|
||||
<span
|
||||
@@ -48,6 +52,6 @@
|
||||
class="file-input file-input-bordered file-input-secondary mt-4 w-full"
|
||||
bind:files
|
||||
accept=".bin,.md5"
|
||||
on:change={confirmBinUpload}
|
||||
onchange={confirmBinUpload}
|
||||
/>
|
||||
</SettingsCard>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
@@ -13,10 +15,10 @@
|
||||
|
||||
const features = useFeatureFlags();
|
||||
|
||||
let apSettings: ApSettings;
|
||||
let apStatus: ApStatus;
|
||||
let apSettings: ApSettings = $state();
|
||||
let apStatus: ApStatus = $state();
|
||||
|
||||
let formField: any;
|
||||
let formField: any = $state();
|
||||
|
||||
async function getAPStatus() {
|
||||
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' }
|
||||
];
|
||||
|
||||
let formErrors = {
|
||||
let formErrors = $state({
|
||||
ssid: false,
|
||||
channel: false,
|
||||
max_clients: false,
|
||||
local_ip: false,
|
||||
gateway_ip: false,
|
||||
subnet_mask: false
|
||||
};
|
||||
});
|
||||
|
||||
async function postAPSettings(data: ApSettings) {
|
||||
const result = await api.post<ApSettings>('/api/wifi/ap/settings', data);
|
||||
@@ -152,8 +154,12 @@
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
<AP slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
<span slot="title">Access Point</span>
|
||||
{#snippet icon()}
|
||||
<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">
|
||||
{#await getAPStatus()}
|
||||
<Spinner />
|
||||
@@ -234,7 +240,7 @@
|
||||
>
|
||||
<form
|
||||
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
|
||||
bind:this={formField}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<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';
|
||||
@@ -10,8 +13,12 @@
|
||||
import { AP, Network, Reload, Cancel } from '$lib/components/icons';
|
||||
|
||||
// provided by <Modals />
|
||||
export let isOpen: boolean;
|
||||
export let storeNetwork: any;
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
storeNetwork: any;
|
||||
}
|
||||
|
||||
let { isOpen, storeNetwork }: Props = $props();
|
||||
|
||||
const encryptionType = [
|
||||
'Open',
|
||||
@@ -25,9 +32,9 @@
|
||||
'WAPI PSK'
|
||||
];
|
||||
|
||||
let listOfNetworks: NetworkItem[] = [];
|
||||
let listOfNetworks: NetworkItem[] = $state([]);
|
||||
|
||||
let scanActive = false;
|
||||
let scanActive = $state(false);
|
||||
|
||||
let pollingId: number;
|
||||
|
||||
@@ -73,15 +80,15 @@
|
||||
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">Scan Networks</h2>
|
||||
<div class="divider my-2" />
|
||||
<div class="divider my-2"></div>
|
||||
<div class="overflow-y-auto">
|
||||
{#if scanActive}<div
|
||||
class="bg-base-100 flex flex-col items-center justify-center p-6"
|
||||
@@ -93,10 +100,10 @@
|
||||
<ul class="menu">
|
||||
{#each listOfNetworks as network, i}
|
||||
<li>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
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);
|
||||
}}
|
||||
role="button"
|
||||
@@ -114,7 +121,7 @@
|
||||
Channel: {network.channel}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div class="flex-grow"></div>
|
||||
<RssiIndicator showDBm={true} rssi={network.rssi} />
|
||||
</div>
|
||||
</li>
|
||||
@@ -122,19 +129,19 @@
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="divider my-2" />
|
||||
<div class="divider my-2"></div>
|
||||
<div class="flex flex-wrap justify-end gap-2">
|
||||
<button
|
||||
class="btn btn-primary inline-flex flex-none items-center"
|
||||
disabled={scanActive}
|
||||
on:click={scanNetworks}
|
||||
onclick={scanNetworks}
|
||||
><Reload class="mr-2 h-5 w-5" /><span>Scan again</span></button
|
||||
>
|
||||
|
||||
<div class="flex-grow" />
|
||||
<div class="flex-grow"></div>
|
||||
<button
|
||||
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>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
<svelte:options immutable={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { openModal, closeModal } from 'svelte-modals';
|
||||
@@ -38,7 +36,7 @@
|
||||
|
||||
const features = useFeatureFlags();
|
||||
|
||||
let networkEditable: KnownNetworkItem = {
|
||||
let networkEditable: KnownNetworkItem = $state({
|
||||
ssid: '',
|
||||
password: '',
|
||||
static_ip_config: false,
|
||||
@@ -47,32 +45,32 @@
|
||||
gateway_ip: undefined,
|
||||
dns_ip_1: undefined,
|
||||
dns_ip_2: undefined
|
||||
};
|
||||
});
|
||||
|
||||
let static_ip_config = false;
|
||||
let static_ip_config = $state(false);
|
||||
|
||||
let newNetwork: boolean = true;
|
||||
let showNetworkEditor: boolean = false;
|
||||
let newNetwork: boolean = $state(true);
|
||||
let showNetworkEditor: boolean = $state(false);
|
||||
|
||||
let wifiStatus: WifiStatus;
|
||||
let wifiSettings: WifiSettings;
|
||||
let wifiStatus: WifiStatus = $state();
|
||||
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,
|
||||
local_ip: false,
|
||||
gateway_ip: false,
|
||||
subnet_mask: false,
|
||||
dns_1: false,
|
||||
dns_2: false
|
||||
};
|
||||
});
|
||||
|
||||
let formErrorhostname = false;
|
||||
let formErrorhostname = $state(false);
|
||||
|
||||
async function getWifiStatus() {
|
||||
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;
|
||||
|
||||
// Validate SSID
|
||||
@@ -286,8 +285,12 @@
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
<Router slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
<span slot="title">WiFi Connection</span>
|
||||
{#snippet icon()}
|
||||
<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">
|
||||
{#await getWifiStatus()}
|
||||
<Spinner />
|
||||
@@ -350,10 +353,10 @@
|
||||
{wifiStatus.rssi} dBm
|
||||
</div>
|
||||
</div>
|
||||
<div class="grow" />
|
||||
<div class="grow"></div>
|
||||
<button
|
||||
class="btn btn-circle btn-ghost btn-sm modal-button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
showWifiDetails = !showWifiDetails;
|
||||
}}
|
||||
>
|
||||
@@ -451,7 +454,7 @@
|
||||
<div class="relative w-full overflow-visible">
|
||||
<button
|
||||
class="btn btn-primary text-primary-content btn-md absolute -top-14 right-16"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
if (checkNetworkList()) {
|
||||
addNetwork();
|
||||
showNetworkEditor = true;
|
||||
@@ -462,7 +465,7 @@
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary text-primary-content btn-md absolute -top-14 right-0"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
if (checkNetworkList()) {
|
||||
scanForNetworks();
|
||||
showNetworkEditor = true;
|
||||
@@ -482,51 +485,49 @@
|
||||
itemSize={60}
|
||||
itemCount={dndNetworkList.length}
|
||||
on:drop={onDrop}
|
||||
let:index
|
||||
>
|
||||
<!-- 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">
|
||||
<Router class="text-primary-content h-auto w-full scale-75" />
|
||||
{#snippet children({ index })}
|
||||
<!-- 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">
|
||||
<Router class="text-primary-content h-auto w-full scale-75" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold">{dndNetworkList[index].ssid}</div>
|
||||
</div>
|
||||
<div class="flex-grow"></div>
|
||||
<div class="space-x-0 px-0 mx-0">
|
||||
<button
|
||||
class="btn btn-ghost btn-sm"
|
||||
onclick={() => {
|
||||
handleEdit(index);
|
||||
}}
|
||||
>
|
||||
<Edit class="h-6 w-6" /></button
|
||||
>
|
||||
<button
|
||||
class="btn btn-ghost btn-sm"
|
||||
onclick={() => {
|
||||
confirmDelete(index);
|
||||
}}
|
||||
>
|
||||
<Delete class="text-error h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold">{dndNetworkList[index].ssid}</div>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div class="space-x-0 px-0 mx-0">
|
||||
<button
|
||||
class="btn btn-ghost btn-sm"
|
||||
on:click={() => {
|
||||
handleEdit(index);
|
||||
}}
|
||||
>
|
||||
<Edit class="h-6 w-6" /></button
|
||||
>
|
||||
<button
|
||||
class="btn btn-ghost btn-sm"
|
||||
on:click={() => {
|
||||
confirmDelete(index);
|
||||
}}
|
||||
>
|
||||
<Delete class="text-error h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</DragDropList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider mb-0" />
|
||||
<div class="divider mb-0"></div>
|
||||
<div
|
||||
class="flex flex-col gap-2 p-0"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<form
|
||||
class=""
|
||||
on:submit|preventDefault={validateWiFiForm}
|
||||
novalidate
|
||||
bind:this={formField}
|
||||
>
|
||||
<form class="" onsubmit={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>
|
||||
<label class="label" for="channel">
|
||||
@@ -566,7 +567,7 @@
|
||||
</div>
|
||||
|
||||
{#if showNetworkEditor}
|
||||
<div class="divider my-0" />
|
||||
<div class="divider my-0"></div>
|
||||
<div
|
||||
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 }}
|
||||
@@ -747,12 +748,12 @@
|
||||
{/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">
|
||||
<button class="btn btn-primary" type="submit" disabled={!showNetworkEditor}>
|
||||
{newNetwork ? 'Add Network' : 'Update Network'}
|
||||
</button>
|
||||
<button class="btn btn-primary" type="button" on:click={validateHostName}>
|
||||
<button class="btn btn-primary" type="button" onclick={validateHostName}>
|
||||
Apply Settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user