Lint and format all files

This commit is contained in:
Djuri Baars 2023-11-19 20:27:22 +01:00
parent 3eaf897dbb
commit d25284e3a4
22 changed files with 1357 additions and 1280 deletions

View file

@ -18,6 +18,9 @@ module.exports = {
es2017: true, es2017: true,
node: true node: true
}, },
rules: {
'no-empty': ['error', { allowEmptyCatch: true }]
},
overrides: [ overrides: [
{ {
files: ['*.svelte'], files: ['*.svelte'],

View file

@ -3,7 +3,7 @@ name: BTClock WebUI CI
on: [push] on: [push]
env: env:
PUBLIC_BASE_URL: "" PUBLIC_BASE_URL: ''
jobs: jobs:
check-changes: check-changes:
@ -12,13 +12,13 @@ jobs:
all_changed_and_modified_files_count: ${{ steps.changed-files.outputs.all_changed_and_modified_files_count }} all_changed_and_modified_files_count: ${{ steps.changed-files.outputs.all_changed_and_modified_files_count }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Get changed files count - name: Get changed files count
id: changed-files id: changed-files
uses: tj-actions/changed-files@v40.1.1 uses: tj-actions/changed-files@v40.1.1
with: with:
files_ignore: "doc/**,README.md,Dockerfile,.*" files_ignore: 'doc/**,README.md,Dockerfile,.*'
files_ignore_separator: ',' files_ignore_separator: ','
- name: Print changed files count - name: Print changed files count
run: > run: >
@ -33,7 +33,7 @@ jobs:
contents: write contents: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
@ -71,7 +71,7 @@ jobs:
- name: Create tarball - name: Create tarball
run: tar czf webui.tgz --strip-components=1 dist run: tar czf webui.tgz --strip-components=1 dist
- name: Build LittleFS - name: Build LittleFS
run: /tmp/mklittlefs/mklittlefs -c build_gz -s 409600 littlefs.bin run: /tmp/mklittlefs/mklittlefs -c build_gz -s 409600 littlefs.bin
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
@ -84,7 +84,7 @@ jobs:
tag: ${{ steps.getBlockHeight.outputs.blockHeight }} tag: ${{ steps.getBlockHeight.outputs.blockHeight }}
commit: main commit: main
name: release-${{ steps.getBlockHeight.outputs.blockHeight }} name: release-${{ steps.getBlockHeight.outputs.blockHeight }}
artifacts: "littlefs.bin,webui.tgz" artifacts: 'littlefs.bin,webui.tgz'
allowUpdates: true allowUpdates: true
removeArtifacts: true removeArtifacts: true
makeLatest: true makeLatest: true

View file

@ -8,7 +8,7 @@ node_modules
!.env.example !.env.example
dist/ dist/
build_gz build_gz
dist/**
# Ignore files for PNPM, NPM and YARN # Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml pnpm-lock.yaml

View file

@ -35,7 +35,7 @@ To upload the firmware to the BTClock, you need to GZIP all the files. You can u
Then you can make a `LittleFS.bin` with mklittlefs: Then you can make a `LittleFS.bin` with mklittlefs:
```bash ```bash
mklittlefs -c build_gz -s 409600 littlefs.bin mklittlefs -c build_gz -s 409600 littlefs.bin
``` ```
You can preview the production build with `yarn preview`. You can preview the production build with `yarn preview`.

View file

@ -1,10 +1,10 @@
import type { Handle } from '@sveltejs/kit' import type { Handle } from '@sveltejs/kit';
import { locale } from 'svelte-i18n' import { locale } from 'svelte-i18n';
export const handle: Handle = async ({ event, resolve }) => { export const handle: Handle = async ({ event, resolve }) => {
const lang = event.request.headers.get('accept-language')?.split(',')[0] const lang = event.request.headers.get('accept-language')?.split(',')[0];
if (lang) { if (lang) {
locale.set(lang) locale.set(lang);
} }
return resolve(event) return resolve(event);
} };

View file

@ -1,13 +1,17 @@
import { browser } from '$app/environment' import { browser } from '$app/environment';
import { init, register } from 'svelte-i18n' import { init, register } from 'svelte-i18n';
const defaultLocale = 'en' const defaultLocale = 'en';
register('en', () => import('../locales/en.json')) register('en', () => import('../locales/en.json'));
register('nl', () => import('../locales/nl.json')) register('nl', () => import('../locales/nl.json'));
register('es', () => import('../locales/es.json')) register('es', () => import('../locales/es.json'));
init({ init({
fallbackLocale: defaultLocale, fallbackLocale: defaultLocale,
initialLocale: browser ? browser && localStorage.getItem('locale') ? localStorage.getItem('locale') : window.navigator.language : defaultLocale, initialLocale: browser
}) ? browser && localStorage.getItem('locale')
? localStorage.getItem('locale')
: window.navigator.language
: defaultLocale
});

View file

@ -1,71 +1,71 @@
{ {
"section": { "section": {
"settings": { "settings": {
"title": "Settings", "title": "Settings",
"textColor": "Text color", "textColor": "Text color",
"backgroundColor": "Background color", "backgroundColor": "Background color",
"ledPowerOnTest": "LED power-on test", "ledPowerOnTest": "LED power-on test",
"ledFlashOnBlock": "LED flash on new block", "ledFlashOnBlock": "LED flash on new block",
"timePerScreen": "Time per screen", "timePerScreen": "Time per screen",
"ledBrightness": "LED brightness", "ledBrightness": "LED brightness",
"timezoneOffset": "Timezone offset", "timezoneOffset": "Timezone offset",
"timeBetweenPriceUpdates": "Time between price updates", "timeBetweenPriceUpdates": "Time between price updates",
"fullRefreshEvery": "Full refresh every", "fullRefreshEvery": "Full refresh every",
"mempoolnstance": "Mempool Instance", "mempoolnstance": "Mempool Instance",
"hostnamePrefix": "Hostname prefix", "hostnamePrefix": "Hostname prefix",
"StealFocusOnNewBlock": "Steal focus on new block", "StealFocusOnNewBlock": "Steal focus on new block",
"useBigCharsMcap": "Use big characters for market cap", "useBigCharsMcap": "Use big characters for market cap",
"otaUpdates": "OTA updates", "otaUpdates": "OTA updates",
"enableMdns": "mDNS", "enableMdns": "mDNS",
"fetchEuroPrice": "Fetch € price", "fetchEuroPrice": "Fetch € price",
"shortAmountsWarning": "Short amounts might shorten lifespan.", "shortAmountsWarning": "Short amounts might shorten lifespan.",
"tzOffsetHelpText": "A restart is required to apply TZ offset.", "tzOffsetHelpText": "A restart is required to apply TZ offset.",
"screens": "Screens" "screens": "Screens"
}, },
"control": { "control": {
"systemInfo": "System info", "systemInfo": "System info",
"version": "Version", "version": "Version",
"buildTime": "Build time", "buildTime": "Build time",
"ledColor": "LED color", "ledColor": "LED color",
"turnOff": "Turn off", "turnOff": "Turn off",
"setColor": "Set color", "setColor": "Set color",
"showText": "Show text", "showText": "Show text",
"text": "Text", "text": "Text",
"title": "Control", "title": "Control",
"hostname": "Hostname" "hostname": "Hostname"
}, },
"status": { "status": {
"title": "Status", "title": "Status",
"screenCycle": "Screen cycle", "screenCycle": "Screen cycle",
"memoryFree": "Memory free", "memoryFree": "Memory free",
"wsPriceConnection": "WS Price connection", "wsPriceConnection": "WS Price connection",
"wsMempoolConnection": "WS Mempool.space connection", "wsMempoolConnection": "WS Mempool.space connection",
"fetchEuroNote": "If you use \"Fetch € price\" the WS Price connection will show ❌ since it uses another data source.", "fetchEuroNote": "If you use \"Fetch € price\" the WS Price connection will show ❌ since it uses another data source.",
"uptime": "Uptime" "uptime": "Uptime"
} }
}, },
"colors": { "colors": {
"black": "Black", "black": "Black",
"white": "White" "white": "White"
}, },
"time": { "time": {
"minutes": "minutes", "minutes": "minutes",
"seconds": "seconds" "seconds": "seconds"
}, },
"restartRequired": "restart required", "restartRequired": "restart required",
"button": { "button": {
"save": "Save", "save": "Save",
"reset": "Reset", "reset": "Reset",
"restart": "Restart", "restart": "Restart",
"forceFullRefresh": "Force full refresh" "forceFullRefresh": "Force full refresh"
}, },
"timer": { "timer": {
"running": "running", "running": "running",
"stopped": "stopped" "stopped": "stopped"
}, },
"sections": { "sections": {
"control": { "control": {
"keepSameColor": "Keep same color" "keepSameColor": "Keep same color"
} }
} }
} }

View file

@ -1,71 +1,71 @@
{ {
"section": { "section": {
"settings": { "settings": {
"title": "Configuración", "title": "Configuración",
"textColor": "Color de texto", "textColor": "Color de texto",
"backgroundColor": "Color de fondo", "backgroundColor": "Color de fondo",
"ledBrightness": "Brillo LED", "ledBrightness": "Brillo LED",
"screens": "Pantallas", "screens": "Pantallas",
"shortAmountsWarning": "Cantidades pequeñas pueden acortar la vida útil.", "shortAmountsWarning": "Cantidades pequeñas pueden acortar la vida útil.",
"fullRefreshEvery": "Actualización completa cada", "fullRefreshEvery": "Actualización completa cada",
"timePerScreen": "Tiempo por pantalla", "timePerScreen": "Tiempo por pantalla",
"tzOffsetHelpText": "Es necesario reiniciar para aplicar la compensación.", "tzOffsetHelpText": "Es necesario reiniciar para aplicar la compensación.",
"timezoneOffset": "Compensación de zona horaria", "timezoneOffset": "Compensación de zona horaria",
"StealFocusOnNewBlock": "Presta atención al nuevo bloque", "StealFocusOnNewBlock": "Presta atención al nuevo bloque",
"ledFlashOnBlock": "El LED parpadea con un bloque nuevo", "ledFlashOnBlock": "El LED parpadea con un bloque nuevo",
"useBigCharsMcap": "Utilice caracteres grandes para la market cap", "useBigCharsMcap": "Utilice caracteres grandes para la market cap",
"fetchEuroPrice": "Obtener precio en €", "fetchEuroPrice": "Obtener precio en €",
"timeBetweenPriceUpdates": "Tiempo entre actualizaciones de precios", "timeBetweenPriceUpdates": "Tiempo entre actualizaciones de precios",
"ledPowerOnTest": "Prueba de encendido del LED", "ledPowerOnTest": "Prueba de encendido del LED",
"enableMdns": "mDNS", "enableMdns": "mDNS",
"hostnamePrefix": "Prefijo de nombre de host", "hostnamePrefix": "Prefijo de nombre de host",
"mempoolnstance": "Instancia de Mempool", "mempoolnstance": "Instancia de Mempool",
"otaUpdates": "Actualización por aire" "otaUpdates": "Actualización por aire"
}, },
"control": { "control": {
"turnOff": "Apagar", "turnOff": "Apagar",
"setColor": "Establecer el color", "setColor": "Establecer el color",
"version": "Versión", "version": "Versión",
"ledColor": "color del LED", "ledColor": "color del LED",
"systemInfo": "Info del sistema", "systemInfo": "Info del sistema",
"showText": "Mostrar texto", "showText": "Mostrar texto",
"text": "Texto", "text": "Texto",
"title": "Control", "title": "Control",
"buildTime": "Tiempo de compilación", "buildTime": "Tiempo de compilación",
"hostname": "Nombre del host" "hostname": "Nombre del host"
}, },
"status": { "status": {
"memoryFree": "Memoria RAM libre", "memoryFree": "Memoria RAM libre",
"wsPriceConnection": "Conexión WebSocket Precio", "wsPriceConnection": "Conexión WebSocket Precio",
"wsMempoolConnection": "Conexión WebSocket Mempool.space", "wsMempoolConnection": "Conexión WebSocket Mempool.space",
"screenCycle": "Ciclo de pantalla", "screenCycle": "Ciclo de pantalla",
"uptime": "Tiempo de funcionamiento", "uptime": "Tiempo de funcionamiento",
"fetchEuroNote": "Si utiliza \"Obtener precio en €\", la conexión de Precio WS mostrará ❌ ya que utiliza otra fuente de datos.", "fetchEuroNote": "Si utiliza \"Obtener precio en €\", la conexión de Precio WS mostrará ❌ ya que utiliza otra fuente de datos.",
"title": "Estado" "title": "Estado"
} }
}, },
"button": { "button": {
"save": "Guardar", "save": "Guardar",
"reset": "Restaurar", "reset": "Restaurar",
"restart": "Reiniciar", "restart": "Reiniciar",
"forceFullRefresh": "Forzar refresco" "forceFullRefresh": "Forzar refresco"
}, },
"colors": { "colors": {
"black": "Negro", "black": "Negro",
"white": "Blanco" "white": "Blanco"
}, },
"restartRequired": "reinicio requerido", "restartRequired": "reinicio requerido",
"time": { "time": {
"minutes": "minutos", "minutes": "minutos",
"seconds": "segundos" "seconds": "segundos"
}, },
"timer": { "timer": {
"running": "funcionando", "running": "funcionando",
"stopped": "detenido" "stopped": "detenido"
}, },
"sections": { "sections": {
"control": { "control": {
"keepSameColor": "Mantén el mismo color" "keepSameColor": "Mantén el mismo color"
} }
} }
} }

View file

@ -1,70 +1,70 @@
{ {
"section": { "section": {
"settings": { "settings": {
"title": "Instellingen", "title": "Instellingen",
"textColor": "Tekstkleur", "textColor": "Tekstkleur",
"backgroundColor": "Achtergrondkleur", "backgroundColor": "Achtergrondkleur",
"timeBetweenPriceUpdates": "Tijd tussen prijs updates", "timeBetweenPriceUpdates": "Tijd tussen prijs updates",
"timezoneOffset": "Tijdzone afwijking", "timezoneOffset": "Tijdzone afwijking",
"ledBrightness": "LED helderheid", "ledBrightness": "LED helderheid",
"timePerScreen": "Tijd per scherm", "timePerScreen": "Tijd per scherm",
"fullRefreshEvery": "Volledig verversen elke", "fullRefreshEvery": "Volledig verversen elke",
"shortAmountsWarning": "Lage waardes verkorten levensduur", "shortAmountsWarning": "Lage waardes verkorten levensduur",
"tzOffsetHelpText": "Herstart nodig voor toepassen afwijking.", "tzOffsetHelpText": "Herstart nodig voor toepassen afwijking.",
"enableMdns": "mDNS", "enableMdns": "mDNS",
"ledPowerOnTest": "LED test bij aanzetten", "ledPowerOnTest": "LED test bij aanzetten",
"StealFocusOnNewBlock": "Pak aandacht bij nieuw blok", "StealFocusOnNewBlock": "Pak aandacht bij nieuw blok",
"ledFlashOnBlock": "Knipper led bij nieuw blok", "ledFlashOnBlock": "Knipper led bij nieuw blok",
"useBigCharsMcap": "Gebruik grote tekens bij market cap", "useBigCharsMcap": "Gebruik grote tekens bij market cap",
"fetchEuroPrice": "Toon € prijs", "fetchEuroPrice": "Toon € prijs",
"screens": "Schermen", "screens": "Schermen",
"hostnamePrefix": "Hostnaam voorvoegsel", "hostnamePrefix": "Hostnaam voorvoegsel",
"mempoolnstance": "Mempool instantie", "mempoolnstance": "Mempool instantie",
"otaUpdates": "OTA updates" "otaUpdates": "OTA updates"
}, },
"control": { "control": {
"systemInfo": "Systeeminformatie", "systemInfo": "Systeeminformatie",
"version": "Versie", "version": "Versie",
"buildTime": "Bouwtijd", "buildTime": "Bouwtijd",
"setColor": "Kleur instellen", "setColor": "Kleur instellen",
"turnOff": "Uitzetten", "turnOff": "Uitzetten",
"ledColor": "LED kleur", "ledColor": "LED kleur",
"showText": "Toon tekst", "showText": "Toon tekst",
"text": "Tekst", "text": "Tekst",
"title": "Besturing" "title": "Besturing"
}, },
"status": { "status": {
"title": "Status", "title": "Status",
"memoryFree": "Geheugen vrij", "memoryFree": "Geheugen vrij",
"screenCycle": "Scherm cyclus", "screenCycle": "Scherm cyclus",
"wsPriceConnection": "WS Prijs verbinding", "wsPriceConnection": "WS Prijs verbinding",
"wsMempoolConnection": "WS Mempool.space verbinding", "wsMempoolConnection": "WS Mempool.space verbinding",
"fetchEuroNote": "Wanneer je \"Toon € prijs\" aanzet, zal de prijsverbinding als ❌ verbroken getoond worden vanwege het gebruik van een andere bron.", "fetchEuroNote": "Wanneer je \"Toon € prijs\" aanzet, zal de prijsverbinding als ❌ verbroken getoond worden vanwege het gebruik van een andere bron.",
"uptime": "Uptime" "uptime": "Uptime"
} }
}, },
"colors": { "colors": {
"black": "Zwart", "black": "Zwart",
"white": "Wit" "white": "Wit"
}, },
"time": { "time": {
"minutes": "minuten", "minutes": "minuten",
"seconds": "seconden" "seconds": "seconden"
}, },
"restartRequired": "herstart nodig", "restartRequired": "herstart nodig",
"button": { "button": {
"save": "Opslaan", "save": "Opslaan",
"reset": "Herstel", "reset": "Herstel",
"restart": "Herstart", "restart": "Herstart",
"forceFullRefresh": "Forceer scherm verversen" "forceFullRefresh": "Forceer scherm verversen"
}, },
"timer": { "timer": {
"running": "actief", "running": "actief",
"stopped": "gestopt" "stopped": "gestopt"
}, },
"sections": { "sections": {
"control": { "control": {
"keepSameColor": "Behoud zelfde kleur" "keepSameColor": "Behoud zelfde kleur"
} }
} }
} }

View file

@ -1,193 +1,197 @@
@import "../node_modules/bootstrap/scss/functions"; @import '../node_modules/bootstrap/scss/functions';
@import "../node_modules/bootstrap/scss/variables"; @import '../node_modules/bootstrap/scss/variables';
@import "../node_modules/bootstrap/scss/variables-dark"; @import '../node_modules/bootstrap/scss/variables-dark';
//@import "@fontsource/antonio/latin-400.css"; //@import "@fontsource/antonio/latin-400.css";
@import "@fontsource/ubuntu/latin-400.css"; @import '@fontsource/ubuntu/latin-400.css';
@import "@fontsource/oswald/latin-400.css"; @import '@fontsource/oswald/latin-400.css';
$form-range-track-bg: #fff; $form-range-track-bg: #fff;
$color-mode-type: media-query; $color-mode-type: media-query;
$font-family-base: "Ubuntu"; $font-family-base: 'Ubuntu';
$font-size-base: 0.9rem; $font-size-base: 0.9rem;
//$font-size-sm: $font-size-base * .875 !default; //$font-size-sm: $font-size-base * .875 !default;
//$form-label-font-size: $font-size-base * .575 !default; //$form-label-font-size: $font-size-base * .575 !default;
//$input-btn-font-size-sm: 0.4rem; //$input-btn-font-size-sm: 0.4rem;
//$form-label-font-size: 0.4rem; //$form-label-font-size: 0.4rem;
$input-font-size-sm: $font-size-base * .875; $input-font-size-sm: $font-size-base * 0.875;
// $border-radius: .675rem; // $border-radius: .675rem;
@import "../node_modules/bootstrap/scss/mixins"; @import '../node_modules/bootstrap/scss/mixins';
@import "../node_modules/bootstrap/scss/maps"; @import '../node_modules/bootstrap/scss/maps';
@import "../node_modules/bootstrap/scss/utilities"; @import '../node_modules/bootstrap/scss/utilities';
@import "../node_modules/bootstrap/scss/root"; @import '../node_modules/bootstrap/scss/root';
@import "../node_modules/bootstrap/scss/reboot"; @import '../node_modules/bootstrap/scss/reboot';
@import "../node_modules/bootstrap/scss/type"; @import '../node_modules/bootstrap/scss/type';
@import "../node_modules/bootstrap/scss/containers"; @import '../node_modules/bootstrap/scss/containers';
@import "../node_modules/bootstrap/scss/grid"; @import '../node_modules/bootstrap/scss/grid';
@import "../node_modules/bootstrap/scss/forms"; @import '../node_modules/bootstrap/scss/forms';
@import "../node_modules/bootstrap/scss/buttons"; @import '../node_modules/bootstrap/scss/buttons';
@import "../node_modules/bootstrap/scss/button-group"; @import '../node_modules/bootstrap/scss/button-group';
@import "../node_modules/bootstrap/scss/pagination"; @import '../node_modules/bootstrap/scss/pagination';
@import "../node_modules/bootstrap/scss/dropdown"; @import '../node_modules/bootstrap/scss/dropdown';
@import "../node_modules/bootstrap/scss/navbar"; @import '../node_modules/bootstrap/scss/navbar';
@import "../node_modules/bootstrap/scss/nav"; @import '../node_modules/bootstrap/scss/nav';
@import "../node_modules/bootstrap/scss/card"; @import '../node_modules/bootstrap/scss/card';
@import "../node_modules/bootstrap/scss/progress"; @import '../node_modules/bootstrap/scss/progress';
@import "../node_modules/bootstrap/scss/helpers"; @import '../node_modules/bootstrap/scss/helpers';
@import "../node_modules/bootstrap/scss/utilities/api"; @import '../node_modules/bootstrap/scss/utilities/api';
@include media-breakpoint-down(xl) { @include media-breakpoint-down(xl) {
html { html {
font-size: 85%; font-size: 85%;
} }
button.btn, button.btn,
input[type="button"].btn, input[type='button'].btn,
input[type="submit"].btn, input[type='submit'].btn,
input[type="reset"].btn { input[type='reset'].btn {
@include button-size($btn-padding-y-sm, $btn-padding-x-sm, $font-size-sm, $btn-border-radius-sm); @include button-size(
} $btn-padding-y-sm,
$btn-padding-x-sm,
$font-size-sm,
$btn-border-radius-sm
);
}
} }
@include media-breakpoint-down(lg) { @include media-breakpoint-down(lg) {
html { html {
font-size: 75%; font-size: 75%;
} }
} }
nav { nav {
margin-bottom: 15px; margin-bottom: 15px;
} }
.splitText div:first-child::after { .splitText div:first-child::after {
display: block; display: block;
content: ''; content: '';
margin-top: 0px; margin-top: 0px;
border-bottom: 2px solid; border-bottom: 2px solid;
margin-bottom: 3px; margin-bottom: 3px;
} }
#btcclock-wrapper { #btcclock-wrapper {
margin: 0 auto; margin: 0 auto;
} }
.btclock { .btclock {
border: 1px solid darkgray; border: 1px solid darkgray;
background: #000; background: #000;
border-radius: 5px; border-radius: 5px;
padding: 10px; padding: 10px;
max-width: 700px; max-width: 700px;
margin: 0 auto; margin: 0 auto;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
align-content: stretch; align-content: stretch;
font-family: 'Oswald', sans-serif; font-family: 'Oswald', sans-serif;
>div { > div {
padding: 5px; padding: 5px;
} }
.digit, .digit,
.splitText, .splitText,
.mediumText { .mediumText {
border: 2px solid gold; border: 2px solid gold;
border-radius: 8px; border-radius: 8px;
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
min-width: 10px; min-width: 10px;
} }
@include media-breakpoint-up(xxl) { @include media-breakpoint-up(xxl) {
min-width: 70px; min-width: 70px;
} }
text-align: center; text-align: center;
color: #fff; color: #fff;
} }
} }
.darkMode .btclock>div { .darkMode .btclock > div {
color: #fff; color: #fff;
border-color: #fff; border-color: #fff;
} }
.lightMode .btclock>div { .lightMode .btclock > div {
background: #fff; background: #fff;
} }
.lightMode .btclock>div { .lightMode .btclock > div {
color: #000; color: #000;
} }
.darkMode .btclock>div { .darkMode .btclock > div {
background: #000; background: #000;
} }
.splitText { .splitText {
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
font-size: 1.0rem; font-size: 1rem;
padding-top: 8px !important; padding-top: 8px !important;
padding-bottom: 9px !important; padding-bottom: 9px !important;
} }
@include media-breakpoint-up(xxl) { @include media-breakpoint-up(xxl) {
font-size: 1.8rem; font-size: 1.8rem;
padding-top: 19px !important; padding-top: 19px !important;
padding-bottom: 20px !important; padding-bottom: 20px !important;
} }
text-align: center; text-align: center;
} }
.mediumText { .mediumText {
font-size: 3rem; font-size: 3rem;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
padding-top: 20px !important; padding-top: 20px !important;
padding-bottom: 20px !important; padding-bottom: 20px !important;
} }
.digit { .digit {
font-size: 5rem; font-size: 5rem;
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
font-size: 2.5rem; font-size: 2.5rem;
} }
@include media-breakpoint-up(xxl) { @include media-breakpoint-up(xxl) {
font-size: 5rem; font-size: 5rem;
} }
padding-left: 10px; padding-left: 10px;
padding-right: 10px; padding-right: 10px;
} }
.digit-blank { .digit-blank {
content: "abc"; content: 'abc';
} }
#customText { #customText {
text-transform: uppercase; text-transform: uppercase;
} }
.system_info { .system_info {
padding: 0; padding: 0;
li { li {
list-style: none; list-style: none;
} }
} }
.card-title { .card-title {
margin-bottom: 0; margin-bottom: 0;
} }
.navbar-brand { .navbar-brand {
font-style: italic; font-style: italic;
font-weight: 600; font-weight: 600;
} }

View file

@ -1,21 +1,19 @@
<script lang="ts"> <script lang="ts">
import { import {
Navbar, Collapse,
NavbarBrand, Dropdown,
DropdownItem,
DropdownMenu,
DropdownToggle,
Nav, Nav,
NavItem, NavItem,
NavLink, NavLink,
Collapse, Navbar,
Dropdown, NavbarBrand
DropdownMenu,
DropdownItem,
DropdownToggle
} from 'sveltestrap'; } from 'sveltestrap';
import { locale, locales, waitLocale } from 'svelte-i18n';
import type { LayoutLoad } from './$types';
import { browser } from '$app/environment';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { locale, locales } from 'svelte-i18n';
export const setLocale = (lang: string) => () => { export const setLocale = (lang: string) => () => {
locale.set(lang); locale.set(lang);
@ -33,7 +31,7 @@
const lowercaseCode = languageCode.toLowerCase(); const lowercaseCode = languageCode.toLowerCase();
// Check if the language code is in the flagMap // Check if the language code is in the flagMap
if (flagMap.hasOwnProperty(lowercaseCode)) { if (Object.prototype.hasOwnProperty.call(flagMap, lowercaseCode)) {
return flagMap[lowercaseCode]; return flagMap[lowercaseCode];
} else { } else {
// Return null for unsupported language codes // Return null for unsupported language codes
@ -50,7 +48,7 @@
const lowercaseCode = languageCode.toLowerCase(); const lowercaseCode = languageCode.toLowerCase();
return languageNames.hasOwnProperty(lowercaseCode) return Object.prototype.hasOwnProperty.call(languageNames, lowercaseCode)
? languageNames[lowercaseCode][lowercaseCode] ? languageNames[lowercaseCode][lowercaseCode]
: null; : null;
}; };
@ -71,7 +69,9 @@
<DropdownToggle nav caret>{getFlagEmoji($locale)} {getLanguageName($locale)}</DropdownToggle> <DropdownToggle nav caret>{getFlagEmoji($locale)} {getLanguageName($locale)}</DropdownToggle>
<DropdownMenu end> <DropdownMenu end>
{#each $locales as locale} {#each $locales as locale}
<DropdownItem on:click={setLocale(locale)}>{getFlagEmoji(locale)} {getLanguageName(locale)}</DropdownItem> <DropdownItem on:click={setLocale(locale)}
>{getFlagEmoji(locale)} {getLanguageName(locale)}</DropdownItem
>
{/each} {/each}
</DropdownMenu> </DropdownMenu>
</Dropdown> </Dropdown>

View file

@ -1,20 +1,19 @@
import "$lib/style/app.scss"; import '$lib/style/app.scss';
import { browser } from '$app/environment';
import { browser } from '$app/environment' import '$lib/i18n'; // Import to initialize. Important :)
import '$lib/i18n' // Import to initialize. Important :) import { locale, waitLocale } from 'svelte-i18n';
import { locale, waitLocale } from 'svelte-i18n' import type { LayoutLoad } from './$types';
import type { LayoutLoad } from './$types'
export const load: LayoutLoad = async () => { export const load: LayoutLoad = async () => {
if (browser && localStorage.getItem('locale')) { if (browser && localStorage.getItem('locale')) {
locale.set(localStorage.getItem('locale')); locale.set(localStorage.getItem('locale'));
} else if (browser) { } else if (browser) {
locale.set(window.navigator.language) locale.set(window.navigator.language);
} }
await waitLocale(); await waitLocale();
} };
export const prerender = true; export const prerender = true;
export const ssr = false; export const ssr = false;
export const csr = true; export const csr = true;

View file

@ -1,45 +1,44 @@
<script lang="ts"> <script lang="ts">
import { PUBLIC_BASE_URL } from '$env/static/public'; import { PUBLIC_BASE_URL } from '$env/static/public';
import { _ } from 'svelte-i18n'; import { Container, Row } from 'sveltestrap';
import { Col, Container, Row } from 'sveltestrap';
import Control from './Control.svelte';
import Status from './Status.svelte';
import Settings from './Settings.svelte';
import { writable } from 'svelte/store';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { writable } from 'svelte/store';
import Control from './Control.svelte';
import Settings from './Settings.svelte';
import Status from './Status.svelte';
let settings = writable({ let settings = writable({
fgColor: "0" fgColor: '0'
}); });
let status = writable({ let status = writable({
data: ["L", "O", "A", "D", "I", "N", "G"], data: ['L', 'O', 'A', 'D', 'I', 'N', 'G'],
espFreeHeap: 0, espFreeHeap: 0,
espHeapSize: 0, espHeapSize: 0,
connectionStatus: { connectionStatus: {
"price": false, price: false,
"blocks": false blocks: false
}, },
leds: [] leds: []
}); });
onMount(() => { onMount(() => {
fetch( PUBLIC_BASE_URL + `/api/settings`) fetch(PUBLIC_BASE_URL + `/api/settings`)
.then((res) => res.json()) .then((res) => res.json())
.then((data) => { .then((data) => {
data.fgColor = String(data.fgColor); data.fgColor = String(data.fgColor);
data.bgColor = String(data.bgColor); data.bgColor = String(data.bgColor);
data.timePerScreen = data.timerSeconds / 60; data.timePerScreen = data.timerSeconds / 60;
if (data.fgColor> 65535) { if (data.fgColor > 65535) {
data.fgColor = "65535"; data.fgColor = '65535';
} }
if (data.bgColor> 65535) { if (data.bgColor > 65535) {
data.bgColor = "65535"; data.bgColor = '65535';
} }
settings.set(data); settings.set(data);
}); });
@ -49,12 +48,12 @@
status.set(data); status.set(data);
}); });
const evtSource = new EventSource(`${PUBLIC_BASE_URL}/events`); const evtSource = new EventSource(`${PUBLIC_BASE_URL}/events`);
evtSource.addEventListener('status', (e) => { evtSource.addEventListener('status', (e) => {
let dataObj = (JSON.parse(e.data)); let dataObj = JSON.parse(e.data);
status.set(dataObj); status.set(dataObj);
}); });
}); });
</script> </script>
@ -65,7 +64,7 @@
<Container fluid> <Container fluid>
<Row> <Row>
<Control bind:settings bind:status></Control> <Control bind:settings bind:status></Control>
<Status bind:settings bind:status></Status> <Status bind:settings bind:status></Status>
<Settings bind:settings></Settings> <Settings bind:settings></Settings>
</Row> </Row>
</Container> </Container>

View file

@ -1,82 +1,78 @@
<script lang="ts"> <script lang="ts">
import { PUBLIC_BASE_URL } from '$env/static/public'; import { PUBLIC_BASE_URL } from '$env/static/public';
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import type { Subscriber, Unsubscriber } from 'svelte/motion';
import type { Writable } from 'svelte/store'; import type { Writable } from 'svelte/store';
import { import {
Button, Button,
ButtonGroup, Card,
Card, CardBody,
CardTitle, CardHeader,
CardBody, CardTitle,
CardHeader, Col,
Col, Form,
Container, Input,
Form, Label,
Input, Row
Label, } from 'sveltestrap';
Row
} from 'sveltestrap';
export let settings = {}; export let settings = {};
export let customText:String; export let customText: string;
export let ledColor:String = "#FFCC00"; export let ledColor: string = '#FFCC00';
export let status:Writable<{leds:[]}>; export let status: Writable<{ leds: [] }>;
let ledStatus = []; let ledStatus = [];
let keepLedsSameColor = false; let keepLedsSameColor = false;
const setCustomText = () => { const setCustomText = () => {
fetch(`${PUBLIC_BASE_URL}/api/show/text/${customText}`).catch(err => { }); fetch(`${PUBLIC_BASE_URL}/api/show/text/${customText}`).catch(() => {});
}; };
const checkSyncLeds = (e:Event) => { const checkSyncLeds = (e: Event) => {
console.log('checksyncleds', keepLedsSameColor); console.log('checksyncleds', keepLedsSameColor);
if (keepLedsSameColor) { if (keepLedsSameColor) {
console.log(e.target.value); console.log(e.target.value);
ledStatus.forEach((element, i) => { ledStatus.forEach((element, i) => {
if (ledStatus[i].hex != e.target_value) { if (ledStatus[i].hex != e.target_value) {
ledStatus[i].hex = e.target.value; ledStatus[i].hex = e.target.value;
} }
}); });
} }
} };
const setLEDcolor = () => { const setLEDcolor = () => {
console.log(`${PUBLIC_BASE_URL}/api/lights/${ledColor}`); console.log(`${PUBLIC_BASE_URL}/api/lights/${ledColor}`);
fetch(`${PUBLIC_BASE_URL}/api/lights`, { fetch(`${PUBLIC_BASE_URL}/api/lights`, {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
method: 'PATCH', method: 'PATCH',
body: JSON.stringify(ledStatus) body: JSON.stringify(ledStatus)
} }).catch(() => {});
).catch(err => { }); };
};
const turnOffLeds = () => { const turnOffLeds = () => {
fetch(`${PUBLIC_BASE_URL}/api/lights/off`).catch(err => { }); fetch(`${PUBLIC_BASE_URL}/api/lights/off`).catch(() => {});
}; };
const restartClock = () => { const restartClock = () => {
fetch(`${PUBLIC_BASE_URL}/api/restart`).catch(err => { }); fetch(`${PUBLIC_BASE_URL}/api/restart`).catch(() => {});
} };
const forceFullRefresh = () => { const forceFullRefresh = () => {
fetch(`${PUBLIC_BASE_URL}/api/full_refresh`).catch(err => { }); fetch(`${PUBLIC_BASE_URL}/api/full_refresh`).catch(() => {});
} };
let firstLedDataSubscription = () => {}; let firstLedDataSubscription = () => {};
firstLedDataSubscription = status.subscribe(async(val) => { firstLedDataSubscription = status.subscribe(async (val) => {
if (val && val.leds) { if (val && val.leds) {
ledStatus = val.leds.map((obj) => ({ ["hex"]: obj["hex"] })); ledStatus = val.leds.map((obj) => ({ ['hex']: obj['hex'] }));
firstLedDataSubscription(); firstLedDataSubscription();
} }
}) });
onDestroy(firstLedDataSubscription); onDestroy(firstLedDataSubscription);
</script> </script>
<Col> <Col>
@ -87,48 +83,72 @@ onDestroy(firstLedDataSubscription);
<CardBody> <CardBody>
<Form> <Form>
<Row> <Row>
<Label md={6} for="customText">{ $_('section.control.text') }</Label> <Label md={6} for="customText">{$_('section.control.text')}</Label>
<Col md="6"> <Col md="6">
<Input type="text" id="customText" bind:value={customText} bsSize="sm" maxLength="{$settings.numScreens}" /> <Input
type="text"
id="customText"
bind:value={customText}
bsSize="sm"
maxLength={$settings.numScreens}
/>
</Col> </Col>
</Row> </Row>
<Button color="primary" on:click={setCustomText}>{ $_('section.control.showText') }</Button> <Button color="primary" on:click={setCustomText}>{$_('section.control.showText')}</Button>
</Form> </Form>
<hr /> <hr />
<h3>LEDs</h3> <h3>LEDs</h3>
<Form> <Form>
<Row> <Row>
<Label md={6} for="ledColorPicker" size="sm">{ $_('section.control.ledColor') }</Label> <Label md={6} for="ledColorPicker" size="sm">{$_('section.control.ledColor')}</Label>
<Col md="6"> <Col md="6">
<Row class="justify-content-between"> <Row class="justify-content-between">
{#if ledStatus} {#if ledStatus}
{#each ledStatus as led, i } {#each ledStatus as led, i}
<Col> <Col>
<Input type="color" id="ledColorPicker[{i}]" bind:value="{led.hex}" class="mx-auto" on:change="{checkSyncLeds}" /> <Input
</Col> type="color"
{/each} id="ledColorPicker[{i}]"
bind:value={led.hex}
class="mx-auto"
on:change={checkSyncLeds}
/>
</Col>
{/each}
{/if} {/if}
<Col> <Col>
<Input bind:checked={keepLedsSameColor} type="switch" class="mx-auto" label="{ $_('sections.control.keepSameColor') }" /> <Input
bind:checked={keepLedsSameColor}
type="switch"
class="mx-auto"
label={$_('sections.control.keepSameColor')}
/>
</Col> </Col>
</Row> </Row>
</Col> </Col>
</Row> </Row>
<Button color="secondary" id="turnOffLedsBtn" on:click={turnOffLeds}>{ $_('section.control.turnOff') }</Button> <Button color="secondary" id="turnOffLedsBtn" on:click={turnOffLeds}
<Button color="primary" on:click={setLEDcolor}>{ $_('section.control.setColor') }</Button> >{$_('section.control.turnOff')}</Button
>
<Button color="primary" on:click={setLEDcolor}>{$_('section.control.setColor')}</Button>
</Form> </Form>
<hr /> <hr />
<h3>{ $_('section.control.systemInfo') }</h3> <h3>{$_('section.control.systemInfo')}</h3>
<ul class="small system_info"> <ul class="small system_info">
<li>{ $_('section.control.version') }: {$settings.gitRev}</li> <li>{$_('section.control.version')}: {$settings.gitRev}</li>
<li>{ $_('section.control.buildTime') }: {new Date(($settings.lastBuildTime * 1000)).toLocaleString()}</li> <li>
{$_('section.control.buildTime')}: {new Date(
$settings.lastBuildTime * 1000
).toLocaleString()}
</li>
<li>IP: {$settings.ip}</li> <li>IP: {$settings.ip}</li>
<li>{ $_('section.control.hostname') }: {$settings.hostname}</li> <li>{$_('section.control.hostname')}: {$settings.hostname}</li>
</ul> </ul>
<Button color="danger" id="restartBtn" on:click="{restartClock}">{ $_('button.restart') }</Button> <Button color="danger" id="restartBtn" on:click={restartClock}>{$_('button.restart')}</Button>
<Button color="warning" id="forceFullRefresh" on:click="{forceFullRefresh}">{ $_('button.forceFullRefresh') }</Button> <Button color="warning" id="forceFullRefresh" on:click={forceFullRefresh}
>{$_('button.forceFullRefresh')}</Button
>
</CardBody> </CardBody>
</Card> </Card>
</Col> </Col>

View file

@ -1,25 +1,25 @@
<script lang="ts"> <script lang="ts">
export let status = {}; export let status = {};
const isSplitText = (str:String) => { const isSplitText = (str: string) => {
return str.includes("/"); return str.includes('/');
} };
</script> </script>
<div class="btcclock-wrapper" id="btcclock-wrapper"> <div class="btcclock-wrapper" id="btcclock-wrapper">
<div class="btclock"> <div class="btclock">
{#each status.data as char} {#each status.data as char}
{#if isSplitText(char)} {#if isSplitText(char)}
<div class="splitText"> <div class="splitText">
{#each char.split("/") as part} {#each char.split('/') as part}
<div class="flex-items">{part}</div> <div class="flex-items">{part}</div>
{/each} {/each}
</div> </div>
{:else if char.length === 0 || char === " "} {:else if char.length === 0 || char === ' '}
<div class="digit">&nbsp;&nbsp;</div> <div class="digit">&nbsp;&nbsp;</div>
{:else} {:else}
<div class="digit">{char}</div> <div class="digit">{char}</div>
{/if} {/if}
{/each} {/each}
</div> </div>
</div> </div>

View file

@ -1,46 +1,41 @@
<script lang="ts"> <script lang="ts">
import { PUBLIC_BASE_URL } from '$env/static/public'; import { PUBLIC_BASE_URL } from '$env/static/public';
import { onMount } from 'svelte';
import { readonly, writable } from 'svelte/store';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import { import {
Col, Button,
Container,
Row,
Card, Card,
CardTitle,
CardHeader,
CardBody, CardBody,
CardHeader,
CardTitle,
Col,
Form, Form,
FormGroup,
FormText, FormText,
Label,
Input, Input,
InputGroup, InputGroup,
InputGroupText, InputGroupText,
Button Label,
Row
} from 'sveltestrap'; } from 'sveltestrap';
export let settings; export let settings;
const onSave = async(e:Event) => { const onSave = async (e: Event) => {
e.preventDefault(); e.preventDefault();
let formSettings = $settings; let formSettings = $settings;
delete formSettings["gitRev"]; delete formSettings['gitRev'];
delete formSettings["ip"]; delete formSettings['ip'];
delete formSettings["lastBuildTime"]; delete formSettings['lastBuildTime'];
const res = await fetch(`${PUBLIC_BASE_URL}/api/json/settings`, { await fetch(`${PUBLIC_BASE_URL}/api/json/settings`, {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, },
body: JSON.stringify(formSettings) body: JSON.stringify(formSettings)
}) });
} };
</script> </script>
<Col> <Col>
@ -51,7 +46,9 @@
<CardBody> <CardBody>
<Form on:submit={onSave}> <Form on:submit={onSave}>
<Row> <Row>
<Label md={6} for="fgColor" size="sm">{$_('section.settings.textColor', { default: 'Text color' })}</Label> <Label md={6} for="fgColor" size="sm"
>{$_('section.settings.textColor', { default: 'Text color' })}</Label
>
<Col md="6"> <Col md="6">
<Input <Input
type="select" type="select"
@ -61,13 +58,13 @@
bsSize="sm" bsSize="sm"
class="form-select-sm" class="form-select-sm"
> >
<option value="0">{ $_('colors.black') }</option> <option value="0">{$_('colors.black')}</option>
<option value="65535">{ $_('colors.white') }</option> <option value="65535">{$_('colors.white')}</option>
</Input> </Input>
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Label md={6} for="bgColor" size="sm">{ $_('section.settings.backgroundColor') }</Label> <Label md={6} for="bgColor" size="sm">{$_('section.settings.backgroundColor')}</Label>
<Col md="6"> <Col md="6">
<Input <Input
type="select" type="select"
@ -77,41 +74,45 @@
bsSize="sm" bsSize="sm"
class="form-select-sm" class="form-select-sm"
> >
<option value="0">{ $_('colors.black') }</option> <option value="0">{$_('colors.black')}</option>
<option value="65535">{ $_('colors.white') }</option> <option value="65535">{$_('colors.white')}</option>
</Input> </Input>
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Label md={6} for="timePerScreen" size="sm">{ $_('section.settings.timePerScreen') }</Label> <Label md={6} for="timePerScreen" size="sm">{$_('section.settings.timePerScreen')}</Label>
<Col md="6"> <Col md="6">
<InputGroup size="sm"> <InputGroup size="sm">
<Input type="number" min={1} step="1" bind:value={$settings.timePerScreen} /> <Input type="number" min={1} step="1" bind:value={$settings.timePerScreen} />
<InputGroupText>{ $_('time.minutes') }</InputGroupText> <InputGroupText>{$_('time.minutes')}</InputGroupText>
</InputGroup> </InputGroup>
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Label md={6} for="fullRefreshMin" size="sm">{ $_('section.settings.fullRefreshEvery') }</Label> <Label md={6} for="fullRefreshMin" size="sm"
>{$_('section.settings.fullRefreshEvery')}</Label
>
<Col md="6"> <Col md="6">
<InputGroup size="sm"> <InputGroup size="sm">
<Input type="number" min={1} step="1" bind:value={$settings.fullRefreshMin} /> <Input type="number" min={1} step="1" bind:value={$settings.fullRefreshMin} />
<InputGroupText>{ $_('time.minutes') }</InputGroupText> <InputGroupText>{$_('time.minutes')}</InputGroupText>
</InputGroup> </InputGroup>
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Label md={6} for="minSecPriceUpd" size="sm">{ $_('section.settings.timeBetweenPriceUpdates') }</Label> <Label md={6} for="minSecPriceUpd" size="sm"
>{$_('section.settings.timeBetweenPriceUpdates')}</Label
>
<Col md="6"> <Col md="6">
<InputGroup size="sm"> <InputGroup size="sm">
<Input type="number" min={1} step="1" bind:value={$settings.minSecPriceUpd} /> <Input type="number" min={1} step="1" bind:value={$settings.minSecPriceUpd} />
<InputGroupText>{ $_('time.seconds') }</InputGroupText> <InputGroupText>{$_('time.seconds')}</InputGroupText>
</InputGroup> </InputGroup>
<FormText>{ $_('section.settings.shortAmountsWarning') }</FormText> <FormText>{$_('section.settings.shortAmountsWarning')}</FormText>
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Label md={6} for="tzOffset" size="sm">{ $_('section.settings.timezoneOffset') }</Label> <Label md={6} for="tzOffset" size="sm">{$_('section.settings.timezoneOffset')}</Label>
<Col md="6"> <Col md="6">
<InputGroup size="sm"> <InputGroup size="sm">
<Input <Input
@ -122,19 +123,19 @@
id="tzOffset" id="tzOffset"
bind:value={$settings.tzOffset} bind:value={$settings.tzOffset}
/> />
<InputGroupText>{ $_('time.minutes') }</InputGroupText> <InputGroupText>{$_('time.minutes')}</InputGroupText>
</InputGroup> </InputGroup>
<FormText>{ $_('section.settings.tzOffsetHelpText') }</FormText> <FormText>{$_('section.settings.tzOffsetHelpText')}</FormText>
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Label md={6} for="ledBrightness" size="sm">{ $_('section.settings.ledBrightness') }</Label> <Label md={6} for="ledBrightness" size="sm">{$_('section.settings.ledBrightness')}</Label>
<Col md="6"> <Col md="6">
<Input <Input
type="range" type="range"
name="ledBrightness" name="ledBrightness"
id="ledBrightness" id="ledBrightness"
bind:value={$settings.ledBrightness} bind:value={$settings.ledBrightness}
min={0} min={0}
max={255} max={255}
step={1} step={1}
@ -142,67 +143,117 @@
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Label md={6} for="mempoolInstance" size="sm">{ $_('section.settings.mempoolnstance') }</Label> <Label md={6} for="mempoolInstance" size="sm"
<Col md="6"> >{$_('section.settings.mempoolnstance')}</Label
>
<Col md="6">
<Input <Input
type="text" type="text"
bind:value={$settings.mempoolInstance} bind:value={$settings.mempoolInstance}
name="mempoolInstance" name="mempoolInstance"
id="mempoolInstance" id="mempoolInstance"
bsSize="sm" bsSize="sm"
> ></Input>
</Input>
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Label md={6} for="hostnamePrefix" size="sm">{ $_('section.settings.hostnamePrefix') }</Label> <Label md={6} for="hostnamePrefix" size="sm"
<Col md="6"> >{$_('section.settings.hostnamePrefix')}</Label
>
<Col md="6">
<Input <Input
type="text" type="text"
bind:value={$settings.hostnamePrefix} bind:value={$settings.hostnamePrefix}
name="hostnamePrefix" name="hostnamePrefix"
id="hostnamePrefix" id="hostnamePrefix"
bsSize="sm" bsSize="sm"
> ></Input>
</Input> </Col>
</Row>
<Row>
<Col md="6">
<Input
id="ledTestOnPower"
bind:checked={$settings.ledTestOnPower}
type="switch"
bsSize="sm"
label={$_('section.settings.ledPowerOnTest')}
/>
</Col>
<Col md="6">
<Input
id="ledFlashOnUpd"
bind:checked={$settings.ledFlashOnUpd}
type="switch"
bsSize="sm"
label={$_('section.settings.ledFlashOnBlock')}
/>
</Col>
<Col md="6">
<Input
id="stealFocus"
bind:checked={$settings.stealFocus}
type="switch"
bsSize="sm"
label={$_('section.settings.StealFocusOnNewBlock')}
/>
</Col>
<Col md="6">
<Input
id="mcapBigChar"
bind:checked={$settings.mcapBigChar}
type="switch"
bsSize="sm"
label={$_('section.settings.useBigCharsMcap')}
/>
</Col>
<Col md="6">
<Input
id="otaEnabled"
bind:checked={$settings.otaEnabled}
type="switch"
bsSize="sm"
label="{$_('section.settings.otaUpdates')} ({$_('restartRequired')})"
/>
</Col>
<Col md="6">
<Input
id="mdnsEnabled"
bind:checked={$settings.mdnsEnabled}
type="switch"
bsSize="sm"
label="{$_('section.settings.enableMdns')} ({$_('restartRequired')})"
/>
</Col>
<Col md="6">
<Input
id="fetchEurPrice"
bind:checked={$settings.fetchEurPrice}
type="switch"
bsSize="sm"
label="{$_('section.settings.fetchEuroPrice')} ({$_('restartRequired')})"
/>
</Col> </Col>
</Row> </Row>
<Row>
<Col md="6">
<Input id="ledTestOnPower" bind:checked={$settings.ledTestOnPower} type="switch" bsSize="sm" label="{ $_('section.settings.ledPowerOnTest') }" />
</Col>
<Col md="6">
<Input id="ledFlashOnUpd" bind:checked={$settings.ledFlashOnUpd} type="switch" bsSize="sm" label="{ $_('section.settings.ledFlashOnBlock') }" />
</Col>
<Col md="6">
<Input id="stealFocus" bind:checked={$settings.stealFocus} type="switch" bsSize="sm" label="{ $_('section.settings.StealFocusOnNewBlock') }" />
</Col>
<Col md="6">
<Input id="mcapBigChar" bind:checked={$settings.mcapBigChar} type="switch" bsSize="sm" label="{ $_('section.settings.useBigCharsMcap') }" />
</Col>
<Col md="6">
<Input id="otaEnabled" bind:checked={$settings.otaEnabled} type="switch" bsSize="sm" label="{ $_('section.settings.otaUpdates') } ({ $_('restartRequired') })" />
</Col>
<Col md="6">
<Input id="mdnsEnabled" bind:checked={$settings.mdnsEnabled} type="switch" bsSize="sm" label="{ $_('section.settings.enableMdns') } ({ $_('restartRequired') })" />
</Col>
<Col md="6">
<Input id="fetchEurPrice" bind:checked={$settings.fetchEurPrice} type="switch" bsSize="sm" label="{ $_('section.settings.fetchEuroPrice') } ({ $_('restartRequired') })" />
</Col>
</Row>
<Row> <Row>
<h3>{ $_('section.settings.screens') }</h3> <h3>{$_('section.settings.screens')}</h3>
{#if $settings.screens} {#if $settings.screens}
{#each $settings.screens as s} {#each $settings.screens as s}
<Col md="6"> <Col md="6">
<Input id="screens_{s.id}" bind:checked={s.enabled} type="switch" bsSize="sm" label="{s.name}" /> <Input
</Col> id="screens_{s.id}"
{/each} bind:checked={s.enabled}
{/if} type="switch"
</Row> bsSize="sm"
<Button type="reset" color="secondary">{ $_('button.reset') }</Button> label={s.name}
<Button color="primary">{ $_('button.save') }</Button> />
</Col>
{/each}
{/if}
</Row>
<Button type="reset" color="secondary">{$_('button.reset')}</Button>
<Button color="primary">{$_('button.save')}</Button>
</Form> </Form>
</CardBody> </CardBody>
</Card> </Card>

View file

@ -1,65 +1,71 @@
<script lang="ts"> <script lang="ts">
import { PUBLIC_BASE_URL } from '$env/static/public'; import { PUBLIC_BASE_URL } from '$env/static/public';
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import { writable, type Writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { Row, Input, Button, ButtonGroup, Card, CardBody, CardHeader, Col, Progress,CardTitle } from 'sveltestrap'; import {
Button,
ButtonGroup,
Card,
CardBody,
CardHeader,
CardTitle,
Col,
Input,
Progress,
Row
} from 'sveltestrap';
import Rendered from './Rendered.svelte'; import Rendered from './Rendered.svelte';
export let settings;
export let status:Writable<{}>;
const toTime = (secs:Number) => { export let settings;
var hours = Math.floor(secs / (60 * 60)); export let status: writable<object>;
var divisor_for_minutes = secs % (60 * 60); const toTime = (secs: number) => {
var minutes = Math.floor(divisor_for_minutes / 60); var hours = Math.floor(secs / (60 * 60));
var divisor_for_seconds = divisor_for_minutes % 60; var divisor_for_minutes = secs % (60 * 60);
var seconds = Math.ceil(divisor_for_seconds); var minutes = Math.floor(divisor_for_minutes / 60);
var obj = { var divisor_for_seconds = divisor_for_minutes % 60;
"h": hours, var seconds = Math.ceil(divisor_for_seconds);
"m": minutes,
"s": seconds
};
return obj;
}
const toUptimeString = (secs:Number):String => { var obj = {
let time = toTime(secs); h: hours,
m: minutes,
s: seconds
};
return obj;
};
return `${time.h}h ${time.m}m ${time.s}s`; const toUptimestring = (secs: number): string => {
} let time = toTime(secs);
let memoryFreePercent:number = 50; return `${time.h}h ${time.m}m ${time.s}s`;
let lightMode:boolean = false; };
status.subscribe((value: {}) => { let memoryFreePercent: number = 50;
memoryFreePercent = Math.round(value.espFreeHeap / value.espHeapSize * 100); let lightMode: boolean = false;
});
settings.subscribe((value: {}) => { status.subscribe((value: object) => {
lightMode = value.bgColor > value.fgColor; memoryFreePercent = Math.round((value.espFreeHeap / value.espHeapSize) * 100);
}); });
const setScreen = (id:number) => () => { settings.subscribe((value: object) => {
fetch(`${PUBLIC_BASE_URL}/api/show/screen/${id}`).catch(err => { }); lightMode = value.bgColor > value.fgColor;
} });
const toggleTimer = (currentStatus:boolean) => () => { const setScreen = (id: number) => () => {
if (currentStatus) { fetch(`${PUBLIC_BASE_URL}/api/show/screen/${id}`).catch(() => {});
fetch(`${PUBLIC_BASE_URL}/api/action/pause`); };
} else {
fetch(`${PUBLIC_BASE_URL}/api/action/timer_restart`);
}
}
const isLightMode = () => {
return $settings.bgColor > $settings.fgColor;
}
const toggleTimer = (currentStatus: boolean) => (e: Event) => {
e.preventDefault();
if (currentStatus) {
fetch(`${PUBLIC_BASE_URL}/api/action/pause`);
} else {
fetch(`${PUBLIC_BASE_URL}/api/action/timer_restart`);
}
};
</script> </script>
<Col> <Col>
@ -68,64 +74,86 @@
<CardTitle>{$_('section.status.title', { default: 'Status' })}</CardTitle> <CardTitle>{$_('section.status.title', { default: 'Status' })}</CardTitle>
</CardHeader> </CardHeader>
<CardBody> <CardBody>
{#if $settings.screens} {#if $settings.screens}
<div class="d-flex justify-content-center"> <div class="d-flex justify-content-center">
<ButtonGroup size="sm"> <ButtonGroup size="sm">
{#each $settings.screens as s} {#each $settings.screens as s}
<Button color="outline-primary" active={$status.currentScreen == s.id} on:click={setScreen(s.id)}>{s.name}</Button> <Button
{/each} color="outline-primary"
</ButtonGroup> active={$status.currentScreen == s.id}
</div> on:click={setScreen(s.id)}>{s.name}</Button
<hr> >
{#if $status.data} {/each}
<section class={lightMode ? 'lightMode': ''}> </ButtonGroup>
<Rendered status="{$status}"></Rendered> </div>
</section> <hr />
{ $_('section.status.screenCycle') }: <a style="cursor: pointer" on:click="{toggleTimer($status.timerRunning)}">{#if $status.timerRunning}&#9205; { $_('timer.running') }{:else}&#9208; { $_('timer.stopped') }{/if}</a> {#if $status.data}
{/if} <section class={lightMode ? 'lightMode' : ''}>
{/if} <Rendered status={$status}></Rendered>
<hr> </section>
<Row class="justify-content-evenly"> {$_('section.status.screenCycle')}:
{#if $status.leds} <a
{#each $status.leds as led} href={'#'}
<Col> style="cursor: pointer"
<Input type="color" id="ledColorPicker" bind:value="{led.hex}" class="mx-auto" disabled /> tabindex="0"
</Col> role="button"
{/each} aria-pressed="false"
{/if} on:click={toggleTimer($status.timerRunning)}
</Row> >{#if $status.timerRunning}&#9205; {$_('timer.running')}{:else}&#9208; {$_(
<hr> 'timer.stopped'
<Progress striped value={memoryFreePercent}>{ memoryFreePercent }%</Progress> )}{/if}</a
<div class="d-flex justify-content-between"> >
<div>{ $_('section.status.memoryFree') } </div> {/if}
<div>{ Math.round($status.espFreeHeap / 1024) } / { Math.round($status.espHeapSize / 1024) } KiB</div> {/if}
</div> <hr />
<hr> <Row class="justify-content-evenly">
{ $_('section.status.uptime') }: {toUptimeString($status.espUptime)} {#if $status.leds}
<br> {#each $status.leds as led}
<p> <Col>
{ $_('section.status.wsPriceConnection') }: <Input
<span> type="color"
{#if $status.connectionStatus && $status.connectionStatus.price} id="ledColorPicker"
&#9989; bind:value={led.hex}
{:else} class="mx-auto"
&#10060; disabled
{/if} />
</span> </Col>
- {/each}
{ $_('section.status.wsMempoolConnection') }: {/if}
<span> </Row>
{#if $status.connectionStatus && $status.connectionStatus.blocks} <hr />
&#9989; <Progress striped value={memoryFreePercent}>{memoryFreePercent}%</Progress>
{:else} <div class="d-flex justify-content-between">
&#10060; <div>{$_('section.status.memoryFree')}</div>
{/if} <div>
</span><br> {Math.round($status.espFreeHeap / 1024)} / {Math.round($status.espHeapSize / 1024)} KiB
{#if $settings.fetchEurPrice} </div>
<small>{ $_('section.status.fetchEuroNote') }</small> </div>
{/if} <hr />
</p> {$_('section.status.uptime')}: {toUptimestring($status.espUptime)}
</CardBody> <br />
<p>
</Card> {$_('section.status.wsPriceConnection')}:
<span>
{#if $status.connectionStatus && $status.connectionStatus.price}
&#9989;
{:else}
&#10060;
{/if}
</span>
-
{$_('section.status.wsMempoolConnection')}:
<span>
{#if $status.connectionStatus && $status.connectionStatus.blocks}
&#9989;
{:else}
&#10060;
{/if}
</span><br />
{#if $settings.fetchEurPrice}
<small>{$_('section.status.fetchEuroNote')}</small>
{/if}
</p>
</CardBody>
</Card>
</Col> </Col>

View file

@ -1,36 +1,31 @@
<script lang="ts"> <script lang="ts">
import { _ } from 'svelte-i18n'; import { Button, Container } from 'sveltestrap';
import { Col, Container, Row, Button } from 'sveltestrap';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
//import * as swaggerJson from '../../../static/swagger.json'; let swaggerLoaded: boolean = false;
// import SwaggerUI from 'swagger-ui';
import 'swagger-ui/dist/swagger-ui.css';
let swaggerLoaded:boolean = false;
onMount(async () => { onMount(async () => {
// @ts-ignore
loadSwagger(); loadSwagger();
}); });
const loadSwagger = () => { const loadSwagger = () => {
if (!SwaggerUIBundle) // @ts-expect-error: SwaggerUIBundle is loaded from external script from CDN
return; if (!SwaggerUIBundle) return; // eslint-disable-line
swaggerLoaded = true; swaggerLoaded = true;
// @ts-expect-error: SwaggerUIBundle is loaded from external script from CDN
// eslint-disable-next-line
window.ui = SwaggerUIBundle({ window.ui = SwaggerUIBundle({
url: '/swagger.json', url: '/swagger.json',
dom_id: '#swagger-ui-container', dom_id: '#swagger-ui-container',
presets: [ presets: [
// @ts-ignore // @ts-expect-error: SwaggerUIBundle is loaded from external script from CDN
SwaggerUIBundle.presets.apis, SwaggerUIBundle.presets.apis, // eslint-disable-line
// @ts-ignore // @ts-expect-error: SwaggerUIStandalonePreset is loaded from external script from CDN
SwaggerUIStandalonePreset SwaggerUIStandalonePreset // eslint-disable-line
], ]
// layout: "StandaloneLayout", // layout: "StandaloneLayout",
}); });
} };
</script> </script>
<svelte:head> <svelte:head>
@ -54,11 +49,9 @@
crossorigin="anonymous" crossorigin="anonymous"
referrerpolicy="no-referrer" referrerpolicy="no-referrer"
/> />
</svelte:head> </svelte:head>
<Container fluid class="bg-light"> <Container fluid class="bg-light">
<section class:invisible={swaggerLoaded}><Button on:click="{loadSwagger}">Load</Button></section> <section class:invisible={swaggerLoaded}><Button on:click={loadSwagger}>Load</Button></section>
<div id="swagger-ui-container" /> <div id="swagger-ui-container" />
</Container> </Container>

View file

@ -1,480 +1,448 @@
{ {
"openapi": "3.0.3", "openapi": "3.0.3",
"info": { "info": {
"title": "BTClock API", "title": "BTClock API",
"version": "3.0", "version": "3.0",
"description": "BTClock V3 API" "description": "BTClock V3 API"
}, },
"servers": [ "servers": [
{ {
"url": "/api/" "url": "/api/"
} }
], ],
"paths": { "paths": {
"/status": { "/status": {
"get": { "get": {
"tags": [ "tags": ["system"],
"system" "summary": "Get current status",
], "responses": {
"summary": "Get current status", "200": {
"responses": { "description": "successful operation"
"200": { }
"description": "successful operation" }
} }
} },
} "/system_status": {
}, "get": {
"/system_status": { "tags": ["system"],
"get": { "summary": "Get system status",
"tags": [ "responses": {
"system" "200": {
], "description": "successful operation"
"summary": "Get system status", }
"responses": { }
"200": { }
"description": "successful operation" },
} "/settings": {
} "get": {
} "tags": ["system"],
}, "summary": "Get current settings",
"/settings": { "responses": {
"get": { "200": {
"tags": [ "description": "successful operation",
"system" "content": {
], "application/json": {
"summary": "Get current settings", "schema": {
"responses": { "$ref": null
"200": { }
"description": "successful operation", }
"content": { }
"application/json": { }
"schema": { }
"$ref": null },
} "post": {
} "tags": ["system"],
} "summary": "Save current settings",
} "requestBody": {
} "content": {
}, "application/json": {
"post": { "schema": {
"tags": [ "$ref": "#/components/schemas/Settings"
"system" }
], }
"summary": "Save current settings", }
"requestBody": { },
"content": { "responses": {
"application/json": { "200": {
"schema": { "description": "successful operation"
"$ref": "#/components/schemas/Settings" }
} }
} },
} "patch": {
}, "tags": ["system"],
"responses": { "summary": "Save current settings",
"200": { "requestBody": {
"description": "successful operation" "content": {
} "application/json": {
} "schema": {
}, "$ref": "#/components/schemas/Settings"
"patch": { }
"tags": [ }
"system" }
], },
"summary": "Save current settings", "responses": {
"requestBody": { "200": {
"content": { "description": "successful operation"
"application/json": { }
"schema": { }
"$ref": "#/components/schemas/Settings" }
} },
} "/action/pause": {
} "get": {
}, "tags": ["timer"],
"responses": { "summary": "Pause screen rotation",
"200": { "responses": {
"description": "successful operation" "200": {
} "description": "successful operation"
} }
} }
}, }
"/action/pause": { },
"get": { "/action/timer_restart": {
"tags": [ "get": {
"timer" "tags": ["timer"],
], "summary": "Restart screen rotation",
"summary": "Pause screen rotation", "responses": {
"responses": { "200": {
"200": { "description": "successful operation"
"description": "successful operation" }
} }
} }
} },
}, "/show/screen/{id}": {
"/action/timer_restart": { "get": {
"get": { "tags": ["screens"],
"tags": [ "summary": "Set screen to show",
"timer" "parameters": [
], {
"summary": "Restart screen rotation", "in": "path",
"responses": { "name": "id",
"200": { "schema": {
"description": "successful operation" "type": "integer",
} "default": 1
} },
} "required": true,
}, "description": "ID of screen to show"
"/show/screen/{id}": { }
"get": { ],
"tags": [ "responses": {
"screens" "200": {
], "description": "successful operation"
"summary": "Set screen to show", }
"parameters": [ }
{ }
"in": "path", },
"name": "id", "/show/text/{text}": {
"schema": { "get": {
"type": "integer", "tags": ["screens"],
"default": 1 "summary": "Set text to show",
}, "parameters": [
"required": true, {
"description": "ID of screen to show" "in": "path",
} "name": "text",
], "schema": {
"responses": { "type": "string",
"200": { "default": "text"
"description": "successful operation" },
} "required": true,
} "description": "Text to show"
} }
}, ],
"/show/text/{text}": { "responses": {
"get": { "200": {
"tags": [ "description": "successful operation"
"screens" }
], }
"summary": "Set text to show", }
"parameters": [ },
{ "/show/custom": {
"in": "path", "post": {
"name": "text", "tags": ["screens"],
"schema": { "summary": "Set text to show (advanced)",
"type": "string", "requestBody": {
"default": "text" "content": {
}, "application/json": {
"required": true, "schema": {
"description": "Text to show" "$ref": "#/components/schemas/CustomText"
} }
], }
"responses": { }
"200": { },
"description": "successful operation" "responses": {
} "200": {
} "description": "successful operation"
} }
}, }
"/show/custom": { }
"post": { },
"tags": [ "/full_refresh": {
"screens" "get": {
], "tags": ["system"],
"summary": "Set text to show (advanced)", "summary": "Force full refresh of all displays",
"requestBody": { "responses": {
"content": { "200": {
"application/json": { "description": "successful operation"
"schema": { }
"$ref": "#/components/schemas/CustomText" }
} }
} },
} "/lights": {
}, "get": {
"responses": { "tags": ["lights"],
"200": { "summary": "Get LEDs status",
"description": "successful operation" "responses": {
} "200": {
} "description": "successful operation",
} "content": {
}, "application/json": {
"/full_refresh": { "schema": {
"get": { "$ref": "#/components/schemas/ArrayOfLeds"
"tags": [ }
"system" }
], }
"summary": "Force full refresh of all displays", }
"responses": { }
"200": { },
"description": "successful operation" "patch": {
} "tags": ["lights"],
} "summary": "Set individual LEDs",
} "requestBody": {
}, "content": {
"/lights": { "application/json": {
"get": { "schema": {
"tags": [ "$ref": "#/components/schemas/ArrayOfLedsInput"
"lights" }
], }
"summary": "Get LEDs status", }
"responses": { },
"200": { "responses": {
"description": "successful operation", "200": {
"content": { "description": "succesful operation"
"application/json": { },
"schema": { "400": {
"$ref": "#/components/schemas/ArrayOfLeds" "description": "invalid colors or wrong amount of LEDs"
} }
} }
} }
} },
} "/lights/{color}": {
}, "get": {
"patch": { "tags": ["lights"],
"tags": [ "summary": "Turn on LEDs with specific color",
"lights" "parameters": [
], {
"summary": "Set individual LEDs", "in": "path",
"requestBody": { "name": "color",
"content": { "schema": {
"application/json": { "type": "string",
"schema": { "default": "FFCC00"
"$ref": "#/components/schemas/ArrayOfLedsInput" },
} "required": true,
} "description": "Color in RGB hex"
} }
}, ],
"responses": { "responses": {
"200": { "200": {
"description": "succesful operation" "description": "successful operation"
}, }
"400": { }
"description": "invalid colors or wrong amount of LEDs" }
} },
} "/lights/off": {
} "get": {
}, "tags": ["lights"],
"/lights/{color}": { "summary": "Turn LEDs off",
"get": { "responses": {
"tags": [ "200": {
"lights" "description": "successful operation"
], }
"summary": "Turn on LEDs with specific color", }
"parameters": [ }
{ },
"in": "path", "/restart": {
"name": "color", "get": {
"schema": { "tags": ["system"],
"type": "string", "summary": "Restart BTClock",
"default": "FFCC00" "responses": {
}, "200": {
"required": true, "description": "successful operation"
"description": "Color in RGB hex" }
} }
], }
"responses": { }
"200": { },
"description": "successful operation" "components": {
} "schemas": {
} "RgbColorValues": {
} "type": "object",
}, "properties": {
"/lights/off": { "red": {
"get": { "type": "integer",
"tags": [ "minimum": 0,
"lights" "maximum": 255,
], "example": 255
"summary": "Turn LEDs off", },
"responses": { "green": {
"200": { "type": "integer",
"description": "successful operation" "minimum": 0,
} "maximum": 255,
} "example": 204
} },
}, "blue": {
"/restart": { "type": "integer",
"get": { "minimum": 0,
"tags": [ "maximum": 255,
"system" "example": 0
], }
"summary": "Restart BTClock", }
"responses": { },
"200": { "RgbColorHex": {
"description": "successful operation" "type": "object",
} "properties": {
} "hex": {
} "type": "string",
} "pattern": "^#(?:[0-9a-fA-F]{3}){1,2}$",
}, "example": "#FFCC00"
"components": { }
"schemas": { }
"RgbColorValues": { },
"type": "object", "RgbColorValueAndHex": {
"properties": { "allOf": [
"red": { {
"type": "integer", "$ref": "#/components/schemas/RgbColorValues"
"minimum": 0, },
"maximum": 255, {
"example": 255 "$ref": "#/components/schemas/RgbColorHex"
}, }
"green": { ]
"type": "integer", },
"minimum": 0, "RgbColorValueOrHex": {
"maximum": 255, "oneOf": [
"example": 204 {
}, "$ref": "#/components/schemas/RgbColorValues"
"blue": { },
"type": "integer", {
"minimum": 0, "$ref": "#/components/schemas/RgbColorHex"
"maximum": 255, }
"example": 0 ]
} },
} "ArrayOfLeds": {
}, "type": "array",
"RgbColorHex": { "items": {
"type": "object", "$ref": "#/components/schemas/RgbColorValueAndHex"
"properties": { }
"hex": { },
"type": "string", "ArrayOfLedsInput": {
"pattern": "^#(?:[0-9a-fA-F]{3}){1,2}$", "type": "array",
"example": "#FFCC00" "items": {
} "$ref": "#/components/schemas/RgbColorValueOrHex"
} }
}, },
"RgbColorValueAndHex": { "Settings": {
"allOf": [ "type": "object",
{ "properties": {
"$ref": "#/components/schemas/RgbColorValues" "fetchEurPrice": {
}, "type": "boolean",
{ "description": "Fetch EUR price instead of USD"
"$ref": "#/components/schemas/RgbColorHex" },
} "fgColor": {
] "type": "string",
}, "default": 16777215,
"RgbColorValueOrHex": { "description": "ePaper foreground (text) color"
"oneOf": [ },
{ "bgColor": {
"$ref": "#/components/schemas/RgbColorValues" "type": "string",
}, "default": 0,
{ "description": "ePaper background color"
"$ref": "#/components/schemas/RgbColorHex" },
} "ledTestOnPower": {
] "type": "boolean",
}, "default": true,
"ArrayOfLeds": { "description": "Do LED test on power-on"
"type": "array", },
"items": { "ledFlashOnUpd": {
"$ref": "#/components/schemas/RgbColorValueAndHex" "type": "boolean",
} "default": false,
}, "description": "Flash LEDs on new block"
"ArrayOfLedsInput": { },
"type": "array", "mdnsEnabled": {
"items": { "type": "boolean",
"$ref": "#/components/schemas/RgbColorValueOrHex" "default": true,
} "description": "Enable mDNS"
}, },
"Settings": { "otaEnabled": {
"type": "object", "type": "boolean",
"properties": { "default": true,
"fetchEurPrice": { "description": "Enable over-the-air updates"
"type": "boolean", },
"description": "Fetch EUR price instead of USD" "stealFocus": {
}, "type": "boolean",
"fgColor": { "default": false,
"type": "string", "description": "Steal focus on new block"
"default": 16777215, },
"description": "ePaper foreground (text) color" "mcapBigChar": {
}, "type": "boolean",
"bgColor": { "default": false,
"type": "string", "description": "Use big characters for market cap screen"
"default": 0, },
"description": "ePaper background color" "mempoolInstance": {
}, "type": "string",
"ledTestOnPower": { "default": "mempool.space",
"type": "boolean", "description": "Mempool.space instance to connect to"
"default": true, },
"description": "Do LED test on power-on" "ledBrightness": {
}, "type": "integer",
"ledFlashOnUpd": { "default": 128,
"type": "boolean", "description": "Brightness of LEDs"
"default": false, },
"description": "Flash LEDs on new block" "fullRefreshMin": {
}, "type": "integer",
"mdnsEnabled": { "default": 60,
"type": "boolean", "description": "Full refresh time of ePaper displays in minutes"
"default": true, },
"description": "Enable mDNS" "screen[0]": {
}, "type": "boolean"
"otaEnabled": { },
"type": "boolean", "screen[1]": {
"default": true, "type": "boolean"
"description": "Enable over-the-air updates" },
}, "screen[2]": {
"stealFocus": { "type": "boolean"
"type": "boolean", },
"default": false, "screen[3]": {
"description": "Steal focus on new block" "type": "boolean"
}, },
"mcapBigChar": { "screen[4]": {
"type": "boolean", "type": "boolean"
"default": false, },
"description": "Use big characters for market cap screen" "screen[5]": {
}, "type": "boolean"
"mempoolInstance": { },
"type": "string", "tzOffset": {
"default": "mempool.space", "type": "integer",
"description": "Mempool.space instance to connect to" "default": 60,
}, "description": "Timezone offset in minutes"
"ledBrightness": { },
"type": "integer", "minSecPriceUpd": {
"default": 128, "type": "integer",
"description": "Brightness of LEDs" "default": 30,
}, "description": "Minimum time between price updates in seconds"
"fullRefreshMin": { },
"type": "integer", "timePerScreen": {
"default": 60, "type": "integer",
"description": "Full refresh time of ePaper displays in minutes" "default": 30,
}, "description": "Time between screens when rotating in minutes"
"screen[0]": { }
"type": "boolean" }
}, },
"screen[1]": { "CustomText": {
"type": "boolean" "type": "array",
}, "items": {
"screen[2]": { "type": "string"
"type": "boolean" },
}, "minItems": 7,
"screen[3]": { "maxItems": 7
"type": "boolean" }
}, }
"screen[4]": { }
"type": "boolean" }
},
"screen[5]": {
"type": "boolean"
},
"tzOffset": {
"type": "integer",
"default": 60,
"description": "Timezone offset in minutes"
},
"minSecPriceUpd": {
"type": "integer",
"default": 30,
"description": "Minimum time between price updates in seconds"
},
"timePerScreen": {
"type": "integer",
"default": 30,
"description": "Time between screens when rotating in minutes"
}
}
},
"CustomText": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 7,
"maxItems": 7
}
}
}
}

View file

@ -31,7 +31,7 @@ paths:
'200': '200':
description: successful operation description: successful operation
content: content:
application/json: application/json:
schema: schema:
$ref: #/components/schemas/ArrayOfLeds $ref: #/components/schemas/ArrayOfLeds
post: post:
@ -140,7 +140,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/ArrayOfLeds' $ref: '#/components/schemas/ArrayOfLeds'
patch: patch:
tags: tags:
- lights - lights
summary: Set individual LEDs summary: Set individual LEDs
requestBody: requestBody:
@ -149,9 +149,9 @@ paths:
schema: schema:
$ref: '#/components/schemas/ArrayOfLedsInput' $ref: '#/components/schemas/ArrayOfLedsInput'
responses: responses:
"200": '200':
description: succesful operation description: succesful operation
"400": '400':
description: invalid colors or wrong amount of LEDs description: invalid colors or wrong amount of LEDs
/lights/{color}: /lights/{color}:
get: get:
@ -188,38 +188,38 @@ paths:
components: components:
schemas: schemas:
RgbColorValues: RgbColorValues:
type: object type: object
properties: properties:
red: red:
type: integer type: integer
minimum: 0 minimum: 0
maximum: 255 maximum: 255
example: 255 example: 255
green: green:
type: integer type: integer
minimum: 0 minimum: 0
maximum: 255 maximum: 255
example: 204 example: 204
blue: blue:
type: integer type: integer
minimum: 0 minimum: 0
maximum: 255 maximum: 255
example: 0 example: 0
RgbColorHex: RgbColorHex:
type: object type: object
properties: properties:
hex: hex:
type: string type: string
pattern: ^#(?:[0-9a-fA-F]{3}){1,2}$ pattern: ^#(?:[0-9a-fA-F]{3}){1,2}$
example: "#FFCC00" example: '#FFCC00'
RgbColorValueAndHex: RgbColorValueAndHex:
allOf: allOf:
- $ref: '#/components/schemas/RgbColorValues' - $ref: '#/components/schemas/RgbColorValues'
- $ref: '#/components/schemas/RgbColorHex' - $ref: '#/components/schemas/RgbColorHex'
RgbColorValueOrHex: RgbColorValueOrHex:
oneOf: oneOf:
- $ref: '#/components/schemas/RgbColorValues' - $ref: '#/components/schemas/RgbColorValues'
- $ref: '#/components/schemas/RgbColorHex' - $ref: '#/components/schemas/RgbColorHex'
ArrayOfLeds: ArrayOfLeds:
type: array type: array
items: items:

View file

@ -5,16 +5,14 @@ import { vitePreprocess } from '@sveltejs/kit/vite';
const config = { const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors // Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors // for more information about preprocessors
preprocess: vitePreprocess({ preprocess: vitePreprocess({}),
build: {
}), rollupOptions: {
build: { output: {
rollupOptions: { assetFilenames: '[hash]'
output: { }
assetFilenames: '[hash]'
} }
} },
},
kit: { kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter. // If your environment is not supported or you settled on a specific environment, switch out the adapter.
@ -24,11 +22,11 @@ build: {
// these options are set automatically — see below // these options are set automatically — see below
pages: 'dist', pages: 'dist',
assets: 'dist', assets: 'dist',
fallback: "bundle.html", fallback: 'bundle.html',
precompress: false, precompress: false,
strict: true strict: true
}), }),
appDir: "build", appDir: 'build'
// inlineStyleThreshold: 9999999999 // inlineStyleThreshold: 9999999999
} }
}; };

View file

@ -1,61 +1,71 @@
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import * as fs from 'fs' import * as fs from 'fs';
import * as path from 'path' import * as path from 'path';
const doRewrap = ({ cssClass }) => { const doRewrap = ({ cssClass }) => {
try { try {
if (fs.existsSync(path.resolve(__dirname, 'dist/bundle.js'))) { if (fs.existsSync(path.resolve(__dirname, 'dist/bundle.js'))) {
return return;
} }
} catch(e) {} } catch (e) {}
console.log("\nStart re-wrapping...") console.log('\nStart re-wrapping...');
fs.readFile(path.resolve(__dirname, 'dist/bundle.html'), 'utf8', function(err, data){ fs.readFile(path.resolve(__dirname, 'dist/bundle.html'), 'utf8', function (err, data) {
if (!data) { if (!data) {
console.log(`[Error]: No bundle.html generated, check svelte.config.js -> config.kit.adapter -> fallback: "bundle.html"`) console.log(
return `[Error]: No bundle.html generated, check svelte.config.js -> config.kit.adapter -> fallback: "bundle.html"`
);
return;
} }
let matchData = data.match(/(?<=<script\b[^>]*>)([\s\S]*?)(?=<\/script>)/gm) const matchData = data.match(/(?<=<script\b[^>]*>)([\s\S]*?)(?=<\/script>)/gm);
if (matchData) { if (matchData) {
let cleanData = matchData[0].trim() const cleanData = matchData[0]
.replace(`document.querySelector('[data-sveltekit-hydrate="45h"]').parentNode`, `document.querySelector(".${cssClass}")`) .trim()
.replace(
`document.querySelector('[data-sveltekit-hydrate="45h"]').parentNode`,
`document.querySelector(".${cssClass}")`
);
fs.writeFile(path.resolve(__dirname, 'dist/bundle.js'), cleanData, (err) => { fs.writeFile(path.resolve(__dirname, 'dist/bundle.js'), cleanData, (err) => {
if (err) if (err) console.log(err);
console.log(err)
else { else {
try { try {
fs.rename(path.resolve(__dirname,'dist/index.page'), path.resolve(__dirname, 'dist/index.html'), (err) => { }) fs.rename(
} catch (e) { } path.resolve(__dirname, 'dist/index.page'),
path.resolve(__dirname, 'dist/index.html'),
() => {}
);
} catch (e) {}
try { try {
fs.unlinkSync(path.resolve(__dirname, 'dist/bundle.html')) fs.unlinkSync(path.resolve(__dirname, 'dist/bundle.html'));
} catch (e) { } } catch (e) {}
console.log("Finished: bundle.js + index.html have been regenerated.\n") console.log('Finished: bundle.js + index.html have been regenerated.\n');
} }
}) });
} else } else console.log(`[Error]: No proper <script> tag found in bundle.html`);
console.log(`[Error]: No proper <script> tag found in bundle.html`) });
}) };
}
export default defineConfig({ export default defineConfig({
plugins: [sveltekit(), { plugins: [
name: 'postbuild-command', sveltekit(),
{
name: 'postbuild-command',
closeBundle: { closeBundle: {
order: 'post', order: 'post',
handler() { handler() {
setTimeout(() => doRewrap({ cssClass: "overlay" }), Math.random()*500+500) setTimeout(() => doRewrap({ cssClass: 'overlay' }), Math.random() * 500 + 500);
} }
} }
}], }
],
build: { build: {
minify: true, minify: true,
cssCodeSplit: false, cssCodeSplit: false,
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: () => 'app', manualChunks: () => 'app',
assetFileNames: "[name][extname]" assetFileNames: '[name][extname]'
} }
} }
} }