Add Web Updater functionality, improved layout for small displays

This commit is contained in:
Djuri 2024-06-09 00:11:27 +02:00
parent 4f15eee72b
commit e53b487236
11 changed files with 401 additions and 92 deletions

View file

@ -1,5 +1,6 @@
<script lang="ts">
import { PUBLIC_BASE_URL } from '$lib/config';
import { screenSize, updateScreenSize } from '$lib/screen';
import { Container, Row, Toast, ToastBody } from 'sveltestrap';
@ -13,6 +14,12 @@
fgColor: '0'
});
let uiSettings = writable({
inputSize: 'sm',
selectClass: '',
btnSize: 'lg'
});
let status = writable({
data: ['L', 'O', 'A', 'D', 'I', 'N', 'G'],
espFreeHeap: 0,
@ -61,8 +68,43 @@
let dataObj = JSON.parse(e.data);
status.set(dataObj);
});
function handleResize() {
updateScreenSize();
}
// Add an event listener to update the screen size when the window is resized
window.addEventListener('resize', handleResize);
// Call the function initially to set the initial screen size
updateScreenSize();
// Cleanup function to remove the event listener when the component is destroyed
return () => {
window.removeEventListener('resize', handleResize);
};
});
$: {
const lgBreakpoint = parseInt(
getComputedStyle(document.documentElement).getPropertyValue('--bs-breakpoint-lg')
);
if ($screenSize >= lgBreakpoint) {
uiSettings.set({
inputSize: 'sm',
selectClass: 'form-select-sm',
btnSize: 'sm'
});
} else {
uiSettings.set({
inputSize: 'lg',
selectClass: 'form-select-lg',
btnSize: 'xl'
});
}
}
let toastIsOpen = false;
let toastColor = 'success';
let toastBody = '';
@ -79,10 +121,15 @@
</svelte:head>
<Container fluid>
<Row>
<Control bind:settings bind:status></Control>
<Row cols={{ lg: 3, sm: 1 }}>
<Control bind:settings bind:uiSettings bind:status></Control>
<Status bind:settings bind:status></Status>
<Settings bind:settings on:showToast={showToast} on:formReset={fetchSettingsData}></Settings>
<Settings
bind:settings
bind:uiSettings
on:showToast={showToast}
on:formReset={fetchSettingsData}
></Settings>
</Row>
</Container>
<div class="position-fixed bottom-0 end-0 p-2">

View file

@ -15,8 +15,11 @@
Label,
Row
} from 'sveltestrap';
import FirmwareUpdater from './FirmwareUpdater.svelte';
export let settings = {};
export let uiSettings;
export let customText: string;
export let status: Writable<{ leds: [] }>;
let ledStatus = [];
@ -102,25 +105,35 @@
<CardBody>
<Form>
<Row>
<Label md={4} for="customText">{$_('section.control.text')}</Label>
<Label md={4} for="customText" size={$uiSettings.inputSize}
>{$_('section.control.text')}</Label
>
<Col md="8">
<Input
type="text"
id="customText"
bind:value={customText}
bsSize="sm"
bsSize="$uiSettings.inputSize"
maxLength={$settings.numScreens}
/>
</Col>
</Row>
<Button color="primary" on:click={setCustomText}>{$_('section.control.showText')}</Button>
<Row>
<Col class="d-flex justify-content-end">
<Button color="primary" on:click={setCustomText} bsSize={$uiSettings.btnSize}
>{$_('section.control.showText')}</Button
>
</Col>
</Row>
</Form>
<hr />
{#if !$settings.disableLeds}
<h3>LEDs</h3>
<Form>
<Row>
<Label md={4} for="ledColorPicker" size="sm">{$_('section.control.ledColor')}</Label>
<Label md={4} for="ledColorPicker" size={$uiSettings.inputSize}
>{$_('section.control.ledColor')}</Label
>
<Col md="8">
<Row class="justify-content-between">
{#if ledStatus}
@ -137,51 +150,84 @@
{/each}
{/if}
</Row>
<Row class="justify-content-between">
<Col>
<Row>
<Col class="d-flex justify-content-end">
<Input
bind:checked={keepLedsSameColor}
type="switch"
class="mx-auto"
label={$_('sections.control.keepSameColor')}
bsSize={$uiSettings.inputSize}
/>
</Col>
</Row>
</Col>
</Row>
<Button color="secondary" id="turnOffLedsBtn" on:click={turnOffLeds}
>{$_('section.control.turnOff')}</Button
>
<Button color="primary" on:click={setLEDcolor}>{$_('section.control.setColor')}</Button>
<Row>
<Col class="d-flex justify-content-end">
<Button
color="secondary"
id="turnOffLedsBtn"
on:click={turnOffLeds}
bsSize={$uiSettings.inputSize}>{$_('section.control.turnOff')}</Button
>
<div class="mx-2"></div>
<Button color="primary" on:click={setLEDcolor} bsSize={$uiSettings.inputSize}
>{$_('section.control.setColor')}</Button
>
</Col>
</Row>
</Form>
<hr />
{/if}
{#if $settings.hasFrontlight}
<h3>{$_('section.control.frontlight')}</h3>
<Button color="secondary" id="turnOffFrontlightBtn" on:click={turnOffFrontlight}
>{$_('section.control.turnOff')}</Button
>
<Button color="primary" on:click={turnOnFrontlight}>{$_('section.control.turnOn')}</Button>
<Button color="success" id="flashFrontlight" on:click={flashFrontlight}
>{$_('section.control.flashFrontlight')}</Button
>
<Row class="d-flex justify-content-between justify-content-md-end">
<Col md="auto" class="">
<Button color="secondary" id="turnOffFrontlightBtn" on:click={turnOffFrontlight}
>{$_('section.control.turnOff')}</Button
>
</Col><Col md="auto" class="">
<Button color="primary" on:click={turnOnFrontlight}
>{$_('section.control.turnOn')}</Button
>
</Col><Col md="auto" class="">
<Button color="success" id="flashFrontlight" on:click={flashFrontlight}
>{$_('section.control.flashFrontlight')}</Button
>
</Col>
</Row>
<hr />
{/if}
<h3>{$_('section.control.systemInfo')}</h3>
<ul class="small system_info">
<li>{$_('section.control.version')}: {$settings.gitRev}</li>
<li>
{$_('section.control.buildTime')}: {new Date(
$settings.lastBuildTime * 1000
).toLocaleString()}
</li>
<li>IP: {$settings.ip}</li>
<li>HW revision: {$settings.hwRev}</li>
<li>{$_('section.control.fwCommit')}: {$settings.gitRev}</li>
<li>WebUI commit: {$settings.fsRev}</li>
<li>{$_('section.control.hostname')}: {$settings.hostname}</li>
</ul>
<Button color="danger" id="restartBtn" on:click={restartClock}>{$_('button.restart')}</Button>
<Button color="warning" id="forceFullRefresh" on:click={forceFullRefresh}
>{$_('button.forceFullRefresh')}</Button
>
<Row>
<Col class="d-flex justify-content-end">
<Button color="danger" id="restartBtn" on:click={restartClock}
>{$_('button.restart')}</Button
>
<div class="mx-2"></div>
<Button color="warning" id="forceFullRefresh" on:click={forceFullRefresh}
>{$_('button.forceFullRefresh')}</Button
>
</Col>
</Row>
{#if $settings.otaEnabled}
<hr />
<h3>{$_('section.control.firmwareUpdate')}</h3>
<FirmwareUpdater bind:settings />
{/if}
</CardBody>
</Card>
</Col>

View file

@ -0,0 +1,155 @@
<script lang="ts">
import { PUBLIC_BASE_URL } from '$lib/config';
import { _ } from 'svelte-i18n';
import { writable } from 'svelte/store';
import { Progress, Alert, Button } from 'sveltestrap';
export let settings = { hwRev: '' };
const countdown = writable(10);
let firmwareUploadFile: File | null = null;
let firmwareWebUiFile: File | null = null;
let firmwareUploadProgress = 0;
let firmwareUploadSuccess = false;
let firmwareUploadError = false;
const handleFileChange = (event: Event, setFile: (file: File) => void) => {
const target = event.target as HTMLInputElement;
if (target.files && target.files.length > 0) {
setFile(target.files[0]);
}
};
function startCountdownToReload(duration: number) {
let timeRemaining = duration;
const interval = setInterval(() => {
timeRemaining -= 1;
countdown.set(timeRemaining);
if (timeRemaining <= 0) {
clearInterval(interval);
location.reload();
}
}, 1000); // Update every second
}
const uploadFile = async (file: File | null, endpoint: string) => {
if (!file) return;
const formData = new FormData();
formData.append('file', file);
firmwareUploadSuccess = false;
firmwareUploadError = false;
try {
const xhr = new XMLHttpRequest();
xhr.open('POST', endpoint);
xhr.upload.onprogress = (event: ProgressEvent) => {
if (event.lengthComputable) {
firmwareUploadProgress = Math.round((event.loaded * 100) / event.total);
}
};
xhr.onload = () => {
if (xhr.status === 200 && xhr.responseText != 'FAIL') {
firmwareUploadSuccess = true;
startCountdownToReload(10);
} else {
firmwareUploadError = true;
}
};
xhr.onerror = () => {
firmwareUploadError = true;
};
xhr.send(formData);
} catch (error) {
firmwareUploadError = true;
console.error(error);
}
};
const uploadFirmwareFile = () => {
uploadFile(firmwareUploadFile, `${PUBLIC_BASE_URL}/upload/firmware`);
};
const uploadWebUiFile = () => {
uploadFile(firmwareWebUiFile, `${PUBLIC_BASE_URL}/upload/webui`);
};
const getFirmwareBinaryName = () => {
let binaryFilename = '';
switch ($settings.hwRev) {
case 'REV_B_EPD_2_13':
binaryFilename = 'btclock_rev_b_213epd_firmware.bin';
break;
case 'REV_A_EPD_2_13':
binaryFilename = 'lolin_s3_mini_213epd_firmware.bin';
break;
case 'REV_A_EPD_2_9':
binaryFilename = 'lolin_s3_mini_29epd_firmware.bin';
break;
default:
binaryFilename = 'Unsupported hardware, unable to determine firmware binary filename';
}
return binaryFilename;
};
</script>
<section class="row row-cols-lg-auto align-items-end">
<div class="col-12">
<label for="firmwareFile" class="form-label">Firmware file ({getFirmwareBinaryName()})</label>
<input
type="file"
id="firmwareFile"
on:change={(e) => handleFileChange(e, (file) => (firmwareUploadFile = file))}
name="update"
class="form-control"
accept=".bin"
/>
</div>
<div class="flex-fill">
<Button block on:click={uploadFirmwareFile} color="primary" disabled={!firmwareUploadFile}
>Update firmware</Button
>
</div>
<div class="col mt-2">
<label for="webuiFile" class="form-label">WebUI file (littlefs.bin)</label>
<input
type="file"
id="webuiFile"
name="update"
class="form-control"
placeholder="littlefs.bin"
on:change={(e) => handleFileChange(e, (file) => (firmwareWebUiFile = file))}
accept=".bin"
/>
</div>
<div class="flex-fill">
<Button block on:click={uploadWebUiFile} color="secondary" disabled={!firmwareWebUiFile}
>Update WebUI</Button
>
</div>
</section>
{#if firmwareUploadProgress > 0}
<Progress striped value={firmwareUploadProgress} class="progress" id="firmwareUploadProgress"
>{$_('section.firmwareUpdater.uploading')}... {firmwareUploadProgress}%</Progress
>
{/if}
{#if firmwareUploadSuccess}
<Alert color="success" class="firmwareUploadStatusAlert"
>{$_('section.firmwareUpdater.fileUploadSuccess', { values: { countdown: $countdown } })}
</Alert>
{/if}
{#if firmwareUploadError}
<Alert color="danger" class="firmwareUploadStatusAlert"
>{$_('section.firmwareUpdater.fileUploadFailed')}</Alert
>
{/if}
<small
>⚠️ <strong>{$_('warning')}</strong>: {$_('section.firmwareUpdater.firmwareUpdateText')}</small
>

View file

@ -20,6 +20,7 @@
} from 'sveltestrap';
export let settings;
export let uiSettings;
const wifiTxPowerMap = new Map<string, number>([
['Default', 80],
@ -89,7 +90,7 @@
<CardBody>
<Form on:submit={onSave}>
<Row>
<Label md={6} for="fgColor" size="sm"
<Label md={6} for="fgColor" size={$uiSettings.inputSize}
>{$_('section.settings.textColor', { default: 'Text color' })}</Label
>
<Col md="6">
@ -98,8 +99,8 @@
bind:value={$settings.fgColor}
name="select"
id="fgColor"
bsSize="sm"
class="form-select-sm"
bsSize={$uiSettings.inputSize}
class={$uiSettings.selectClass}
>
<option value="0">{$_('colors.black')}</option>
<option value="65535">{$_('colors.white')}</option>
@ -107,15 +108,17 @@
</Col>
</Row>
<Row>
<Label md={6} for="bgColor" size="sm">{$_('section.settings.backgroundColor')}</Label>
<Label md={6} for="bgColor" size={$uiSettings.inputSize}
>{$_('section.settings.backgroundColor')}</Label
>
<Col md="6">
<Input
type="select"
bind:value={$settings.bgColor}
name="select"
id="bgColor"
bsSize="sm"
class="form-select-sm"
bsSize={$uiSettings.inputSize}
class={$uiSettings.selectClass}
>
<option value="0">{$_('colors.black')}</option>
<option value="65535">{$_('colors.white')}</option>
@ -123,9 +126,11 @@
</Col>
</Row>
<Row>
<Label md={6} for="timePerScreen" size="sm">{$_('section.settings.timePerScreen')}</Label>
<Label md={6} for="timePerScreen" size={$uiSettings.inputSize}
>{$_('section.settings.timePerScreen')}</Label
>
<Col md="6">
<InputGroup size="sm">
<InputGroup size={$uiSettings.inputSize}>
<Input
type="number"
id="timePerScreen"
@ -138,11 +143,11 @@
</Col>
</Row>
<Row>
<Label md={6} for="fullRefreshMin" size="sm"
<Label md={6} for="fullRefreshMin" size={$uiSettings.inputSize}
>{$_('section.settings.fullRefreshEvery')}</Label
>
<Col md="6">
<InputGroup size="sm">
<InputGroup size={$uiSettings.inputSize}>
<Input
type="number"
id="fullRefreshMin"
@ -155,11 +160,11 @@
</Col>
</Row>
<Row>
<Label md={6} for="minSecPriceUpd" size="sm"
<Label md={6} for="minSecPriceUpd" size={$uiSettings.inputSize}
>{$_('section.settings.timeBetweenPriceUpdates')}</Label
>
<Col md="6">
<InputGroup size="sm">
<InputGroup size={$uiSettings.inputSize}>
<Input
type="number"
id="minSecPriceUpd"
@ -173,9 +178,11 @@
</Col>
</Row>
<Row>
<Label md={6} for="tzOffset" size="sm">{$_('section.settings.timezoneOffset')}</Label>
<Label md={6} for="tzOffset" size={$uiSettings.inputSize}
>{$_('section.settings.timezoneOffset')}</Label
>
<Col md="6">
<InputGroup size="sm">
<InputGroup size={$uiSettings.inputSize}>
<Input
type="number"
step="1"
@ -189,7 +196,9 @@
</Col>
</Row>
<Row>
<Label md={6} for="ledBrightness" size="sm">{$_('section.settings.ledBrightness')}</Label>
<Label md={6} for="ledBrightness" size={$uiSettings.inputSize}
>{$_('section.settings.ledBrightness')}</Label
>
<Col md="6">
<Input
type="range"
@ -204,7 +213,7 @@
</Row>
{#if $settings.hasFrontlight}
<Row>
<Label md={6} for="flMaxBrightness" size="sm"
<Label md={6} for="flMaxBrightness" size={$uiSettings.inputSize}
>{$_('section.settings.flMaxBrightness')}</Label
>
<Col md="6">
@ -221,7 +230,7 @@
</Col>
</Row>
<Row>
<Label md={6} for="flEffectDelay" size="sm"
<Label md={6} for="flEffectDelay" size={$uiSettings.inputSize}
>{$_('section.settings.flEffectDelay')}</Label
>
<Col md="6">
@ -238,7 +247,7 @@
</Row>
{/if}
<Row>
<Label md={6} for="hostnamePrefix" size="sm"
<Label md={6} for="hostnamePrefix" size={$uiSettings.inputSize}
>{$_('section.settings.hostnamePrefix')}</Label
>
<Col md="6">
@ -247,12 +256,12 @@
bind:value={$settings.hostnamePrefix}
name="hostnamePrefix"
id="hostnamePrefix"
bsSize="sm"
bsSize={$uiSettings.inputSize}
></Input>
</Col>
</Row>
<Row>
<Label md={6} for="wifiTxPower" size="sm"
<Label md={6} for="wifiTxPower" size={$uiSettings.inputSize}
>{$_('section.settings.wifiTxPower', { default: 'WiFi Tx Power' })}</Label
>
<Col md="6">
@ -261,8 +270,8 @@
bind:value={$settings.txPower}
name="select"
id="fgColor"
bsSize="sm"
class="form-select-sm"
bsSize={$uiSettings.inputSize}
class={$uiSettings.selectClass}
>
{#each wifiTxPowerMap as [key, value]}
<option {value}>{key}</option>
@ -277,7 +286,7 @@
id="ledTestOnPower"
bind:checked={$settings.ledTestOnPower}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label={$_('section.settings.ledPowerOnTest')}
/>
</Col>
@ -286,7 +295,7 @@
id="ledFlashOnUpd"
bind:checked={$settings.ledFlashOnUpd}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label={$_('section.settings.ledFlashOnBlock')}
/>
</Col>
@ -295,7 +304,7 @@
id="stealFocus"
bind:checked={$settings.stealFocus}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label={$_('section.settings.StealFocusOnNewBlock')}
/>
</Col>
@ -304,7 +313,7 @@
id="mcapBigChar"
bind:checked={$settings.mcapBigChar}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label={$_('section.settings.useBigCharsMcap')}
/>
</Col>
@ -313,7 +322,7 @@
id="otaEnabled"
bind:checked={$settings.otaEnabled}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label="{$_('section.settings.otaUpdates')} ({$_('restartRequired')})"
/>
</Col>
@ -322,7 +331,7 @@
id="mdnsEnabled"
bind:checked={$settings.mdnsEnabled}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label="{$_('section.settings.enableMdns')} ({$_('restartRequired')})"
/>
</Col>
@ -331,7 +340,7 @@
id="fetchEurPrice"
bind:checked={$settings.fetchEurPrice}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label="{$_('section.settings.fetchEuroPrice')} ({$_('restartRequired')})"
/>
</Col>
@ -340,7 +349,7 @@
id="useBlkCountdown"
bind:checked={$settings.useBlkCountdown}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label={$_('section.settings.useBlkCountdown')}
/>
</Col>
@ -349,7 +358,7 @@
id="useSatsSymbol"
bind:checked={$settings.useSatsSymbol}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label={$_('section.settings.useSatsSymbol')}
/>
</Col>
@ -358,7 +367,7 @@
id="suffixPrice"
bind:checked={$settings.suffixPrice}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label={$_('section.settings.suffixPrice')}
/>
</Col>
@ -367,7 +376,7 @@
id="disableLeds"
bind:checked={$settings.disableLeds}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label={$_('section.settings.disableLeds')}
/>
</Col>
@ -376,7 +385,7 @@
id="ownDataSource"
bind:checked={$settings.ownDataSource}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label="{$_('section.settings.ownDataSource')} ({$_('restartRequired')})"
/>
</Col>
@ -386,7 +395,7 @@
id="flAlwaysOn"
bind:checked={$settings.flAlwaysOn}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label={$_('section.settings.flAlwaysOn')}
/>
</Col>
@ -395,7 +404,7 @@
id="flFlashOnUpd"
bind:checked={$settings.flFlashOnUpd}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label={$_('section.settings.flFlashOnUpd')}
/>
</Col>
@ -411,15 +420,20 @@
id="screens_{s.id}"
bind:checked={s.enabled}
type="switch"
bsSize="sm"
bsSize={$uiSettings.inputSize}
label={s.name}
/>
</Col>
{/each}
{/if}
</Row>
<Button on:click={handleReset} color="secondary">{$_('button.reset')}</Button>
<Button color="primary">{$_('button.save')}</Button>
<Row>
<Col class="d-flex justify-content-end">
<Button on:click={handleReset} color="secondary">{$_('button.reset')}</Button>
<div class="mx-2"></div>
<Button color="primary">{$_('button.save')}</Button>
</Col>
</Row>
</Form>
</CardBody>
</Card>